././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1633956150.3574667 flake8-4.0.1/0000755000175000017500000000000000000000000014361 5ustar00asottileasottile00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633909928.0 flake8-4.0.1/CONTRIBUTING.rst0000644000175000017500000000017100000000000017021 0ustar00asottileasottile00000000000000Please refer to `Contributing to Flake8 `_ on our website. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633909928.0 flake8-4.0.1/CONTRIBUTORS.txt0000644000175000017500000000073100000000000017060 0ustar00asottileasottile00000000000000Project created by Tarek Ziadé. Contributors (by order of appearance) : - Tamás Gulácsi - Nicolas Dumazet - Stefan Scherfke - Chris Adams - Ben Bass - Ask Solem - Steven Kryskalla - Gustavo Picon - Jannis Leidel - Miki Tebeka - David Cramer - Peter Teichman - Ian Cordasco - Oleg Broytman - Marc Labbé - Bruno Miguel Custódio - Florent Xicluna - Austin Morton - Michael McNeil Forbes - Christian Long - Tyrel Souza - Corey Farwell - Michael Penkov - Anthony Sottile ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633909928.0 flake8-4.0.1/LICENSE0000644000175000017500000000222400000000000015366 0ustar00asottileasottile00000000000000== Flake8 License (MIT) == Copyright (C) 2011-2013 Tarek Ziade Copyright (C) 2012-2016 Ian Cordasco Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633909928.0 flake8-4.0.1/MANIFEST.in0000644000175000017500000000033100000000000016114 0ustar00asottileasottile00000000000000include *.rst include CONTRIBUTORS.txt include LICENSE include *.ini global-exclude *.pyc recursive-include docs *.rst *.py recursive-include tests *.py *.ini *.rst *_diff recursive-include src *.py prune docs/build/ ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1633956150.3574667 flake8-4.0.1/PKG-INFO0000644000175000017500000000726600000000000015471 0ustar00asottileasottile00000000000000Metadata-Version: 2.1 Name: flake8 Version: 4.0.1 Summary: the modular source code checker: pep8 pyflakes and co Home-page: https://github.com/pycqa/flake8 Author: Tarek Ziade Author-email: tarek@ziade.org Maintainer: Ian Stapleton Cordasco Maintainer-email: graffatcolmingov@gmail.com License: MIT Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: Environment :: Console Classifier: Framework :: Flake8 Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: MIT License Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3 :: Only Classifier: Programming Language :: Python :: 3.6 Classifier: Programming Language :: Python :: 3.7 Classifier: Programming Language :: Python :: 3.8 Classifier: Programming Language :: Python :: 3.9 Classifier: Programming Language :: Python :: 3.10 Classifier: Programming Language :: Python :: Implementation :: CPython Classifier: Programming Language :: Python :: Implementation :: PyPy Classifier: Topic :: Software Development :: Libraries :: Python Modules Classifier: Topic :: Software Development :: Quality Assurance Requires-Python: >=3.6 Description-Content-Type: text/x-rst License-File: LICENSE .. image:: https://github.com/PyCQA/flake8/workflows/main/badge.svg :target: https://github.com/PyCQA/flake8/actions?query=workflow%3Amain :alt: build status .. image:: https://results.pre-commit.ci/badge/github/PyCQA/flake8/main.svg :target: https://results.pre-commit.ci/latest/github/PyCQA/flake8/main :alt: pre-commit.ci status ======== Flake8 ======== Flake8 is a wrapper around these tools: - PyFlakes - pycodestyle - Ned Batchelder's McCabe script Flake8 runs all the tools by launching the single ``flake8`` command. It displays the warnings in a per-file, merged output. It also adds a few features: - files that contain this line are skipped:: # flake8: noqa - lines that contain a ``# noqa`` comment at the end will not issue warnings. - you can ignore specific errors on a line with ``# noqa: ``, e.g., ``# noqa: E234``. Multiple codes can be given, separated by comma. The ``noqa`` token is case insensitive, the colon before the list of codes is required otherwise the part after ``noqa`` is ignored - Git and Mercurial hooks - extendable through ``flake8.extension`` and ``flake8.formatting`` entry points Quickstart ========== See our `quickstart documentation `_ for how to install and get started with Flake8. Frequently Asked Questions ========================== Flake8 maintains an `FAQ `_ in its documentation. Questions or Feedback ===================== If you have questions you'd like to ask the developers, or feedback you'd like to provide, feel free to use the mailing list: code-quality@python.org We would love to hear from you. Additionally, if you have a feature you'd like to suggest, the mailing list would be the best place for it. Links ===== * `Flake8 Documentation `_ * `GitHub Project `_ * `All (Open and Closed) Issues `_ * `Code-Quality Archives `_ * `Code of Conduct `_ * `Getting Started Contributing `_ Maintenance =========== Flake8 was created by Tarek Ziadé and is currently maintained by `Ian Cordasco `_ ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633913947.0 flake8-4.0.1/README.rst0000644000175000017500000000464700000000000016063 0ustar00asottileasottile00000000000000.. image:: https://github.com/PyCQA/flake8/workflows/main/badge.svg :target: https://github.com/PyCQA/flake8/actions?query=workflow%3Amain :alt: build status .. image:: https://results.pre-commit.ci/badge/github/PyCQA/flake8/main.svg :target: https://results.pre-commit.ci/latest/github/PyCQA/flake8/main :alt: pre-commit.ci status ======== Flake8 ======== Flake8 is a wrapper around these tools: - PyFlakes - pycodestyle - Ned Batchelder's McCabe script Flake8 runs all the tools by launching the single ``flake8`` command. It displays the warnings in a per-file, merged output. It also adds a few features: - files that contain this line are skipped:: # flake8: noqa - lines that contain a ``# noqa`` comment at the end will not issue warnings. - you can ignore specific errors on a line with ``# noqa: ``, e.g., ``# noqa: E234``. Multiple codes can be given, separated by comma. The ``noqa`` token is case insensitive, the colon before the list of codes is required otherwise the part after ``noqa`` is ignored - Git and Mercurial hooks - extendable through ``flake8.extension`` and ``flake8.formatting`` entry points Quickstart ========== See our `quickstart documentation `_ for how to install and get started with Flake8. Frequently Asked Questions ========================== Flake8 maintains an `FAQ `_ in its documentation. Questions or Feedback ===================== If you have questions you'd like to ask the developers, or feedback you'd like to provide, feel free to use the mailing list: code-quality@python.org We would love to hear from you. Additionally, if you have a feature you'd like to suggest, the mailing list would be the best place for it. Links ===== * `Flake8 Documentation `_ * `GitHub Project `_ * `All (Open and Closed) Issues `_ * `Code-Quality Archives `_ * `Code of Conduct `_ * `Getting Started Contributing `_ Maintenance =========== Flake8 was created by Tarek Ziadé and is currently maintained by `Ian Cordasco `_ ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1633956150.3334746 flake8-4.0.1/docs/0000755000175000017500000000000000000000000015311 5ustar00asottileasottile00000000000000././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1633956150.3334746 flake8-4.0.1/docs/source/0000755000175000017500000000000000000000000016611 5ustar00asottileasottile00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633909928.0 flake8-4.0.1/docs/source/conf.py0000644000175000017500000002333500000000000020116 0ustar00asottileasottile00000000000000# # flake8 documentation build configuration file, created by # sphinx-quickstart on Tue Jan 19 07:14:10 2016. # # This file is execfile()d with the current directory set to its # containing dir. # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. import os import sys # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. # sys.path.insert(0, os.path.abspath('.')) # -- General configuration ------------------------------------------------ # If your documentation needs a minimal Sphinx version, state it here. needs_sphinx = "1.3" # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ "sphinx.ext.autodoc", "sphinx.ext.doctest", "sphinx.ext.extlinks", "sphinx.ext.intersphinx", "sphinx.ext.todo", "sphinx.ext.coverage", "sphinx.ext.viewcode", "sphinx-prompt", ] # Add any paths that contain templates here, relative to this directory. templates_path = ["_templates"] # The suffix(es) of source filenames. # You can specify multiple suffix as a list of string: # source_suffix = ['.rst', '.md'] source_suffix = ".rst" # The encoding of source files. # source_encoding = 'utf-8-sig' # The master toctree document. master_doc = "index" # General information about the project. project = "flake8" copyright = "2016, Ian Stapleton Cordasco" author = "Ian Stapleton Cordasco" import flake8 # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The short X.Y version. version = flake8.__version__ # The full version, including alpha/beta/rc tags. release = flake8.__version__ rst_epilog = """ .. |Flake8| replace:: :program:`Flake8` """ # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. # # This is also used if you do content translation via gettext catalogs. # Usually you set "language" from the command line for these cases. language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: # today = '' # Else, today_fmt is used as the format for a strftime call. # today_fmt = '%B %d, %Y' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. exclude_patterns = [] # The reST default role (used for this markup: `text`) to use for all # documents. # default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. # add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). # add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. # show_authors = False # The name of the Pygments (syntax highlighting) style to use. pygments_style = "sphinx" # A list of ignored prefixes for module index sorting. # modindex_common_prefix = [] # If true, keep warnings as "system message" paragraphs in the built documents. # keep_warnings = False # If true, `todo` and `todoList` produce output, else they produce nothing. todo_include_todos = True # -- Options for HTML output ---------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. html_theme = "sphinx_rtd_theme" # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. # html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. # html_theme_path = [] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". # html_title = None # A shorter title for the navigation bar. Default is the same as html_title. # html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. # html_logo = None # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. # html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". # html_static_path = ['_static'] # Add any extra paths that contain custom files (such as robots.txt or # .htaccess) here, relative to this directory. These files are copied # directly to the root of the documentation. # html_extra_path = [] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. # html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. # html_use_smartypants = True # Custom sidebar templates, maps document names to template names. # html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. # html_additional_pages = {} # If false, no module index is generated. # html_domain_indices = True # If false, no index is generated. # html_use_index = True # If true, the index is split into individual pages for each letter. # html_split_index = False # If true, links to the reST sources are added to the pages. # html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. # html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. # html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. # html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). # html_file_suffix = None # Language to be used for generating the HTML full-text search index. # Sphinx supports the following languages: # 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja' # 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr' # html_search_language = 'en' # A dictionary with options for the search language support, empty by default. # Now only 'ja' uses this config value # html_search_options = {'type': 'default'} # The name of a javascript file (relative to the configuration directory) that # implements a search results scorer. If empty, the default will be used. # html_search_scorer = 'scorer.js' # Output file base name for HTML help builder. htmlhelp_basename = "flake8doc" # -- Options for LaTeX output --------------------------------------------- latex_elements = { # The paper size ('letterpaper' or 'a4paper'). #'papersize': 'letterpaper', # The font size ('10pt', '11pt' or '12pt'). #'pointsize': '10pt', # Additional stuff for the LaTeX preamble. #'preamble': '', # Latex figure (float) alignment #'figure_align': 'htbp', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ ( master_doc, "flake8.tex", "flake8 Documentation", "Ian Stapleton Cordasco", "manual", ), ] # The name of an image file (relative to this directory) to place at the top of # the title page. # latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. # latex_use_parts = False # If true, show page references after internal links. # latex_show_pagerefs = False # If true, show URL addresses after external links. # latex_show_urls = False # Documents to append as an appendix to all manuals. # latex_appendices = [] # If false, no module index is generated. # latex_domain_indices = True # -- Options for manual page output --------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ ("manpage", "flake8", "Flake8 Command Line Documentation", [author], 1) ] # If true, show URL addresses after external links. # man_show_urls = False # -- Options for Texinfo output ------------------------------------------- # Grouping the document tree into Texinfo files. List of tuples # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ ( "index", "Flake8", "Flake8 Documentation", "Tarek Ziade", "Flake8", "Code checking using pycodestyle, pyflakes and mccabe", "Miscellaneous", ), ] # Documents to append as an appendix to all manuals. # texinfo_appendices = [] # If false, no module index is generated. # texinfo_domain_indices = True # How to display URL addresses: 'footnote', 'no', or 'inline'. # texinfo_show_urls = 'footnote' # If true, do not generate a @detailmenu in the "Top" node's menu. # texinfo_no_detailmenu = False # Example configuration for intersphinx: refer to the Python standard library. intersphinx_mapping = {"python": ("https://docs.python.org/3/", None)} extlinks = { "issue": ("https://github.com/pycqa/flake8/issues/%s", "#"), "pull": ("https://github.com/pycqa/flake8/pull/%s", "#"), } ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633909928.0 flake8-4.0.1/docs/source/faq.rst0000644000175000017500000000403600000000000020115 0ustar00asottileasottile00000000000000============================ Frequently Asked Questions ============================ When is Flake8 released? ======================== |Flake8| is released *as necessary*. Sometimes there are specific goals and drives to get to a release. Usually, we release as users report and fix bugs. How can I help Flake8 release faster? ===================================== Look at the next milestone. If there's work you can help us complete, that will help us get to the next milestone. If there's a show-stopping bug that needs to be released, let us know but please be kind. |Flake8| is developed and released entirely on volunteer time. What is the next version of Flake8? =================================== In general we try to use milestones to indicate this. If the last release on PyPI is 3.1.5 and you see a milestone for 3.2.0 in GitHub, there's a good chance that 3.2.0 is the next release. Why does Flake8 use ranges for its dependencies? ================================================ |Flake8| uses ranges for mccabe, pyflakes, and pycodestyle because each of those projects tend to add *new* checks in minor releases. It has been an implicit design goal of |Flake8|'s to make the list of error codes stable in its own minor releases. That way if you install something from the 2.5 series today, you will not find new checks in the same series in a month from now when you install it again. |Flake8|'s dependencies tend to avoid new checks in patch versions which is why |Flake8| expresses its dependencies roughly as:: pycodestyle >= 2.0.0, < 2.1.0 pyflakes >= 0.8.0, != 1.2.0, != 1.2.1, != 1.2.2, < 1.3.0 mccabe >= 0.5.0, < 0.6.0 This allows those projects to release patch versions that fix bugs and for |Flake8| users to consume those fixes. Should I file an issue when a new version of a dependency is available? ======================================================================= **No.** The current Flake8 core team (of one person) is also a core developer of pycodestyle, pyflakes, and mccabe. They are aware of these releases. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633909928.0 flake8-4.0.1/docs/source/glossary.rst0000644000175000017500000000422600000000000021212 0ustar00asottileasottile00000000000000.. _glossary: ================================================ Glossary of Terms Used in Flake8 Documentation ================================================ .. glossary:: :sorted: formatter A :term:`plugin` that augments the output of |Flake8| when passed to :option:`flake8 --format`. plugin A package that is typically installed from PyPI to augment the behaviour of |Flake8| either through adding one or more additional :term:`check`\ s or providing additional :term:`formatter`\ s. check A piece of logic that corresponds to an error code. A check may be a style check (e.g., check the length of a given line against the user configured maximum) or a lint check (e.g., checking for unused imports) or some other check as defined by a plugin. error error code violation The symbol associated with a specific :term:`check`. For example, pycodestyle implements :term:`check`\ s that look for whitespace around binary operators and will either return an error code of ``W503`` or ``W504``. warning Typically the ``W`` class of :term:`error code`\ s from pycodestyle. class error class A larger grouping of related :term:`error code`\ s. For example, ``W503`` and ``W504`` are two codes related to whitespace. ``W50`` would be the most specific class of codes relating to whitespace. ``W`` would be the warning class that subsumes all whitespace errors. pyflakes The project |Flake8| depends on to lint files (check for unused imports, variables, etc.). This uses the ``F`` :term:`class` of :term:`error code`\ s reported by |Flake8|. pycodestyle The project |Flake8| depends on to provide style enforcement. pycodestyle implements :term:`check`\ s for :pep:`8`. This uses the ``E`` and ``W`` :term:`class`\ es of :term:`error code`\ s. mccabe The project |Flake8| depends on to calculate the McCabe complexity of a unit of code (e.g., a function). This uses the ``C`` :term:`class` of :term:`error code`\ s. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633909928.0 flake8-4.0.1/docs/source/index.rst0000644000175000017500000000601200000000000020451 0ustar00asottileasottile00000000000000.. flake8 documentation master file, created by sphinx-quickstart on Tue Jan 19 07:14:10 2016. You can adapt this file completely to your liking, but it should at least contain the root `toctree` directive. =============================================== Flake8: Your Tool For Style Guide Enforcement =============================================== Quickstart ========== .. _installation-guide: Installation ------------ To install |Flake8|, open an interactive shell and run: .. code:: python -m pip install flake8 If you want |Flake8| to be installed for your default Python installation, you can instead use: .. code:: python -m pip install flake8 .. note:: It is **very** important to install |Flake8| on the *correct* version of Python for your needs. If you want |Flake8| to properly parse new language features in Python 3.5 (for example), you need it to be installed on 3.5 for |Flake8| to understand those features. In many ways, Flake8 is tied to the version of Python on which it runs. Using Flake8 ------------ To start using |Flake8|, open an interactive shell and run: .. code:: flake8 path/to/code/to/check.py # or flake8 path/to/code/ .. note:: If you have installed |Flake8| on a particular version of Python (or on several versions), it may be best to instead run ``python -m flake8``. If you only want to see the instances of a specific warning or error, you can *select* that error like so: .. code:: flake8 --select E123,W503 path/to/code/ Alternatively, if you want to *ignore* only one specific warning or error: .. code:: flake8 --ignore E24,W504 path/to/code/ Please read our user guide for more information about how to use and configure |Flake8|. FAQ and Glossary ================ .. toctree:: :maxdepth: 2 faq glossary User Guide ========== All users of |Flake8| should read this portion of the documentation. This provides examples and documentation around |Flake8|'s assortment of options and how to specify them on the command-line or in configuration files. .. toctree:: :maxdepth: 2 user/index Flake8 man page Plugin Developer Guide ====================== If you're maintaining a plugin for |Flake8| or creating a new one, you should read this section of the documentation. It explains how you can write your plugins and distribute them to others. .. toctree:: :maxdepth: 2 plugin-development/index Contributor Guide ================= If you are reading |Flake8|'s source code for fun or looking to contribute, you should read this portion of the documentation. This is a mix of documenting the internal-only interfaces |Flake8| and documenting reasoning for Flake8's design. .. toctree:: :maxdepth: 2 internal/index Release Notes and History ========================= .. toctree:: :maxdepth: 2 release-notes/index General Indices =============== * :ref:`genindex` * :ref:`Index of Documented Public Modules ` * :ref:`Glossary of terms ` ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1633956150.3374734 flake8-4.0.1/docs/source/internal/0000755000175000017500000000000000000000000020425 5ustar00asottileasottile00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633909928.0 flake8-4.0.1/docs/source/internal/checker.rst0000644000175000017500000000434200000000000022566 0ustar00asottileasottile00000000000000==================== How Checks are Run ==================== In |Flake8| 2.x, |Flake8| delegated check running to pep8. In 3.0 |Flake8| takes on that responsibility. This has allowed for simpler handling of the ``--jobs`` parameter (using :mod:`multiprocessing`) and simplified our fallback if something goes awry with concurrency. At the lowest level we have a |FileChecker|. Instances of |FileChecker| are created for *each* file to be analyzed by |Flake8|. Each instance, has a copy of all of the plugins registered with setuptools in the ``flake8.extension`` entry-point group. The |FileChecker| instances are managed by an instance of |Manager|. The |Manager| instance handles creating sub-processes with :mod:`multiprocessing` module and falling back to running checks in serial if an operating system level error arises. When creating |FileChecker| instances, the |Manager| is responsible for determining if a particular file has been excluded. Processing Files ---------------- Unfortunately, since |Flake8| took over check running from pep8/pycodestyle, it also had to take over parsing and processing files for the checkers to use. Since it couldn't reuse pycodestyle's functionality (since it did not separate cleanly the processing from check running) that function was isolated into the :class:`~flake8.processor.FileProcessor` class. We moved several helper functions into the :mod:`flake8.processor` module (see also :ref:`Processor Utility Functions `). API Reference ------------- .. autoclass:: flake8.checker.FileChecker :members: .. autoclass:: flake8.checker.Manager :members: .. autoclass:: flake8.processor.FileProcessor :members: .. _processor_utility_functions: Utility Functions ````````````````` .. autofunction:: flake8.processor.count_parentheses .. autofunction:: flake8.processor.expand_indent .. autofunction:: flake8.processor.is_eol_token .. autofunction:: flake8.processor.is_multiline_string .. autofunction:: flake8.processor.log_token .. autofunction:: flake8.processor.mutate_string .. autofunction:: flake8.processor.token_is_newline .. Substitutions .. |FileChecker| replace:: :class:`~flake8.checker.FileChecker` .. |Manager| replace:: :class:`~flake8.checker.Manager` ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633909928.0 flake8-4.0.1/docs/source/internal/cli.rst0000644000175000017500000000151100000000000021724 0ustar00asottileasottile00000000000000Command Line Interface ====================== The command line interface of |Flake8| is modeled as an application via :class:`~flake8.main.cli.Application`. When a user runs ``flake8`` at their command line, :func:`~flake8.main.cli.main` is run which handles management of the application. User input is parsed *twice* to accommodate logging and verbosity options passed by the user as early as possible. This is so as much logging can be produced as possible. The default |Flake8| options are registered by :func:`~flake8.main.options.register_default_options`. Trying to register these options in plugins will result in errors. API Documentation ----------------- .. autofunction:: flake8.main.cli.main .. autoclass:: flake8.main.application.Application :members: .. autofunction:: flake8.main.options.register_default_options ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633909928.0 flake8-4.0.1/docs/source/internal/contributing.rst0000644000175000017500000001457600000000000023703 0ustar00asottileasottile00000000000000======================== Contributing to Flake8 ======================== There are many ways to contribute to |Flake8|, and we encourage them all: - contributing bug reports and feature requests - contributing documentation (and yes that includes this document) - reviewing and triaging bugs and merge requests Before you go any further, please allow me to reassure you that I do want *your* contribution. If you think your contribution might not be valuable, I reassure you that any help you can provide *is* valuable. Code of Conduct =============== |Flake8| adheres to the `Python Code Quality Authority's Code of Conduct`_. Any violations of the Code of Conduct should be reported to Ian Stapleton Cordasco (graffatcolmingov [at] gmail [dot] com). Setting Up A Development Environment ==================================== To contribute to |Flake8|'s development, you simply need: - Python (one of the versions we support) - `tox`_ We suggest installing this like: .. prompt:: bash pip install --user tox Or .. prompt:: bash python -m pip install --user tox - your favorite editor Filing a Bug ============ When filing a bug against |Flake8|, please fill out the issue template as it is provided to you by `GitHub`_. If your bug is in reference to one of the checks that |Flake8| reports by default, please do not report them to |Flake8| unless |Flake8| is doing something to prevent the check from running or you have some reason to believe |Flake8| is inhibiting the effectiveness of the check. **Please search for closed and open bug reports before opening new ones.** All bug reports about checks should go to their respective projects: - Error codes starting with ``E`` and ``W`` should be reported to `pycodestyle`_. - Error codes starting with ``F`` should be reported to `pyflakes`_ - Error codes starting with ``C`` should be reported to `mccabe`_ Requesting a New Feature ======================== When requesting a new feature in |Flake8|, please fill out the issue template. Please also note if there are any existing alternatives to your new feature either via plugins, or combining command-line options. Please provide example use cases. For example, do not ask for a feature like this: I need feature frobulate for my job. Instead ask: I need |Flake8| to frobulate these files because my team expects them to frobulated but |Flake8| currently does not frobulate them. We tried using ``--filename`` but we could not create a pattern that worked. The more you explain about *why* you need a feature, the more likely we are to understand your needs and help you to the best of our ability. Contributing Documentation ========================== To contribute to |Flake8|'s documentation, you might want to first read a little about reStructuredText or Sphinx. |Flake8| has a :ref:`guide of best practices ` when contributing to our documentation. For the most part, you should be fine following the structure and style of the rest of |Flake8|'s documentation. All of |Flake8|'s documentation is written in reStructuredText and rendered by Sphinx. The source (reStructuredText) lives in ``docs/source/``. To build the documentation the way our Continuous Integration does, run: .. prompt:: bash tox -e docs To view the documentation locally, you can also run: .. prompt:: bash tox -e serve-docs You can run the latter in a separate terminal and continuously re-run the documentation generation and refresh the documentation you're working on. .. note:: We lint our documentation just like we lint our code. You should also run: .. prompt:: bash tox -e linters After making changes and before pushing them to ensure that they will pass our CI tests. Contributing Code ================= |Flake8| development happens on `GitHub`_. Code contributions should be submitted there. Merge requests should: - Fix one issue and fix it well Fix the issue, but do not include extraneous refactoring or code reformatting. In other words, keep the diff short, but only as short as is necessary to fix the bug appropriately and add sufficient testing around it. Long diffs are fine, so long as everything that it includes is necessary to the purpose of the merge request. - Have descriptive titles and descriptions Searching old merge requests is made easier when a merge request is well described. - Have commits that follow this style: .. code:: Create a short title that is 50 characters long Ensure the title and commit message use the imperative voice. The commit and you are doing something. Also, please ensure that the body of the commit message does not exceed 72 characters. The body may have multiple paragraphs as necessary. The final line of the body references the issue appropriately. - Follow the guidelines in :ref:`writing-code` - Avoid having :code:`.gitignore` file in your PR Changes to :code:`.gitignore` will rarely be accepted. If you need to add files to :code:`.gitignore` you have multiple options - Create a global :code:`.gitignore` file - Create/update :code:`.git/info/exclude` file. Both these options are explained in detail `here `_ Reviewing and Triaging Issues and Merge Requests ================================================ When reviewing other people's merge requests and issues, please be **especially** mindful of how the words you choose can be read by someone else. We strive for professional code reviews that do not insult the contributor's intelligence or impugn their character. The code review should be focused on the code, its effectiveness, and whether it is appropriate for |Flake8|. If you have the ability to edit an issue or merge request's labels, please do so to make search and prioritization easier. |Flake8| uses milestones with both issues and merge requests. This provides direction for other contributors about when an issue or merge request will be delivered. .. links .. _Python Code Quality Authority's Code of Conduct: http://meta.pycqa.org/en/latest/code-of-conduct.html .. _tox: https://tox.readthedocs.io/ .. _GitHub: https://github.com/pycqa/flake8 .. _pycodestyle: https://github.com/pycqa/pycodestyle .. _pyflakes: https://github.com/pyflakes/pyflakes .. _mccabe: https://github.com/pycqa/mccabe ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633909928.0 flake8-4.0.1/docs/source/internal/formatters.rst0000644000175000017500000000301500000000000023344 0ustar00asottileasottile00000000000000===================== Built-in Formatters ===================== By default |Flake8| has two formatters built-in, ``default`` and ``pylint``. These correspond to two classes |DefaultFormatter| and |PylintFormatter|. In |Flake8| 2.0, pep8 handled formatting of errors and also allowed users to specify an arbitrary format string as a parameter to ``--format``. In order to allow for this backwards compatibility, |Flake8| 3.0 made two choices: #. To not limit a user's choices for ``--format`` to the format class names #. To make the default formatter attempt to use the string provided by the user if it cannot find a formatter with that name. Default Formatter ================= The |DefaultFormatter| continues to use the same default format string as pep8: ``'%(path)s:%(row)d:%(col)d: %(code)s %(text)s'``. To provide the default functionality it overrides two methods: #. ``after_init`` #. ``format`` The former allows us to inspect the value provided to ``--format`` by the user and alter our own format based on that value. The second simply uses that format string to format the error. .. autoclass:: flake8.formatting.default.Default :members: Pylint Formatter ================ The |PylintFormatter| simply defines the default Pylint format string from pep8: ``'%(path)s:%(row)d: [%(code)s] %(text)s'``. .. autoclass:: flake8.formatting.default.Pylint :members: .. |DefaultFormatter| replace:: :class:`~flake8.formatting.default.Default` .. |PylintFormatter| replace:: :class:`~flake8.formatting.default.Pylint` ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633909928.0 flake8-4.0.1/docs/source/internal/index.rst0000644000175000017500000000143000000000000022264 0ustar00asottileasottile00000000000000============================== Exploring Flake8's Internals ============================== While writing |Flake8| 3.0, the developers attempted to capture some reasoning and decision information in internal documentation meant for future developers and maintainers. Most of this information is unnecessary for users and plugin developers. Some of it, however, is linked to from the plugin development documentation. Keep in mind that not everything will be here and you may need to help pull information out of the developers' heads and into these documents. Please pull gently. .. toctree:: :maxdepth: 2 contributing writing-documentation writing-code releases start-to-finish checker cli formatters option_handling plugin_handling utils ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633913947.0 flake8-4.0.1/docs/source/internal/option_handling.rst0000644000175000017500000002153500000000000024341 0ustar00asottileasottile00000000000000Option and Configuration Handling ================================= Option Management ----------------- Command-line options are often also set in configuration files for |Flake8|. While not all options are meant to be parsed from configuration files, many default options are also parsed from configuration files as well as most plugin options. In |Flake8| 2, plugins received a :class:`optparse.OptionParser` instance and called :meth:`optparse.OptionParser.add_option` to register options. If the plugin author also wanted to have that option parsed from config files they also had to do something like: .. code-block:: python parser.config_options.append('my_config_option') parser.config_options.extend(['config_opt1', 'config_opt2']) This was previously undocumented and led to a lot of confusion about why registered options were not automatically parsed from configuration files. Since |Flake8| 3 was rewritten from scratch, we decided to take a different approach to configuration file parsing. Instead of needing to know about an undocumented attribute that pep8 looks for, |Flake8| 3 now accepts a parameter to ``add_option``, specifically ``parse_from_config`` which is a boolean value. |Flake8| does this by creating its own abstractions on top of :mod:`argparse`. The first abstraction is the :class:`flake8.options.manager.Option` class. The second is the :class:`flake8.options.manager.OptionManager`. In fact, we add three new parameters: - ``parse_from_config`` - ``comma_separated_list`` - ``normalize_paths`` The last two are not specifically for configuration file handling, but they do improve that dramatically. We found that there were options that, when specified in a configuration file, often necessitated being spit multiple lines and those options were almost always comma-separated. For example, let's consider a user's list of ignored error codes for a project: .. code-block:: ini [flake8] ignore = # Reasoning E111, # Reasoning E711, # Reasoning E712, # Reasoning E121, # Reasoning E122, # Reasoning E123, # Reasoning E131, # Reasoning E251 It makes sense here to allow users to specify the value this way, but, the standard library's :class:`configparser.RawConfigParser` class does returns a string that looks like .. code-block:: python "\nE111, \nE711, \nE712, \nE121, \nE122, \nE123, \nE131, \nE251 " This means that a typical call to :meth:`str.split` with ``','`` will not be sufficient here. Telling |Flake8| that something is a comma-separated list (e.g., ``comma_separated_list=True``) will handle this for you. |Flake8| will return: .. code-block:: python ["E111", "E711", "E712", "E121", "E122", "E123", "E131", "E251"] Next let's look at how users might like to specify their ``exclude`` list. Presently OpenStack's Nova project has this line in their `tox.ini`_: .. code-block:: ini exclude = .venv,.git,.tox,dist,doc,*openstack/common/*,*lib/python*,*egg,build,tools/xenserver*,releasenotes We think we can all agree that this would be easier to read like this: .. code-block:: ini exclude = .venv, .git, .tox, dist, doc, *openstack/common/*, *lib/python*, *egg, build, tools/xenserver*, releasenotes In this case, since these are actually intended to be paths, we would specify both ``comma_separated_list=True`` and ``normalize_paths=True`` because we want the paths to be provided to us with some consistency (either all absolute paths or not). Now let's look at how this will actually be used. Most plugin developers will receive an instance of :class:`~flake8.options.manager.OptionManager` so to ease the transition we kept the same API as the :class:`optparse.OptionParser` object. The only difference is that :meth:`~flake8.options.manager.OptionManager.add_option` accepts the three extra arguments we highlighted above. .. _tox.ini: https://github.com/openstack/nova/blob/3eb190c4cfc0eefddac6c2cc1b94a699fb1687f8/tox.ini#L155 Configuration File Management ----------------------------- In |Flake8| 2, configuration file discovery and management was handled by pep8. In pep8's 1.6 release series, it drastically broke how discovery and merging worked (as a result of trying to improve it). To avoid a dependency breaking |Flake8| again in the future, we have created our own discovery and management in 3.0.0. In 4.0.0 we have once again changed how this works and we removed support for user-level config files. - Project files (files stored in the current directory) are read next and merged on top of the user file. In other words, configuration in project files takes precedence over configuration in user files. - **New in 3.0.0** The user can specify ``--append-config `` repeatedly to include extra configuration files that should be read and take precedence over user and project files. - **New in 3.0.0** The user can specify ``--config `` to so this file is the only configuration file used. This is a change from |Flake8| 2 where pep8 would simply merge this configuration file into the configuration generated by user and project files (where this takes precedence). - **New in 3.0.0** The user can specify ``--isolated`` to disable configuration via discovered configuration files. To facilitate the configuration file management, we've taken a different approach to discovery and management of files than pep8. In pep8 1.5, 1.6, and 1.7 configuration discovery and management was centralized in `66 lines of very terse python`_ which was confusing and not very explicit. The terseness of this function (|Flake8| 3.0.0's authors believe) caused the confusion and problems with pep8's 1.6 series. As such, |Flake8| has separated out discovery, management, and merging into a module to make reasoning about each of these pieces easier and more explicit (as well as easier to test). Configuration file discovery is managed by the :class:`~flake8.options.config.ConfigFileFinder` object. This object needs to know information about the program's name, any extra arguments passed to it, and any configuration files that should be appended to the list of discovered files. It provides methods for finding the files and similar methods for parsing those fles. For example, it provides :meth:`~flake8.options.config.ConfigFileFinder.local_config_files` to find known local config files (and append the extra configuration files) and it also provides :meth:`~flake8.options.config.ConfigFileFinder.local_configs` to parse those configuration files. .. note:: ``local_config_files`` also filters out non-existent files. Configuration file merging and managemnt is controlled by the :class:`~flake8.options.config.ConfigParser`. This requires the instance of :class:`~flake8.options.manager.OptionManager` that the program is using, the list of appended config files, and the list of extra arguments. This object is currently the sole user of the :class:`~flake8.options.config.ConfigFileFinder` object. It appropriately initializes the object and uses it in each of - :meth:`~flake8.options.config.ConfigParser.parse_cli_config` - :meth:`~flake8.options.config.ConfigParser.parse_local_config` Finally, :meth:`~flake8.options.config.ConfigParser.parse` returns the appropriate configuration dictionary for this execution of |Flake8|. The main usage of the ``ConfigParser`` is in :func:`~flake8.options.aggregator.aggregate_options`. Aggregating Configuration File and Command Line Arguments --------------------------------------------------------- :func:`~flake8.options.aggregator.aggregate_options` accepts an instance of :class:`~flake8.options.manager.OptionManager` and does the work to parse the command-line arguments passed by the user necessary for creating an instance of :class:`~flake8.options.config.ConfigParser`. After parsing the configuration file, we determine the default ignore list. We use the defaults from the OptionManager and update those with the parsed configuration files. Finally we parse the user-provided options one last time using the option defaults and configuration file values as defaults. The parser merges on the command-line specified arguments for us so we have our final, definitive, aggregated options. .. _66 lines of very terse python: https://github.com/PyCQA/pep8/blob/b8088a2b6bc5b76bece174efad877f764529bc74/pep8.py#L1981..L2047 API Documentation ----------------- .. autofunction:: flake8.options.aggregator.aggregate_options .. autoclass:: flake8.options.manager.Option :members: __init__, normalize, to_argparse .. autoclass:: flake8.options.manager.OptionManager :members: :special-members: .. autoclass:: flake8.options.config.ConfigFileFinder :members: :special-members: .. autoclass:: flake8.options.config.ConfigParser :members: :special-members: ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633909928.0 flake8-4.0.1/docs/source/internal/plugin_handling.rst0000644000175000017500000000617400000000000024331 0ustar00asottileasottile00000000000000Plugin Handling =============== Plugin Management ----------------- |Flake8| 3.0 added support for other plugins besides those which define new checks. It now supports: - extra checks - alternative report formatters To facilitate this, |Flake8| needed a more mature way of managing plugins. Thus, we developed the |PluginManager| which accepts a namespace and will load the plugins for that namespace. A |PluginManager| creates and manages many |Plugin| instances. A |Plugin| lazily loads the underlying entry-point provided by setuptools. The entry-point will be loaded either by calling :meth:`~flake8.plugins.manager.Plugin.load_plugin` or accessing the ``plugin`` attribute. We also use this abstraction to retrieve options that the plugin wishes to register and parse. The only public method the |PluginManager| provides is :meth:`~flake8.plugins.manager.PluginManager.map`. This will accept a function (or other callable) and call it with each plugin as the first parameter. We build atop the |PluginManager| with the |PTM|. It is expected that users of the |PTM| will subclass it and specify the ``namespace``, e.g., .. code-block:: python class ExamplePluginType(flake8.plugin.manager.PluginTypeManager): namespace = 'example-plugins' This provides a few extra methods via the |PluginManager|'s ``map`` method. Finally, we create two classes of plugins: - :class:`~flake8.plugins.manager.Checkers` - :class:`~flake8.plugins.manager.ReportFormatters` These are used to interact with each of the types of plugins individually. .. note:: Our inspiration for our plugin handling comes from the author's extensive experience with ``stevedore``. Default Plugins --------------- Finally, |Flake8| has always provided its own plugin shim for Pyflakes. As part of that we carry our own shim in-tree and now store that in :mod:`flake8.plugins.pyflakes`. |Flake8| also registers plugins for pep8. Each check in pep8 requires different parameters and it cannot easily be shimmed together like Pyflakes was. As such, plugins have a concept of a "group". If you look at our :file:`setup.py` you will see that we register pep8 checks roughly like so: .. code:: pep8. = pep8: We do this to identify that ``>`` is part of a group. This also enables us to special-case how we handle reporting those checks. Instead of reporting each check in the ``--version`` output, we report ``pep8`` and check ``pep8`` the module for a ``__version__`` attribute. We only report it once to avoid confusing users. API Documentation ----------------- .. autoclass:: flake8.plugins.manager.PluginManager :members: :special-members: __init__ .. autoclass:: flake8.plugins.manager.Plugin :members: :special-members: __init__ .. autoclass:: flake8.plugins.manager.PluginTypeManager :members: .. autoclass:: flake8.plugins.manager.Checkers :members: .. autoclass:: flake8.plugins.manager.ReportFormatters .. |PluginManager| replace:: :class:`~flake8.plugins.manager.PluginManager` .. |Plugin| replace:: :class:`~flake8.plugins.manager.Plugin` .. |PTM| replace:: :class:`~flake8.plugins.manager.PluginTypeManager` ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633917979.0 flake8-4.0.1/docs/source/internal/releases.rst0000644000175000017500000000523300000000000022765 0ustar00asottileasottile00000000000000================== Releasing Flake8 ================== There is not much that is hard to find about how |Flake8| is released. - We use **major** releases (e.g., 2.0.0, 3.0.0, etc.) for big, potentially backwards incompatible, releases. - We use **minor** releases (e.g., 2.1.0, 2.2.0, 3.1.0, 3.2.0, etc.) for releases that contain features and dependency version changes. - We use **patch** releases (e.g., 2.1.1, 2.1.2, 3.0.1, 3.0.10, etc.) for releases that contain *only* bug fixes. In this sense we follow semantic versioning. But we follow it as more of a set of guidelines. We're also not perfect, so we may make mistakes, and that's fine. Major Releases ============== Major releases are often associated with backwards incompatibility. |Flake8| hopes to avoid those, but will occasionally need them. Historically, |Flake8| has generated major releases for: - Unvendoring dependencies (2.0) - Large scale refactoring (2.0, 3.0) - Subtly breaking CLI changes (3.0, 4.0) - Breaking changes to its plugin interface (3.0) Major releases can also contain: - Bug fixes (which may have backwards incompatible solutions) - New features - Dependency changes Minor Releases ============== Minor releases often have new features in them, which we define roughly as: - New command-line flags - New behaviour that does not break backwards compatibility - New errors detected by dependencies, e.g., by raising the upper limit on PyFlakes we introduce F405 - Bug fixes Patch Releases ============== Patch releases should only ever have bug fixes in them. We do not update dependency constraints in patch releases. If you do not install |Flake8| from PyPI, there is a chance that your packager is using different requirements. Some downstream redistributors have been known to force a new version of PyFlakes, pep8/PyCodestyle, or McCabe into place. Occasionally this will cause breakage when using |Flake8|. There is little we can do to help you in those cases. Process ======= To prepare a release, we create a file in :file:`docs/source/release-notes/` named: ``{{ release_number }}.rst`` (e.g., ``3.0.0.rst``). We note bug fixes, improvements, and dependency version changes as well as other items of note for users. Before releasing, the following tox test environments must pass: - Python 3.6 (a.k.a., ``tox -e py36``) - Python 3.7 (a.k.a., ``tox -e py37``) - PyPy 3 (a.k.a., ``tox -e pypy3``) - Linters (a.k.a., ``tox -e linters``) We tag the most recent commit that passes those items and contains our release notes. Finally, we run ``tox -e release`` to build source distributions (e.g., ``flake8-3.0.0.tar.gz``), universal wheels, and upload them to PyPI with Twine. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633909928.0 flake8-4.0.1/docs/source/internal/start-to-finish.rst0000644000175000017500000001011600000000000024211 0ustar00asottileasottile00000000000000================================== What Happens When You Run Flake8 ================================== Given |Flake8| 3.0's new organization and structure, it might be a bit much for some people to understand what happens from when you call ``flake8`` on the command-line to when it completes. This section aims to give you something of a technical overview of what exactly happens. Invocation ========== The exact way that we end up in our ``main`` function for Flake8 depends on how you invoke it. If you do something like: .. prompt:: bash flake8 Then your shell looks up where ``flake8`` the executable lives and executes it. In almost every case, this is a tiny python script generated by ``setuptools`` using the console script entry points that |Flake8| declares in its :file:`setup.py`. This might look something like: .. code-block:: python #!/path/to/python # EASY-INSTALL-ENTRY-SCRIPT: 'flake8==3.0.0','console_scripts','flake8' __requires__ = 'flake8==3.0.0' import sys from pkg_resources import load_entry_point if __name__ == '__main__': sys.exit( load_entry_point('flake8==3.0.0', 'console_scripts', 'flake8')() ) If instead you invoke it like: .. prompt:: bash python -m flake8 Then you're relying on Python to find :mod:`flake8.__main__` and run that. In both cases, however, you end up in :func:`flake8.main.cli.main`. This is the primary way that users will end up starting Flake8. This function creates an instance of |Application|. Application Logic ================= When we create our |Application| instance, we record the start time and parse our command-line arguments so we can configure the verbosity of |Flake8|'s logging. For the most part, every path then calls :meth:`~flake8.main.application.Application.run` which in turn calls: - :meth:`~flake8.main.application.Application.initialize` - :meth:`~flake8.main.application.Application.run_checks` - :meth:`~flake8.main.application.Application.report_errors` - :meth:`~flake8.main.application.Application.report_benchmarks` Our Git hook, however, runs these individually. Application Initialization -------------------------- :meth:`~flake8.main.application.Application.initialize` loads all of our :term:`plugin`\ s, registers the options for those plugins, parses the command-line arguments, makes our formatter (as selected by the user), makes our :class:`~flake8.style_guide.StyleGuide` and finally makes our :class:`file checker manager `. Running Our Checks ------------------ :meth:`~flake8.main.application.Application.run_checks` then creates an instance of :class:`flake8.checker.FileChecker` for each file to be checked after aggregating all of the files that are not excluded and match the provided file-patterns. Then, if we're on a system that supports :mod:`multiprocessing` **and** :option:`flake8 --jobs` is either ``auto`` or a number greater than 1, we will begin processing the files in subprocesses. Otherwise, we'll run the checks in parallel. After we start running the checks, we start aggregating the reported :term:`violation`\ s in the main process. After the checks are done running, we record the end time. Reporting Violations -------------------- Next, the application takes the violations from the file checker manager, and feeds them through the :class:`~flake8.style_guide.StyleGuide`. This relies on a :class:`~flake8.style_guide.DecisionEngine` instance to determine whether the particular :term:`error code` is selected or ignored and then appropriately sends it to the formatter (or not). Reporting Benchmarks -------------------- Finally, if the user has asked to see benchmarks (i.e., :option:`flake8 --benchmark`) then we print the benchmarks. Exiting ======= Once :meth:`~flake8.main.application.Application.run` has finished, we then call :meth:`~flake8.main.application.Application.exit` which looks at how many errors were reported and whether the user specified :option:`flake8 --exit-zero` and exits with the appropriate exit code. .. Replacements .. |Application| replace:: :class:`~flake8.main.application.Application` ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633909928.0 flake8-4.0.1/docs/source/internal/utils.rst0000644000175000017500000001011000000000000022310 0ustar00asottileasottile00000000000000=================== Utility Functions =================== |Flake8| has a few utility functions that it uses internally. .. warning:: As should be implied by where these are documented, these are all **internal** utility functions. Their signatures and return types may change between releases without notice. Bugs reported about these **internal** functions will be closed immediately. If functions are needed by plugin developers, they may be requested in the bug tracker and after careful consideration they *may* be added to the *documented* stable API. .. autofunction:: flake8.utils.parse_comma_separated_list :func:`~flake8.utils.parse_comma_separated_list` takes either a string like .. code-block:: python "E121,W123,F904" "E121,\nW123,\nF804" " E121,\n\tW123,\n\tF804 " " E121\n\tW123 \n\tF804" And converts it to a list that looks as follows .. code-block:: python ["E121", "W123", "F904"] This function helps normalize any kind of comma-separated input you or |Flake8| might receive. This is most helpful when taking advantage of |Flake8|'s additional parameters to :class:`~flake8.options.manager.Option`. .. autofunction:: flake8.utils.normalize_path This utility takes a string that represents a path and returns the absolute path if the string has a ``/`` in it. It also removes trailing ``/``\ s. .. autofunction:: flake8.utils.normalize_paths This function utilizes :func:`~flake8.utils.normalize_path` to normalize a sequence of paths. See :func:`~flake8.utils.normalize_path` for what defines a normalized path. .. autofunction:: flake8.utils.stdin_get_value This function retrieves and caches the value provided on ``sys.stdin``. This allows plugins to use this to retrieve ``stdin`` if necessary. .. autofunction:: flake8.utils.is_windows This provides a convenient and explicitly named function that checks if we are currently running on a Windows (or ``nt``) operating system. .. autofunction:: flake8.utils.is_using_stdin Another helpful function that is named only to be explicit given it is a very trivial check, this checks if the user specified ``-`` in their arguments to |Flake8| to indicate we should read from stdin. .. autofunction:: flake8.utils.filenames_from When provided an argument to |Flake8|, we need to be able to traverse directories in a convenient manner. For example, if someone runs .. code:: $ flake8 flake8/ Then they want us to check all of the files in the directory ``flake8/``. This function will handle that while also handling the case where they specify a file like: .. code:: $ flake8 flake8/__init__.py .. autofunction:: flake8.utils.fnmatch The standard library's :func:`fnmatch.fnmatch` is excellent at deciding if a filename matches a single pattern. In our use case, however, we typically have a list of patterns and want to know if the filename matches any of them. This function abstracts that logic away with a little extra logic. .. autofunction:: flake8.utils.parameters_for |Flake8| analyzes the parameters to plugins to determine what input they are expecting. Plugins may expect one of the following: - ``physical_line`` to receive the line as it appears in the file - ``logical_line`` to receive the logical line (not as it appears in the file) - ``tree`` to receive the abstract syntax tree (AST) for the file We also analyze the rest of the parameters to provide more detail to the plugin. This function will return the parameters in a consistent way across versions of Python and will handle both classes and functions that are used as plugins. Further, if the plugin is a class, it will strip the ``self`` argument so we can check the parameters of the plugin consistently. .. autofunction:: flake8.utils.parse_unified_diff To handle usage of :option:`flake8 --diff`, |Flake8| needs to be able to parse the name of the files in the diff as well as the ranges indicated the sections that have been changed. This function either accepts the diff as an argument or reads the diff from standard-in. It then returns a dictionary with filenames as the keys and sets of line numbers as the value. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633909928.0 flake8-4.0.1/docs/source/internal/writing-code.rst0000644000175000017500000001622000000000000023553 0ustar00asottileasottile00000000000000.. _writing-code: ========================= Writing Code for Flake8 ========================= The maintainers of |Flake8| unsurprisingly have some opinions about the style of code maintained in the project. At the time of this writing, |Flake8| enables all of PyCodeStyle's checks, all of PyFlakes' checks, and sets a maximum complexity value (for McCabe) of 10. On top of that, we enforce PEP-0257 style doc-strings via PyDocStyle (disabling only D203) and Google's import order style using flake8-import-order. The last two are a little unusual, so we provide examples below. PEP-0257 style doc-strings ========================== |Flake8| attempts to document both internal interfaces as well as our API and doc-strings provide a very convenient way to do so. Even if a function, class, or method isn't included specifically in our documentation having a doc-string is still preferred. Further, |Flake8| has some style preferences that are not checked by PyDocStyle. For example, while most people will never read the doc-string for :func:`flake8.main.git.hook` that doc-string still provides value to the maintainers and future collaborators. They (very explicitly) describe the purpose of the function, a little of what it does, and what parameters it accepts as well as what it returns. .. code-block:: python # src/flake8/main/git.py def hook(lazy=False, strict=False): """Execute Flake8 on the files in git's index. Determine which files are about to be committed and run Flake8 over them to check for violations. :param bool lazy: Find files not added to the index prior to committing. This is useful if you frequently use ``git commit -a`` for example. This defaults to False since it will otherwise include files not in the index. :param bool strict: If True, return the total number of errors/violations found by Flake8. This will cause the hook to fail. :returns: Total number of errors found during the run. :rtype: int """ # NOTE(sigmavirus24): Delay import of application until we need it. from flake8.main import application app = application.Application() with make_temporary_directory() as tempdir: filepaths = list(copy_indexed_files_to(tempdir, lazy)) app.initialize(['.']) app.options.exclude = update_excludes(app.options.exclude, tempdir) app.run_checks(filepaths) app.report_errors() if strict: return app.result_count return 0 Note that because the parameters ``hook`` and ``strict`` are simply boolean parameters, we inline the type declaration for those parameters, e.g., .. code-block:: restructuredtext :param bool lazy: Also note that we begin the description of the parameter on a new-line and indented 4 spaces. On the other hand, we also separate the parameter type declaration in some places where the name is a little longer, e.g., .. code-block:: python # src/flake8/formatting/base.py def format(self, error): """Format an error reported by Flake8. This method **must** be implemented by subclasses. :param error: This will be an instance of :class:`~flake8.style_guide.Error`. :type error: flake8.style_guide.Error :returns: The formatted error string. :rtype: str """ Here we've separated ``:param error:`` and ``:type error:``. Following the above examples and guidelines should help you write doc-strings that are stylistically correct for |Flake8|. Imports ======= |Flake8| follows the import guidelines that Google published in their Python Style Guide. In short this includes: - Only importing modules - Grouping imports into * standard library imports * third-party dependency imports * local application imports - Ordering imports alphabetically In practice this would look something like: .. code-block:: python import configparser import logging from os import path import requests from flake8 import exceptions from flake8.formatting import base As a result, of the above, we do not: - Import objects into a namespace to make them accessible from that namespace - Import only the objects we're using - Add comments explaining that an import is a standard library module or something else Other Stylistic Preferences =========================== Finally, |Flake8| has a few other stylistic preferences that it does not presently enforce automatically. Multi-line Function/Method Calls -------------------------------- When you find yourself having to split a call to a function or method up across multiple lines, insert a new-line after the opening parenthesis, e.g., .. code-block:: python # src/flake8/main/options.py add_option( '-v', '--verbose', default=0, action='count', parse_from_config=True, help='Print more information about what is happening in flake8.' ' This option is repeatable and will increase verbosity each ' 'time it is repeated.', ) # src/flake8/formatting/base.py def show_statistics(self, statistics): """Format and print the statistics.""" for error_code in statistics.error_codes(): stats_for_error_code = statistics.statistics_for(error_code) statistic = next(stats_for_error_code) count = statistic.count count += sum(stat.count for stat in stats_for_error_code) self._write(f'{count:<5} {error_code} {statistic.message}') In the first example, we put a few of the parameters all on one line, and then added the last two on their own. In the second example, each parameter has its own line. This particular rule is a little subjective. The general idea is that putting one parameter per-line is preferred, but sometimes it's reasonable and understandable to group a few together on one line. Comments -------- If you're adding an important comment, be sure to sign it. In |Flake8| we generally sign comments by preceding them with ``NOTE()``. For example, .. code-block:: python # NOTE(sigmavirus24): The format strings are a little confusing, even # to me, so here's a quick explanation: # We specify the named value first followed by a ':' to indicate we're # formatting the value. # Next we use '<' to indicate we want the value left aligned. # Then '10' is the width of the area. # For floats, finally, we only want only want at most 3 digits after # the decimal point to be displayed. This is the precision and it # can not be specified for integers which is why we need two separate # format strings. float_format = '{value:<10.3} {statistic}'.format int_format = '{value:<10} {statistic}'.format Ian is well known across most websites as ``sigmavirus24`` so he signs his comments that way. Verbs Belong in Function Names ------------------------------ |Flake8| prefers that functions have verbs in them. If you're writing a function that returns a generator of files then ``generate_files`` will always be preferable to ``make_files`` or ``files``. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633909928.0 flake8-4.0.1/docs/source/internal/writing-documentation.rst0000644000175000017500000001214400000000000025513 0ustar00asottileasottile00000000000000.. _docs-style: ================================== Writing Documentation for Flake8 ================================== The maintainers of |Flake8| believe strongly in benefit of style guides. Hence, for all contributors who wish to work on our documentation, we've put together a loose set of guidelines and best practices when adding to our documentation. View the docs locally before submitting ======================================= You can and should generate the docs locally before you submit a pull request with your changes. You can build the docs by running: .. prompt:: bash tox -e docs From the directory containing the ``tox.ini`` file (which also contains the ``docs/`` directory that this file lives in). .. note:: If the docs don't build locally, they will not build in our continuous integration system. We will generally not merge any pull request that fails continuous integration. Run the docs linter tests before submitting =========================================== You should run the ``doc8`` linter job before you're ready to commit and fix any errors found. Capitalize Flake8 in prose ========================== We believe that by capitalizing |Flake8| in prose, we can help reduce confusion between the command-line usage of ``flake8`` and the project. We also have defined a global replacement ``|Flake8|`` that should be used and will replace each instance with ``:program:`Flake8```. Use the prompt directive for command-line examples ================================================== When documenting something on the command-line, use the ``.. prompt::`` directive to make it easier for users to copy and paste into their terminal. Example: .. code-block:: restructuredtext .. prompt:: bash flake8 --select E123,W503 dir/ flake8 --ignore E24,W504 dir Wrap lines around 79 characters =============================== We use a maximum line-length in our documentation that is similar to the default in |Flake8|. Please wrap lines at 79 characters (or less). Use two new-lines before new sections ===================================== After the final paragraph of a section and before the next section title, use two new-lines to separate them. This makes reading the plain-text document a little nicer. Sphinx ignores these when rendering so they have no semantic meaning. Example: .. code-block:: restructuredtext Section Header ============== Paragraph. Next Section Header =================== Paragraph. Surround document titles with equal symbols =========================================== To indicate the title of a document, we place an equal number of ``=`` symbols on the lines before and after the title. For example: .. code-block:: restructuredtext ================================== Writing Documentation for Flake8 ================================== Note also that we "center" the title by adding a leading space and having extra ``=`` symbols at the end of those lines. Use the option template for new options ======================================= All of |Flake8|'s command-line options are documented in the User Guide. Each option is documented individually using the ``.. option::`` directive provided by Sphinx. At the top of the document, in a reStructuredText comment, is a template that should be copied and pasted into place when documening new options. .. note:: The ordering of the options page is the order that options are printed in the output of: .. prompt:: bash flake8 --help Please insert your option documentation according to that order. Use anchors for easy reference linking ====================================== Use link anchors to allow for other areas of the documentation to use the ``:ref:`` role for intralinking documentation. Example: .. code-block:: restructuredtext .. _use-anchors: Use anchors for easy reference linking ====================================== .. code-block:: restructuredtext Somewhere in this paragraph we will :ref:`reference anchors `. .. note:: You do not need to provide custom text for the ``:ref:`` if the title of the section has a title that is sufficient. Keep your audience in mind ========================== |Flake8|'s documentation has three distinct (but not separate) audiences: #. Users #. Plugin Developers #. Flake8 Developers and Contributors At the moment, you're one of the third group (because you're contributing or thinking of contributing). Consider that most Users aren't very interested in the internal working of |Flake8|. When writing for Users, focus on how to do something or the behaviour of a certain piece of configuration or invocation. Plugin developers will only care about the internals of |Flake8| as much as they will have to interact with that. Keep discussions of internal to the mininmum required. Finally, Flake8 Developers and Contributors need to know how everything fits together. We don't need detail about every line of code, but cogent explanations and design specifications will help future developers understand the Hows and Whys of |Flake8|'s internal design. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633909928.0 flake8-4.0.1/docs/source/manpage.rst0000644000175000017500000001343100000000000020755 0ustar00asottileasottile00000000000000======== flake8 ======== SYNOPSIS ======== .. code:: flake8 [options] [ ...] flake8 --help DESCRIPTION =========== ``flake8`` is a command-line utility for enforcing style consistency across Python projects. By default it includes lint checks provided by the PyFlakes project, PEP-0008 inspired style checks provided by the PyCodeStyle project, and McCabe complexity checking provided by the McCabe project. It will also run third-party extensions if they are found and installed. OPTIONS ======= It is important to note that third-party extensions may add options which are not represented here. To see all options available in your installation, run:: flake8 --help All options available as of Flake8 3.1.0:: --version show program's version number and exit -h, --help show this help message and exit -v, --verbose Print more information about what is happening in flake8. This option is repeatable and will increase verbosity each time it is repeated. -q, --quiet Report only file names, or nothing. This option is repeatable. --count Print total number of errors and warnings to standard error and set the exit code to 1 if total is not empty. --diff Report changes only within line number ranges in the unified diff provided on standard in by the user. --exclude=patterns Comma-separated list of files or directories to exclude. (Default: .svn,CVS,.bzr,.hg,.git,__pycache__,.tox,.eggs,*.egg) --filename=patterns Only check for filenames matching the patterns in this comma-separated list. (Default: *.py) --stdin-display-name=STDIN_DISPLAY_NAME The name used when reporting errors from code passed via stdin. This is useful for editors piping the file contents to flake8. (Default: stdin) --format=format Format errors according to the chosen formatter. --hang-closing Hang closing bracket instead of matching indentation of opening bracket's line. --ignore=errors Comma-separated list of errors and warnings to ignore (or skip). For example, ``--ignore=E4,E51,W234``. (Default: E121,E123,E126,E226,E24,E704,W503,W504) --max-line-length=n Maximum allowed line length for the entirety of this run. (Default: 79) --select=errors Comma-separated list of errors and warnings to enable. For example, ``--select=E4,E51,W234``. (Default: E,F,W,C90) --disable-noqa Disable the effect of "# noqa". This will report errors on lines with "# noqa" at the end. --show-source Show the source generate each error or warning. --statistics Count errors and warnings. --enable-extensions=ENABLE_EXTENSIONS Enable plugins and extensions that are otherwise disabled by default --exit-zero Exit with status code "0" even if there are errors. -j JOBS, --jobs=JOBS Number of subprocesses to use to run checks in parallel. This is ignored on Windows. The default, "auto", will auto-detect the number of processors available to use. (Default: auto) --output-file=OUTPUT_FILE Redirect report to a file. --tee Write to stdout and output-file. --append-config=APPEND_CONFIG Provide extra config files to parse in addition to the files found by Flake8 by default. These files are the last ones read and so they take the highest precedence when multiple files provide the same option. --config=CONFIG Path to the config file that will be the authoritative config source. This will cause Flake8 to ignore all other configuration files. --isolated Ignore all configuration files. --benchmark Print benchmark information about this run of Flake8 --bug-report Print information necessary when preparing a bug report --builtins=BUILTINS define more built-ins, comma separated --doctests check syntax of the doctests --include-in-doctest=INCLUDE_IN_DOCTEST Run doctests only on these files --exclude-from-doctest=EXCLUDE_FROM_DOCTEST Skip these files when running doctests --max-complexity=MAX_COMPLEXITY McCabe complexity threshold EXAMPLES ======== Simply running flake8 against the current directory:: flake8 flake8 . Running flake8 against a specific path:: flake8 path/to/file.py Ignoring violations from flake8:: flake8 --ignore E101 flake8 --ignore E1,E202 Only report certain violations:: flake8 --select E101 flake8 --select E2,E742 Analyzing only a diff:: git diff -U0 | flake8 --diff - Generate information for a bug report:: flake8 --bug-report SEE ALSO ======== Flake8 documentation: http://flake8.pycqa.org Flake8 Options and Examples: http://flake8.pycqa.org/en/latest/user/options.html PyCodeStyle documentation: http://pycodestyle.pycqa.org PyFlakes: https://github.com/pycqa/pyflakes McCabe: https://github.com/pycqa/mccabe BUGS ==== Please report all bugs to https://github.com/pycqa/flake8 ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1633956150.3374734 flake8-4.0.1/docs/source/plugin-development/0000755000175000017500000000000000000000000022427 5ustar00asottileasottile00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633909928.0 flake8-4.0.1/docs/source/plugin-development/cross-compatibility.rst0000644000175000017500000001506600000000000027171 0ustar00asottileasottile00000000000000==================================== Writing Plugins For Flake8 2 and 3 ==================================== Plugins have existed for |Flake8| 2.x for a few years. There are a number of these on PyPI already. While it did not seem reasonable for |Flake8| to attempt to provide a backwards compatible shim for them, we did decide to try to document the easiest way to write a plugin that's compatible across both versions. .. note:: If your plugin does not register options, it *should* Just Work. The **only two** breaking changes in |Flake8| 3.0 is the fact that we no longer check the option parser for a list of strings to parse from a config file and we no longer patch pep8 or pycodestyle's ``stdin_get_value`` functions. On |Flake8| 2.x, to have an option parsed from the configuration files that |Flake8| finds and parses you would have to do something like: .. code-block:: python parser.add_option('-X', '--example-flag', type='string', help='...') parser.config_options.append('example-flag') For |Flake8| 3.0, we have added *three* arguments to the :meth:`~flake8.options.manager.OptionManager.add_option` method you will call on the parser you receive: - ``parse_from_config`` which expects ``True`` or ``False`` When ``True``, |Flake8| will parse the option from the config files |Flake8| finds. - ``comma_separated_list`` which expects ``True`` or ``False`` When ``True``, |Flake8| will split the string intelligently and handle extra whitespace. The parsed value will be a list. - ``normalize_paths`` which expects ``True`` or ``False`` When ``True``, |Flake8| will: * remove trailing path separators (i.e., ``os.path.sep``) * return the absolute path for values that have the separator in them All three of these options can be combined or used separately. Parsing Options from Configuration Files ======================================== The example from |Flake8| 2.x now looks like: .. code-block:: python parser.add_option('-X', '--example-flag', type='string', parse_from_config=True, help='...') Parsing Comma-Separated Lists ============================= Now let's imagine that the option we want to add is expecting a comma-separatd list of values from the user (e.g., ``--select E123,W503,F405``). |Flake8| 2.x often forced users to parse these lists themselves since pep8 special-cased certain flags and left others on their own. |Flake8| 3.0 adds ``comma_separated_list`` so that the parsed option is already a list for plugin authors. When combined with ``parse_from_config`` this means that users can also do something like: .. code-block:: ini example-flag = first, second, third, fourth, fifth And |Flake8| will just return the list: .. code-block:: python ["first", "second", "third", "fourth", "fifth"] Normalizing Values that Are Paths ================================= Finally, let's imagine that our new option wants a path or list of paths. To ensure that these paths are semi-normalized (the way |Flake8| 2.x used to work) we need only pass ``normalize_paths=True``. If you have specified ``comma_separated_list=True`` then this will parse the value as a list of paths that have been normalized. Otherwise, this will parse the value as a single path. Option Handling on Flake8 2 and 3 ================================= To ease the transition, the |Flake8| maintainers have released `flake8-polyfill`_. |polyfill| provides a convenience function to help users transition between Flake8 2 and 3 without issue. For example, if your plugin has to work on Flake8 2.x and 3.x but you want to take advantage of some of the new options to ``add_option``, you can do .. code-block:: python from flake8_polyfill import options class MyPlugin(object): @classmethod def add_options(cls, parser): options.register( parser, '--application-names', default='', type='string', help='Names of the applications to be checked.', parse_from_config=True, comma_separated_list=True, ) options.register( parser, '--style-name', default='', type='string', help='The name of the style convention you want to use', parse_from_config=True, ) options.register( parser, '--application-paths', default='', type='string', help='Locations of the application code', parse_from_config=True, comma_separated_list=True, normalize_paths=True, ) @classmethod def parse_options(cls, parsed_options): cls.application_names = parsed_options.application_names cls.style_name = parsed_options.style_name cls.application_paths = parsed_options.application_paths |polyfill| will handle these extra options using *callbacks* to the option parser. The project has direct replications of the functions that |Flake8| uses to provide the same functionality. This means that the values you receive should be identically parsed whether you're using Flake8 2.x or 3.x. .. autofunction:: flake8_polyfill.options.register Standard In Handling on Flake8 2.5, 2.6, and 3 ============================================== After releasing |Flake8| 2.6, handling standard-in became a bit trickier for some plugins. |Flake8| 2.5 and earlier had started monkey-patching pep8's ``stdin_get_value`` function. 2.6 switched to pycodestyle and only monkey-patched that. 3.0 has its own internal implementation and uses that but does not directly provide anything for plugins using pep8 and pycodestyle's ``stdin_get_value`` function. |polyfill| provides this functionality for plugin developers via its :mod:`flake8_polyfill.stdin` module. If a plugin needs to read the content from stdin, it can do the following: .. code-block:: python from flake8_polyfill import stdin stdin.monkey_patch('pep8') # To monkey-patch only pep8 stdin.monkey_patch('pycodestyle') # To monkey-patch only pycodestyle stdin.monkey_patch('all') # To monkey-patch both pep8 and pycodestyle Further, when using ``all``, |polyfill| does not require both packages to be installed but will attempt to monkey-patch both and will silently ignore the fact that pep8 or pycodestyle is not installed. .. autofunction:: flake8_polyfill.stdin.monkey_patch .. links .. _flake8-polyfill: https://pypi.org/project/flake8-polyfill/ .. |polyfill| replace:: ``flake8-polyfill`` ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633909928.0 flake8-4.0.1/docs/source/plugin-development/formatters.rst0000644000175000017500000000343000000000000025347 0ustar00asottileasottile00000000000000.. _formatting-plugins: =========================================== Developing a Formatting Plugin for Flake8 =========================================== |Flake8| allowed for custom formatting plugins in version 3.0.0. Let's write a plugin together: .. code-block:: python from flake8.formatting import base class Example(base.BaseFormatter): """Flake8's example formatter.""" pass We notice, as soon as we start, that we inherit from |Flake8|'s :class:`~flake8.formatting.base.BaseFormatter` class. If we follow the :ref:`instructions to register a plugin ` and try to use our example formatter, e.g., ``flake8 --format=example`` then |Flake8| will fail because we did not implement the ``format`` method. Let's do that next. .. code-block:: python class Example(base.BaseFormatter): """Flake8's example formatter.""" def format(self, error): return 'Example formatter: {0!r}'.format(error) With that we're done. Obviously this isn't a very useful formatter, but it should highlight the simplicity of creating a formatter with Flake8. If we wanted to instead create a formatter that aggregated the results and returned XML, JSON, or subunit we could also do that. |Flake8| interacts with the formatter in two ways: #. It creates the formatter and provides it the options parsed from the configuration files and command-line #. It uses the instance of the formatter and calls ``handle`` with the error. By default :meth:`flake8.formatting.base.BaseFormatter.handle` simply calls the ``format`` method and then ``write``. Any extra handling you wish to do for formatting purposes should override the ``handle`` method. API Documentation ================= .. autoclass:: flake8.formatting.base.BaseFormatter :members: ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633909928.0 flake8-4.0.1/docs/source/plugin-development/index.rst0000644000175000017500000000367000000000000024276 0ustar00asottileasottile00000000000000============================ Writing Plugins for Flake8 ============================ Since |Flake8| 2.0, the |Flake8| tool has allowed for extensions and custom plugins. In |Flake8| 3.0, we're expanding that ability to customize and extend **and** we're attempting to thoroughly document it. Some of the documentation in this section may reference third-party documentation to reduce duplication and to point you, the developer, towards the authoritative documentation for those pieces. Getting Started =============== To get started writing a |Flake8| :term:`plugin` you first need: - An idea for a plugin - An available package name on PyPI - One or more versions of Python installed - A text editor or IDE of some kind - An idea of what *kind* of plugin you want to build: * Formatter * Check Once you've gathered these things, you can get started. All plugins for |Flake8| must be registered via `entry points`_. In this section we cover: - How to register your plugin so |Flake8| can find it - How to make |Flake8| provide your check plugin with information (via command-line flags, function/class parameters, etc.) - How to make a formatter plugin - How to write your check plugin so that it works with |Flake8| 2.x and 3.x Video Tutorial ============== Here's a tutorial which goes over building an ast checking plugin from scratch: .. raw:: html
.. toctree:: :caption: Plugin Developer Documentation :maxdepth: 2 registering-plugins plugin-parameters formatters cross-compatibility .. _entry points: https://setuptools.readthedocs.io/en/latest/pkg_resources.html#entry-points ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633909928.0 flake8-4.0.1/docs/source/plugin-development/plugin-parameters.rst0000644000175000017500000001541700000000000026630 0ustar00asottileasottile00000000000000.. _plugin-parameters: ========================================== Receiving Information For A Check Plugin ========================================== Plugins to |Flake8| have a great deal of information that they can request from a :class:`~flake8.processor.FileProcessor` instance. Historically, |Flake8| has supported two types of plugins: #. classes that accept parsed abstract syntax trees (ASTs) #. functions that accept a range of arguments |Flake8| now does not distinguish between the two types of plugins. Any plugin can accept either an AST or a range of arguments. Further, any plugin that has certain callable attributes can also register options and receive parsed options. Indicating Desired Data ======================= |Flake8| inspects the plugin's signature to determine what parameters it expects using :func:`flake8.utils.parameters_for`. :attr:`flake8.plugins.manager.Plugin.parameters` caches the values so that each plugin makes that fairly expensive call once per plugin. When processing a file, a plugin can ask for any of the following: - :attr:`~flake8.processor.FileProcessor.blank_before` - :attr:`~flake8.processor.FileProcessor.blank_lines` - :attr:`~flake8.processor.FileProcessor.checker_state` - :attr:`~flake8.processor.FileProcessor.indent_char` - :attr:`~flake8.processor.FileProcessor.indent_level` - :attr:`~flake8.processor.FileProcessor.line_number` - :attr:`~flake8.processor.FileProcessor.logical_line` - :attr:`~flake8.processor.FileProcessor.multiline` - :attr:`~flake8.processor.FileProcessor.noqa` - :attr:`~flake8.processor.FileProcessor.previous_indent_level` - :attr:`~flake8.processor.FileProcessor.previous_logical` - :attr:`~flake8.processor.FileProcessor.previous_unindented_logical_line` - :attr:`~flake8.processor.FileProcessor.tokens` Some properties are set once per file for plugins which iterate itself over the data instead of being called on each physical or logical line. - :attr:`~flake8.processor.FileProcessor.filename` - :attr:`~flake8.processor.FileProcessor.file_tokens` - :attr:`~flake8.processor.FileProcessor.lines` - :attr:`~flake8.processor.FileProcessor.max_line_length` - :attr:`~flake8.processor.FileProcessor.max_doc_length` - :attr:`~flake8.processor.FileProcessor.total_lines` - :attr:`~flake8.processor.FileProcessor.verbose` These parameters can also be supplied to plugins working on each line separately. Plugins that depend on ``physical_line`` or ``logical_line`` are run on each physical or logical line once. These parameters should be the first in the list of arguments (with the exception of ``self``). Plugins that need an AST (e.g., PyFlakes and McCabe) should depend on ``tree``. These plugins will run once per file. The parameters listed above can be combined with ``physical_line``, ``logical_line``, and ``tree``. Registering Options =================== Any plugin that has callable attributes ``add_options`` and ``parse_options`` can parse option information and register new options. Your ``add_options`` function should expect to receive an instance of |OptionManager|. An |OptionManager| instance behaves very similarly to :class:`optparse.OptionParser`. It, however, uses the layer that |Flake8| has developed on top of :mod:`argparse` to also handle configuration file parsing. :meth:`~flake8.options.manager.OptionManager.add_option` creates an |Option| which accepts the same parameters as :mod:`optparse` as well as three extra boolean parameters: - ``parse_from_config`` The command-line option should also be parsed from config files discovered by |Flake8|. .. note:: This takes the place of appending strings to a list on the :class:`optparse.OptionParser`. - ``comma_separated_list`` The value provided to this option is a comma-separated list. After parsing the value, it should be further broken up into a list. This also allows us to handle values like: .. code:: E123,E124, E125, E126 - ``normalize_paths`` The value provided to this option is a path. It should be normalized to be an absolute path. This can be combined with ``comma_separated_list`` to allow a comma-separated list of paths. Each of these options works individually or can be combined. Let's look at a couple examples from |Flake8|. In each example, we will have ``option_manager`` which is an instance of |OptionManager|. .. code-block:: python option_manager.add_option( '--max-line-length', type='int', metavar='n', default=defaults.MAX_LINE_LENGTH, parse_from_config=True, help='Maximum allowed line length for the entirety of this run. ' '(Default: %(default)s)', ) Here we are adding the ``--max-line-length`` command-line option which is always an integer and will be parsed from the configuration file. Since we provide a default, we take advantage of :mod:`argparse`\ 's willingness to display that in the help text with ``%(default)s``. .. code-block:: python option_manager.add_option( '--select', metavar='errors', default='', parse_from_config=True, comma_separated_list=True, help='Comma-separated list of errors and warnings to enable.' ' For example, ``--select=E4,E51,W234``. (Default: %(default)s)', ) In adding the ``--select`` command-line option, we're also indicating to the |OptionManager| that we want the value parsed from the config files and parsed as a comma-separated list. .. code-block:: python option_manager.add_option( '--exclude', metavar='patterns', default=defaults.EXCLUDE, comma_separated_list=True, parse_from_config=True, normalize_paths=True, help='Comma-separated list of files or directories to exclude.' '(Default: %(default)s)', ) Finally, we show an option that uses all three extra flags. Values from ``--exclude`` will be parsed from the config, converted from a comma-separated list, and then each item will be normalized. For information about other parameters to :meth:`~flake8.options.manager.OptionManager.add_option` refer to the documentation of :mod:`argparse`. Accessing Parsed Options ======================== When a plugin has a callable ``parse_options`` attribute, |Flake8| will call it and attempt to provide the |OptionManager| instance, the parsed options which will be an instance of :class:`argparse.Namespace`, and the extra arguments that were not parsed by the |OptionManager|. If that fails, we will just pass the :class:`argparse.Namespace`. In other words, your ``parse_options`` callable will have one of the following signatures: .. code-block:: python def parse_options(option_manager, options, args): pass # or def parse_options(options): pass .. substitutions .. |OptionManager| replace:: :class:`~flake8.options.manager.OptionManager` .. |Option| replace:: :class:`~flake8.options.manager.Option` ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633909928.0 flake8-4.0.1/docs/source/plugin-development/registering-plugins.rst0000644000175000017500000000767100000000000027175 0ustar00asottileasottile00000000000000.. _register-a-plugin: ================================== Registering a Plugin with Flake8 ================================== To register any kind of plugin with |Flake8|, you need: #. A way to install the plugin (whether it is packaged on its own or as part of something else). In this section, we will use a ``setup.py`` written for an example plugin. #. A name for your plugin that will (ideally) be unique. #. A somewhat recent version of setuptools (newer than 0.7.0 but preferably as recent as you can attain). |Flake8| relies on functionality provided by setuptools called `Entry Points`_. These allow any package to register a plugin with |Flake8| via that package's ``setup.py`` file. Let's presume that we already have our plugin written and it's in a module called ``flake8_example``. We might have a ``setup.py`` that looks something like: .. code-block:: python import setuptools requires = [ "flake8 > 3.0.0", ] flake8_entry_point = # ... setuptools.setup( name="flake8_example", license="MIT", version="0.1.0", description="our extension to flake8", author="Me", author_email="example@example.com", url="https://github.com/me/flake8_example", packages=[ "flake8_example", ], install_requires=requires, entry_points={ flake8_entry_point: [ 'X = flake8_example:ExamplePlugin', ], }, classifiers=[ "Framework :: Flake8", "Environment :: Console", "Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Programming Language :: Python", "Programming Language :: Python :: 3", "Topic :: Software Development :: Libraries :: Python Modules", "Topic :: Software Development :: Quality Assurance", ], ) Note specifically these lines: .. code-block:: python flake8_entry_point = # ... setuptools.setup( # snip ... entry_points={ flake8_entry_point: [ 'X = flake8_example:ExamplePlugin', ], }, # snip ... ) We tell setuptools to register our entry point ``X`` inside the specific grouping of entry-points that flake8 should look in. |Flake8| presently looks at two groups: - ``flake8.extension`` - ``flake8.report`` If your plugin is one that adds checks to |Flake8|, you will use ``flake8.extension``. If your plugin performs extra report handling (formatting, filtering, etc.) it will use ``flake8.report``. If our ``ExamplePlugin`` is something that adds checks, our code would look like: .. code-block:: python setuptools.setup( # snip ... entry_points={ 'flake8.extension': [ 'X = flake8_example:ExamplePlugin', ], }, # snip ... ) The ``X`` in checking plugins define what error codes it is going to report. So if the plugin reports only the error code ``X101`` your entry-point would look like:: X101 = flake8_example:ExamplePlugin If your plugin reports several error codes that all start with ``X10``, then it would look like:: X10 = flake8_example:ExamplePlugin If all of your plugin's error codes start with ``X1`` then it would look like:: X1 = flake8_example:ExamplePlugin Finally, if all of your plugin's error codes start with just ``X`` then it would look like the original example. |Flake8| requires each entry point to be unique amongst all plugins installed in the users environment. Selecting an entry point that is already used can cause plugins to be deactivated without warning! **Please Note:** Your entry point does not need to be exactly 4 characters as of |Flake8| 3.0. *Consider using an entry point with 3 letters followed by 3 numbers (i.e.* ``ABC123`` *).* .. _Entry Points: https://setuptools.readthedocs.io/en/latest/pkg_resources.html#entry-points ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1633956150.3454707 flake8-4.0.1/docs/source/release-notes/0000755000175000017500000000000000000000000021357 5ustar00asottileasottile00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633909928.0 flake8-4.0.1/docs/source/release-notes/0.6.0.rst0000644000175000017500000000011100000000000022543 0ustar00asottileasottile000000000000000.6 - 2010-02-15 ---------------- - Fix the McCabe metric on some loops ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633909928.0 flake8-4.0.1/docs/source/release-notes/0.7.0.rst0000644000175000017500000000030200000000000022546 0ustar00asottileasottile000000000000000.7 - 2010-02-18 ---------------- - Fix pep8 initialization when run through Hg - Make pep8 short options work when run through the command line - Skip duplicates when controlling files via Hg ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633909928.0 flake8-4.0.1/docs/source/release-notes/0.8.0.rst0000644000175000017500000000013400000000000022552 0ustar00asottileasottile000000000000000.8 - 2011-02-27 ---------------- - fixed hg hook - discard unexisting files on hook check ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633909928.0 flake8-4.0.1/docs/source/release-notes/0.9.0.rst0000644000175000017500000000016400000000000022556 0ustar00asottileasottile000000000000000.9 - 2011-11-09 ---------------- - update pep8 version to 0.6.1 - mccabe check: gracefully handle compile failure ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633909928.0 flake8-4.0.1/docs/source/release-notes/1.0.0.rst0000644000175000017500000000023300000000000022543 0ustar00asottileasottile000000000000001.0 - 2011-11-29 ---------------- - Deactivates by default the complexity checker - Introduces the complexity option in the HG hook and the command line. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633909928.0 flake8-4.0.1/docs/source/release-notes/1.1.0.rst0000644000175000017500000000041200000000000022543 0ustar00asottileasottile000000000000001.1 - 2012-02-14 ---------------- - fixed the value returned by --version - allow the flake8: header to be more generic - fixed the "hg hook raises 'physical lines'" bug - allow three argument form of raise - now uses setuptools if available, for 'develop' command ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633909928.0 flake8-4.0.1/docs/source/release-notes/1.2.0.rst0000644000175000017500000000020700000000000022546 0ustar00asottileasottile000000000000001.2 - 2012-02-12 ---------------- - added a git hook - now Python 3 compatible - mccabe and pyflakes have warning codes like pep8 now ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633909928.0 flake8-4.0.1/docs/source/release-notes/1.3.0.rst0000644000175000017500000000012300000000000022544 0ustar00asottileasottile000000000000001.3 - 2012-03-12 ---------------- - fixed false W402 warning on exception blocks. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633909928.0 flake8-4.0.1/docs/source/release-notes/1.3.1.rst0000644000175000017500000000010600000000000022546 0ustar00asottileasottile000000000000001.3.1 - 2012-05-19 ------------------ - fixed support for Python 2.5 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633909928.0 flake8-4.0.1/docs/source/release-notes/1.4.0.rst0000644000175000017500000000014700000000000022553 0ustar00asottileasottile000000000000001.4 - 2012-07-12 ---------------- - git_hook: Only check staged changes for compliance - use pep8 1.2 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633909928.0 flake8-4.0.1/docs/source/release-notes/1.5.0.rst0000644000175000017500000000043000000000000022547 0ustar00asottileasottile000000000000001.5 - 2012-10-13 ---------------- - fixed the stdin - make sure mccabe catches the syntax errors as warnings - pep8 upgrade - added max_line_length default value - added Flake8Command and entry points if setuptools is around - using the setuptools console wrapper when available ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633909928.0 flake8-4.0.1/docs/source/release-notes/1.6.0.rst0000644000175000017500000000123300000000000022552 0ustar00asottileasottile000000000000001.6 - 2012-11-16 ---------------- - changed the signatures of the ``check_file`` function in flake8/run.py, ``skip_warning`` in flake8/util.py and the ``check``, ``checkPath`` functions in flake8/pyflakes.py. - fix ``--exclude`` and ``--ignore`` command flags (#14, #19) - fix the git hook that wasn't catching files not already added to the index (#29) - pre-emptively includes the addition to pep8 to ignore certain lines. Add ``# nopep8`` to the end of a line to ignore it. (#37) - ``check_file`` can now be used without any special prior setup (#21) - unpacking exceptions will no longer cause an exception (#20) - fixed crash on non-existent file (#38) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633909928.0 flake8-4.0.1/docs/source/release-notes/1.6.1.rst0000644000175000017500000000035500000000000022557 0ustar00asottileasottile000000000000001.6.1 - 2012-11-24 ------------------ - fixed the mercurial hook, a change from a previous patch was not properly applied - fixed an assumption about warnings/error messages that caused an exception to be thrown when McCabe is used ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633909928.0 flake8-4.0.1/docs/source/release-notes/1.6.2.rst0000644000175000017500000000015100000000000022552 0ustar00asottileasottile000000000000001.6.2 - 2012-11-25 ------------------ - fixed the NameError: global name 'message' is not defined (#46) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633909928.0 flake8-4.0.1/docs/source/release-notes/1.7.0.rst0000644000175000017500000000037200000000000022556 0ustar00asottileasottile000000000000001.7.0 - 2012-12-21 ------------------ - Fixes part of #35: Exception for no WITHITEM being an attribute of Checker for Python 3.3 - Support stdin - Incorporate @phd's builtins pull request - Fix the git hook - Update pep8.py to the latest version ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633909928.0 flake8-4.0.1/docs/source/release-notes/2.0.0.rst0000644000175000017500000000116600000000000022552 0ustar00asottileasottile000000000000002.0.0 - 2013-02-23 ------------------ - Pyflakes errors are prefixed by an ``F`` instead of an ``E`` - McCabe complexity warnings are prefixed by a ``C`` instead of a ``W`` - Flake8 supports extensions through entry points - Due to the above support, we **require** setuptools - We publish the `documentation `_ - Fixes #13: pep8, pyflakes and mccabe become external dependencies - Split run.py into main.py, engine.py and hooks.py for better logic - Expose our parser for our users - New feature: Install git and hg hooks automagically - By relying on pyflakes (0.6.1), we also fixed #45 and #35 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633909928.0 flake8-4.0.1/docs/source/release-notes/2.1.0.rst0000644000175000017500000000071300000000000022550 0ustar00asottileasottile000000000000002.1.0 - 2013-10-26 ------------------ - Add FLAKE8_LAZY and FLAKE8_IGNORE environment variable support to git and mercurial hooks - Force git and mercurial hooks to repsect configuration in setup.cfg - Only check staged files if that is specified - Fix hook file permissions - Fix the git hook on python 3 - Ignore non-python files when running the git hook - Ignore .tox directories by default - Flake8 now reports the column number for PyFlakes messages ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633909928.0 flake8-4.0.1/docs/source/release-notes/2.2.0.rst0000644000175000017500000000104700000000000022552 0ustar00asottileasottile000000000000002.2.0 - 2014-06-22 ------------------ - New option ``doctests`` to run Pyflakes checks on doctests too - New option ``jobs`` to launch multiple jobs in parallel - Turn on using multiple jobs by default using the CPU count - Add support for ``python -m flake8`` on Python 2.7 and Python 3 - Fix Git and Mercurial hooks: issues #88, #133, #148 and #149 - Fix crashes with Python 3.4 by upgrading dependencies - Fix traceback when running tests with Python 2.6 - Fix the setuptools command ``python setup.py flake8`` to read the project configuration ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633909928.0 flake8-4.0.1/docs/source/release-notes/2.2.1.rst0000644000175000017500000000023500000000000022551 0ustar00asottileasottile000000000000002.2.1 - 2014-06-30 ------------------ - Turn off multiple jobs by default. To enable automatic use of all CPUs, use ``--jobs=auto``. Fixes #155 and #154. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633909928.0 flake8-4.0.1/docs/source/release-notes/2.2.2.rst0000644000175000017500000000020200000000000022544 0ustar00asottileasottile000000000000002.2.2 - 2014-07-04 ------------------ - Re-enable multiprocessing by default while fixing the issue Windows users were seeing. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633909928.0 flake8-4.0.1/docs/source/release-notes/2.2.3.rst0000644000175000017500000000012500000000000022551 0ustar00asottileasottile000000000000002.2.3 - 2014-08-25 ------------------ - Actually turn multiprocessing on by default ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633909928.0 flake8-4.0.1/docs/source/release-notes/2.2.4.rst0000644000175000017500000000074400000000000022561 0ustar00asottileasottile000000000000002.2.4 - 2014-10-09 ------------------ - Fix bugs triggered by turning multiprocessing on by default (again) Multiprocessing is forcibly disabled in the following cases: - Passing something in via stdin - Analyzing a diff - Using windows - Fix --install-hook when there are no config files present for pep8 or flake8. - Fix how the setuptools command parses excludes in config files - Fix how the git hook determines which files to analyze (Thanks Chris Buccella!) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633909928.0 flake8-4.0.1/docs/source/release-notes/2.2.5.rst0000644000175000017500000000021200000000000022550 0ustar00asottileasottile000000000000002.2.5 - 2014-10-19 ------------------ - Flush standard out when using multiprocessing - Make the check for "# flake8: noqa" more strict ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633909928.0 flake8-4.0.1/docs/source/release-notes/2.3.0.rst0000644000175000017500000000033400000000000022551 0ustar00asottileasottile000000000000002.3.0 - 2015-01-04 ------------------ - **Feature**: Add ``--output-file`` option to specify a file to write to instead of ``stdout``. - **Bug** Fix interleaving of output while using multiprocessing (:issue:`60`) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633909928.0 flake8-4.0.1/docs/source/release-notes/2.4.0.rst0000644000175000017500000000141500000000000022553 0ustar00asottileasottile000000000000002.4.0 - 2015-03-07 ------------------ - **Bug** Print filenames when using multiprocessing and ``-q`` option. (:issue:`74`) - **Bug** Put upper cap on dependencies. The caps for 2.4.0 are: - ``pep8 < 1.6`` (Related to :issue:`78`) - ``mccabe < 0.4`` - ``pyflakes < 0.9`` See also :issue:`75` - **Bug** Files excluded in a config file were not being excluded when flake8 was run from a git hook. (:issue:`2`) - **Improvement** Print warnings for users who are providing mutually exclusive options to flake8. (:issue:`51`, :issue:`386`) - **Feature** Allow git hook configuration to live in ``.git/config``. See the updated `VCS hooks docs`_ for more details. (:issue:`387`) .. _VCS hooks docs: https://flake8.readthedocs.io/en/latest/user/using-hooks.html ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633909928.0 flake8-4.0.1/docs/source/release-notes/2.4.1.rst0000644000175000017500000000046000000000000022553 0ustar00asottileasottile000000000000002.4.1 - 2015-05-18 ------------------ - **Bug** Do not raise a ``SystemError`` unless there were errors in the setuptools command. (:issue:`82`, :issue:`390`) - **Bug** Do not verify dependencies of extensions loaded via entry-points. - **Improvement** Blacklist versions of pep8 we know are broken ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633909928.0 flake8-4.0.1/docs/source/release-notes/2.5.0.rst0000644000175000017500000000112400000000000022551 0ustar00asottileasottile000000000000002.5.0 - 2015-10-26 ------------------ - **Improvement** Raise cap on PyFlakes for Python 3.5 support - **Improvement** Avoid deprecation warnings when loading extensions (:issue:`102`, :issue:`445`) - **Improvement** Separate logic to enable "off-by-default" extensions (:issue:`110`) - **Bug** Properly parse options to setuptools Flake8 command (:issue:`408`) - **Bug** Fix exceptions when output on stdout is truncated before Flake8 finishes writing the output (:issue:`112`) - **Bug** Fix error on OS X where Flake8 can no longer acquire or create new semaphores (:issue:`117`) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633909928.0 flake8-4.0.1/docs/source/release-notes/2.5.1.rst0000644000175000017500000000047500000000000022562 0ustar00asottileasottile000000000000002.5.1 - 2015-12-08 ------------------ - **Bug** Properly look for ``.flake8`` in current working directory (:issue:`458`) - **Bug** Monkey-patch ``pep8.stdin_get_value`` to cache the actual value in stdin. This helps plugins relying on the function when run with multiprocessing. (:issue:`460`, :issue:`462`) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633909928.0 flake8-4.0.1/docs/source/release-notes/2.5.2.rst0000644000175000017500000000030600000000000022554 0ustar00asottileasottile000000000000002.5.2 - 2016-01-30 ------------------ - **Bug** Parse ``output_file`` and ``enable_extensions`` from config files - **Improvement** Raise upper bound on mccabe plugin to allow for version 0.4.0 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633909928.0 flake8-4.0.1/docs/source/release-notes/2.5.3.rst0000644000175000017500000000017600000000000022562 0ustar00asottileasottile000000000000002.5.3 - 2016-02-11 ------------------ - **Bug** Actually parse ``output_file`` and ``enable_extensions`` from config files ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633909928.0 flake8-4.0.1/docs/source/release-notes/2.5.4.rst0000644000175000017500000000014700000000000022561 0ustar00asottileasottile000000000000002.5.4 - 2016-02-11 ------------------ - **Bug** Missed an attribute rename during the v2.5.3 release. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633909928.0 flake8-4.0.1/docs/source/release-notes/2.5.5.rst0000644000175000017500000000030300000000000022554 0ustar00asottileasottile000000000000002.5.5 - 2016-06-14 ------------------ - **Bug** Fix setuptools integration when parsing config files - **Bug** Don't pass the user's config path as the config_file when creating a StyleGuide ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633909928.0 flake8-4.0.1/docs/source/release-notes/2.6.0.rst0000644000175000017500000000071300000000000022555 0ustar00asottileasottile000000000000002.6.0 - 2016-06-15 ------------------ - **Requirements Change** Switch to pycodestyle as all future pep8 releases will use that package name - **Improvement** Allow for Windows users on *select* versions of Python to use ``--jobs`` and multiprocessing - **Improvement** Update bounds on McCabe - **Improvement** Update bounds on PyFlakes and blacklist known broken versions - **Improvement** Handle new PyFlakes warning with a new error code: F405 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633909928.0 flake8-4.0.1/docs/source/release-notes/2.6.1.rst0000644000175000017500000000033100000000000022552 0ustar00asottileasottile000000000000002.6.1 - 2016-06-25 ------------------ - **Bug** Update the config files to search for to include ``setup.cfg`` and ``tox.ini``. This was broken in 2.5.5 when we stopped passing ``config_file`` to our Style Guide ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633909928.0 flake8-4.0.1/docs/source/release-notes/2.6.2.rst0000644000175000017500000000013500000000000022555 0ustar00asottileasottile000000000000002.6.2 - 2016-06-25 ------------------ - **Bug** Fix packaging error during release process. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633909928.0 flake8-4.0.1/docs/source/release-notes/3.0.0.rst0000644000175000017500000000513700000000000022555 0ustar00asottileasottile000000000000003.0.0 -- 2016-07-25 ------------------- - Rewrite our documentation from scratch! (http://flake8.pycqa.org) - Drop explicit support for Pythons 2.6, 3.2, and 3.3. - Remove dependence on pep8/pycodestyle for file processing, plugin dispatching, and more. We now control all of this while keeping backwards compatibility. - ``--select`` and ``--ignore`` can now both be specified and try to find the most specific rule from each. For example, if you do ``--select E --ignore E123`` then we will report everything that starts with ``E`` except for ``E123``. Previously, you would have had to do ``--ignore E123,F,W`` which will also still work, but the former should be far more intuitive. - Add support for in-line ``# noqa`` comments to specify **only** the error codes to be ignored, e.g., ``# noqa: E123,W503`` - Add entry-point for formatters as well as a base class that new formatters can inherit from. See the documentation for more details. - Add detailed verbose output using the standard library logging module. - Enhance our usage of optparse for plugin developers by adding new parameters to the ``add_option`` that plugins use to register new options. - Update ``--install-hook`` to require the name of version control system hook you wish to install a Flake8. - Stop checking sub-directories more than once via the setuptools command - When passing a file on standard-in, allow the caller to specify ``--stdin-display-name`` so the output is properly formatted - The Git hook now uses ``sys.executable`` to format the shebang line. This allows Flake8 to install a hook script from a virtualenv that points to that virtualenv's Flake8 as opposed to a global one (without the virtualenv being sourced). - Print results in a deterministic and consistent ordering when used with multiprocessing - When using ``--count``, the output is no longer written to stderr. - AST plugins can either be functions or classes and all plugins can now register options so long as there are callable attributes named as we expect. - Stop forcibly re-adding ``.tox``, ``.eggs``, and ``*.eggs`` to ``--exclude``. Flake8 2.x started always appending those three patterns to any exclude list (including the default and any user supplied list). Flake8 3 has stopped adding these in, so you may see errors when upgrading due to these patterns no longer being forcibly excluded by default if you have your own exclude patterns specified. To fix this, add the appropriate patterns to your exclude patterns list. .. note:: This item was added in November of 2016, as a result of a bug report. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633909928.0 flake8-4.0.1/docs/source/release-notes/3.0.1.rst0000644000175000017500000000061200000000000022547 0ustar00asottileasottile000000000000003.0.1 -- 2016-07-25 ------------------- - Fix regression in handling of ``# noqa`` for multiline strings. (See also :issue:`1024`) - Fix regression in handling of ``--output-file`` when not also using ``--verbose``. (See also :issue:`1026`) - Fix regression in handling of ``--quiet``. (See also :issue:`1026`) - Fix regression in handling of ``--statistics``. (See also :issue:`1026`) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633909928.0 flake8-4.0.1/docs/source/release-notes/3.0.2.rst0000644000175000017500000000050000000000000022544 0ustar00asottileasottile000000000000003.0.2 -- 2016-07-26 ------------------- - Fix local config file discovery. (See also :issue:`528`) - Fix indexing of column numbers. We accidentally were starting column indices at 0 instead of 1. - Fix regression in handling of errors like E402 that rely on a combination of attributes. (See also :issue:`530`) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633909928.0 flake8-4.0.1/docs/source/release-notes/3.0.3.rst0000644000175000017500000000230600000000000022553 0ustar00asottileasottile000000000000003.0.3 -- 2016-07-30 ------------------- - Disable ``--jobs`` for any version of Python on Windows. (See also `this Python bug report`_) - Raise exception when entry_point in plugin not callable. This raises an informative error when a plugin fails to load because its entry_point is not callable, which can happen with a plugin which is buggy or not updated for the current version of flake8. This is nicer than raising a `PicklingError` about failing to pickle a module (See also :issue:`1014`) - Fix ``# noqa`` comments followed by a ``:`` and explanation broken by 3.0.0 (See also :issue:`1025`) - Always open our output file in append mode so we do not overwrite log messages. (See also :issue:`535`) - When normalizing path values read from configuration, keep in context the directory where the configuration was found so that relative paths work. (See also :issue:`1036`) - Fix issue where users were unable to ignore plugin errors that were on by default. (See also :issue:`1037`) - Fix our legacy API StyleGuide's ``init_report`` method to actually override the previous formatter. (See also :issue:`136`) .. links .. _this Python bug report: https://bugs.python.org/issue27649 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633909928.0 flake8-4.0.1/docs/source/release-notes/3.0.4.rst0000644000175000017500000000051000000000000022547 0ustar00asottileasottile000000000000003.0.4 -- 2016-08-08 ------------------- - Side-step a Pickling Error when using Flake8 with multiprocessing on Unix systems. (See also :issue:`1014`) - Fix an Attribute Error raised when dealing with Invalid Syntax. (See also :issue:`539`) - Fix an unhandled Syntax Error when tokenizing files. (See also :issue:`540`) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633909928.0 flake8-4.0.1/docs/source/release-notes/3.1.0.rst0000644000175000017500000000411100000000000022545 0ustar00asottileasottile000000000000003.1.0 -- 2016-11-14 ------------------- You can view the `3.1.0 milestone`_ on GitHub for more details. - Add ``--bug-report`` flag to make issue reporters' lives easier. - Collect configuration files from the current directory when using our Git hook. (See also :issue:`142`, :issue:`150`, :issue:`155`) - Avoid unhandled exceptions when dealing with SyntaxErrors. (See also :issue:`146`, :issue:`170`) - Exit early if the value for ``--diff`` is empty. (See also :issue:`158`) - Handle empty ``--stdin-display-name`` values. (See also :issue:`167`) - Properly report the column number of Syntax Errors. We were assuming that all reports of column numbers were 0-indexed, however, SyntaxErrors report the column number as 1-indexed. This caused us to report a column number that was 1 past the actual position. Further, when combined with SyntaxErrors that occur at a newline, this caused the position to be visually off by two. (See also :issue:`169`) - Fix the behaviour of ``--enable-extensions``. Previously, items specified here were still ignored due to the fact that the off-by-default extension codes were being left in the ``ignore`` list. (See also :issue:`171`) - Fix problems around ``--select`` and ``--ignore`` behaviour that prevented codes that were neither explicitly selected nor explicitly ignored from being reported. (See also :issue:`174`) - Truly be quiet when the user specifies ``-q`` one or more times. Previously, we were showing the if the user specified ``-q`` and ``--show-source``. We have fixed this bug. (See also :issue:`177`) - Add new File Processor attribute, ``previous_unindented_logical_line`` to accommodate pycodestyle 2.1.0. (See also :issue:`178`) - When something goes wrong, exit non-zero. (See also :issue:`180`, :issue:`141`) - Add ``--tee`` as an option to allow use of ``--output-file`` and printing to standard out. - Allow the git plugin to actually be lazy when collecting files. - Allow for pycodestyle 2.1 series and pyflakes 1.3 series. .. links .. _3.1.0 milestone: https://github.com/pycqa/flake8/milestone/12 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633909928.0 flake8-4.0.1/docs/source/release-notes/3.1.1.rst0000644000175000017500000000072100000000000022551 0ustar00asottileasottile000000000000003.1.1 -- 2016-11-14 ------------------- You can view the `3.1.1 milestone`_ on GitHub for more details. - Do not attempt to install/distribute a ``man`` file with the Python package; leave this for others to do. (See also :issue:`186`) - Fix packaging bug where wheel version constraints specified in setup.cfg did not match the constraints in setup.py. (See also :issue:`187`) .. links .. _3.1.1 milestone: https://github.com/pycqa/flake8/milestone/13 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633909928.0 flake8-4.0.1/docs/source/release-notes/3.2.0.rst0000644000175000017500000000041400000000000022550 0ustar00asottileasottile000000000000003.2.0 -- 2016-11-14 ------------------- You can view the `3.2.0 milestone`_ on GitHub for more details. - Allow for pycodestyle 2.2.0 which fixes a bug in E305 (See also :issue:`188`) .. links .. _3.2.0 milestone: https://github.com/pycqa/flake8/milestone/14 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633909928.0 flake8-4.0.1/docs/source/release-notes/3.2.1.rst0000644000175000017500000000114700000000000022555 0ustar00asottileasottile000000000000003.2.1 -- 2016-11-21 ------------------- You can view the `3.2.1 milestone`_ on GitHub for more details. - Fix subtle bug when deciding whether to report an on-by-default's violation (See also :issue:`189`) - Fix another bug around SyntaxErrors not being reported at the right column and row (See also :issue:`191` and :issue:`169` for a related, previously fixed bug) - Fix regression from 2.x where we run checks against explicitly provided files, even if they don't match the filename patterns. (See also :issue:`198`) .. links .. _3.2.1 milestone: https://github.com/pycqa/flake8/milestone/15 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633909928.0 flake8-4.0.1/docs/source/release-notes/3.3.0.rst0000644000175000017500000000230300000000000022550 0ustar00asottileasottile000000000000003.3.0 -- 2017-02-06 ------------------- You can view the `3.3.0 milestone`_ on GitHub for more details. - Add support for Python 3.6 (via dependencies). **Note** Flake8 does not guarantee that all plugins will support Python 3.6. - Added unique error codes for all missing PyFlakes messages. (14 new codes, see "Error / Violation Codes") - Dramatically improve the performance of Flake8. (See also :issue:`829`) - Display the local file path instead of the temporary file path when using the git hook. (See also :issue:`176`) - Add methods to Report class that will be called when Flake8 starts and finishes processing a file. (See also :issue:`183`) - Fix problem where hooks should only check \*.py files. (See also :issue:`200`) - Fix handling of SyntaxErrors that do not include physical line information. (See also :issue:`542`) - Update upper bound on PyFlakes to allow for PyFlakes 1.5.0. (See also :issue:`549`) - Update setuptools integration to less eagerly deduplicate packages. (See also :issue:`552`) - Force ``flake8 --version`` to be repeatable between invocations. (See also :issue:`554`) .. all links .. _3.3.0 milestone: https://github.com/pycqa/flake8/milestone/16 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633909928.0 flake8-4.0.1/docs/source/release-notes/3.4.0.rst0000644000175000017500000000143500000000000022556 0ustar00asottileasottile000000000000003.4.0 -- 2017-07-27 ------------------- You can view the `3.4.0 milestone`_ on GitHub for more details. - Refine logic around ``--select`` and ``--ignore`` when combined with the default values for each. (See also :issue:`572`) - Handle spaces as an alternate separate for error codes, e.g., ``--ignore 'E123 E234'``. (See also :issue:`580`) - Filter out empty select and ignore codes, e.g., ``--ignore E123,,E234``. (See also :issue:`581`) - Specify dependencies appropriately in ``setup.py`` (See also :issue:`592`) - Fix bug in parsing ``--quiet`` and ``--verbose`` from config files. (See also :issue:`1169`) - Remove unused import of ``os`` in the git hook template (See also :issue:`1170`) .. all links .. _3.4.0 milestone: https://github.com/pycqa/flake8/milestone/17 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633909928.0 flake8-4.0.1/docs/source/release-notes/3.4.1.rst0000644000175000017500000000051400000000000022554 0ustar00asottileasottile000000000000003.4.1 -- 2017-07-28 ------------------- You can view the `3.4.1 milestone`_ on GitHub for more details. - Fix minor regression when users specify only a ``--select`` list with items in the enabled/extended select list. (See also :issue:`605`) .. all links .. _3.4.1 milestone: https://github.com/pycqa/flake8/milestone/18 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633909928.0 flake8-4.0.1/docs/source/release-notes/3.5.0.rst0000644000175000017500000000145400000000000022560 0ustar00asottileasottile000000000000003.5.0 -- 2017-10-23 ------------------- You can view the `3.5.0 milestone`_ on GitHub for more details. New Dependency Information ~~~~~~~~~~~~~~~~~~~~~~~~~~ - Allow for PyFlakes 1.6.0 (See also :issue:`1058`) - Start using new PyCodestyle checks for bare excepts and ambiguous identifier (See also :issue:`611`) Features ~~~~~~~~ - Print out information about configuring VCS hooks (See also :issue:`586`) - Allow users to develop plugins "local" to a repository without using setuptools. See our documentation on local plugins for more information. (See also :issue:`608`) Bugs Fixed ~~~~~~~~~~ - Catch and helpfully report ``UnicodeDecodeError``\ s when parsing configuration files. (See also :issue:`609`) .. all links .. _3.5.0 milestone: https://github.com/pycqa/flake8/milestone/19 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633909928.0 flake8-4.0.1/docs/source/release-notes/3.6.0.rst0000644000175000017500000000345200000000000022561 0ustar00asottileasottile000000000000003.6.0 -- 2018-10-23 ------------------- You can view the `3.6.0 milestone`_ on GitHub for more details. New Dependency Information ~~~~~~~~~~~~~~~~~~~~~~~~~~ - pycodestyle has been updated to >= 2.4.0, < 2.5.0 (See also :issue:`1068`, :issue:`652`, :issue:`869`, :issue:`881`, :issue:`1239`) - Pyflakes has been updated to >= 2.0.0, < 2.1.0 (See also :issue:`655`, :issue:`883`) - flake8 requires python 2.x >= 2.7 or python 3.x >= 3.4 (See also :issue:`876`) Features ~~~~~~~~ - Add ``paths`` to allow local plugins to exist outside of ``sys.path`` (See also :issue:`1067`, :issue:`1237`) - Copy ``setup.cfg`` files to the temporary git hook execution directory (See also :issue:`1299`) - Only skip a file if ``# flake8: noqa`` is on a line by itself (See also :issue:`259`, :issue:`873`) - Provide a better user experience for broken plugins (See also :issue:`1178`) - Report ``E902`` when a file passed on the command line does not exist (See also :issue:`645`, :issue:`878`) - Add ``--extend-ignore`` for extending the default ``ignore`` instead of overriding it (See also :issue:`1061`, :issue:`1180`) Bugs Fixed ~~~~~~~~~~ - Respect a formatter's newline setting when printing (See also :issue:`1238`) - Fix leaking of processes in the legacy api (See also :issue:`650`, :issue:`879`) - Fix a ``SyntaxWarning`` for an invalid escape sequence (See also :issue:`1186`) - Fix ``DeprecationWarning`` due to import of ``abc`` classes from the ``collections`` module (See also :issue:`887`) - Defer ``setuptools`` import to improve flake8 startup time (See also :issue:`1190`) - Fix inconsistent line endings in ``FileProcessor.lines`` when running under python 3.x (See also :issue:`263`, :issue:`889`) .. all links .. _3.6.0 milestone: https://github.com/pycqa/flake8/milestone/20 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633909928.0 flake8-4.0.1/docs/source/release-notes/3.7.0.rst0000644000175000017500000000242300000000000022557 0ustar00asottileasottile000000000000003.7.0 -- 2019-01-29 ------------------- You can view the `3.7.0 milestone`_ on GitHub for more details. New Dependency Information ~~~~~~~~~~~~~~~~~~~~~~~~~~ - Add dependency on ``entrypoints`` >= 0.3, < 0.4 (See also :issue:`897`, :issue:`1197`) - Pyflakes has been updated to >= 2.1.0, < 2.2.0 (See also :issue:`912`, :issue:`913`) - pycodestyle has been updated to >= 2.5.0, < 2.6.0 (See also :issue:`915`) Features ~~~~~~~~ - Add support for ``per-file-ignores`` (See also :issue:`892`, :issue:`511`, :issue:`911`, :issue:`277`) - Enable use of ``float`` and ``complex`` option types (See also :issue:`894`, :issue:`258`) - Improve startup performance by switching from ``pkg_resources`` to ``entrypoints`` (See also :issue:`897`) - Add metadata for use through the `pre-commit`_ git hooks framework (See also :issue:`901`, :issue:`1196`) - Allow physical line checks to return more than one result (See also :issue:`902`) - Allow ``# noqa:X123`` comments without space between the colon and codes list (See also :issue:`906`, :issue:`276`) - Remove broken and unused ``flake8.listen`` plugin type (See also :issue:`907`, :issue:`663`) .. all links .. _3.7.0 milestone: https://github.com/pycqa/flake8/milestone/22 .. _pre-commit: https://pre-commit.com/ ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633909928.0 flake8-4.0.1/docs/source/release-notes/3.7.1.rst0000644000175000017500000000047100000000000022561 0ustar00asottileasottile000000000000003.7.1 -- 2019-01-30 ------------------- You can view the `3.7.1 milestone`_ on GitHub for more details. Bugs Fixed ~~~~~~~~~~ - Fix capitalized filenames in ``per-file-ignores`` setting (See also :issue:`917`, :issue:`287`) .. all links .. _3.7.1 milestone: https://github.com/pycqa/flake8/milestone/23 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633909928.0 flake8-4.0.1/docs/source/release-notes/3.7.2.rst0000644000175000017500000000100200000000000022551 0ustar00asottileasottile000000000000003.7.2 -- 2019-01-30 ------------------- You can view the `3.7.2 milestone`_ on GitHub for more details. Bugs Fixed ~~~~~~~~~~ - Fix broken ``flake8 --diff`` (regressed in 3.7.0) (See also :issue:`919`, :issue:`667`) - Fix typo in plugin exception reporting (See also :issue:`908`, :issue:`668`) - Fix ``AttributeError`` while attempting to use the legacy api (regressed in 3.7.0) (See also :issue:`1198`, :issue:`673`) .. all links .. _3.7.2 milestone: https://github.com/pycqa/flake8/milestone/24 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633909928.0 flake8-4.0.1/docs/source/release-notes/3.7.3.rst0000644000175000017500000000111200000000000022554 0ustar00asottileasottile000000000000003.7.3 -- 2019-01-30 ------------------- You can view the `3.7.3 milestone`_ on GitHub for more details. Bugs Fixed ~~~~~~~~~~ - Fix imports of ``typing`` in python 3.5.0 / 3.5.1 (See also :issue:`1199`, :issue:`674`) - Fix ``flake8 --statistics`` (See also :issue:`920`, :issue:`675`) - Gracefully ignore ``flake8-per-file-ignores`` plugin if installed (See also :issue:`1201`, :issue:`671`) - Improve error message for malformed ``per-file-ignores`` (See also :issue:`921`, :issue:`288`) .. all links .. _3.7.3 milestone: https://github.com/pycqa/flake8/milestone/25 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633909928.0 flake8-4.0.1/docs/source/release-notes/3.7.4.rst0000644000175000017500000000051000000000000022556 0ustar00asottileasottile000000000000003.7.4 -- 2019-01-31 ------------------- You can view the `3.7.4 milestone`_ on GitHub for more details. Bugs Fixed ~~~~~~~~~~ - Fix performance regression with lots of ``per-file-ignores`` and errors (See also :issue:`922`, :issue:`677`) .. all links .. _3.7.4 milestone: https://github.com/pycqa/flake8/milestone/26 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633909928.0 flake8-4.0.1/docs/source/release-notes/3.7.5.rst0000644000175000017500000000047700000000000022573 0ustar00asottileasottile000000000000003.7.5 -- 2019-02-04 ------------------- You can view the `3.7.5 milestone`_ on GitHub for more details. Bugs Fixed ~~~~~~~~~~ - Fix reporting of pyflakes "referenced before assignment" error (See also :issue:`923`, :issue:`679`) .. all links .. _3.7.5 milestone: https://github.com/pycqa/flake8/milestone/27 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633909928.0 flake8-4.0.1/docs/source/release-notes/3.7.6.rst0000644000175000017500000000061100000000000022562 0ustar00asottileasottile000000000000003.7.6 -- 2019-02-18 ------------------- You can view the `3.7.6 milestone`_ on GitHub for more details. Bugs Fixed ~~~~~~~~~~ - Fix ``--per-file-ignores`` for multi-letter error codes (See also :issue:`1203`, :issue:`683`) - Improve flake8 speed when only 1 filename is passed (See also :issue:`1204`) .. all links .. _3.7.6 milestone: https://github.com/pycqa/flake8/milestone/28 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633909928.0 flake8-4.0.1/docs/source/release-notes/3.7.7.rst0000644000175000017500000000051300000000000022564 0ustar00asottileasottile000000000000003.7.7 -- 2019-02-25 ------------------- You can view the `3.7.7 milestone`_ on GitHub for more details. Bugs Fixed ~~~~~~~~~~ - Fix crashes in plugins causing ``flake8`` to hang while unpickling errors (See also :issue:`1206`, :issue:`681`) .. all links .. _3.7.7 milestone: https://github.com/pycqa/flake8/milestone/29 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633909928.0 flake8-4.0.1/docs/source/release-notes/3.7.8.rst0000644000175000017500000000114700000000000022571 0ustar00asottileasottile000000000000003.7.8 -- 2019-07-08 ------------------- You can view the `3.7.8 milestone`_ on GitHub for more details. Bugs Fixed ~~~~~~~~~~ - Fix handling of ``Application.parse_preliminary_options_and_args`` when argv is an empty list (See also :issue:`1303`, :issue:`694`) - Fix crash when a file parses but fails to tokenize (See also :issue:`1210`, :issue:`1088`) - Log the full traceback on plugin exceptions (See also :issue:`926`) - Fix ``# noqa: ...`` comments with multi-letter codes (See also :issue:`931`, :issue:`1101`) .. all links .. _3.7.8 milestone: https://github.com/pycqa/flake8/milestone/30 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633909928.0 flake8-4.0.1/docs/source/release-notes/3.7.9.rst0000644000175000017500000000054500000000000022573 0ustar00asottileasottile000000000000003.7.9 -- 2019-10-28 ------------------- You can view the `3.7.9 milestone`_ on GitHub for more details. Bugs Fixed ~~~~~~~~~~ - Disable multiprocessing when the multiprocessing method is ``spawn`` (such as on macos in python3.8) (See also :issue:`956`, :issue:`315`) .. all links .. _3.7.9 milestone: https://github.com/pycqa/flake8/milestone/32 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633909928.0 flake8-4.0.1/docs/source/release-notes/3.8.0.rst0000644000175000017500000001036500000000000022564 0ustar00asottileasottile000000000000003.8.0 -- 2020-05-11 ------------------- You can view the `3.8.0 milestone`_ on GitHub for more details. Bugs Fixed ~~~~~~~~~~ - Fix logical checks which report positions out of bounds (See also :issue:`987`, :issue:`723`) - Fix ``--exclude=.*`` accidentally matching ``.`` and ``..`` (See also :issue:`441`, :issue:`360`) Deprecations ~~~~~~~~~~~~ - Add deprecation message for vcs hooks (See also :issue:`985`, :issue:`296`) 3.8.0a2 -- 2020-04-24 --------------------- You can view the `3.8.0 milestone`_ on GitHub for more details. Bugs Fixed ~~~~~~~~~~ - Fix ``type="str"`` optparse options (See also :issue:`984`) 3.8.0a1 -- 2020-04-24 --------------------- You can view the `3.8.0 milestone`_ on GitHub for more details. New Dependency Information ~~~~~~~~~~~~~~~~~~~~~~~~~~ - Remove dependency on ``entrypoints`` and add dependency on ``importlib-metadata`` (only for ``python<3.8``) (See also :issue:`1297`, :issue:`297`) - Pyflakes has been updated to >= 2.2.0, < 2.3.0 (See also :issue:`982`) - pycodestyle has been updated to >= 2.6.0a1, < 2.7.0 (See also :issue:`983`) Features ~~~~~~~~ - Add ``--extend-exclude`` option to add to ``--exclude`` without overwriting (See also :issue:`1211`, :issue:`1091`) - Move argument parsing from ``optparse`` to ``argparse`` (See also :issue:`939` - Group plugin options in ``--help`` (See also :issue:`1219`, :issue:`294`) - Remove parsing of ``verbose`` from configuration files as it was not consistently applied (See also :issue:`1245`, :issue:`245`) - Remove parsing of ``output_file`` from configuration files as it was not consistently applied (See also :issue:`1246`) - Resolve configuration files relative to ``cwd`` instead of common prefix of passed filenames. You may need to change ``flake8 subproject`` to ``cd subproject && flake8 .`` (See also :issue:`952`) - Officially support python3.8 (See also :issue:`963`) - ``--disable-noqa`` now also disables ``# flake8: noqa`` (See also :issue:`1296`, :issue:`318`) - Ensure that a missing file produces a ``E902`` error (See also :issue:`1262`, :issue:`328`) - ``# noqa`` comments now apply to all of the lines in an explicit ``\`` continuation or in a line continued by a multi-line string (See also :issue:`1266`, :issue:`621`) Bugs Fixed ~~~~~~~~~~ - Fix ``--exclude=./t.py`` to only match ``t.py`` at the top level (See also :issue:`1208`, :issue:`628`) - Fix ``--show-source`` when a file is indented with tabs (See also :issue:`1218`, :issue:`719`) - Fix crash when ``--max-line-length`` is given a non-integer (See also :issue:`939`, :issue:`704`) - Prevent flip-flopping of ``indent_char`` causing extra ``E101`` errors (See also :issue:`949`, `pycodestyle#886`_) - Only enable multiprocessing when the method is ``fork`` fixing issues on macos with python3.8+ (See also :issue:`955`, :issue:`315`) (note: this fix also landed in 3.7.9) - ``noqa`` is now only handled by flake8 fixing specific-noqa. Plugins requesting this parameter will always receive ``False`` (See also :issue:`1214`, :issue:`1104`) - Fix duplicate loading of plugins when invoked via ``python -m flake8`` (See also :issue:`1297`) - Fix early exit when ``--exit-zero`` and ``--diff`` are provided and the diff is empty (See also :issue:`970`) - Consistently split lines when ``\f`` is present when reading from stdin (See also :issue:`976`, :issue:`202`) Deprecations ~~~~~~~~~~~~ - ``python setup.py flake8`` (setuptools integration) is now deprecated and will be removed in a future version (See also :issue:`935`, :issue:`1098`) - ``type='string'`` (optparse) types are deprecated, use ``type=callable`` (argparse) instead. Support for ``type='string'`` will be removed in a future version (See also :issue:`939`) - ``%default`` in plugin option help text is deprecated, use ``%(default)s`` instead. Support for ``%default`` will be removed in a future version (See also :issue:`939`) - optparse-style ``action='callback'`` setting for options is deprecated, use argparse action classes instead. This will be removed in a future version (See also :issue:`939`) .. all links .. _3.8.0 milestone: https://github.com/pycqa/flake8/milestone/31 .. issue links .. _pycodestyle#886: https://github.com/PyCQA/pycodestyle/issues/886 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633909928.0 flake8-4.0.1/docs/source/release-notes/3.8.1.rst0000644000175000017500000000045400000000000022563 0ustar00asottileasottile000000000000003.8.1 -- 2020-05-11 ------------------- You can view the `3.8.1 milestone`_ on GitHub for more details. Bugs Fixed ~~~~~~~~~~ - Fix ``--output-file`` (regression in 3.8.0) (See also :issue:`990`, :issue:`725`) .. all links .. _3.8.1 milestone: https://github.com/pycqa/flake8/milestone/33 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633909928.0 flake8-4.0.1/docs/source/release-notes/3.8.2.rst0000644000175000017500000000122000000000000022554 0ustar00asottileasottile000000000000003.8.2 -- 2020-05-22 ------------------- You can view the `3.8.2 milestone`_ on GitHub for more details. Bugs Fixed ~~~~~~~~~~ - Improve performance by eliminating unnecessary sort (See also :issue:`991`) - Improve messaging of ``--jobs`` argument by utilizing ``argparse`` (See also :issue:`1269`, :issue:`1110`) - Fix file configuration options to be relative to the config passed on the command line (See also :issue:`442`, :issue:`736`) - Fix incorrect handling of ``--extend-exclude`` by treating its values as files (See also :issue:`1271`, :issue:`738`) .. all links .. _3.8.2 milestone: https://github.com/pycqa/flake8/milestone/34 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633909928.0 flake8-4.0.1/docs/source/release-notes/3.8.3.rst0000644000175000017500000000061200000000000022561 0ustar00asottileasottile000000000000003.8.3 -- 2020-06-08 ------------------- You can view the `3.8.3 milestone`_ on GitHub for more details. Bugs Fixed ~~~~~~~~~~ - Also catch ``SyntaxError`` when tokenizing (See also :issue:`992`, :issue:`747`) - Fix ``--jobs`` default display in ``flake8 --help`` (See also :issue:`1272`, :issue:`750`) .. all links .. _3.8.3 milestone: https://github.com/pycqa/flake8/milestone/35 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633909928.0 flake8-4.0.1/docs/source/release-notes/3.8.4.rst0000644000175000017500000000066100000000000022566 0ustar00asottileasottile000000000000003.8.4 -- 2020-10-02 ------------------- You can view the `3.8.4 milestone`_ on GitHub for more details. Bugs Fixed ~~~~~~~~~~ - Fix multiprocessing errors on platforms without ``sem_open`` syscall. (See also :issue:`1282`) - Fix skipping of physical checks on the last line of a file which does not end in a newline (See also :issue:`997`) .. all links .. _3.8.4 milestone: https://github.com/pycqa/flake8/milestone/36 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633909928.0 flake8-4.0.1/docs/source/release-notes/3.9.0.rst0000644000175000017500000000127400000000000022564 0ustar00asottileasottile000000000000003.9.0 -- 2021-03-14 ------------------- You can view the `3.9.0 milestone`_ on GitHub for more details. New Dependency Information ~~~~~~~~~~~~~~~~~~~~~~~~~~ - Pyflakes has been updated to >= 2.3.0, < 2.4.0 (See also :issue:`1006`) - pycodestyle has been updated to >= 2.7.0, < 2.8.0 (See also :issue:`1007`) Deprecations ~~~~~~~~~~~~ - Drop support for python 3.4 (See also :issue:`1283`) Features ~~~~~~~~ - Add ``--no-show-source`` option to disable ``--show-source`` (See also :issue:`995`) Bugs Fixed ~~~~~~~~~~ - Fix handling of ``crlf`` line endings when linting stdin (See also :issue:`1002`) .. all links .. _3.9.0 milestone: https://github.com/pycqa/flake8/milestone/37 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633909928.0 flake8-4.0.1/docs/source/release-notes/3.9.1.rst0000644000175000017500000000047100000000000022563 0ustar00asottileasottile000000000000003.9.1 -- 2021-04-15 ------------------- You can view the `3.9.1 milestone`_ on GitHub for more details. Bugs Fixed ~~~~~~~~~~ - Fix codes being ignored by plugins utilizing ``extend_default_ignore`` (See also :pull:`1317`) .. all links .. _3.9.1 milestone: https://github.com/PyCQA/flake8/milestone/38 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633909928.0 flake8-4.0.1/docs/source/release-notes/3.9.2.rst0000644000175000017500000000070000000000000022557 0ustar00asottileasottile000000000000003.9.2 -- 2021-05-08 ------------------- You can view the `3.9.2 milestone`_ on GitHub for more details. Bugs Fixed ~~~~~~~~~~ - Fix error message for ``E111`` in ``pycodestyle`` (See also :pull:`1328`, :issue:`1327`). Deprecations ~~~~~~~~~~~~ - ``indent_size_str`` is deprecated, use ``str(indent_size)`` instead (See also :pull:`1328`, :issue:`1327`). .. all links .. _3.9.2 milestone: https://github.com/PyCQA/flake8/milestone/40 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633917591.0 flake8-4.0.1/docs/source/release-notes/4.0.0.rst0000644000175000017500000000302100000000000022544 0ustar00asottileasottile000000000000004.0.0 -- 2021-10-10 ------------------- You can view the `4.0.0 milestone`_ on GitHub for more details. Backwards Incompatible Changes ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Remove ``--install-hook`` vcs integration (See also :issue:`1008`). - Remove ``setuptools`` command (See also :issue:`1009`). - Migrate from GitLab to GitHub (See also :pull:`1305`). - Due to constant confusion by users, user-level |Flake8| configuration files are no longer supported. Files will not be searched for in the user's home directory (e.g., ``~/.flake8``) nor in the XDG config directory (e.g., ``~/.config/flake8``). (See also :pull:`1404`). New Dependency Information ~~~~~~~~~~~~~~~~~~~~~~~~~~ - pycodestyle has been updated to >= 2.8.0, < 2.9.0 (See also :pull:`1406`). - Pyflakes has been updated to >= 2.4.0, < 2.5.0 (See also :pull:`1406`). - flake8 requires python >= 3.6 (See also :issue:`1010`). Features ~~~~~~~~ - Add ``--extend-select`` option (See also :pull:`1312` :issue:`1061`). - Automatically create directories for output files (See also :pull:`1329`). Bugs Fixed ~~~~~~~~~~ - ``ast`` parse before tokenizing to improve ``SyntaxError`` errors (See also :pull:`1320` :issue:`740`). - Fix warning in ``--indent-size`` argparse help (See also :pull:`1367`). - Fix handling ``SyntaxError`` in python 3.10+ (See also :pull:`1374` :issue:`1372`). - Fix writing non-cp1252-encodable when output is piped on windows (See also :pull:`1382` :issue:`1381`). .. all links .. _4.0.0 milestone: https://github.com/PyCQA/flake8/milestone/39 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633955783.0 flake8-4.0.1/docs/source/release-notes/4.0.1.rst0000644000175000017500000000046500000000000022556 0ustar00asottileasottile000000000000004.0.1 -- 2021-10-11 ------------------- You can view the `4.0.1 milestone`_ on GitHub for more details. Bugs Fixed ~~~~~~~~~~ - Fix parallel execution collecting a ``SyntaxError`` (See also :pull:`1410` :issue:`1408`). .. all links .. _4.0.1 milestone: https://github.com/PyCQA/flake8/milestone/41 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633955669.0 flake8-4.0.1/docs/source/release-notes/index.rst0000644000175000017500000000217400000000000023224 0ustar00asottileasottile00000000000000=========================== Release Notes and History =========================== All of the release notes that have been recorded for Flake8 are organized here with the newest releases first. 4.x Release Series ================== .. toctree:: 4.0.1 4.0.0 3.x Release Series ================== .. toctree:: 3.9.2 3.9.1 3.9.0 3.8.4 3.8.3 3.8.2 3.8.1 3.8.0 3.7.9 3.7.8 3.7.7 3.7.6 3.7.5 3.7.4 3.7.3 3.7.2 3.7.1 3.7.0 3.6.0 3.5.0 3.4.1 3.4.0 3.3.0 3.2.1 3.2.0 3.1.1 3.1.0 3.0.4 3.0.3 3.0.2 3.0.1 3.0.0 2.x Release Series ================== .. toctree:: 2.6.2 2.6.1 2.6.0 2.5.5 2.5.4 2.5.3 2.5.2 2.5.1 2.5.0 2.4.1 2.4.0 2.3.0 2.2.5 2.2.4 2.2.3 2.2.2 2.2.1 2.2.0 2.1.0 2.0.0 1.x Release Series ================== .. toctree:: 1.7.0 1.6.2 1.6.1 1.6.0 1.5.0 1.4.0 1.3.1 1.3.0 1.2.0 1.1.0 1.0.0 0.x Release Series ================== .. toctree:: 0.9.0 0.8.0 0.7.0 0.6.0 ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1633956150.3454707 flake8-4.0.1/docs/source/user/0000755000175000017500000000000000000000000017567 5ustar00asottileasottile00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633909928.0 flake8-4.0.1/docs/source/user/configuration.rst0000644000175000017500000002333300000000000023174 0ustar00asottileasottile00000000000000.. _configuration: ==================== Configuring Flake8 ==================== Once you have learned how to :ref:`invoke ` |Flake8|, you will soon want to learn how to configure it so you do not have to specify the same options every time you use it. This section will show you how to make .. prompt:: bash flake8 Remember that you want to specify certain options without writing .. prompt:: bash flake8 --select E123,W456 --enable-extensions H111 Configuration Locations ======================= |Flake8| supports storing its configuration in the following places: - Your top-level user directory - In your project in one of ``setup.cfg``, ``tox.ini``, or ``.flake8``. Values set at the command line have highest priority, then those in the project configuration file, then those in your user directory, and finally there are the defaults. However, there are additional command line options which can alter this. "User" Configuration -------------------- |Flake8| allows a user to use "global" configuration file to store preferences. The user configuration file is expected to be stored somewhere in the user's "home" directory. - On Windows the "home" directory will be something like ``C:\\Users\sigmavirus24``, a.k.a, ``~\``. - On Linux and other Unix like systems (including OS X) we will look in ``~/``. Note that |Flake8| looks for ``~\.flake8`` on Windows and ``~/.config/flake8`` on Linux and other Unix systems. User configuration files use the same syntax as Project Configuration files. Keep reading to see that syntax. Project Configuration --------------------- |Flake8| is written with the understanding that people organize projects into sub-directories. Let's take for example |Flake8|'s own project structure .. code:: flake8 ├── docs │   ├── build │   └── source │   ├── _static │   ├── _templates │   ├── dev │   ├── internal │   └── user ├── flake8 │   ├── formatting │   ├── main │   ├── options │   └── plugins └── tests ├── fixtures │   └── config_files ├── integration └── unit In the top-level ``flake8`` directory (which contains ``docs``, ``flake8``, and ``tests``) there's also ``tox.ini`` and ``setup.cfg`` files. In our case, we keep our |Flake8| configuration in ``tox.ini``. Regardless of whether you keep your config in ``.flake8``, ``setup.cfg``, or ``tox.ini`` we expect you to use INI to configure |Flake8| (since each of these files already uses INI as a format). This means that any |Flake8| configuration you wish to set needs to be in the ``flake8`` section, which means it needs to start like so: .. code-block:: ini [flake8] Each command-line option that you want to specify in your config file can be named in either of two ways: #. Using underscores (``_``) instead of hyphens (``-``) #. Simply using hyphens (without the leading hyphens) .. note:: Not every |Flake8| command-line option can be specified in the configuration file. See :ref:`our list of options ` to determine which options will be parsed from the configuration files. Let's actually look at |Flake8|'s own configuration section: .. code-block:: ini [flake8] ignore = D203 exclude = .git,__pycache__,docs/source/conf.py,old,build,dist max-complexity = 10 This is equivalent to: .. prompt:: bash flake8 --ignore D203 \ --exclude .git,__pycache__,docs/source/conf.py,old,build,dist \ --max-complexity 10 In our case, if we wanted to, we could also do .. code-block:: ini [flake8] ignore = D203 exclude = .git, __pycache__, docs/source/conf.py, old, build, dist max-complexity = 10 This allows us to add comments for why we're excluding items, e.g. .. code-block:: ini [flake8] ignore = D203 exclude = # No need to traverse our git directory .git, # There's no value in checking cache directories __pycache__, # The conf file is mostly autogenerated, ignore it docs/source/conf.py, # The old directory contains Flake8 2.0 old, # This contains our built documentation build, # This contains builds of flake8 that we don't want to check dist max-complexity = 10 .. note:: Following the recommended settings for `Python's configparser `_, |Flake8| does not support inline comments for any of the keys. So while this is fine: .. code-block:: ini [flake8] per-file-ignores = # imported but unused __init__.py: F401 this is not: .. code-block:: ini [flake8] per-file-ignores = __init__.py: F401 # imported but unused .. note:: If you're using Python 2, you will notice that we download the :mod:`configparser` backport from PyPI. That backport enables us to support this behaviour on all supported versions of Python. Please do **not** open issues about this dependency to |Flake8|. .. note:: You can also specify ``--max-complexity`` as ``max_complexity = 10``. This is also useful if you have a long list of error codes to ignore. Let's look at a portion of a project's Flake8 configuration in their ``tox.ini``: .. code-block:: ini [flake8] # it's not a bug that we aren't using all of hacking, ignore: # F812: list comprehension redefines ... # H101: Use TODO(NAME) # H202: assertRaises Exception too broad # H233: Python 3.x incompatible use of print operator # H301: one import per line # H306: imports not in alphabetical order (time, os) # H401: docstring should not start with a space # H403: multi line docstrings should end on a new line # H404: multi line docstring should start without a leading new line # H405: multi line docstring summary not separated with an empty line # H501: Do not use self.__dict__ for string formatting ignore = F812,H101,H202,H233,H301,H306,H401,H403,H404,H405,H501 They use the comments to describe the check but they could also write this as: .. code-block:: ini [flake8] # it's not a bug that we aren't using all of hacking ignore = # F812: list comprehension redefines ... F812, # H101: Use TODO(NAME) H101, # H202: assertRaises Exception too broad H202, # H233: Python 3.x incompatible use of print operator H233, # H301: one import per line H301, # H306: imports not in alphabetical order (time, os) H306, # H401: docstring should not start with a space H401, # H403: multi line docstrings should end on a new line H403, # H404: multi line docstring should start without a leading new line H404, # H405: multi line docstring summary not separated with an empty line H405, # H501: Do not use self.__dict__ for string formatting H501 Or they could use each comment to describe **why** they've ignored the check. |Flake8| knows how to parse these lists and will appropriately handle these situations. Using Local Plugins ------------------- .. versionadded:: 3.5.0 |Flake8| allows users to write plugins that live locally in a project. These plugins do not need to use setuptools or any of the other overhead associated with plugins distributed on PyPI. To use these plugins, users must specify them in their configuration file (i.e., ``.flake8``, ``setup.cfg``, or ``tox.ini``). This must be configured in a separate INI section named ``flake8:local-plugins``. Users may configure plugins that check source code, i.e., ``extension`` plugins, and plugins that report errors, i.e., ``report`` plugins. An example configuration might look like: .. code-block:: ini [flake8:local-plugins] extension = MC1 = project.flake8.checkers:MyChecker1 MC2 = project.flake8.checkers:MyChecker2 report = MR1 = project.flake8.reporters:MyReporter1 MR2 = project.flake8.reporters:MyReporter2 |Flake8| will also, however, allow for commas to separate the plugins for example: .. code-block:: ini [flake8:local-plugins] extension = MC1 = project.flake8.checkers:MyChecker1, MC2 = project.flake8.checkers:MyChecker2 report = MR1 = project.flake8.reporters:MyReporter1, MR2 = project.flake8.reporters:MyReporter2 These configurations will allow you to select your own custom reporter plugin that you've designed or will utilize your new check classes. If your package is installed in the same virtualenv that |Flake8| will run from, and your local plugins are part of that package, you're all set; |Flake8| will be able to import your local plugins. However, if you are working on a project that isn't set up as an installable package, or |Flake8| doesn't run from the same virtualenv your code runs in, you may need to tell |Flake8| where to import your local plugins from. You can do this via the ``paths`` option in the ``local-plugins`` section of your config: .. code-block:: ini [flake8:local-plugins] extension = MC1 = myflake8plugin:MyChecker1 paths = ./path/to Relative paths will be interpreted relative to the config file. Multiple paths can be listed (comma separated just like ``exclude``) as needed. If your local plugins have any dependencies, it's up to you to ensure they are installed in whatever Python environment |Flake8| runs in. .. note:: These plugins otherwise follow the same guidelines as regular plugins. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633909928.0 flake8-4.0.1/docs/source/user/error-codes.rst0000644000175000017500000002136000000000000022547 0ustar00asottileasottile00000000000000.. _error_codes: ========================= Error / Violation Codes ========================= Flake8 and its plugins assign a code to each message that we refer to as an :term:`error code` (or :term:`violation`). Most plugins will list their error codes in their documentation or README. Flake8 installs ``pycodestyle``, ``pyflakes``, and ``mccabe`` by default and generates its own :term:`error code`\ s for ``pyflakes``: +------+---------------------------------------------------------------------+ | Code | Example Message | +======+=====================================================================+ | F401 | ``module`` imported but unused | +------+---------------------------------------------------------------------+ | F402 | import ``module`` from line ``N`` shadowed by loop variable | +------+---------------------------------------------------------------------+ | F403 | 'from ``module`` import \*' used; unable to detect undefined names | +------+---------------------------------------------------------------------+ | F404 | future import(s) ``name`` after other statements | +------+---------------------------------------------------------------------+ | F405 | ``name`` may be undefined, or defined from star imports: ``module`` | +------+---------------------------------------------------------------------+ | F406 | 'from ``module`` import \*' only allowed at module level | +------+---------------------------------------------------------------------+ | F407 | an undefined ``__future__`` feature name was imported | +------+---------------------------------------------------------------------+ +------+---------------------------------------------------------------------+ | F501 | invalid ``%`` format literal | +------+---------------------------------------------------------------------+ | F502 | ``%`` format expected mapping but got sequence | +------+---------------------------------------------------------------------+ | F503 | ``%`` format expected sequence but got mapping | +------+---------------------------------------------------------------------+ | F504 | ``%`` format unused named arguments | +------+---------------------------------------------------------------------+ | F505 | ``%`` format missing named arguments | +------+---------------------------------------------------------------------+ | F506 | ``%`` format mixed positional and named arguments | +------+---------------------------------------------------------------------+ | F507 | ``%`` format mismatch of placeholder and argument count | +------+---------------------------------------------------------------------+ | F508 | ``%`` format with ``*`` specifier requires a sequence | +------+---------------------------------------------------------------------+ | F509 | ``%`` format with unsupported format character | +------+---------------------------------------------------------------------+ | F521 | ``.format(...)`` invalid format string | +------+---------------------------------------------------------------------+ | F522 | ``.format(...)`` unused named arguments | +------+---------------------------------------------------------------------+ | F523 | ``.format(...)`` unused positional arguments | +------+---------------------------------------------------------------------+ | F524 | ``.format(...)`` missing argument | +------+---------------------------------------------------------------------+ | F525 | ``.format(...)`` mixing automatic and manual numbering | +------+---------------------------------------------------------------------+ | F541 | f-string without any placeholders | +------+---------------------------------------------------------------------+ +------+---------------------------------------------------------------------+ | F601 | dictionary key ``name`` repeated with different values | +------+---------------------------------------------------------------------+ | F602 | dictionary key variable ``name`` repeated with different values | +------+---------------------------------------------------------------------+ | F621 | too many expressions in an assignment with star-unpacking | +------+---------------------------------------------------------------------+ | F622 | two or more starred expressions in an assignment ``(a, *b, *c = d)``| +------+---------------------------------------------------------------------+ | F631 | assertion test is a tuple, which is always ``True`` | +------+---------------------------------------------------------------------+ | F632 | use ``==/!=`` to compare ``str``, ``bytes``, and ``int`` literals | +------+---------------------------------------------------------------------+ | F633 | use of ``>>`` is invalid with ``print`` function | +------+---------------------------------------------------------------------+ | F634 | if test is a tuple, which is always ``True`` | +------+---------------------------------------------------------------------+ +------+---------------------------------------------------------------------+ | F701 | a ``break`` statement outside of a ``while`` or ``for`` loop | +------+---------------------------------------------------------------------+ | F702 | a ``continue`` statement outside of a ``while`` or ``for`` loop | +------+---------------------------------------------------------------------+ | F703 | a ``continue`` statement in a ``finally`` block in a loop | +------+---------------------------------------------------------------------+ | F704 | a ``yield`` or ``yield from`` statement outside of a function | +------+---------------------------------------------------------------------+ | F705 | a ``return`` statement with arguments inside a generator | +------+---------------------------------------------------------------------+ | F706 | a ``return`` statement outside of a function/method | +------+---------------------------------------------------------------------+ | F707 | an ``except:`` block as not the last exception handler | +------+---------------------------------------------------------------------+ | F721 | syntax error in doctest | +------+---------------------------------------------------------------------+ | F722 | syntax error in forward annotation | +------+---------------------------------------------------------------------+ | F723 | syntax error in type comment | +------+---------------------------------------------------------------------+ +------+---------------------------------------------------------------------+ | F811 | redefinition of unused ``name`` from line ``N`` | +------+---------------------------------------------------------------------+ | F821 | undefined name ``name`` | +------+---------------------------------------------------------------------+ | F822 | undefined name ``name`` in ``__all__`` | +------+---------------------------------------------------------------------+ | F823 | local variable ``name`` ... referenced before assignment | +------+---------------------------------------------------------------------+ | F831 | duplicate argument ``name`` in function definition | +------+---------------------------------------------------------------------+ | F841 | local variable ``name`` is assigned to but never used | +------+---------------------------------------------------------------------+ +------+---------------------------------------------------------------------+ | F901 | ``raise NotImplemented`` should be ``raise NotImplementedError`` | +------+---------------------------------------------------------------------+ We also report one extra error: ``E999``. We report ``E999`` when we fail to compile a file into an Abstract Syntax Tree for the plugins that require it. ``mccabe`` only ever reports one :term:`violation` - ``C901`` based on the complexity value provided by the user. Users should also reference `pycodestyle's list of error codes`_. .. links .. _pycodestyle's list of error codes: https://pycodestyle.readthedocs.io/en/latest/intro.html#error-codes ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633909928.0 flake8-4.0.1/docs/source/user/index.rst0000644000175000017500000000115100000000000021426 0ustar00asottileasottile00000000000000============== Using Flake8 ============== |Flake8| can be used in many ways. A few: - invoked on the command-line - invoked via Python This guide will cover all of these and the nuances for using |Flake8|. .. note:: This portion of |Flake8|'s documentation does not cover installation. See the :ref:`installation-guide` section for how to install |Flake8|. .. toctree:: :maxdepth: 2 invocation configuration options error-codes violations using-plugins using-hooks python-api .. config files .. command-line tutorial .. VCS usage .. installing and using plugins ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633909928.0 flake8-4.0.1/docs/source/user/invocation.rst0000644000175000017500000001456100000000000022501 0ustar00asottileasottile00000000000000.. _invocation: ================= Invoking Flake8 ================= Once you have :ref:`installed ` |Flake8|, you can begin using it. Most of the time, you will be able to generically invoke |Flake8| like so: .. prompt:: bash flake8 ... Where you simply allow the shell running in your terminal to locate |Flake8|. In some cases, though, you may have installed |Flake8| for multiple versions of Python (e.g., Python 3.8 and Python 3.9) and you need to call a specific version. In that case, you will have much better results using: .. prompt:: bash python3.8 -m flake8 Or .. prompt:: bash python3.9 -m flake8 Since that will tell the correct version of Python to run |Flake8|. .. note:: Installing |Flake8| once will not install it on both Python 3.8 and Python 3.9. It will only install it for the version of Python that is running pip. It is also possible to specify command-line options directly to |Flake8|: .. prompt:: bash flake8 --select E123 Or .. prompt:: bash python -m flake8 --select E123 .. note:: This is the last time we will show both versions of an invocation. From now on, we'll simply use ``flake8`` and assume that the user knows they can instead use ``python -m flake8`` instead. It's also possible to narrow what |Flake8| will try to check by specifying exactly the paths and directories you want it to check. Let's assume that we have a directory with python files and sub-directories which have python files (and may have more sub-directories) called ``my_project``. Then if we only want errors from files found inside ``my_project`` we can do: .. prompt:: bash flake8 my_project And if we only want certain errors (e.g., ``E123``) from files in that directory we can also do: .. prompt:: bash flake8 --select E123 my_project If you want to explore more options that can be passed on the command-line, you can use the ``--help`` option: .. prompt:: bash flake8 --help And you should see something like: .. code:: Usage: flake8 [options] file file ... Options: --version show program's version number and exit -h, --help show this help message and exit -v, --verbose Print more information about what is happening in flake8. This option is repeatable and will increase verbosity each time it is repeated. -q, --quiet Report only file names, or nothing. This option is repeatable. --count Print total number of errors and warnings to standard error and set the exit code to 1 if total is not empty. --diff Report changes only within line number ranges in the unified diff provided on standard in by the user. --exclude=patterns Comma-separated list of files or directories to exclude.(Default: .svn,CVS,.bzr,.hg,.git,__pycache__,.tox,.eggs,*.egg) --filename=patterns Only check for filenames matching the patterns in this comma-separated list. (Default: *.py) --format=format Format errors according to the chosen formatter. --hang-closing Hang closing bracket instead of matching indentation of opening bracket's line. --ignore=errors Comma-separated list of errors and warnings to ignore (or skip). For example, ``--ignore=E4,E51,W234``. (Default: E121,E123,E126,E226,E24,E704) --extend-ignore=errors Comma-separated list of errors and warnings to add to the list of ignored ones. For example, ``--extend- ignore=E4,E51,W234``. --max-line-length=n Maximum allowed line length for the entirety of this run. (Default: 79) --select=errors Comma-separated list of errors and warnings to enable. For example, ``--select=E4,E51,W234``. (Default: ) --extend-select errors Comma-separated list of errors and warnings to add to the list of selected ones. For example, ``--extend- select=E4,E51,W234``. --disable-noqa Disable the effect of "# noqa". This will report errors on lines with "# noqa" at the end. --show-source Show the source generate each error or warning. --statistics Count errors and warnings. --enabled-extensions=ENABLED_EXTENSIONS Enable plugins and extensions that are otherwise disabled by default --exit-zero Exit with status code "0" even if there are errors. -j JOBS, --jobs=JOBS Number of subprocesses to use to run checks in parallel. This is ignored on Windows. The default, "auto", will auto-detect the number of processors available to use. (Default: auto) --output-file=OUTPUT_FILE Redirect report to a file. --tee Write to stdout and output-file. --append-config=APPEND_CONFIG Provide extra config files to parse in addition to the files found by Flake8 by default. These files are the last ones read and so they take the highest precedence when multiple files provide the same option. --config=CONFIG Path to the config file that will be the authoritative config source. This will cause Flake8 to ignore all other configuration files. --isolated Ignore all configuration files. --builtins=BUILTINS define more built-ins, comma separated --doctests check syntax of the doctests --include-in-doctest=INCLUDE_IN_DOCTEST Run doctests only on these files --exclude-from-doctest=EXCLUDE_FROM_DOCTEST Skip these files when running doctests Installed plugins: pyflakes: 1.0.0, pep8: 1.7.0 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633909928.0 flake8-4.0.1/docs/source/user/options.rst0000644000175000017500000005534400000000000022027 0ustar00asottileasottile00000000000000.. _options-list: ================================================ Full Listing of Options and Their Descriptions ================================================ .. NOTE(sigmavirus24): When adding new options here, please follow the following _rough_ template: .. option:: --[=] :ref:`Go back to index ` Active description of option's purpose (note that each description starts with an active verb) Command-line usage: .. prompt:: bash flake8 --[=] [positional params] This **can[ not]** be specified in config files. (If it can be, an example using .. code-block:: ini) Thank you for your contribution to Flake8's documentation. .. _top: Index of Options ================ - :option:`flake8 --version` - :option:`flake8 --help` - :option:`flake8 --verbose` - :option:`flake8 --quiet` - :option:`flake8 --count` - :option:`flake8 --diff` - :option:`flake8 --exclude` - :option:`flake8 --filename` - :option:`flake8 --stdin-display-name` - :option:`flake8 --format` - :option:`flake8 --hang-closing` - :option:`flake8 --ignore` - :option:`flake8 --extend-ignore` - :option:`flake8 --per-file-ignores` - :option:`flake8 --max-line-length` - :option:`flake8 --max-doc-length` - :option:`flake8 --indent-size` - :option:`flake8 --select` - :option:`flake8 --extend-select` - :option:`flake8 --disable-noqa` - :option:`flake8 --show-source` - :option:`flake8 --statistics` - :option:`flake8 --enable-extensions` - :option:`flake8 --exit-zero` - :option:`flake8 --jobs` - :option:`flake8 --output-file` - :option:`flake8 --tee` - :option:`flake8 --append-config` - :option:`flake8 --config` - :option:`flake8 --isolated` - :option:`flake8 --builtins` - :option:`flake8 --doctests` - :option:`flake8 --include-in-doctest` - :option:`flake8 --exclude-from-doctest` - :option:`flake8 --benchmark` - :option:`flake8 --bug-report` - :option:`flake8 --max-complexity` Options and their Descriptions ============================== .. program:: flake8 .. option:: --version :ref:`Go back to index ` Show |Flake8|'s version as well as the versions of all plugins installed. Command-line usage: .. prompt:: bash flake8 --version This **can not** be specified in config files. .. option:: -h, --help :ref:`Go back to index ` Show a description of how to use |Flake8| and its options. Command-line usage: .. prompt:: bash flake8 --help flake8 -h This **can not** be specified in config files. .. option:: -v, --verbose :ref:`Go back to index ` Increase the verbosity of |Flake8|'s output. Each time you specify it, it will print more and more information. Command-line example: .. prompt:: bash flake8 -vv This **can not** be specified in config files. .. option:: -q, --quiet :ref:`Go back to index ` Decrease the verbosity of |Flake8|'s output. Each time you specify it, it will print less and less information. Command-line example: .. prompt:: bash flake8 -q This **can** be specified in config files. Example config file usage: .. code-block:: ini quiet = 1 .. option:: --count :ref:`Go back to index ` Print the total number of errors. Command-line example: .. prompt:: bash flake8 --count dir/ This **can** be specified in config files. Example config file usage: .. code-block:: ini count = True .. option:: --diff :ref:`Go back to index ` Use the unified diff provided on standard in to only check the modified files and report errors included in the diff. Command-line example: .. prompt:: bash git diff -u | flake8 --diff This **can not** be specified in config files. .. option:: --exclude= :ref:`Go back to index ` Provide a comma-separated list of glob patterns to exclude from checks. This defaults to: ``.svn,CVS,.bzr,.hg,.git,__pycache__,.tox,.eggs,*.egg`` Example patterns: - ``*.pyc`` will match any file that ends with ``.pyc`` - ``__pycache__`` will match any path that has ``__pycache__`` in it - ``lib/python`` will look expand that using :func:`os.path.abspath` and look for matching paths Command-line example: .. prompt:: bash flake8 --exclude=*.pyc dir/ This **can** be specified in config files. Example config file usage: .. code-block:: ini exclude = .tox, __pycache__ .. option:: --extend-exclude= :ref:`Go back to index ` .. versionadded:: 3.8.0 Provide a comma-separated list of glob patterns to add to the list of excluded ones. Similar considerations as in :option:`--exclude` apply here with regard to the value. The difference to the :option:`--exclude` option is, that this option can be used to selectively add individual patterns without overriding the default list entirely. Command-line example: .. prompt:: bash flake8 --extend-exclude=legacy/,vendor/ dir/ This **can** be specified in config files. Example config file usage: .. code-block:: ini extend-exclude = legacy/, vendor/ extend-exclude = legacy/,vendor/ .. option:: --filename= :ref:`Go back to index ` Provide a comma-separate list of glob patterns to include for checks. This defaults to: ``*.py`` Example patterns: - ``*.py`` will match any file that ends with ``.py`` - ``__pycache__`` will match any path that has ``__pycache__`` in it - ``lib/python`` will look expand that using :func:`os.path.abspath` and look for matching paths Command-line example: .. prompt:: bash flake8 --filename=*.py dir/ This **can** be specified in config files. Example config file usage: .. code-block:: ini filename = example.py, another-example*.py .. option:: --stdin-display-name= :ref:`Go back to index ` Provide the name to use to report warnings and errors from code on stdin. Instead of reporting an error as something like: .. code:: stdin:82:73 E501 line too long You can specify this option to have it report whatever value you want instead of stdin. This defaults to: ``stdin`` Command-line example: .. prompt:: bash cat file.py | flake8 --stdin-display-name=file.py - This **can not** be specified in config files. .. option:: --format= :ref:`Go back to index ` Select the formatter used to display errors to the user. This defaults to: ``default`` By default, there are two formatters available: - default - pylint Other formatters can be installed. Refer to their documentation for the name to use to select them. Further, users can specify their own format string. The variables available are: - code - col - path - row - text The default formatter has a format string of: .. code-block:: python '%(path)s:%(row)d:%(col)d: %(code)s %(text)s' Command-line example: .. prompt:: bash flake8 --format=pylint dir/ flake8 --format='%(path)s::%(row)d,%(col)d::%(code)s::%(text)s' dir/ This **can** be specified in config files. Example config file usage: .. code-block:: ini format=pylint format=%(path)s::%(row)d,%(col)d::%(code)s::%(text)s .. option:: --hang-closing :ref:`Go back to index ` Toggle whether pycodestyle should enforce matching the indentation of the opening bracket's line. When you specify this, it will prefer that you hang the closing bracket rather than match the indentation. Command-line example: .. prompt:: bash flake8 --hang-closing dir/ This **can** be specified in config files. Example config file usage: .. code-block:: ini hang_closing = True hang-closing = True .. option:: --ignore= :ref:`Go back to index ` Specify a list of codes to ignore. The list is expected to be comma-separated, and does not need to specify an error code exactly. Since |Flake8| 3.0, this **can** be combined with :option:`--select`. See :option:`--select` for more information. For example, if you wish to only ignore ``W234``, then you can specify that. But if you want to ignore all codes that start with ``W23`` you need only specify ``W23`` to ignore them. This also works for ``W2`` and ``W`` (for example). This defaults to: ``E121,E123,E126,E226,E24,E704,W503,W504`` Command-line example: .. prompt:: bash flake8 --ignore=E121,E123 dir/ flake8 --ignore=E24,E704 dir/ This **can** be specified in config files. Example config file usage: .. code-block:: ini ignore = E121, E123 ignore = E121,E123 .. option:: --extend-ignore= :ref:`Go back to index ` .. versionadded:: 3.6.0 Specify a list of codes to add to the list of ignored ones. Similar considerations as in :option:`--ignore` apply here with regard to the value. The difference to the :option:`--ignore` option is, that this option can be used to selectively add individual codes without overriding the default list entirely. Command-line example: .. prompt:: bash flake8 --extend-ignore=E4,E51,W234 dir/ This **can** be specified in config files. Example config file usage: .. code-block:: ini extend-ignore = E4, E51, W234 extend-ignore = E4,E51,W234 .. option:: --per-file-ignores=[ ] :ref:`Go back to index ` .. versionadded:: 3.7.0 Specify a list of mappings of files and the codes that should be ignored for the entirety of the file. This allows for a project to have a default list of violations that should be ignored as well as file-specific violations for files that have not been made compliant with the project rules. This option supports syntax similar to :option:`--exclude` such that glob patterns will also work here. This can be combined with both :option:`--ignore` and :option:`--extend-ignore` to achieve a full flexibility of style options. Command-line usage: .. prompt:: bash flake8 --per-file-ignores='project/__init__.py:F401 setup.py:E121' flake8 --per-file-ignores='project/*/__init__.py:F401 setup.py:E121' This **can** be specified in config files. .. code-block:: ini per-file-ignores = project/__init__.py:F401 setup.py:E121 other_project/*:W9 .. option:: --max-line-length= :ref:`Go back to index ` Set the maximum length that any line (with some exceptions) may be. Exceptions include lines that are either strings or comments which are entirely URLs. For example: .. code-block:: python # https://some-super-long-domain-name.com/with/some/very/long/path url = ( 'http://...' ) This defaults to: ``79`` Command-line example: .. prompt:: bash flake8 --max-line-length 99 dir/ This **can** be specified in config files. Example config file usage: .. code-block:: ini max-line-length = 79 .. option:: --max-doc-length= :ref:`Go back to index ` Set the maximum length that a comment or docstring line may be. By default, there is no limit on documentation line length. Command-line example: .. prompt:: bash flake8 --max-doc-length 99 dir/ This **can** be specified in config files. Example config file usage: .. code-block:: ini max-doc-length = 79 .. option:: --indent-size= :ref:`Go back to index ` Set the number of spaces used for indentation. By default, 4. Command-line example: .. prompt:: bash flake8 --indent-size 2 dir/ This **can** be specified in config files. Example config file usage: .. code-block:: ini indent-size = 2 .. option:: --select= :ref:`Go back to index ` Specify the list of error codes you wish |Flake8| to report. Similarly to :option:`--ignore`. You can specify a portion of an error code to get all that start with that string. For example, you can use ``E``, ``E4``, ``E43``, and ``E431``. This defaults to: ``E,F,W,C90`` Command-line example: .. prompt:: bash flake8 --select=E431,E5,W,F dir/ flake8 --select=E,W dir/ This can also be combined with :option:`--ignore`: .. prompt:: bash flake8 --select=E --ignore=E432 dir/ This will report all codes that start with ``E``, but ignore ``E432`` specifically. This is more flexibly than the |Flake8| 2.x and 1.x used to be. This **can** be specified in config files. Example config file usage: .. code-block:: ini select = E431, W, F .. option:: --extend-select= :ref:`Go back to index ` .. versionadded:: 4.0.0 Specify a list of codes to add to the list of selected ones. Similar considerations as in :option:`--select` apply here with regard to the value. The difference to the :option:`--select` option is, that this option can be used to selectively add individual codes without overriding the default list entirely. Command-line example: .. prompt:: bash flake8 --extend-select=E4,E51,W234 dir/ This **can** be specified in config files. Example config file usage: .. code-block:: ini extend-select = E4, E51, W234 .. option:: --disable-noqa :ref:`Go back to index ` Report all errors, even if it is on the same line as a ``# NOQA`` comment. ``# NOQA`` can be used to silence messages on specific lines. Sometimes, users will want to see what errors are being silenced without editing the file. This option allows you to see all the warnings, errors, etc. reported. Command-line example: .. prompt:: bash flake8 --disable-noqa dir/ This **can** be specified in config files. Example config file usage: .. code-block:: ini disable_noqa = True disable-noqa = True .. option:: --show-source :ref:`Go back to index ` Print the source code generating the error/warning in question. Command-line example: .. prompt:: bash flake8 --show-source dir/ This **can** be specified in config files. Example config file usage: .. code-block:: ini show_source = True show-source = True .. option:: --statistics :ref:`Go back to index ` Count the number of occurrences of each error/warning code and print a report. Command-line example: .. prompt:: bash flake8 --statistics This **can** be specified in config files. Example config file usage: .. code-block:: ini statistics = True .. option:: --enable-extensions= :ref:`Go back to index ` Enable off-by-default extensions. Plugins to |Flake8| have the option of registering themselves as off-by-default. These plugins effectively add themselves to the default ignore list. Command-line example: .. prompt:: bash flake8 --enable-extensions=H111 dir/ This **can** be specified in config files. Example config file usage: .. code-block:: ini enable-extensions = H111, G123 enable_extensions = H111, G123 .. option:: --exit-zero :ref:`Go back to index ` Force |Flake8| to use the exit status code 0 even if there are errors. By default |Flake8| will exit with a non-zero integer if there are errors. Command-line example: .. prompt:: bash flake8 --exit-zero dir/ This **can not** be specified in config files. .. option:: --jobs= :ref:`Go back to index ` Specify the number of subprocesses that |Flake8| will use to run checks in parallel. .. note:: This option is ignored on platforms where ``fork`` is not a supported ``multiprocessing`` method. This defaults to: ``auto`` The default behaviour will use the number of CPUs on your machine as reported by :func:`multiprocessing.cpu_count`. Command-line example: .. prompt:: bash flake8 --jobs=8 dir/ This **can** be specified in config files. Example config file usage: .. code-block:: ini jobs = 8 .. option:: --output-file= :ref:`Go back to index ` Redirect all output to the specified file. Command-line example: .. prompt:: bash flake8 --output-file=output.txt dir/ flake8 -vv --output-file=output.txt dir/ .. option:: --tee :ref:`Go back to index ` Also print output to stdout if output-file has been configured. Command-line example: .. prompt:: bash flake8 --tee --output-file=output.txt dir/ This **can** be specified in config files. Example config file usage: .. code-block:: ini output-file = output.txt tee = True .. option:: --append-config= :ref:`Go back to index ` .. versionadded:: 3.6.0 Provide extra config files to parse in after and in addition to the files that |Flake8| found on its own. Since these files are the last ones read into the Configuration Parser, so it has the highest precedence if it provides an option specified in another config file. Command-line example: .. prompt:: bash flake8 --append-config=my-extra-config.ini dir/ This **can not** be specified in config files. .. option:: --config= :ref:`Go back to index ` Provide a path to a config file that will be the only config file read and used. This will cause |Flake8| to ignore all other config files that exist. Command-line example: .. prompt:: bash flake8 --config=my-only-config.ini dir/ This **can not** be specified in config files. .. option:: --isolated :ref:`Go back to index ` Ignore any config files and use |Flake8| as if there were no config files found. Command-line example: .. prompt:: bash flake8 --isolated dir/ This **can not** be specified in config files. .. option:: --builtins= :ref:`Go back to index ` Provide a custom list of builtin functions, objects, names, etc. This allows you to let pyflakes know about builtins that it may not immediately recognize so it does not report warnings for using an undefined name. This is registered by the default PyFlakes plugin. Command-line example: .. prompt:: bash flake8 --builtins=_,_LE,_LW dir/ This **can** be specified in config files. Example config file usage: .. code-block:: ini builtins = _, _LE, _LW .. option:: --doctests :ref:`Go back to index ` Enable PyFlakes syntax checking of doctests in docstrings. This is registered by the default PyFlakes plugin. Command-line example: .. prompt:: bash flake8 --doctests dir/ This **can** be specified in config files. Example config file usage: .. code-block:: ini doctests = True .. option:: --include-in-doctest= :ref:`Go back to index ` Specify which files are checked by PyFlakes for doctest syntax. This is registered by the default PyFlakes plugin. Command-line example: .. prompt:: bash flake8 --include-in-doctest=dir/subdir/file.py,dir/other/file.py dir/ This **can** be specified in config files. Example config file usage: .. code-block:: ini include-in-doctest = dir/subdir/file.py, dir/other/file.py include_in_doctest = dir/subdir/file.py, dir/other/file.py .. option:: --exclude-from-doctest= :ref:`Go back to index ` Specify which files are not to be checked by PyFlakes for doctest syntax. This is registered by the default PyFlakes plugin. Command-line example: .. prompt:: bash flake8 --exclude-from-doctest=dir/subdir/file.py,dir/other/file.py dir/ This **can** be specified in config files. Example config file usage: .. code-block:: ini exclude-from-doctest = dir/subdir/file.py, dir/other/file.py exclude_from_doctest = dir/subdir/file.py, dir/other/file.py .. option:: --benchmark :ref:`Go back to index ` Collect and print benchmarks for this run of |Flake8|. This aggregates the total number of: - tokens - physical lines - logical lines - files and the number of elapsed seconds. Command-line usage: .. prompt:: bash flake8 --benchmark dir/ This **can not** be specified in config files. .. option:: --bug-report :ref:`Go back to index ` Generate information necessary to file a complete bug report for Flake8. This will pretty-print a JSON blob that should be copied and pasted into a bug report for Flake8. Command-line usage: .. prompt:: bash flake8 --bug-report The output should look vaguely like: .. code-block:: js { "dependencies": [ { "dependency": "setuptools", "version": "25.1.1" } ], "platform": { "python_implementation": "CPython", "python_version": "2.7.12", "system": "Darwin" }, "plugins": [ { "plugin": "mccabe", "version": "0.5.1" }, { "plugin": "pycodestyle", "version": "2.0.0" }, { "plugin": "pyflakes", "version": "1.2.3" } ], "version": "3.1.0.dev0" } This **can not** be specified in config files. .. option:: --max-complexity= :ref:`Go back to index ` Set the maximum allowed McCabe complexity value for a block of code. This option is provided by the ``mccabe`` dependency's |Flake8| plugin. Command-line usage: .. prompt:: bash flake8 --max-complexity 15 dir/ This **can** be specified in config files. Example config file usage: .. code-block:: ini max-complexity = 15 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633909928.0 flake8-4.0.1/docs/source/user/python-api.rst0000644000175000017500000000671400000000000022421 0ustar00asottileasottile00000000000000=================== Public Python API =================== |Flake8| 3.0.0 presently does not have a public, stable Python API. When it does it will be located in :mod:`flake8.api` and that will be documented here. Legacy API ========== When |Flake8| broke its hard dependency on the tricky internals of pycodestyle, it lost the easy backwards compatibility as well. To help existing users of that API we have :mod:`flake8.api.legacy`. This module includes a couple classes (which are documented below) and a function. The main usage that the developers of Flake8 observed was using the :func:`~flake8.api.legacy.get_style_guide` function and then calling :meth:`~flake8.api.legacy.StyleGuide.check_files`. To a lesser extent, people also seemed to use the :meth:`~flake8.api.legacy.Report.get_statistics` method on what ``check_files`` returns. We then sought to preserve that API in this module. Let's look at an example piece of code together: .. code-block:: python from flake8.api import legacy as flake8 style_guide = flake8.get_style_guide(ignore=['E24', 'W503']) report = style_guide.check_files([...]) assert report.get_statistics('E') == [], 'Flake8 found violations' This represents the basic universal usage of all existing Flake8 2.x integrations. Each example we found was obviously slightly different, but this is kind of the gist, so let's walk through this. Everything that is backwards compatible for our API is in the :mod:`flake8.api.legacy` submodule. This is to indicate, clearly, that the old API is being used. We create a |StyleGuide| by calling |style_guide|. We can pass options to |style_guide| that correspond to the command-line options one might use. For example, we can pass ``ignore``, ``select``, ``exclude``, ``format``, etc. Our legacy API, does not enforce legacy behaviour, so we can combine ``ignore`` and ``select`` like we might on the command-line, e.g., .. code-block:: python style_guide = flake8.get_style_guide( ignore=['E24', 'W5'], select=['E', 'W', 'F'], format='pylint', ) Once we have our |StyleGuide| we can use the same methods that we used before, namely .. automethod:: flake8.api.legacy.StyleGuide.check_files .. automethod:: flake8.api.legacy.StyleGuide.excluded .. automethod:: flake8.api.legacy.StyleGuide.init_report .. automethod:: flake8.api.legacy.StyleGuide.input_file .. warning:: These are not *perfectly* backwards compatible. Not all arguments are respsected, and some of the types necessary for something to work have changed. Most people, we observed, were using :meth:`~flake8.api.legacy.StyleGuide.check_files`. You can use this to specify a list of filenames or directories to check. In |Flake8| 3.0, however, we return a different object that has similar methods. We return a |Report| which has the method .. automethod:: flake8.api.legacy.Report.get_statistics Most usage of this method that we noted was as documented above. Keep in mind, however, that it provides a list of strings and not anything more malleable. Autogenerated Legacy Documentation ---------------------------------- .. automodule:: flake8.api.legacy :members: .. autoclass:: flake8.api.legacy.StyleGuide :members: options, paths .. autoclass:: flake8.api.legacy.Report :members: total_errors .. |style_guide| replace:: :func:`flake8.api.legacy.get_style_guide` .. |StyleGuide| replace:: :class:`flake8.api.legacy.StyleGuide` .. |Report| replace:: :class:`flake8.api.legacy.Report` ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633909928.0 flake8-4.0.1/docs/source/user/using-hooks.rst0000644000175000017500000000254100000000000022571 0ustar00asottileasottile00000000000000============================= Using Version Control Hooks ============================= Usage with the `pre-commit`_ git hooks framework ================================================ |Flake8| can be included as a hook for `pre-commit`_. The easiest way to get started is to add this configuration to your ``.pre-commit-config.yaml``: .. code-block:: yaml - repo: https://github.com/pycqa/flake8 rev: '' # pick a git hash / tag to point to hooks: - id: flake8 See the `pre-commit docs`_ for how to customize this configuration. Checked-in python files will be passed as positional arguments. ``flake8`` will always lint explicitly passed arguments (:option:`flake8 --exclude` has no effect). Instead use ``pre-commit``'s ``exclude: ...`` regex to exclude files. ``pre-commit`` won't ever pass untracked files to ``flake8`` so excluding ``.git`` / ``.tox`` / etc. is unnecessary. .. code-block:: yaml - id: flake8 exclude: ^testing/(data|examples)/ ``pre-commit`` creates an isolated environment for hooks. To use ``flake8`` plugins, use the ``additional_dependencies`` setting. .. code-block:: yaml - id: flake8 additional_dependencies: [flake8-docstrings] .. _pre-commit: https://pre-commit.com/ .. _pre-commit docs: https://pre-commit.com/#pre-commit-configyaml---hooks ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633909928.0 flake8-4.0.1/docs/source/user/using-plugins.rst0000644000175000017500000000413500000000000023130 0ustar00asottileasottile00000000000000================================== Using Plugins For Fun and Profit ================================== |Flake8| is useful on its own but a lot of |Flake8|'s popularity is due to its extensibility. Our community has developed :term:`plugin`\ s that augment |Flake8|'s behaviour. Most of these plugins are uploaded to PyPI_. The developers of these plugins often have some style they wish to enforce. For example, `flake8-docstrings`_ adds a check for :pep:`257` style conformance. Others attempt to enforce consistency, like `flake8-quotes`_. .. note:: The accuracy or reliability of these plugins may vary wildly from plugin to plugin and not all plugins are guaranteed to work with |Flake8| 3.0. To install a third-party plugin, make sure that you know which version of Python (or pip) you used to install |Flake8|. You can then use the most appropriate of: .. prompt:: bash pip install pip3 install python -m pip install python3 -m pip install python3.9 -m pip install To install the plugin, where ```` is the package name on PyPI_. To verify installation use: .. prompt:: bash flake8 --version python -m flake8 --version To see the plugin's name and version in the output. .. seealso:: :ref:`How to Invoke Flake8 ` After installation, most plugins immediately start reporting :term:`error`\ s. Check the plugin's documentation for which error codes it returns and if it disables any by default. .. note:: You can use both :option:`flake8 --select` and :option:`flake8 --ignore` with plugins. Some plugins register new options, so be sure to check :option:`flake8 --help` for new flags and documentation. These plugins may also allow these flags to be specified in your configuration file. Hopefully, the plugin authors have documented this for you. .. seealso:: :ref:`Configuring Flake8 ` .. _PyPI: https://pypi.org/ .. _flake8-docstrings: https://pypi.org/project/flake8-docstrings/ .. _flake8-quotes: https://pypi.org/project/flake8-quotes/ ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633909928.0 flake8-4.0.1/docs/source/user/violations.rst0000644000175000017500000002253400000000000022516 0ustar00asottileasottile00000000000000=================================== Selecting and Ignoring Violations =================================== It is possible to select and ignore certain violations reported by |Flake8| and the plugins we've installed. It's also possible as of |Flake8| 3.0 to combine usage of :option:`flake8 --select` and :option:`flake8 --ignore`. This chapter of the User Guide aims to educate about how Flake8 will report errors based on different inputs. Ignoring Violations with Flake8 =============================== By default, |Flake8| has a list of error codes that it ignores. The list used by a version of |Flake8| may be different than the list used by a different version. To see the default list, :option:`flake8 --help` will show the output with the current default list. Extending the Default Ignore List --------------------------------- If we want to extend the default list of ignored error codes, we can use :option:`flake8 --extend-ignore` to specify a comma-separated list of codes for a specific run on the command line, e.g., .. prompt:: bash flake8 --extend-ignore=E1,E23 path/to/files/ path/to/more/files This tells |Flake8| to ignore any error codes starting with ``E1`` and ``E23``, in addition the default ignore list. To view the default error code ignore list, run :option:`flake8 --help` and refer to the help text for :option:`flake8 --ignore`. .. The section below used to be titled `Changing the Default Ignore List`, but was renamed for clarity. Explicitly retain the old section anchor so as to not break links: .. _changing-the-ignore-list: Overriding the Default Ignore List ---------------------------------- If we want to *completely* override the default list of ignored error codes, we can use :option:`flake8 --ignore` to specify a comma-separated list of codes for a specific run on the command-line, e.g., .. prompt:: bash flake8 --ignore=E1,E23,W503 path/to/files/ path/to/more/files/ This tells |Flake8| to *only* ignore error codes starting with ``E1``, ``E23``, or ``W503`` while it is running. .. note:: The documentation for :option:`flake8 --ignore` shows examples for how to change the ignore list in the configuration file. See also :ref:`configuration` as well for details about how to use configuration files. In-line Ignoring Errors ----------------------- In some cases, we might not want to ignore an error code (or class of error codes) for the entirety of our project. Instead, we might want to ignore the specific error code on a specific line. Let's take for example a line like .. code-block:: python example = lambda: 'example' Sometimes we genuinely need something this simple. We could instead define a function like we normally would. Note, in some contexts this distracts from what is actually happening. In those cases, we can also do: .. code-block:: python example = lambda: 'example' # noqa: E731 This will only ignore the error from pycodestyle that checks for lambda assignments and generates an ``E731``. If there are other errors on the line then those will be reported. ``# noqa`` is case-insensitive, without the colon the part after ``# noqa`` would be ignored. .. note:: If we ever want to disable |Flake8| respecting ``# noqa`` comments, we can refer to :option:`flake8 --disable-noqa`. If we instead had more than one error that we wished to ignore, we could list all of the errors with commas separating them: .. code-block:: python # noqa: E731,E123 Finally, if we have a particularly bad line of code, we can ignore every error using simply ``# noqa`` with nothing after it. Contents before and after the ``# noqa: ...`` portion are ignored so multiple comments may appear on one line. Here are several examples: .. code-block:: python # mypy requires `# type: ignore` to appear first x = 5 # type: ignore # noqa: ABC123 # can use to add useful user information to a noqa comment y = 6 # noqa: ABC456 # TODO: will fix this later Ignoring Entire Files --------------------- Imagine a situation where we are adding |Flake8| to a codebase. Let's further imagine that with the exception of a few particularly bad files, we can add |Flake8| easily and move on with our lives. There are two ways to ignore the file: #. By explicitly adding it to our list of excluded paths (see: :option:`flake8 --exclude`) #. By adding ``# flake8: noqa`` to the file The former is the **recommended** way of ignoring entire files. By using our exclude list, we can include it in our configuration file and have one central place to find what files aren't included in |Flake8| checks. The latter has the benefit that when we run |Flake8| with :option:`flake8 --disable-noqa` all of the errors in that file will show up without having to modify our configuration. Both exist so we can choose which is better for us. Selecting Violations with Flake8 ================================ |Flake8| has a default list of violation classes that we use. This list is: - ``C90`` All ``C90`` class violations are reported when the user specifies :option:`flake8 --max-complexity` - ``E`` All ``E`` class violations are "errors" reported by pycodestyle - ``F`` All ``F`` class violations are reported by pyflakes - ``W`` All ``W`` class violations are "warnings" reported by pycodestyle This list can be overridden by specifying :option:`flake8 --select`. Just as specifying :option:`flake8 --ignore` will change the behaviour of |Flake8|, so will :option:`flake8 --select`. Let's look through some examples using this sample code: .. code-block:: python # example.py def foo(): print( "Hello" "World" ) By default, if we run ``flake8`` on this file we'll get: .. prompt:: bash flake8 example.py .. code:: text example.py:4:9: E131 continuation line unaligned for hanging indent Now let's select all ``E`` class violations: .. prompt:: bash flake8 --select E example.py .. code:: text example.py:3:17: E126 continuation line over-indented for hanging indent example.py:4:9: E131 continuation line unaligned for hanging indent example.py:5:9: E121 continuation line under-indented for hanging indent Suddenly we now have far more errors that are reported to us. Using ``--select`` alone will override the default ``--ignore`` list. In these cases, the user is telling us that they want all ``E`` violations and so we ignore our list of violations that we ignore by default. We can also be highly specific. For example, we can do .. prompt:: bash flake8 --select E121 example.py .. code:: text example.py:5:9: E121 continuation line under-indented for hanging indent We can also specify lists of items to select both on the command-line and in our configuration files. .. prompt:: bash flake8 --select E121,E131 example.py .. code:: text example.py:4:9: E131 continuation line unaligned for hanging indent example.py:5:9: E121 continuation line under-indented for hanging indent Selecting and Ignoring Simultaneously For Fun and Profit ======================================================== Prior to |Flake8| 3.0, all handling of :option:`flake8 --select` and :option:`flake8 --ignore` was delegated to pycodestyle. Its handling of the options significantly differs from how |Flake8| 3.0 has been designed. pycodestyle has always preferred ``--ignore`` over ``--select`` and will ignore ``--select`` if the user provides both. |Flake8| 3.0 will now do its best to intuitively combine both options provided by the user. Let's look at some examples using: .. code-block:: python # example.py import os def foo(): var = 1 print( "Hello" "World" ) If we run |Flake8| with its default settings we get: .. prompt:: bash flake8 example.py .. code:: text example.py:1:1: F401 'os' imported but unused example.py:5:5: F841 local variable 'var' is assigned to but never used example.py:8:9: E131 continuation line unaligned for hanging indent Now let's select all ``E`` and ``F`` violations including those in the default ignore list. .. prompt:: bash flake8 --select E,F example.py .. code:: text example.py:1:1: F401 'os' imported but unused example.py:5:5: F841 local variable 'var' is assigned to but never used example.py:7:17: E126 continuation line over-indented for hanging indent example.py:8:9: E131 continuation line unaligned for hanging indent example.py:9:9: E121 continuation line under-indented for hanging indent Now let's selectively ignore some of these while selecting the rest: .. prompt:: bash flake8 --select E,F --ignore F401,E121 example.py .. code:: text example.py:5:5: F841 local variable 'var' is assigned to but never used example.py:7:17: E126 continuation line over-indented for hanging indent example.py:8:9: E131 continuation line unaligned for hanging indent Via this example, we can see that the *most specific* **user-specified** rule will win. So in the above, we had very vague select rules and two very specific ignore rules. Let's look at a different example: .. prompt:: bash flake8 --select F401,E131 --ignore E,F example.py .. code:: text example.py:1:1: F401 'os' imported but unused example.py:8:9: E131 continuation line unaligned for hanging indent In this case, we see that since our selected violation codes were more specific those were reported. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633909928.0 flake8-4.0.1/pytest.ini0000644000175000017500000000022400000000000016410 0ustar00asottileasottile00000000000000[pytest] norecursedirs = .git .* *.egg* old docs dist build addopts = -rw filterwarnings = error ignore:SelectableGroups:DeprecationWarning ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1633956150.3574667 flake8-4.0.1/setup.cfg0000644000175000017500000001204600000000000016205 0ustar00asottileasottile00000000000000[metadata] name = flake8 version = attr: flake8.__version__ description = the modular source code checker: pep8 pyflakes and co long_description = file: README.rst long_description_content_type = text/x-rst url = https://github.com/pycqa/flake8 author = Tarek Ziade author_email = tarek@ziade.org maintainer = Ian Stapleton Cordasco maintainer_email = graffatcolmingov@gmail.com license = MIT license_file = LICENSE classifiers = Development Status :: 5 - Production/Stable Environment :: Console Framework :: Flake8 Intended Audience :: Developers License :: OSI Approved :: MIT License Programming Language :: Python Programming Language :: Python :: 3 Programming Language :: Python :: 3 :: Only Programming Language :: Python :: 3.6 Programming Language :: Python :: 3.7 Programming Language :: Python :: 3.8 Programming Language :: Python :: 3.9 Programming Language :: Python :: 3.10 Programming Language :: Python :: Implementation :: CPython Programming Language :: Python :: Implementation :: PyPy Topic :: Software Development :: Libraries :: Python Modules Topic :: Software Development :: Quality Assurance [options] packages = find: package_dir = =src install_requires = mccabe>=0.6.0,<0.7.0 pycodestyle>=2.8.0,<2.9.0 pyflakes>=2.4.0,<2.5.0 importlib-metadata<4.3;python_version<"3.8" python_requires = >=3.6 [options.packages.find] where = src [options.entry_points] console_scripts = flake8 = flake8.main.cli:main flake8.extension = F = flake8.plugins.pyflakes:FlakesChecker pycodestyle.ambiguous_identifier = pycodestyle:ambiguous_identifier pycodestyle.bare_except = pycodestyle:bare_except pycodestyle.blank_lines = pycodestyle:blank_lines pycodestyle.break_after_binary_operator = pycodestyle:break_after_binary_operator pycodestyle.break_before_binary_operator = pycodestyle:break_before_binary_operator pycodestyle.comparison_negative = pycodestyle:comparison_negative pycodestyle.comparison_to_singleton = pycodestyle:comparison_to_singleton pycodestyle.comparison_type = pycodestyle:comparison_type pycodestyle.compound_statements = pycodestyle:compound_statements pycodestyle.continued_indentation = pycodestyle:continued_indentation pycodestyle.explicit_line_join = pycodestyle:explicit_line_join pycodestyle.extraneous_whitespace = pycodestyle:extraneous_whitespace pycodestyle.imports_on_separate_lines = pycodestyle:imports_on_separate_lines pycodestyle.indentation = pycodestyle:indentation pycodestyle.maximum_doc_length = pycodestyle:maximum_doc_length pycodestyle.maximum_line_length = pycodestyle:maximum_line_length pycodestyle.missing_whitespace = pycodestyle:missing_whitespace pycodestyle.missing_whitespace_after_import_keyword = pycodestyle:missing_whitespace_after_import_keyword pycodestyle.missing_whitespace_around_operator = pycodestyle:missing_whitespace_around_operator pycodestyle.module_imports_on_top_of_file = pycodestyle:module_imports_on_top_of_file pycodestyle.python_3000_async_await_keywords = pycodestyle:python_3000_async_await_keywords pycodestyle.python_3000_backticks = pycodestyle:python_3000_backticks pycodestyle.python_3000_has_key = pycodestyle:python_3000_has_key pycodestyle.python_3000_invalid_escape_sequence = pycodestyle:python_3000_invalid_escape_sequence pycodestyle.python_3000_not_equal = pycodestyle:python_3000_not_equal pycodestyle.python_3000_raise_comma = pycodestyle:python_3000_raise_comma pycodestyle.tabs_obsolete = pycodestyle:tabs_obsolete pycodestyle.tabs_or_spaces = pycodestyle:tabs_or_spaces pycodestyle.trailing_blank_lines = pycodestyle:trailing_blank_lines pycodestyle.trailing_whitespace = pycodestyle:trailing_whitespace pycodestyle.whitespace_around_comma = pycodestyle:whitespace_around_comma pycodestyle.whitespace_around_keywords = pycodestyle:whitespace_around_keywords pycodestyle.whitespace_around_named_parameter_equals = pycodestyle:whitespace_around_named_parameter_equals pycodestyle.whitespace_around_operator = pycodestyle:whitespace_around_operator pycodestyle.whitespace_before_comment = pycodestyle:whitespace_before_comment pycodestyle.whitespace_before_parameters = pycodestyle:whitespace_before_parameters flake8.report = default = flake8.formatting.default:Default pylint = flake8.formatting.default:Pylint quiet-filename = flake8.formatting.default:FilenameOnly quiet-nothing = flake8.formatting.default:Nothing [bdist_wheel] universal = 1 [mypy] check_untyped_defs = true disallow_any_generics = true disallow_incomplete_defs = true no_implicit_optional = true warn_unused_ignores = true [mypy-flake8.defaults] disallow_untyped_defs = true [mypy-flake8.exceptions] disallow_untyped_defs = true [mypy-flake8.formatting.*] disallow_untyped_defs = true [mypy-flake8.options.manager] disallow_untyped_defs = true [mypy-flake8.main.cli] disallow_untyped_defs = true [mypy-flake8.processor] disallow_untyped_defs = true [mypy-flake8.statistics] disallow_untyped_defs = true [mypy-flake8.style_guide] disallow_untyped_defs = true [mypy-flake8.utils] disallow_untyped_defs = true [mypy-tests.*] disallow_untyped_defs = false [egg_info] tag_build = tag_date = 0 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633909928.0 flake8-4.0.1/setup.py0000644000175000017500000000024200000000000016071 0ustar00asottileasottile00000000000000"""Packaging logic for Flake8.""" import os import sys import setuptools sys.path.insert(0, os.path.join(os.path.dirname(__file__), "src")) setuptools.setup() ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1633956150.3334746 flake8-4.0.1/src/0000755000175000017500000000000000000000000015150 5ustar00asottileasottile00000000000000././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1633956150.3494692 flake8-4.0.1/src/flake8/0000755000175000017500000000000000000000000016322 5ustar00asottileasottile00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633955791.0 flake8-4.0.1/src/flake8/__init__.py0000644000175000017500000000415700000000000020442 0ustar00asottileasottile00000000000000"""Top-level module for Flake8. This module - initializes logging for the command-line tool - tracks the version of the package - provides a way to configure logging for the command-line tool .. autofunction:: flake8.configure_logging """ import logging import sys from typing import Type LOG = logging.getLogger(__name__) LOG.addHandler(logging.NullHandler()) __version__ = "4.0.1" __version_info__ = tuple(int(i) for i in __version__.split(".") if i.isdigit()) # There is nothing lower than logging.DEBUG (10) in the logging library, # but we want an extra level to avoid being too verbose when using -vv. _EXTRA_VERBOSE = 5 logging.addLevelName(_EXTRA_VERBOSE, "VERBOSE") _VERBOSITY_TO_LOG_LEVEL = { # output more than warnings but not debugging info 1: logging.INFO, # INFO is a numerical level of 20 # output debugging information 2: logging.DEBUG, # DEBUG is a numerical level of 10 # output extra verbose debugging information 3: _EXTRA_VERBOSE, } LOG_FORMAT = ( "%(name)-25s %(processName)-11s %(relativeCreated)6d " "%(levelname)-8s %(message)s" ) def configure_logging(verbosity, filename=None, logformat=LOG_FORMAT): """Configure logging for flake8. :param int verbosity: How verbose to be in logging information. :param str filename: Name of the file to append log information to. If ``None`` this will log to ``sys.stderr``. If the name is "stdout" or "stderr" this will log to the appropriate stream. """ if verbosity <= 0: return if verbosity > 3: verbosity = 3 log_level = _VERBOSITY_TO_LOG_LEVEL[verbosity] if not filename or filename in ("stderr", "stdout"): fileobj = getattr(sys, filename or "stderr") handler_cls: Type[logging.Handler] = logging.StreamHandler else: fileobj = filename handler_cls = logging.FileHandler handler = handler_cls(fileobj) handler.setFormatter(logging.Formatter(logformat)) LOG.addHandler(handler) LOG.setLevel(log_level) LOG.debug( "Added a %s logging handler to logger root at %s", filename, __name__ ) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633909928.0 flake8-4.0.1/src/flake8/__main__.py0000644000175000017500000000013400000000000020412 0ustar00asottileasottile00000000000000"""Module allowing for ``python -m flake8 ...``.""" from flake8.main import cli cli.main() ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633909928.0 flake8-4.0.1/src/flake8/_compat.py0000644000175000017500000000041000000000000020311 0ustar00asottileasottile00000000000000"""Expose backports in a single place.""" import sys if sys.version_info >= (3, 8): # pragma: no cover (PY38+) import importlib.metadata as importlib_metadata else: # pragma: no cover ( argparse.Namespace: """Return application's options. An instance of :class:`argparse.Namespace` containing parsed options. """ return self._application.options @property def paths(self): """Return the extra arguments passed as paths.""" return self._application.paths def check_files(self, paths=None): """Run collected checks on the files provided. This will check the files passed in and return a :class:`Report` instance. :param list paths: List of filenames (or paths) to check. :returns: Object that mimic's Flake8 2.0's Reporter class. :rtype: flake8.api.legacy.Report """ self._application.run_checks(paths) self._application.report_errors() return Report(self._application) def excluded(self, filename, parent=None): """Determine if a file is excluded. :param str filename: Path to the file to check if it is excluded. :param str parent: Name of the parent directory containing the file. :returns: True if the filename is excluded, False otherwise. :rtype: bool """ return self._file_checker_manager.is_path_excluded(filename) or ( parent and self._file_checker_manager.is_path_excluded( os.path.join(parent, filename) ) ) def init_report(self, reporter=None): """Set up a formatter for this run of Flake8.""" if reporter is None: return if not issubclass(reporter, formatter.BaseFormatter): raise ValueError( "Report should be subclass of " "flake8.formatter.BaseFormatter." ) self._application.formatter = None self._application.make_formatter(reporter) self._application.guide = None # NOTE(sigmavirus24): This isn't the intended use of # Application#make_guide but it works pretty well. # Stop cringing... I know it's gross. self._application.make_guide() self._application.file_checker_manager = None self._application.make_file_checker_manager() def input_file(self, filename, lines=None, expected=None, line_offset=0): """Run collected checks on a single file. This will check the file passed in and return a :class:`Report` instance. :param str filename: The path to the file to check. :param list lines: Ignored since Flake8 3.0. :param expected: Ignored since Flake8 3.0. :param int line_offset: Ignored since Flake8 3.0. :returns: Object that mimic's Flake8 2.0's Reporter class. :rtype: flake8.api.legacy.Report """ return self.check_files([filename]) class Report: """Public facing object that mimic's Flake8 2.0's API. .. note:: There are important changes in how this object behaves compared to the object provided in Flake8 2.x. .. warning:: This should not be instantiated by users. .. versionchanged:: 3.0.0 """ def __init__(self, application): """Initialize the Report for the user. .. warning:: This should not be instantiated by users. """ self._application = application self._style_guide = application.guide self._stats = self._style_guide.stats @property def total_errors(self): """Return the total number of errors.""" return self._application.result_count def get_statistics(self, violation): """Get the list of occurrences of a violation. :returns: List of occurrences of a violation formatted as: {Count} {Error Code} {Message}, e.g., ``8 E531 Some error message about the error`` :rtype: list """ return [ f"{s.count} {s.error_code} {s.message}" for s in self._stats.statistics_for(violation) ] ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633955638.0 flake8-4.0.1/src/flake8/checker.py0000644000175000017500000006344500000000000020314 0ustar00asottileasottile00000000000000"""Checker Manager and Checker classes.""" import collections import errno import itertools import logging import signal import tokenize from typing import Dict from typing import List from typing import Optional from typing import Tuple from flake8 import defaults from flake8 import exceptions from flake8 import processor from flake8 import utils try: import multiprocessing.pool except ImportError: multiprocessing = None # type: ignore Results = List[Tuple[str, int, int, str, Optional[str]]] LOG = logging.getLogger(__name__) SERIAL_RETRY_ERRNOS = { # ENOSPC: Added by sigmavirus24 # > On some operating systems (OSX), multiprocessing may cause an # > ENOSPC error while trying to trying to create a Semaphore. # > In those cases, we should replace the customized Queue Report # > class with pep8's StandardReport class to ensure users don't run # > into this problem. # > (See also: https://github.com/pycqa/flake8/issues/117) errno.ENOSPC, # NOTE(sigmavirus24): When adding to this list, include the reasoning # on the lines before the error code and always append your error # code. Further, please always add a trailing `,` to reduce the visual # noise in diffs. } def _multiprocessing_is_fork(): # type () -> bool """Class state is only preserved when using the `fork` strategy.""" return multiprocessing and multiprocessing.get_start_method() == "fork" class Manager: """Manage the parallelism and checker instances for each plugin and file. This class will be responsible for the following: - Determining the parallelism of Flake8, e.g.: * Do we use :mod:`multiprocessing` or is it unavailable? * Do we automatically decide on the number of jobs to use or did the user provide that? - Falling back to a serial way of processing files if we run into an OSError related to :mod:`multiprocessing` - Organizing the results of each checker so we can group the output together and make our output deterministic. """ def __init__(self, style_guide, arguments, checker_plugins): """Initialize our Manager instance. :param style_guide: The instantiated style guide for this instance of Flake8. :type style_guide: flake8.style_guide.StyleGuide :param list arguments: The extra arguments parsed from the CLI (if any) :param checker_plugins: The plugins representing checks parsed from entry-points. :type checker_plugins: flake8.plugins.manager.Checkers """ self.arguments = arguments self.style_guide = style_guide self.options = style_guide.options self.checks = checker_plugins self.jobs = self._job_count() self._all_checkers: List[FileChecker] = [] self.checkers: List[FileChecker] = [] self.statistics = { "files": 0, "logical lines": 0, "physical lines": 0, "tokens": 0, } self.exclude = tuple( itertools.chain(self.options.exclude, self.options.extend_exclude) ) def _process_statistics(self): for checker in self.checkers: for statistic in defaults.STATISTIC_NAMES: self.statistics[statistic] += checker.statistics[statistic] self.statistics["files"] += len(self.checkers) def _job_count(self) -> int: # First we walk through all of our error cases: # - multiprocessing library is not present # - we're running on windows in which case we know we have significant # implementation issues # - the user provided stdin and that's not something we can handle # well # - we're processing a diff, which again does not work well with # multiprocessing and which really shouldn't require multiprocessing # - the user provided some awful input if not _multiprocessing_is_fork(): LOG.warning( "The multiprocessing module is not available. " "Ignoring --jobs arguments." ) return 0 if utils.is_using_stdin(self.arguments): LOG.warning( "The --jobs option is not compatible with supplying " "input using - . Ignoring --jobs arguments." ) return 0 if self.options.diff: LOG.warning( "The --diff option was specified with --jobs but " "they are not compatible. Ignoring --jobs arguments." ) return 0 jobs = self.options.jobs # If the value is "auto", we want to let the multiprocessing library # decide the number based on the number of CPUs. However, if that # function is not implemented for this particular value of Python we # default to 1 if jobs.is_auto: try: return multiprocessing.cpu_count() except NotImplementedError: return 0 # Otherwise, we know jobs should be an integer and we can just convert # it to an integer return jobs.n_jobs def _handle_results(self, filename, results): style_guide = self.style_guide reported_results_count = 0 for (error_code, line_number, column, text, physical_line) in results: reported_results_count += style_guide.handle_error( code=error_code, filename=filename, line_number=line_number, column_number=column, text=text, physical_line=physical_line, ) return reported_results_count def is_path_excluded(self, path: str) -> bool: """Check if a path is excluded. :param str path: Path to check against the exclude patterns. :returns: True if there are exclude patterns and the path matches, otherwise False. :rtype: bool """ if path == "-": if self.options.stdin_display_name == "stdin": return False path = self.options.stdin_display_name return utils.matches_filename( path, patterns=self.exclude, log_message='"%(path)s" has %(whether)sbeen excluded', logger=LOG, ) def make_checkers(self, paths: Optional[List[str]] = None) -> None: """Create checkers for each file.""" if paths is None: paths = self.arguments if not paths: paths = ["."] filename_patterns = self.options.filename running_from_diff = self.options.diff # NOTE(sigmavirus24): Yes this is a little unsightly, but it's our # best solution right now. def should_create_file_checker(filename, argument): """Determine if we should create a file checker.""" matches_filename_patterns = utils.fnmatch( filename, filename_patterns ) is_stdin = filename == "-" # NOTE(sigmavirus24): If a user explicitly specifies something, # e.g, ``flake8 bin/script`` then we should run Flake8 against # that. Since should_create_file_checker looks to see if the # filename patterns match the filename, we want to skip that in # the event that the argument and the filename are identical. # If it was specified explicitly, the user intended for it to be # checked. explicitly_provided = not running_from_diff and ( argument == filename ) return ( explicitly_provided or matches_filename_patterns ) or is_stdin checks = self.checks.to_dictionary() self._all_checkers = [ FileChecker(filename, checks, self.options) for argument in paths for filename in utils.filenames_from( argument, self.is_path_excluded ) if should_create_file_checker(filename, argument) ] self.checkers = [c for c in self._all_checkers if c.should_process] LOG.info("Checking %d files", len(self.checkers)) def report(self) -> Tuple[int, int]: """Report all of the errors found in the managed file checkers. This iterates over each of the checkers and reports the errors sorted by line number. :returns: A tuple of the total results found and the results reported. :rtype: tuple(int, int) """ results_reported = results_found = 0 for checker in self._all_checkers: results = sorted(checker.results, key=lambda tup: (tup[1], tup[2])) filename = checker.display_name with self.style_guide.processing_file(filename): results_reported += self._handle_results(filename, results) results_found += len(results) return (results_found, results_reported) def run_parallel(self) -> None: """Run the checkers in parallel.""" # fmt: off final_results: Dict[str, List[Tuple[str, int, int, str, Optional[str]]]] = collections.defaultdict(list) # noqa: E501 final_statistics: Dict[str, Dict[str, int]] = collections.defaultdict(dict) # noqa: E501 # fmt: on pool = _try_initialize_processpool(self.jobs) if pool is None: self.run_serial() return pool_closed = False try: pool_map = pool.imap_unordered( _run_checks, self.checkers, chunksize=calculate_pool_chunksize( len(self.checkers), self.jobs ), ) for ret in pool_map: filename, results, statistics = ret final_results[filename] = results final_statistics[filename] = statistics pool.close() pool.join() pool_closed = True finally: if not pool_closed: pool.terminate() pool.join() for checker in self.checkers: filename = checker.display_name checker.results = final_results[filename] checker.statistics = final_statistics[filename] def run_serial(self) -> None: """Run the checkers in serial.""" for checker in self.checkers: checker.run_checks() def run(self) -> None: """Run all the checkers. This will intelligently decide whether to run the checks in parallel or whether to run them in serial. If running the checks in parallel causes a problem (e.g., https://github.com/pycqa/flake8/issues/117) this also implements fallback to serial processing. """ try: if self.jobs > 1 and len(self.checkers) > 1: self.run_parallel() else: self.run_serial() except KeyboardInterrupt: LOG.warning("Flake8 was interrupted by the user") raise exceptions.EarlyQuit("Early quit while running checks") def start(self, paths=None): """Start checking files. :param list paths: Path names to check. This is passed directly to :meth:`~Manager.make_checkers`. """ LOG.info("Making checkers") self.make_checkers(paths) def stop(self): """Stop checking files.""" self._process_statistics() class FileChecker: """Manage running checks for a file and aggregate the results.""" def __init__(self, filename, checks, options): """Initialize our file checker. :param str filename: Name of the file to check. :param checks: The plugins registered to check the file. :type checks: dict :param options: Parsed option values from config and command-line. :type options: argparse.Namespace """ self.options = options self.filename = filename self.checks = checks self.results: Results = [] self.statistics = { "tokens": 0, "logical lines": 0, "physical lines": 0, } self.processor = self._make_processor() self.display_name = filename self.should_process = False if self.processor is not None: self.display_name = self.processor.filename self.should_process = not self.processor.should_ignore_file() self.statistics["physical lines"] = len(self.processor.lines) def __repr__(self) -> str: """Provide helpful debugging representation.""" return f"FileChecker for {self.filename}" def _make_processor(self) -> Optional[processor.FileProcessor]: try: return processor.FileProcessor(self.filename, self.options) except OSError as e: # If we can not read the file due to an IOError (e.g., the file # does not exist or we do not have the permissions to open it) # then we need to format that exception for the user. # NOTE(sigmavirus24): Historically, pep8 has always reported this # as an E902. We probably *want* a better error code for this # going forward. self.report("E902", 0, 0, f"{type(e).__name__}: {e}") return None def report( self, error_code: Optional[str], line_number: int, column: int, text: str, ) -> str: """Report an error by storing it in the results list.""" if error_code is None: error_code, text = text.split(" ", 1) # If we're recovering from a problem in _make_processor, we will not # have this attribute. if hasattr(self, "processor") and self.processor is not None: line = self.processor.noqa_line_for(line_number) else: line = None self.results.append((error_code, line_number, column, text, line)) return error_code def run_check(self, plugin, **arguments): """Run the check in a single plugin.""" LOG.debug("Running %r with %r", plugin, arguments) assert self.processor is not None try: self.processor.keyword_arguments_for( plugin["parameters"], arguments ) except AttributeError as ae: LOG.error("Plugin requested unknown parameters.") raise exceptions.PluginRequestedUnknownParameters( plugin=plugin, exception=ae ) try: return plugin["plugin"](**arguments) except Exception as all_exc: LOG.critical( "Plugin %s raised an unexpected exception", plugin["name"], exc_info=True, ) raise exceptions.PluginExecutionFailed( plugin=plugin, exception=all_exc ) @staticmethod def _extract_syntax_information(exception: Exception) -> Tuple[int, int]: if ( len(exception.args) > 1 and exception.args[1] and len(exception.args[1]) > 2 ): token = exception.args[1] row, column = token[1:3] elif ( isinstance(exception, tokenize.TokenError) and len(exception.args) == 2 and len(exception.args[1]) == 2 ): token = () row, column = exception.args[1] else: token = () row, column = (1, 0) if ( column > 0 and token and isinstance(exception, SyntaxError) and len(token) == 4 # Python 3.9 or earlier ): # NOTE(sigmavirus24): SyntaxErrors report 1-indexed column # numbers. We need to decrement the column number by 1 at # least. column_offset = 1 row_offset = 0 # See also: https://github.com/pycqa/flake8/issues/169, # https://github.com/PyCQA/flake8/issues/1372 # On Python 3.9 and earlier, token will be a 4-item tuple with the # last item being the string. Starting with 3.10, they added to # the tuple so now instead of it ending with the code that failed # to parse, it ends with the end of the section of code that # failed to parse. Luckily the absolute position in the tuple is # stable across versions so we can use that here physical_line = token[3] # NOTE(sigmavirus24): Not all "tokens" have a string as the last # argument. In this event, let's skip trying to find the correct # column and row values. if physical_line is not None: # NOTE(sigmavirus24): SyntaxErrors also don't exactly have a # "physical" line so much as what was accumulated by the point # tokenizing failed. # See also: https://github.com/pycqa/flake8/issues/169 lines = physical_line.rstrip("\n").split("\n") row_offset = len(lines) - 1 logical_line = lines[0] logical_line_length = len(logical_line) if column > logical_line_length: column = logical_line_length row -= row_offset column -= column_offset return row, column def run_ast_checks(self) -> None: """Run all checks expecting an abstract syntax tree.""" assert self.processor is not None ast = self.processor.build_ast() for plugin in self.checks["ast_plugins"]: checker = self.run_check(plugin, tree=ast) # If the plugin uses a class, call the run method of it, otherwise # the call should return something iterable itself try: runner = checker.run() except AttributeError: runner = checker for (line_number, offset, text, _) in runner: self.report( error_code=None, line_number=line_number, column=offset, text=text, ) def run_logical_checks(self): """Run all checks expecting a logical line.""" assert self.processor is not None comments, logical_line, mapping = self.processor.build_logical_line() if not mapping: return self.processor.update_state(mapping) LOG.debug('Logical line: "%s"', logical_line.rstrip()) for plugin in self.checks["logical_line_plugins"]: self.processor.update_checker_state_for(plugin) results = self.run_check(plugin, logical_line=logical_line) or () for offset, text in results: line_number, column_offset = find_offset(offset, mapping) if line_number == column_offset == 0: LOG.warning("position of error out of bounds: %s", plugin) self.report( error_code=None, line_number=line_number, column=column_offset, text=text, ) self.processor.next_logical_line() def run_physical_checks(self, physical_line): """Run all checks for a given physical line. A single physical check may return multiple errors. """ assert self.processor is not None for plugin in self.checks["physical_line_plugins"]: self.processor.update_checker_state_for(plugin) result = self.run_check(plugin, physical_line=physical_line) if result is not None: # This is a single result if first element is an int column_offset = None try: column_offset = result[0] except (IndexError, TypeError): pass if isinstance(column_offset, int): # If we only have a single result, convert to a collection result = (result,) for result_single in result: column_offset, text = result_single self.report( error_code=None, line_number=self.processor.line_number, column=column_offset, text=text, ) def process_tokens(self): """Process tokens and trigger checks. Instead of using this directly, you should use :meth:`flake8.checker.FileChecker.run_checks`. """ assert self.processor is not None parens = 0 statistics = self.statistics file_processor = self.processor prev_physical = "" for token in file_processor.generate_tokens(): statistics["tokens"] += 1 self.check_physical_eol(token, prev_physical) token_type, text = token[0:2] processor.log_token(LOG, token) if token_type == tokenize.OP: parens = processor.count_parentheses(parens, text) elif parens == 0: if processor.token_is_newline(token): self.handle_newline(token_type) prev_physical = token[4] if file_processor.tokens: # If any tokens are left over, process them self.run_physical_checks(file_processor.lines[-1]) self.run_logical_checks() def run_checks(self) -> Tuple[str, Results, Dict[str, int]]: """Run checks against the file.""" assert self.processor is not None try: self.run_ast_checks() self.process_tokens() except (SyntaxError, tokenize.TokenError) as e: code = "E902" if isinstance(e, tokenize.TokenError) else "E999" row, column = self._extract_syntax_information(e) self.report(code, row, column, f"{type(e).__name__}: {e.args[0]}") return self.filename, self.results, self.statistics logical_lines = self.processor.statistics["logical lines"] self.statistics["logical lines"] = logical_lines return self.filename, self.results, self.statistics def handle_newline(self, token_type): """Handle the logic when encountering a newline token.""" assert self.processor is not None if token_type == tokenize.NEWLINE: self.run_logical_checks() self.processor.reset_blank_before() elif len(self.processor.tokens) == 1: # The physical line contains only this token. self.processor.visited_new_blank_line() self.processor.delete_first_token() else: self.run_logical_checks() def check_physical_eol( self, token: processor._Token, prev_physical: str ) -> None: """Run physical checks if and only if it is at the end of the line.""" assert self.processor is not None # a newline token ends a single physical line. if processor.is_eol_token(token): # if the file does not end with a newline, the NEWLINE # token is inserted by the parser, but it does not contain # the previous physical line in `token[4]` if token[4] == "": self.run_physical_checks(prev_physical) else: self.run_physical_checks(token[4]) elif processor.is_multiline_string(token): # Less obviously, a string that contains newlines is a # multiline string, either triple-quoted or with internal # newlines backslash-escaped. Check every physical line in the # string *except* for the last one: its newline is outside of # the multiline string, so we consider it a regular physical # line, and will check it like any other physical line. # # Subtleties: # - have to wind self.line_number back because initially it # points to the last line of the string, and we want # check_physical() to give accurate feedback line_no = token[2][0] with self.processor.inside_multiline(line_number=line_no): for line in self.processor.split_line(token): self.run_physical_checks(line + "\n") def _pool_init() -> None: """Ensure correct signaling of ^C using multiprocessing.Pool.""" signal.signal(signal.SIGINT, signal.SIG_IGN) def _try_initialize_processpool( job_count: int, ) -> Optional[multiprocessing.pool.Pool]: """Return a new process pool instance if we are able to create one.""" try: return multiprocessing.Pool(job_count, _pool_init) except OSError as err: if err.errno not in SERIAL_RETRY_ERRNOS: raise except ImportError: pass return None def calculate_pool_chunksize(num_checkers, num_jobs): """Determine the chunksize for the multiprocessing Pool. - For chunksize, see: https://docs.python.org/3/library/multiprocessing.html#multiprocessing.pool.Pool.imap # noqa - This formula, while not perfect, aims to give each worker two batches of work. - See: https://github.com/pycqa/flake8/issues/829#note_18878876 - See: https://github.com/pycqa/flake8/issues/197 """ return max(num_checkers // (num_jobs * 2), 1) def _run_checks(checker): return checker.run_checks() def find_offset( offset: int, mapping: processor._LogicalMapping ) -> Tuple[int, int]: """Find the offset tuple for a single offset.""" if isinstance(offset, tuple): return offset for token in mapping: token_offset = token[0] if offset <= token_offset: position = token[1] break else: position = (0, 0) offset = token_offset = 0 return (position[0], position[1] + offset - token_offset) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633909928.0 flake8-4.0.1/src/flake8/defaults.py0000644000175000017500000000215100000000000020502 0ustar00asottileasottile00000000000000"""Constants that define defaults.""" import re EXCLUDE = ( ".svn", "CVS", ".bzr", ".hg", ".git", "__pycache__", ".tox", ".eggs", "*.egg", ) IGNORE = ("E121", "E123", "E126", "E226", "E24", "E704", "W503", "W504") SELECT = ("E", "F", "W", "C90") MAX_LINE_LENGTH = 79 INDENT_SIZE = 4 TRUTHY_VALUES = {"true", "1", "t"} # Other constants WHITESPACE = frozenset(" \t") STATISTIC_NAMES = ("logical lines", "physical lines", "tokens") NOQA_INLINE_REGEXP = re.compile( # We're looking for items that look like this: # ``# noqa`` # ``# noqa: E123`` # ``# noqa: E123,W451,F921`` # ``# noqa:E123,W451,F921`` # ``# NoQA: E123,W451,F921`` # ``# NOQA: E123,W451,F921`` # ``# NOQA:E123,W451,F921`` # We do not want to capture the ``: `` that follows ``noqa`` # We do not care about the casing of ``noqa`` # We want a comma-separated list of errors # https://regex101.com/r/4XUuax/2 full explanation of the regex r"# noqa(?::[\s]?(?P([A-Z]+[0-9]+(?:[,\s]+)?)+))?", re.IGNORECASE, ) NOQA_FILE = re.compile(r"\s*# flake8[:=]\s*noqa", re.I) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633909928.0 flake8-4.0.1/src/flake8/exceptions.py0000644000175000017500000000430700000000000021061 0ustar00asottileasottile00000000000000"""Exception classes for all of Flake8.""" from typing import Dict class Flake8Exception(Exception): """Plain Flake8 exception.""" class EarlyQuit(Flake8Exception): """Except raised when encountering a KeyboardInterrupt.""" class ExecutionError(Flake8Exception): """Exception raised during execution of Flake8.""" class FailedToLoadPlugin(Flake8Exception): """Exception raised when a plugin fails to load.""" FORMAT = 'Flake8 failed to load plugin "%(name)s" due to %(exc)s.' def __init__(self, plugin_name: str, exception: Exception) -> None: """Initialize our FailedToLoadPlugin exception.""" self.plugin_name = plugin_name self.original_exception = exception super().__init__(plugin_name, exception) def __str__(self) -> str: """Format our exception message.""" return self.FORMAT % { "name": self.plugin_name, "exc": self.original_exception, } class PluginRequestedUnknownParameters(Flake8Exception): """The plugin requested unknown parameters.""" FORMAT = '"%(name)s" requested unknown parameters causing %(exc)s' def __init__(self, plugin: Dict[str, str], exception: Exception) -> None: """Pop certain keyword arguments for initialization.""" self.plugin = plugin self.original_exception = exception super().__init__(plugin, exception) def __str__(self) -> str: """Format our exception message.""" return self.FORMAT % { "name": self.plugin["plugin_name"], "exc": self.original_exception, } class PluginExecutionFailed(Flake8Exception): """The plugin failed during execution.""" FORMAT = '"%(name)s" failed during execution due to "%(exc)s"' def __init__(self, plugin: Dict[str, str], exception: Exception) -> None: """Utilize keyword arguments for message generation.""" self.plugin = plugin self.original_exception = exception super().__init__(plugin, exception) def __str__(self) -> str: """Format our exception message.""" return self.FORMAT % { "name": self.plugin["plugin_name"], "exc": self.original_exception, } ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1633956150.3494692 flake8-4.0.1/src/flake8/formatting/0000755000175000017500000000000000000000000020474 5ustar00asottileasottile00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633909928.0 flake8-4.0.1/src/flake8/formatting/__init__.py0000644000175000017500000000007600000000000022610 0ustar00asottileasottile00000000000000"""Submodule containing the default formatters for Flake8.""" ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633909928.0 flake8-4.0.1/src/flake8/formatting/base.py0000644000175000017500000001663600000000000021774 0ustar00asottileasottile00000000000000"""The base class and interface for all formatting plugins.""" import argparse import os import sys from typing import IO from typing import List from typing import Optional from typing import Tuple from typing import TYPE_CHECKING if TYPE_CHECKING: from flake8.statistics import Statistics from flake8.style_guide import Violation class BaseFormatter: """Class defining the formatter interface. .. attribute:: options The options parsed from both configuration files and the command-line. .. attribute:: filename If specified by the user, the path to store the results of the run. .. attribute:: output_fd Initialized when the :meth:`start` is called. This will be a file object opened for writing. .. attribute:: newline The string to add to the end of a line. This is only used when the output filename has been specified. """ def __init__(self, options: argparse.Namespace) -> None: """Initialize with the options parsed from config and cli. This also calls a hook, :meth:`after_init`, so subclasses do not need to call super to call this method. :param options: User specified configuration parsed from both configuration files and the command-line interface. :type options: :class:`argparse.Namespace` """ self.options = options self.filename = options.output_file self.output_fd: Optional[IO[str]] = None self.newline = "\n" self.after_init() def after_init(self) -> None: """Initialize the formatter further.""" def beginning(self, filename: str) -> None: """Notify the formatter that we're starting to process a file. :param str filename: The name of the file that Flake8 is beginning to report results from. """ def finished(self, filename: str) -> None: """Notify the formatter that we've finished processing a file. :param str filename: The name of the file that Flake8 has finished reporting results from. """ def start(self) -> None: """Prepare the formatter to receive input. This defaults to initializing :attr:`output_fd` if :attr:`filename` """ if self.filename: dirname = os.path.dirname(os.path.abspath(self.filename)) os.makedirs(dirname, exist_ok=True) self.output_fd = open(self.filename, "a") def handle(self, error: "Violation") -> None: """Handle an error reported by Flake8. This defaults to calling :meth:`format`, :meth:`show_source`, and then :meth:`write`. To extend how errors are handled, override this method. :param error: This will be an instance of :class:`~flake8.style_guide.Violation`. :type error: flake8.style_guide.Violation """ line = self.format(error) source = self.show_source(error) self.write(line, source) def format(self, error: "Violation") -> Optional[str]: """Format an error reported by Flake8. This method **must** be implemented by subclasses. :param error: This will be an instance of :class:`~flake8.style_guide.Violation`. :type error: flake8.style_guide.Violation :returns: The formatted error string. :rtype: str """ raise NotImplementedError( "Subclass of BaseFormatter did not implement" " format." ) def show_statistics(self, statistics: "Statistics") -> None: """Format and print the statistics.""" for error_code in statistics.error_codes(): stats_for_error_code = statistics.statistics_for(error_code) statistic = next(stats_for_error_code) count = statistic.count count += sum(stat.count for stat in stats_for_error_code) self._write(f"{count:<5} {error_code} {statistic.message}") def show_benchmarks(self, benchmarks: List[Tuple[str, float]]) -> None: """Format and print the benchmarks.""" # NOTE(sigmavirus24): The format strings are a little confusing, even # to me, so here's a quick explanation: # We specify the named value first followed by a ':' to indicate we're # formatting the value. # Next we use '<' to indicate we want the value left aligned. # Then '10' is the width of the area. # For floats, finally, we only want only want at most 3 digits after # the decimal point to be displayed. This is the precision and it # can not be specified for integers which is why we need two separate # format strings. float_format = "{value:<10.3} {statistic}".format int_format = "{value:<10} {statistic}".format for statistic, value in benchmarks: if isinstance(value, int): benchmark = int_format(statistic=statistic, value=value) else: benchmark = float_format(statistic=statistic, value=value) self._write(benchmark) def show_source(self, error: "Violation") -> Optional[str]: """Show the physical line generating the error. This also adds an indicator for the particular part of the line that is reported as generating the problem. :param error: This will be an instance of :class:`~flake8.style_guide.Violation`. :type error: flake8.style_guide.Violation :returns: The formatted error string if the user wants to show the source. If the user does not want to show the source, this will return ``None``. :rtype: str """ if not self.options.show_source or error.physical_line is None: return "" # Because column numbers are 1-indexed, we need to remove one to get # the proper number of space characters. indent = "".join( c if c.isspace() else " " for c in error.physical_line[: error.column_number - 1] ) # Physical lines have a newline at the end, no need to add an extra # one return f"{error.physical_line}{indent}^" def _write(self, output: str) -> None: """Handle logic of whether to use an output file or print().""" if self.output_fd is not None: self.output_fd.write(output + self.newline) if self.output_fd is None or self.options.tee: sys.stdout.buffer.write(output.encode() + self.newline.encode()) def write(self, line: Optional[str], source: Optional[str]) -> None: """Write the line either to the output file or stdout. This handles deciding whether to write to a file or print to standard out for subclasses. Override this if you want behaviour that differs from the default. :param str line: The formatted string to print or write. :param str source: The source code that has been formatted and associated with the line of output. """ if line: self._write(line) if source: self._write(source) def stop(self) -> None: """Clean up after reporting is finished.""" if self.output_fd is not None: self.output_fd.close() self.output_fd = None ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633909928.0 flake8-4.0.1/src/flake8/formatting/default.py0000644000175000017500000000516400000000000022500 0ustar00asottileasottile00000000000000"""Default formatting class for Flake8.""" from typing import Optional from typing import Set from typing import TYPE_CHECKING from flake8.formatting import base if TYPE_CHECKING: from flake8.style_guide import Violation class SimpleFormatter(base.BaseFormatter): """Simple abstraction for Default and Pylint formatter commonality. Sub-classes of this need to define an ``error_format`` attribute in order to succeed. The ``format`` method relies on that attribute and expects the ``error_format`` string to use the old-style formatting strings with named parameters: * code * text * path * row * col """ error_format: str def format(self, error: "Violation") -> Optional[str]: """Format and write error out. If an output filename is specified, write formatted errors to that file. Otherwise, print the formatted error to standard out. """ return self.error_format % { "code": error.code, "text": error.text, "path": error.filename, "row": error.line_number, "col": error.column_number, } class Default(SimpleFormatter): """Default formatter for Flake8. This also handles backwards compatibility for people specifying a custom format string. """ error_format = "%(path)s:%(row)d:%(col)d: %(code)s %(text)s" def after_init(self) -> None: """Check for a custom format string.""" if self.options.format.lower() != "default": self.error_format = self.options.format class Pylint(SimpleFormatter): """Pylint formatter for Flake8.""" error_format = "%(path)s:%(row)d: [%(code)s] %(text)s" class FilenameOnly(SimpleFormatter): """Only print filenames, e.g., flake8 -q.""" error_format = "%(path)s" def after_init(self) -> None: """Initialize our set of filenames.""" self.filenames_already_printed: Set[str] = set() def show_source(self, error: "Violation") -> Optional[str]: """Do not include the source code.""" def format(self, error: "Violation") -> Optional[str]: """Ensure we only print each error once.""" if error.filename not in self.filenames_already_printed: self.filenames_already_printed.add(error.filename) return super().format(error) else: return None class Nothing(base.BaseFormatter): """Print absolutely nothing.""" def format(self, error: "Violation") -> Optional[str]: """Do nothing.""" def show_source(self, error: "Violation") -> Optional[str]: """Do not print the source.""" ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1633956150.3494692 flake8-4.0.1/src/flake8/main/0000755000175000017500000000000000000000000017246 5ustar00asottileasottile00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633909928.0 flake8-4.0.1/src/flake8/main/__init__.py0000644000175000017500000000007700000000000021363 0ustar00asottileasottile00000000000000"""Module containing the logic for the Flake8 entry-points.""" ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633909928.0 flake8-4.0.1/src/flake8/main/application.py0000644000175000017500000003501500000000000022127 0ustar00asottileasottile00000000000000"""Module containing the application logic for Flake8.""" import argparse import logging import sys import time from typing import Dict from typing import List from typing import Optional from typing import Set from typing import Tuple from typing import Type from typing import TYPE_CHECKING import flake8 from flake8 import checker from flake8 import defaults from flake8 import exceptions from flake8 import style_guide from flake8 import utils from flake8.main import options from flake8.options import aggregator from flake8.options import config from flake8.options import manager from flake8.plugins import manager as plugin_manager if TYPE_CHECKING: from flake8.formatting.base import BaseFormatter LOG = logging.getLogger(__name__) class Application: """Abstract our application into a class.""" def __init__(self, program="flake8", version=flake8.__version__): """Initialize our application. :param str program: The name of the program/application that we're executing. :param str version: The version of the program/application we're executing. """ #: The timestamp when the Application instance was instantiated. self.start_time = time.time() #: The timestamp when the Application finished reported errors. self.end_time: Optional[float] = None #: The name of the program being run self.program = program #: The version of the program being run self.version = version #: The prelimary argument parser for handling options required for #: obtaining and parsing the configuration file. self.prelim_arg_parser = argparse.ArgumentParser(add_help=False) options.register_preliminary_options(self.prelim_arg_parser) #: The instance of :class:`flake8.options.manager.OptionManager` used #: to parse and handle the options and arguments passed by the user self.option_manager = manager.OptionManager( prog="flake8", version=flake8.__version__, parents=[self.prelim_arg_parser], ) options.register_default_options(self.option_manager) #: The instance of :class:`flake8.plugins.manager.Checkers` self.check_plugins: Optional[plugin_manager.Checkers] = None #: The instance of :class:`flake8.plugins.manager.ReportFormatters` self.formatting_plugins: Optional[ plugin_manager.ReportFormatters ] = None #: The user-selected formatter from :attr:`formatting_plugins` self.formatter: Optional[BaseFormatter] = None #: The :class:`flake8.style_guide.StyleGuideManager` built from the #: user's options self.guide: Optional[style_guide.StyleGuideManager] = None #: The :class:`flake8.checker.Manager` that will handle running all of #: the checks selected by the user. self.file_checker_manager: Optional[checker.Manager] = None #: The user-supplied options parsed into an instance of #: :class:`argparse.Namespace` self.options: Optional[argparse.Namespace] = None #: The left over arguments that were not parsed by #: :attr:`option_manager` self.args: Optional[List[str]] = None #: The number of errors, warnings, and other messages after running #: flake8 and taking into account ignored errors and lines. self.result_count = 0 #: The total number of errors before accounting for ignored errors and #: lines. self.total_result_count = 0 #: Whether or not something catastrophic happened and we should exit #: with a non-zero status code self.catastrophic_failure = False #: Whether the program is processing a diff or not self.running_against_diff = False #: The parsed diff information self.parsed_diff: Dict[str, Set[int]] = {} def parse_preliminary_options( self, argv: List[str] ) -> Tuple[argparse.Namespace, List[str]]: """Get preliminary options from the CLI, pre-plugin-loading. We need to know the values of a few standard options so that we can locate configuration files and configure logging. Since plugins aren't loaded yet, there may be some as-yet-unknown options; we ignore those for now, they'll be parsed later when we do real option parsing. :param list argv: Command-line arguments passed in directly. :returns: Populated namespace and list of remaining argument strings. :rtype: (argparse.Namespace, list) """ args, rest = self.prelim_arg_parser.parse_known_args(argv) # XXX (ericvw): Special case "forwarding" the output file option so # that it can be reparsed again for the BaseFormatter.filename. if args.output_file: rest.extend(("--output-file", args.output_file)) return args, rest def exit(self) -> None: """Handle finalization and exiting the program. This should be the last thing called on the application instance. It will check certain options and exit appropriately. """ assert self.options is not None if self.options.count: print(self.result_count) if self.options.exit_zero: raise SystemExit(self.catastrophic_failure) else: raise SystemExit( (self.result_count > 0) or self.catastrophic_failure ) def find_plugins(self, config_finder: config.ConfigFileFinder) -> None: """Find and load the plugins for this application. Set the :attr:`check_plugins` and :attr:`formatting_plugins` attributes based on the discovered plugins found. :param config.ConfigFileFinder config_finder: The finder for finding and reading configuration files. """ local_plugins = config.get_local_plugins(config_finder) sys.path.extend(local_plugins.paths) self.check_plugins = plugin_manager.Checkers(local_plugins.extension) self.formatting_plugins = plugin_manager.ReportFormatters( local_plugins.report ) self.check_plugins.load_plugins() self.formatting_plugins.load_plugins() def register_plugin_options(self) -> None: """Register options provided by plugins to our option manager.""" assert self.check_plugins is not None self.check_plugins.register_options(self.option_manager) self.check_plugins.register_plugin_versions(self.option_manager) assert self.formatting_plugins is not None self.formatting_plugins.register_options(self.option_manager) def parse_configuration_and_cli( self, config_finder: config.ConfigFileFinder, argv: List[str], ) -> None: """Parse configuration files and the CLI options. :param config.ConfigFileFinder config_finder: The finder for finding and reading configuration files. :param list argv: Command-line arguments passed in directly. """ self.options, self.args = aggregator.aggregate_options( self.option_manager, config_finder, argv, ) self.running_against_diff = self.options.diff if self.running_against_diff: self.parsed_diff = utils.parse_unified_diff() if not self.parsed_diff: self.exit() assert self.check_plugins is not None self.check_plugins.provide_options( self.option_manager, self.options, self.args ) assert self.formatting_plugins is not None self.formatting_plugins.provide_options( self.option_manager, self.options, self.args ) def formatter_for(self, formatter_plugin_name): """Retrieve the formatter class by plugin name.""" assert self.formatting_plugins is not None default_formatter = self.formatting_plugins["default"] formatter_plugin = self.formatting_plugins.get(formatter_plugin_name) if formatter_plugin is None: LOG.warning( '"%s" is an unknown formatter. Falling back to default.', formatter_plugin_name, ) formatter_plugin = default_formatter return formatter_plugin.execute def make_formatter( self, formatter_class: Optional[Type["BaseFormatter"]] = None ) -> None: """Initialize a formatter based on the parsed options.""" assert self.options is not None format_plugin = self.options.format if 1 <= self.options.quiet < 2: format_plugin = "quiet-filename" elif 2 <= self.options.quiet: format_plugin = "quiet-nothing" if formatter_class is None: formatter_class = self.formatter_for(format_plugin) self.formatter = formatter_class(self.options) def make_guide(self) -> None: """Initialize our StyleGuide.""" assert self.formatter is not None assert self.options is not None self.guide = style_guide.StyleGuideManager( self.options, self.formatter ) if self.running_against_diff: self.guide.add_diff_ranges(self.parsed_diff) def make_file_checker_manager(self) -> None: """Initialize our FileChecker Manager.""" self.file_checker_manager = checker.Manager( style_guide=self.guide, arguments=self.args, checker_plugins=self.check_plugins, ) def run_checks(self, files: Optional[List[str]] = None) -> None: """Run the actual checks with the FileChecker Manager. This method encapsulates the logic to make a :class:`~flake8.checker.Manger` instance run the checks it is managing. :param list files: List of filenames to process """ assert self.file_checker_manager is not None if self.running_against_diff: files = sorted(self.parsed_diff) self.file_checker_manager.start(files) try: self.file_checker_manager.run() except exceptions.PluginExecutionFailed as plugin_failed: print(str(plugin_failed)) print("Run flake8 with greater verbosity to see more details") self.catastrophic_failure = True LOG.info("Finished running") self.file_checker_manager.stop() self.end_time = time.time() def report_benchmarks(self): """Aggregate, calculate, and report benchmarks for this run.""" assert self.options is not None if not self.options.benchmark: return assert self.file_checker_manager is not None assert self.end_time is not None time_elapsed = self.end_time - self.start_time statistics = [("seconds elapsed", time_elapsed)] add_statistic = statistics.append for statistic in defaults.STATISTIC_NAMES + ("files",): value = self.file_checker_manager.statistics[statistic] total_description = f"total {statistic} processed" add_statistic((total_description, value)) per_second_description = f"{statistic} processed per second" add_statistic((per_second_description, int(value / time_elapsed))) assert self.formatter is not None self.formatter.show_benchmarks(statistics) def report_errors(self) -> None: """Report all the errors found by flake8 3.0. This also updates the :attr:`result_count` attribute with the total number of errors, warnings, and other messages found. """ LOG.info("Reporting errors") assert self.file_checker_manager is not None results = self.file_checker_manager.report() self.total_result_count, self.result_count = results LOG.info( "Found a total of %d violations and reported %d", self.total_result_count, self.result_count, ) def report_statistics(self): """Aggregate and report statistics from this run.""" assert self.options is not None if not self.options.statistics: return assert self.formatter is not None assert self.guide is not None self.formatter.show_statistics(self.guide.stats) def initialize(self, argv: List[str]) -> None: """Initialize the application to be run. This finds the plugins, registers their options, and parses the command-line arguments. """ # NOTE(sigmavirus24): When updating this, make sure you also update # our legacy API calls to these same methods. prelim_opts, remaining_args = self.parse_preliminary_options(argv) flake8.configure_logging(prelim_opts.verbose, prelim_opts.output_file) config_finder = config.ConfigFileFinder( self.program, prelim_opts.append_config, config_file=prelim_opts.config, ignore_config_files=prelim_opts.isolated, ) self.find_plugins(config_finder) self.register_plugin_options() self.parse_configuration_and_cli( config_finder, remaining_args, ) self.make_formatter() self.make_guide() self.make_file_checker_manager() def report(self): """Report errors, statistics, and benchmarks.""" assert self.formatter is not None self.formatter.start() self.report_errors() self.report_statistics() self.report_benchmarks() self.formatter.stop() def _run(self, argv: List[str]) -> None: self.initialize(argv) self.run_checks() self.report() def run(self, argv: List[str]) -> None: """Run our application. This method will also handle KeyboardInterrupt exceptions for the entirety of the flake8 application. If it sees a KeyboardInterrupt it will forcibly clean up the :class:`~flake8.checker.Manager`. """ try: self._run(argv) except KeyboardInterrupt as exc: print("... stopped") LOG.critical("Caught keyboard interrupt from user") LOG.exception(exc) self.catastrophic_failure = True except exceptions.ExecutionError as exc: print("There was a critical error during execution of Flake8:") print(exc) LOG.exception(exc) self.catastrophic_failure = True except exceptions.EarlyQuit: self.catastrophic_failure = True print("... stopped while processing files") ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633909928.0 flake8-4.0.1/src/flake8/main/cli.py0000644000175000017500000000110400000000000020363 0ustar00asottileasottile00000000000000"""Command-line implementation of flake8.""" import sys from typing import List from typing import Optional from flake8.main import application def main(argv: Optional[List[str]] = None) -> None: """Execute the main bit of the application. This handles the creation of an instance of :class:`Application`, runs it, and then exits the application. :param list argv: The arguments to be passed to the application for parsing. """ if argv is None: argv = sys.argv[1:] app = application.Application() app.run(argv) app.exit() ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633909928.0 flake8-4.0.1/src/flake8/main/debug.py0000644000175000017500000000374200000000000020714 0ustar00asottileasottile00000000000000"""Module containing the logic for our debugging logic.""" import argparse import json import platform from typing import Dict from typing import List class DebugAction(argparse.Action): """argparse action to print debug information.""" def __init__(self, *args, **kwargs): """Initialize the action. This takes an extra `option_manager` keyword argument which will be used to delay response. """ self._option_manager = kwargs.pop("option_manager") super().__init__(*args, **kwargs) def __call__(self, parser, namespace, values, option_string=None): """Perform the argparse action for printing debug information.""" # NOTE(sigmavirus24): Flake8 parses options twice. The first time, we # will not have any registered plugins. We can skip this one and only # take action on the second time we're called. if not self._option_manager.registered_plugins: return print( json.dumps( information(self._option_manager), indent=2, sort_keys=True ) ) raise SystemExit(0) def information(option_manager): """Generate the information to be printed for the bug report.""" return { "version": option_manager.version, "plugins": plugins_from(option_manager), "dependencies": dependencies(), "platform": { "python_implementation": platform.python_implementation(), "python_version": platform.python_version(), "system": platform.system(), }, } def plugins_from(option_manager): """Generate the list of plugins installed.""" return [ { "plugin": plugin.name, "version": plugin.version, "is_local": plugin.local, } for plugin in sorted(option_manager.registered_plugins) ] def dependencies() -> List[Dict[str, str]]: """Generate the list of dependencies we care about.""" return [] ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633909928.0 flake8-4.0.1/src/flake8/main/options.py0000644000175000017500000002410100000000000021311 0ustar00asottileasottile00000000000000"""Contains the logic for all of the default options for Flake8.""" import argparse import functools from flake8 import defaults from flake8.main import debug def register_preliminary_options(parser: argparse.ArgumentParser) -> None: """Register the preliminary options on our OptionManager. The preliminary options include: - ``-v``/``--verbose`` - ``--output-file`` - ``--append-config`` - ``--config`` - ``--isolated`` """ add_argument = parser.add_argument add_argument( "-v", "--verbose", default=0, action="count", help="Print more information about what is happening in flake8." " This option is repeatable and will increase verbosity each " "time it is repeated.", ) add_argument( "--output-file", default=None, help="Redirect report to a file." ) # Config file options add_argument( "--append-config", action="append", help="Provide extra config files to parse in addition to the files " "found by Flake8 by default. These files are the last ones read " "and so they take the highest precedence when multiple files " "provide the same option.", ) add_argument( "--config", default=None, help="Path to the config file that will be the authoritative config " "source. This will cause Flake8 to ignore all other " "configuration files.", ) add_argument( "--isolated", default=False, action="store_true", help="Ignore all configuration files.", ) class JobsArgument: """Type callback for the --jobs argument.""" def __init__(self, arg: str) -> None: """Parse and validate the --jobs argument. :param str arg: The argument passed by argparse for validation """ self.is_auto = False self.n_jobs = -1 if arg == "auto": self.is_auto = True elif arg.isdigit(): self.n_jobs = int(arg) else: raise argparse.ArgumentTypeError( f"{arg!r} must be 'auto' or an integer.", ) def __str__(self): """Format our JobsArgument class.""" return "auto" if self.is_auto else str(self.n_jobs) def register_default_options(option_manager): """Register the default options on our OptionManager. The default options include: - ``-q``/``--quiet`` - ``--count`` - ``--diff`` - ``--exclude`` - ``--extend-exclude`` - ``--filename`` - ``--format`` - ``--hang-closing`` - ``--ignore`` - ``--extend-ignore`` - ``--per-file-ignores`` - ``--max-line-length`` - ``--max-doc-length`` - ``--indent-size`` - ``--select`` - ``--extend-select`` - ``--disable-noqa`` - ``--show-source`` - ``--statistics`` - ``--enable-extensions`` - ``--exit-zero`` - ``-j``/``--jobs`` - ``--tee`` - ``--benchmark`` - ``--bug-report`` """ add_option = option_manager.add_option # pep8 options add_option( "-q", "--quiet", default=0, action="count", parse_from_config=True, help="Report only file names, or nothing. This option is repeatable.", ) add_option( "--count", action="store_true", parse_from_config=True, help="Print total number of errors and warnings to standard error and" " set the exit code to 1 if total is not empty.", ) add_option( "--diff", action="store_true", help="Report changes only within line number ranges in the unified " "diff provided on standard in by the user.", ) add_option( "--exclude", metavar="patterns", default=",".join(defaults.EXCLUDE), comma_separated_list=True, parse_from_config=True, normalize_paths=True, help="Comma-separated list of files or directories to exclude." " (Default: %(default)s)", ) add_option( "--extend-exclude", metavar="patterns", default="", parse_from_config=True, comma_separated_list=True, normalize_paths=True, help="Comma-separated list of files or directories to add to the list" " of excluded ones.", ) add_option( "--filename", metavar="patterns", default="*.py", parse_from_config=True, comma_separated_list=True, help="Only check for filenames matching the patterns in this comma-" "separated list. (Default: %(default)s)", ) add_option( "--stdin-display-name", default="stdin", help="The name used when reporting errors from code passed via stdin." " This is useful for editors piping the file contents to flake8." " (Default: %(default)s)", ) # TODO(sigmavirus24): Figure out --first/--repeat # NOTE(sigmavirus24): We can't use choices for this option since users can # freely provide a format string and that will break if we restrict their # choices. add_option( "--format", metavar="format", default="default", parse_from_config=True, help="Format errors according to the chosen formatter.", ) add_option( "--hang-closing", action="store_true", parse_from_config=True, help="Hang closing bracket instead of matching indentation of opening" " bracket's line.", ) add_option( "--ignore", metavar="errors", default=",".join(defaults.IGNORE), parse_from_config=True, comma_separated_list=True, help="Comma-separated list of errors and warnings to ignore (or skip)." " For example, ``--ignore=E4,E51,W234``. (Default: %(default)s)", ) add_option( "--extend-ignore", metavar="errors", default="", parse_from_config=True, comma_separated_list=True, help="Comma-separated list of errors and warnings to add to the list" " of ignored ones. For example, ``--extend-ignore=E4,E51,W234``.", ) add_option( "--per-file-ignores", default="", parse_from_config=True, help="A pairing of filenames and violation codes that defines which " "violations to ignore in a particular file. The filenames can be " "specified in a manner similar to the ``--exclude`` option and the " "violations work similarly to the ``--ignore`` and ``--select`` " "options.", ) add_option( "--max-line-length", type=int, metavar="n", default=defaults.MAX_LINE_LENGTH, parse_from_config=True, help="Maximum allowed line length for the entirety of this run. " "(Default: %(default)s)", ) add_option( "--max-doc-length", type=int, metavar="n", default=None, parse_from_config=True, help="Maximum allowed doc line length for the entirety of this run. " "(Default: %(default)s)", ) add_option( "--indent-size", type=int, metavar="n", default=defaults.INDENT_SIZE, parse_from_config=True, help="Number of spaces used for indentation (Default: %(default)s)", ) add_option( "--select", metavar="errors", default=",".join(defaults.SELECT), parse_from_config=True, comma_separated_list=True, help="Comma-separated list of errors and warnings to enable." " For example, ``--select=E4,E51,W234``. (Default: %(default)s)", ) add_option( "--extend-select", metavar="errors", default="", parse_from_config=True, comma_separated_list=True, help=( "Comma-separated list of errors and warnings to add to the list " "of selected ones. For example, ``--extend-select=E4,E51,W234``." ), ) add_option( "--disable-noqa", default=False, parse_from_config=True, action="store_true", help='Disable the effect of "# noqa". This will report errors on ' 'lines with "# noqa" at the end.', ) # TODO(sigmavirus24): Decide what to do about --show-pep8 add_option( "--show-source", action="store_true", parse_from_config=True, help="Show the source generate each error or warning.", ) add_option( "--no-show-source", action="store_false", dest="show_source", parse_from_config=False, help="Negate --show-source", ) add_option( "--statistics", action="store_true", parse_from_config=True, help="Count errors and warnings.", ) # Flake8 options add_option( "--enable-extensions", default="", parse_from_config=True, comma_separated_list=True, help="Enable plugins and extensions that are otherwise disabled " "by default", ) add_option( "--exit-zero", action="store_true", help='Exit with status code "0" even if there are errors.', ) add_option( "-j", "--jobs", default="auto", parse_from_config=True, type=JobsArgument, help="Number of subprocesses to use to run checks in parallel. " 'This is ignored on Windows. The default, "auto", will ' "auto-detect the number of processors available to use." " (Default: %(default)s)", ) add_option( "--tee", default=False, parse_from_config=True, action="store_true", help="Write to stdout and output-file.", ) # Benchmarking add_option( "--benchmark", default=False, action="store_true", help="Print benchmark information about this run of Flake8", ) # Debugging add_option( "--bug-report", action=functools.partial( debug.DebugAction, option_manager=option_manager ), nargs=0, help="Print information necessary when preparing a bug report", ) ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1633956150.3494692 flake8-4.0.1/src/flake8/options/0000755000175000017500000000000000000000000020015 5ustar00asottileasottile00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633909928.0 flake8-4.0.1/src/flake8/options/__init__.py0000644000175000017500000000071500000000000022131 0ustar00asottileasottile00000000000000"""Package containing the option manager and config management logic. - :mod:`flake8.options.config` contains the logic for finding, parsing, and merging configuration files. - :mod:`flake8.options.manager` contains the logic for managing customized Flake8 command-line and configuration options. - :mod:`flake8.options.aggregator` uses objects from both of the above modules to aggregate configuration into one object used by plugins and Flake8. """ ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633913947.0 flake8-4.0.1/src/flake8/options/aggregator.py0000644000175000017500000000621400000000000022514 0ustar00asottileasottile00000000000000"""Aggregation function for CLI specified options and config file options. This holds the logic that uses the collected and merged config files and applies the user-specified command-line configuration on top of it. """ import argparse import logging from typing import List from typing import Tuple from flake8.options import config from flake8.options.manager import OptionManager LOG = logging.getLogger(__name__) def aggregate_options( manager: OptionManager, config_finder: config.ConfigFileFinder, argv: List[str], ) -> Tuple[argparse.Namespace, List[str]]: """Aggregate and merge CLI and config file options. :param flake8.options.manager.OptionManager manager: The instance of the OptionManager that we're presently using. :param flake8.options.config.ConfigFileFinder config_finder: The config file finder to use. :param list argv: The list of remaining command-line arguments that were unknown during preliminary option parsing to pass to ``manager.parse_args``. :returns: Tuple of the parsed options and extra arguments returned by ``manager.parse_args``. :rtype: tuple(argparse.Namespace, list) """ # Get defaults from the option parser default_values, _ = manager.parse_args([]) # Make our new configuration file mergerator config_parser = config.ConfigParser( option_manager=manager, config_finder=config_finder ) # Get the parsed config parsed_config = config_parser.parse() # Extend the default ignore value with the extended default ignore list, # registered by plugins. extended_default_ignore = manager.extended_default_ignore.copy() # Let's store our extended default ignore for use by the decision engine default_values.extended_default_ignore = ( manager.extended_default_ignore.copy() ) LOG.debug( "Extended default ignore list: %s", list(extended_default_ignore) ) extended_default_ignore.update(default_values.ignore) default_values.ignore = list(extended_default_ignore) LOG.debug("Merged default ignore list: %s", default_values.ignore) extended_default_select = manager.extended_default_select.copy() LOG.debug( "Extended default select list: %s", list(extended_default_select) ) default_values.extended_default_select = extended_default_select # Merge values parsed from config onto the default values returned for config_name, value in parsed_config.items(): dest_name = config_name # If the config name is somehow different from the destination name, # fetch the destination name from our Option if not hasattr(default_values, config_name): dest_name = config_parser.config_options[config_name].dest LOG.debug( 'Overriding default value of (%s) for "%s" with (%s)', getattr(default_values, dest_name, None), dest_name, value, ) # Override the default values with the config values setattr(default_values, dest_name, value) # Finally parse the command-line options return manager.parse_args(argv, default_values) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633913947.0 flake8-4.0.1/src/flake8/options/config.py0000644000175000017500000002646700000000000021653 0ustar00asottileasottile00000000000000"""Config handling logic for Flake8.""" import collections import configparser import logging import os.path from typing import List from typing import Optional from typing import Tuple from flake8 import utils LOG = logging.getLogger(__name__) __all__ = ("ConfigFileFinder", "ConfigParser") class ConfigFileFinder: """Encapsulate the logic for finding and reading config files.""" def __init__( self, program_name: str, extra_config_files: Optional[List[str]] = None, config_file: Optional[str] = None, ignore_config_files: bool = False, ) -> None: """Initialize object to find config files. :param str program_name: Name of the current program (e.g., flake8). :param list extra_config_files: Extra configuration files specified by the user to read. :param str config_file: Configuration file override to only read configuration from. :param bool ignore_config_files: Determine whether to ignore configuration files or not. """ # The values of --append-config from the CLI if extra_config_files is None: extra_config_files = [] self.extra_config_files = utils.normalize_paths(extra_config_files) # The value of --config from the CLI. self.config_file = config_file # The value of --isolated from the CLI. self.ignore_config_files = ignore_config_files # User configuration file. self.program_name = program_name # List of filenames to find in the local/project directory self.project_filenames = ("setup.cfg", "tox.ini", f".{program_name}") self.local_directory = os.path.abspath(os.curdir) @staticmethod def _read_config( *files: str, ) -> Tuple[configparser.RawConfigParser, List[str]]: config = configparser.RawConfigParser() found_files = [] for filename in files: try: found_files.extend(config.read(filename)) except UnicodeDecodeError: LOG.exception( "There was an error decoding a config file." "The file with a problem was %s.", filename, ) except configparser.ParsingError: LOG.exception( "There was an error trying to parse a config " "file. The file with a problem was %s.", filename, ) return (config, found_files) def cli_config(self, files: str) -> configparser.RawConfigParser: """Read and parse the config file specified on the command-line.""" config, found_files = self._read_config(files) if found_files: LOG.debug("Found cli configuration files: %s", found_files) return config def generate_possible_local_files(self): """Find and generate all local config files.""" parent = tail = os.getcwd() found_config_files = False while tail and not found_config_files: for project_filename in self.project_filenames: filename = os.path.abspath( os.path.join(parent, project_filename) ) if os.path.exists(filename): yield filename found_config_files = True self.local_directory = parent (parent, tail) = os.path.split(parent) def local_config_files(self): """Find all local config files which actually exist. Filter results from :meth:`~ConfigFileFinder.generate_possible_local_files` based on whether the filename exists or not. :returns: List of files that exist that are local project config files with extra config files appended to that list (which also exist). :rtype: [str] """ exists = os.path.exists return [ filename for filename in self.generate_possible_local_files() ] + [f for f in self.extra_config_files if exists(f)] def local_configs_with_files(self): """Parse all local config files into one config object. Return (config, found_config_files) tuple. """ config, found_files = self._read_config(*self.local_config_files()) if found_files: LOG.debug("Found local configuration files: %s", found_files) return (config, found_files) def local_configs(self): """Parse all local config files into one config object.""" return self.local_configs_with_files()[0] class ConfigParser: """Encapsulate merging different types of configuration files. This parses out the options registered that were specified in the configuration files, handles extra configuration files, and returns dictionaries with the parsed values. """ #: Set of actions that should use the #: :meth:`~configparser.RawConfigParser.getbool` method. GETBOOL_ACTIONS = {"store_true", "store_false"} def __init__(self, option_manager, config_finder): """Initialize the ConfigParser instance. :param flake8.options.manager.OptionManager option_manager: Initialized OptionManager. :param flake8.options.config.ConfigFileFinder config_finder: Initialized ConfigFileFinder. """ #: Our instance of flake8.options.manager.OptionManager self.option_manager = option_manager #: The prog value for the cli parser self.program_name = option_manager.program_name #: Mapping of configuration option names to #: :class:`~flake8.options.manager.Option` instances self.config_options = option_manager.config_options_dict #: Our instance of our :class:`~ConfigFileFinder` self.config_finder = config_finder def _normalize_value(self, option, value, parent=None): if parent is None: parent = self.config_finder.local_directory final_value = option.normalize(value, parent) LOG.debug( '%r has been normalized to %r for option "%s"', value, final_value, option.config_name, ) return final_value def _parse_config(self, config_parser, parent=None): config_dict = {} for option_name in config_parser.options(self.program_name): if option_name not in self.config_options: LOG.debug( 'Option "%s" is not registered. Ignoring.', option_name ) continue option = self.config_options[option_name] # Use the appropriate method to parse the config value method = config_parser.get if option.type is int or option.action == "count": method = config_parser.getint elif option.action in self.GETBOOL_ACTIONS: method = config_parser.getboolean value = method(self.program_name, option_name) LOG.debug('Option "%s" returned value: %r', option_name, value) final_value = self._normalize_value(option, value, parent) config_dict[option.config_name] = final_value return config_dict def is_configured_by(self, config): """Check if the specified config parser has an appropriate section.""" return config.has_section(self.program_name) def parse_local_config(self): """Parse and return the local configuration files.""" config = self.config_finder.local_configs() if not self.is_configured_by(config): LOG.debug( "Local configuration files have no %s section", self.program_name, ) return {} LOG.debug("Parsing local configuration files.") return self._parse_config(config) def parse_cli_config(self, config_path): """Parse and return the file specified by --config.""" config = self.config_finder.cli_config(config_path) if not self.is_configured_by(config): LOG.debug( "CLI configuration files have no %s section", self.program_name, ) return {} LOG.debug("Parsing CLI configuration files.") return self._parse_config(config, os.path.dirname(config_path)) def parse(self): """Parse and return the local config files. :returns: Dictionary of parsed configuration options :rtype: dict """ if self.config_finder.ignore_config_files: LOG.debug( "Refusing to parse configuration files due to user-" "requested isolation" ) return {} if self.config_finder.config_file: LOG.debug( "Ignoring user and locally found configuration files. " 'Reading only configuration from "%s" specified via ' "--config by the user", self.config_finder.config_file, ) return self.parse_cli_config(self.config_finder.config_file) return self.parse_local_config() def get_local_plugins(config_finder): """Get local plugins lists from config files. :param flake8.options.config.ConfigFileFinder config_finder: The config file finder to use. :returns: LocalPlugins namedtuple containing two lists of plugin strings, one for extension (checker) plugins and one for report plugins. :rtype: flake8.options.config.LocalPlugins """ local_plugins = LocalPlugins(extension=[], report=[], paths=[]) if config_finder.ignore_config_files: LOG.debug( "Refusing to look for local plugins in configuration" "files due to user-requested isolation" ) return local_plugins if config_finder.config_file: LOG.debug( 'Reading local plugins only from "%s" specified via ' "--config by the user", config_finder.config_file, ) config = config_finder.cli_config(config_finder.config_file) config_files = [config_finder.config_file] else: config, config_files = config_finder.local_configs_with_files() base_dirs = {os.path.dirname(cf) for cf in config_files} section = f"{config_finder.program_name}:local-plugins" for plugin_type in ["extension", "report"]: if config.has_option(section, plugin_type): local_plugins_string = config.get(section, plugin_type).strip() plugin_type_list = getattr(local_plugins, plugin_type) plugin_type_list.extend( utils.parse_comma_separated_list( local_plugins_string, regexp=utils.LOCAL_PLUGIN_LIST_RE ) ) if config.has_option(section, "paths"): raw_paths = utils.parse_comma_separated_list( config.get(section, "paths").strip() ) norm_paths: List[str] = [] for base_dir in base_dirs: norm_paths.extend( path for path in utils.normalize_paths(raw_paths, parent=base_dir) if os.path.exists(path) ) local_plugins.paths.extend(norm_paths) return local_plugins LocalPlugins = collections.namedtuple("LocalPlugins", "extension report paths") ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633909928.0 flake8-4.0.1/src/flake8/options/manager.py0000644000175000017500000004611200000000000022005 0ustar00asottileasottile00000000000000"""Option handling and Option management logic.""" import argparse import collections import contextlib import enum import functools import logging from typing import Any from typing import Callable from typing import cast from typing import Dict from typing import Generator from typing import List from typing import Mapping from typing import Optional from typing import Sequence from typing import Set from typing import Tuple from typing import Type from typing import TYPE_CHECKING from typing import Union from flake8 import utils if TYPE_CHECKING: from typing import NoReturn LOG = logging.getLogger(__name__) # represent a singleton of "not passed arguments". # an enum is chosen to trick mypy _ARG = enum.Enum("_ARG", "NO") _optparse_callable_map: Dict[str, Union[Type[Any], _ARG]] = { "int": int, "long": int, "string": str, "float": float, "complex": complex, "choice": _ARG.NO, # optparse allows this but does not document it "str": str, } class _CallbackAction(argparse.Action): """Shim for optparse-style callback actions.""" def __init__(self, *args: Any, **kwargs: Any) -> None: self._callback = kwargs.pop("callback") self._callback_args = kwargs.pop("callback_args", ()) self._callback_kwargs = kwargs.pop("callback_kwargs", {}) super().__init__(*args, **kwargs) def __call__( self, parser: argparse.ArgumentParser, namespace: argparse.Namespace, values: Optional[Union[Sequence[str], str]], option_string: Optional[str] = None, ) -> None: if not values: values = None elif isinstance(values, list) and len(values) > 1: values = tuple(values) self._callback( self, option_string, values, parser, *self._callback_args, **self._callback_kwargs, ) def _flake8_normalize( value: str, *args: str, **kwargs: bool ) -> Union[str, List[str]]: comma_separated_list = kwargs.pop("comma_separated_list", False) normalize_paths = kwargs.pop("normalize_paths", False) if kwargs: raise TypeError(f"Unexpected keyword args: {kwargs}") ret: Union[str, List[str]] = value if comma_separated_list and isinstance(ret, str): ret = utils.parse_comma_separated_list(value) if normalize_paths: if isinstance(ret, str): ret = utils.normalize_path(ret, *args) else: ret = utils.normalize_paths(ret, *args) return ret class Option: """Our wrapper around an argparse argument parsers to add features.""" def __init__( self, short_option_name: Union[str, _ARG] = _ARG.NO, long_option_name: Union[str, _ARG] = _ARG.NO, # Options below here are taken from the optparse.Option class action: Union[str, Type[argparse.Action], _ARG] = _ARG.NO, default: Union[Any, _ARG] = _ARG.NO, type: Union[str, Callable[..., Any], _ARG] = _ARG.NO, dest: Union[str, _ARG] = _ARG.NO, nargs: Union[int, str, _ARG] = _ARG.NO, const: Union[Any, _ARG] = _ARG.NO, choices: Union[Sequence[Any], _ARG] = _ARG.NO, help: Union[str, _ARG] = _ARG.NO, metavar: Union[str, _ARG] = _ARG.NO, # deprecated optparse-only options callback: Union[Callable[..., Any], _ARG] = _ARG.NO, callback_args: Union[Sequence[Any], _ARG] = _ARG.NO, callback_kwargs: Union[Mapping[str, Any], _ARG] = _ARG.NO, # Options below are taken from argparse.ArgumentParser.add_argument required: Union[bool, _ARG] = _ARG.NO, # Options below here are specific to Flake8 parse_from_config: bool = False, comma_separated_list: bool = False, normalize_paths: bool = False, ) -> None: """Initialize an Option instance. The following are all passed directly through to argparse. :param str short_option_name: The short name of the option (e.g., ``-x``). This will be the first argument passed to ``ArgumentParser.add_argument`` :param str long_option_name: The long name of the option (e.g., ``--xtra-long-option``). This will be the second argument passed to ``ArgumentParser.add_argument`` :param default: Default value of the option. :param dest: Attribute name to store parsed option value as. :param nargs: Number of arguments to parse for this option. :param const: Constant value to store on a common destination. Usually used in conjunction with ``action="store_const"``. :param iterable choices: Possible values for the option. :param str help: Help text displayed in the usage information. :param str metavar: Name to use instead of the long option name for help text. :param bool required: Whether this option is required or not. The following options may be passed directly through to :mod:`argparse` but may need some massaging. :param type: A callable to normalize the type (as is the case in :mod:`argparse`). Deprecated: you can also pass through type strings such as ``'int'`` which are handled by :mod:`optparse`. :param str action: Any action allowed by :mod:`argparse`. Deprecated: this also understands the ``action='callback'`` action from :mod:`optparse`. :param callable callback: Callback used if the action is ``"callback"``. Deprecated: please use ``action=`` instead. :param iterable callback_args: Additional positional arguments to the callback callable. Deprecated: please use ``action=`` instead (probably with ``functools.partial``). :param dictionary callback_kwargs: Keyword arguments to the callback callable. Deprecated: please use ``action=`` instead (probably with ``functools.partial``). The following parameters are for Flake8's option handling alone. :param bool parse_from_config: Whether or not this option should be parsed out of config files. :param bool comma_separated_list: Whether the option is a comma separated list when parsing from a config file. :param bool normalize_paths: Whether the option is expecting a path or list of paths and should attempt to normalize the paths to absolute paths. """ if ( long_option_name is _ARG.NO and short_option_name is not _ARG.NO and short_option_name.startswith("--") ): short_option_name, long_option_name = _ARG.NO, short_option_name # optparse -> argparse `%default` => `%(default)s` if help is not _ARG.NO and "%default" in help: LOG.warning( "option %s: please update `help=` text to use %%(default)s " "instead of %%default -- this will be an error in the future", long_option_name, ) help = help.replace("%default", "%(default)s") # optparse -> argparse for `callback` if action == "callback": LOG.warning( "option %s: please update from optparse `action='callback'` " "to argparse action classes -- this will be an error in the " "future", long_option_name, ) action = _CallbackAction if type is _ARG.NO: nargs = 0 # optparse -> argparse for `type` if isinstance(type, str): LOG.warning( "option %s: please update from optparse string `type=` to " "argparse callable `type=` -- this will be an error in the " "future", long_option_name, ) type = _optparse_callable_map[type] # flake8 special type normalization if comma_separated_list or normalize_paths: type = functools.partial( _flake8_normalize, comma_separated_list=comma_separated_list, normalize_paths=normalize_paths, ) self.short_option_name = short_option_name self.long_option_name = long_option_name self.option_args = [ x for x in (short_option_name, long_option_name) if x is not _ARG.NO ] self.action = action self.default = default self.type = type self.dest = dest self.nargs = nargs self.const = const self.choices = choices self.callback = callback self.callback_args = callback_args self.callback_kwargs = callback_kwargs self.help = help self.metavar = metavar self.required = required self.option_kwargs: Dict[str, Union[Any, _ARG]] = { "action": self.action, "default": self.default, "type": self.type, "dest": self.dest, "nargs": self.nargs, "const": self.const, "choices": self.choices, "callback": self.callback, "callback_args": self.callback_args, "callback_kwargs": self.callback_kwargs, "help": self.help, "metavar": self.metavar, "required": self.required, } # Set our custom attributes self.parse_from_config = parse_from_config self.comma_separated_list = comma_separated_list self.normalize_paths = normalize_paths self.config_name: Optional[str] = None if parse_from_config: if long_option_name is _ARG.NO: raise ValueError( "When specifying parse_from_config=True, " "a long_option_name must also be specified." ) self.config_name = long_option_name[2:].replace("-", "_") self._opt = None @property def filtered_option_kwargs(self) -> Dict[str, Any]: """Return any actually-specified arguments.""" return { k: v for k, v in self.option_kwargs.items() if v is not _ARG.NO } def __repr__(self) -> str: # noqa: D105 parts = [] for arg in self.option_args: parts.append(arg) for k, v in self.filtered_option_kwargs.items(): parts.append(f"{k}={v!r}") return f"Option({', '.join(parts)})" def normalize(self, value: Any, *normalize_args: str) -> Any: """Normalize the value based on the option configuration.""" if self.comma_separated_list and isinstance(value, str): value = utils.parse_comma_separated_list(value) if self.normalize_paths: if isinstance(value, list): value = utils.normalize_paths(value, *normalize_args) else: value = utils.normalize_path(value, *normalize_args) return value def normalize_from_setuptools( self, value: str ) -> Union[int, float, complex, bool, str]: """Normalize the value received from setuptools.""" value = self.normalize(value) if self.type is int or self.action == "count": return int(value) elif self.type is float: return float(value) elif self.type is complex: return complex(value) if self.action in ("store_true", "store_false"): value = str(value).upper() if value in ("1", "T", "TRUE", "ON"): return True if value in ("0", "F", "FALSE", "OFF"): return False return value def to_argparse(self) -> Tuple[List[str], Dict[str, Any]]: """Convert a Flake8 Option to argparse ``add_argument`` arguments.""" return self.option_args, self.filtered_option_kwargs @property def to_optparse(self) -> "NoReturn": """No longer functional.""" raise AttributeError("to_optparse: flake8 now uses argparse") PluginVersion = collections.namedtuple( "PluginVersion", ["name", "version", "local"] ) class OptionManager: """Manage Options and OptionParser while adding post-processing.""" def __init__( self, prog: str, version: str, usage: str = "%(prog)s [options] file file ...", parents: Optional[List[argparse.ArgumentParser]] = None, ) -> None: # noqa: E501 """Initialize an instance of an OptionManager. :param str prog: Name of the actual program (e.g., flake8). :param str version: Version string for the program. :param str usage: Basic usage string used by the OptionParser. :param argparse.ArgumentParser parents: A list of ArgumentParser objects whose arguments should also be included. """ if parents is None: parents = [] self.parser: argparse.ArgumentParser = argparse.ArgumentParser( prog=prog, usage=usage, parents=parents ) self._current_group: Optional[argparse._ArgumentGroup] = None self.version_action = cast( "argparse._VersionAction", self.parser.add_argument( "--version", action="version", version=version ), ) self.parser.add_argument("filenames", nargs="*", metavar="filename") self.config_options_dict: Dict[str, Option] = {} self.options: List[Option] = [] self.program_name = prog self.version = version self.registered_plugins: Set[PluginVersion] = set() self.extended_default_ignore: Set[str] = set() self.extended_default_select: Set[str] = set() @contextlib.contextmanager def group(self, name: str) -> Generator[None, None, None]: """Attach options to an argparse group during this context.""" group = self.parser.add_argument_group(name) self._current_group, orig_group = group, self._current_group try: yield finally: self._current_group = orig_group def add_option(self, *args: Any, **kwargs: Any) -> None: """Create and register a new option. See parameters for :class:`~flake8.options.manager.Option` for acceptable arguments to this method. .. note:: ``short_option_name`` and ``long_option_name`` may be specified positionally as they are with argparse normally. """ option = Option(*args, **kwargs) option_args, option_kwargs = option.to_argparse() if self._current_group is not None: self._current_group.add_argument(*option_args, **option_kwargs) else: self.parser.add_argument(*option_args, **option_kwargs) self.options.append(option) if option.parse_from_config: name = option.config_name assert name is not None # nosec (for mypy) self.config_options_dict[name] = option self.config_options_dict[name.replace("_", "-")] = option LOG.debug('Registered option "%s".', option) def remove_from_default_ignore(self, error_codes: Sequence[str]) -> None: """Remove specified error codes from the default ignore list. :param list error_codes: List of strings that are the error/warning codes to attempt to remove from the extended default ignore list. """ LOG.debug("Removing %r from the default ignore list", error_codes) for error_code in error_codes: try: self.extended_default_ignore.remove(error_code) except (ValueError, KeyError): LOG.debug( "Attempted to remove %s from default ignore" " but it was not a member of the list.", error_code, ) def extend_default_ignore(self, error_codes: Sequence[str]) -> None: """Extend the default ignore list with the error codes provided. :param list error_codes: List of strings that are the error/warning codes with which to extend the default ignore list. """ LOG.debug("Extending default ignore list with %r", error_codes) self.extended_default_ignore.update(error_codes) def extend_default_select(self, error_codes: Sequence[str]) -> None: """Extend the default select list with the error codes provided. :param list error_codes: List of strings that are the error/warning codes with which to extend the default select list. """ LOG.debug("Extending default select list with %r", error_codes) self.extended_default_select.update(error_codes) def generate_versions( self, format_str: str = "%(name)s: %(version)s", join_on: str = ", " ) -> str: """Generate a comma-separated list of versions of plugins.""" return join_on.join( format_str % plugin._asdict() for plugin in sorted(self.registered_plugins) ) def update_version_string(self) -> None: """Update the flake8 version string.""" self.version_action.version = "{} ({}) {}".format( self.version, self.generate_versions(), utils.get_python_version() ) def generate_epilog(self) -> None: """Create an epilog with the version and name of each of plugin.""" plugin_version_format = "%(name)s: %(version)s" self.parser.epilog = "Installed plugins: " + self.generate_versions( plugin_version_format ) def parse_args( self, args: Optional[List[str]] = None, values: Optional[argparse.Namespace] = None, ) -> Tuple[argparse.Namespace, List[str]]: """Proxy to calling the OptionParser's parse_args method.""" self.generate_epilog() self.update_version_string() if values: self.parser.set_defaults(**vars(values)) parsed_args = self.parser.parse_args(args) # TODO: refactor callers to not need this return parsed_args, parsed_args.filenames def parse_known_args( self, args: Optional[List[str]] = None ) -> Tuple[argparse.Namespace, List[str]]: """Parse only the known arguments from the argument values. Replicate a little argparse behaviour while we're still on optparse. """ self.generate_epilog() self.update_version_string() return self.parser.parse_known_args(args) def register_plugin( self, name: str, version: str, local: bool = False ) -> None: """Register a plugin relying on the OptionManager. :param str name: The name of the checker itself. This will be the ``name`` attribute of the class or function loaded from the entry-point. :param str version: The version of the checker that we're using. :param bool local: Whether the plugin is local to the project/repository or not. """ self.registered_plugins.add(PluginVersion(name, version, local)) ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1633956150.3494692 flake8-4.0.1/src/flake8/plugins/0000755000175000017500000000000000000000000020003 5ustar00asottileasottile00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633909928.0 flake8-4.0.1/src/flake8/plugins/__init__.py0000644000175000017500000000007100000000000022112 0ustar00asottileasottile00000000000000"""Submodule of built-in plugins and plugin managers.""" ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633909928.0 flake8-4.0.1/src/flake8/plugins/manager.py0000644000175000017500000004340400000000000021774 0ustar00asottileasottile00000000000000"""Plugin loading and management logic and classes.""" import logging from typing import Any from typing import Dict from typing import List from typing import Optional from typing import Set from flake8 import exceptions from flake8 import utils from flake8._compat import importlib_metadata LOG = logging.getLogger(__name__) __all__ = ("Checkers", "Plugin", "PluginManager", "ReportFormatters") NO_GROUP_FOUND = object() class Plugin: """Wrap an EntryPoint from setuptools and other logic.""" def __init__(self, name, entry_point, local=False): """Initialize our Plugin. :param str name: Name of the entry-point as it was registered with setuptools. :param entry_point: EntryPoint returned by setuptools. :type entry_point: setuptools.EntryPoint :param bool local: Is this a repo-local plugin? """ self.name = name self.entry_point = entry_point self.local = local self._plugin: Any = None self._parameters = None self._parameter_names: Optional[List[str]] = None self._group = None self._plugin_name = None self._version = None def __repr__(self) -> str: """Provide an easy to read description of the current plugin.""" return 'Plugin(name="{}", entry_point="{}")'.format( self.name, self.entry_point.value ) def to_dictionary(self): """Convert this plugin to a dictionary.""" return { "name": self.name, "parameters": self.parameters, "parameter_names": self.parameter_names, "plugin": self.plugin, "plugin_name": self.plugin_name, } def is_in_a_group(self): """Determine if this plugin is in a group. :returns: True if the plugin is in a group, otherwise False. :rtype: bool """ return self.group() is not None def group(self): """Find and parse the group the plugin is in.""" if self._group is None: name = self.name.split(".", 1) if len(name) > 1: self._group = name[0] else: self._group = NO_GROUP_FOUND if self._group is NO_GROUP_FOUND: return None return self._group @property def parameters(self): """List of arguments that need to be passed to the plugin.""" if self._parameters is None: self._parameters = utils.parameters_for(self) return self._parameters @property def parameter_names(self) -> List[str]: """List of argument names that need to be passed to the plugin.""" if self._parameter_names is None: self._parameter_names = list(self.parameters) return self._parameter_names @property def plugin(self): """Load and return the plugin associated with the entry-point. This property implicitly loads the plugin and then caches it. """ self.load_plugin() return self._plugin @property def version(self) -> str: """Return the version of the plugin.""" version = self._version if version is None: if self.is_in_a_group(): version = self._version = version_for(self) else: version = self._version = self.plugin.version return version @property def plugin_name(self): """Return the name of the plugin.""" if self._plugin_name is None: if self.is_in_a_group(): self._plugin_name = self.group() else: self._plugin_name = self.plugin.name return self._plugin_name @property def off_by_default(self): """Return whether the plugin is ignored by default.""" return getattr(self.plugin, "off_by_default", False) def execute(self, *args, **kwargs): r"""Call the plugin with \*args and \*\*kwargs.""" return self.plugin(*args, **kwargs) # pylint: disable=not-callable def _load(self): self._plugin = self.entry_point.load() if not callable(self._plugin): msg = ( f"Plugin {self._plugin!r} is not a callable. It might be " f"written for an older version of flake8 and might not work " f"with this version" ) LOG.critical(msg) raise TypeError(msg) def load_plugin(self): """Retrieve the plugin for this entry-point. This loads the plugin, stores it on the instance and then returns it. It does not reload it after the first time, it merely returns the cached plugin. :returns: Nothing """ if self._plugin is None: LOG.info('Loading plugin "%s" from entry-point.', self.name) try: self._load() except Exception as load_exception: LOG.exception(load_exception) failed_to_load = exceptions.FailedToLoadPlugin( plugin_name=self.name, exception=load_exception ) LOG.critical(str(failed_to_load)) raise failed_to_load def enable(self, optmanager, options=None): """Remove plugin name from the default ignore list.""" optmanager.remove_from_default_ignore([self.name]) optmanager.extend_default_select([self.name]) if not options: return try: options.ignore.remove(self.name) except (ValueError, KeyError): LOG.debug( "Attempted to remove %s from the ignore list but it was " "not a member of the list.", self.name, ) def disable(self, optmanager): """Add the plugin name to the default ignore list.""" optmanager.extend_default_ignore([self.name]) def provide_options(self, optmanager, options, extra_args): """Pass the parsed options and extra arguments to the plugin.""" parse_options = getattr(self.plugin, "parse_options", None) if parse_options is not None: LOG.debug('Providing options to plugin "%s".', self.name) try: parse_options(optmanager, options, extra_args) except TypeError: parse_options(options) if self.name in options.enable_extensions: self.enable(optmanager, options) def register_options(self, optmanager): """Register the plugin's command-line options on the OptionManager. :param optmanager: Instantiated OptionManager to register options on. :type optmanager: flake8.options.manager.OptionManager :returns: Nothing """ add_options = getattr(self.plugin, "add_options", None) if add_options is not None: LOG.debug( 'Registering options from plugin "%s" on OptionManager %r', self.name, optmanager, ) with optmanager.group(self.plugin_name): add_options(optmanager) if self.off_by_default: self.disable(optmanager) class PluginManager: # pylint: disable=too-few-public-methods """Find and manage plugins consistently.""" def __init__( self, namespace: str, local_plugins: Optional[List[str]] = None ) -> None: """Initialize the manager. :param str namespace: Namespace of the plugins to manage, e.g., 'flake8.extension'. :param list local_plugins: Plugins from config (as "X = path.to:Plugin" strings). """ self.namespace = namespace self.plugins: Dict[str, Plugin] = {} self.names: List[str] = [] self._load_local_plugins(local_plugins or []) self._load_entrypoint_plugins() def _load_local_plugins(self, local_plugins): """Load local plugins from config. :param list local_plugins: Plugins from config (as "X = path.to:Plugin" strings). """ for plugin_str in local_plugins: name, _, entry_str = plugin_str.partition("=") name, entry_str = name.strip(), entry_str.strip() entry_point = importlib_metadata.EntryPoint( name, entry_str, self.namespace ) self._load_plugin_from_entrypoint(entry_point, local=True) def _load_entrypoint_plugins(self): LOG.info('Loading entry-points for "%s".', self.namespace) eps = importlib_metadata.entry_points().get(self.namespace, ()) # python2.7 occasionally gives duplicate results due to redundant # `local/lib` -> `../lib` symlink on linux in virtualenvs so we # eliminate duplicates here for entry_point in sorted(frozenset(eps)): if entry_point.name == "per-file-ignores": LOG.warning( "flake8-per-file-ignores plugin is incompatible with " "flake8>=3.7 (which implements per-file-ignores itself)." ) continue self._load_plugin_from_entrypoint(entry_point) def _load_plugin_from_entrypoint(self, entry_point, local=False): """Load a plugin from a setuptools EntryPoint. :param EntryPoint entry_point: EntryPoint to load plugin from. :param bool local: Is this a repo-local plugin? """ name = entry_point.name self.plugins[name] = Plugin(name, entry_point, local=local) self.names.append(name) LOG.debug('Loaded %r for plugin "%s".', self.plugins[name], name) def map(self, func, *args, **kwargs): r"""Call ``func`` with the plugin and \*args and \**kwargs after. This yields the return value from ``func`` for each plugin. :param collections.Callable func: Function to call with each plugin. Signature should at least be: .. code-block:: python def myfunc(plugin): pass Any extra positional or keyword arguments specified with map will be passed along to this function after the plugin. The plugin passed is a :class:`~flake8.plugins.manager.Plugin`. :param args: Positional arguments to pass to ``func`` after each plugin. :param kwargs: Keyword arguments to pass to ``func`` after each plugin. """ for name in self.names: yield func(self.plugins[name], *args, **kwargs) def versions(self): # () -> (str, str) """Generate the versions of plugins. :returns: Tuples of the plugin_name and version :rtype: tuple """ plugins_seen: Set[str] = set() for entry_point_name in self.names: plugin = self.plugins[entry_point_name] plugin_name = plugin.plugin_name if plugin.plugin_name in plugins_seen: continue plugins_seen.add(plugin_name) yield (plugin_name, plugin.version) def version_for(plugin): # (Plugin) -> Optional[str] """Determine the version of a plugin by its module. :param plugin: The loaded plugin :type plugin: Plugin :returns: version string for the module :rtype: str """ module_name = plugin.plugin.__module__ try: module = __import__(module_name) except ImportError: return None return getattr(module, "__version__", None) class PluginTypeManager: """Parent class for most of the specific plugin types.""" namespace: str def __init__(self, local_plugins=None): """Initialize the plugin type's manager. :param list local_plugins: Plugins from config file instead of entry-points """ self.manager = PluginManager( self.namespace, local_plugins=local_plugins ) self.plugins_loaded = False def __contains__(self, name): """Check if the entry-point name is in this plugin type manager.""" LOG.debug('Checking for "%s" in plugin type manager.', name) return name in self.plugins def __getitem__(self, name): """Retrieve a plugin by its name.""" LOG.debug('Retrieving plugin for "%s".', name) return self.plugins[name] def get(self, name, default=None): """Retrieve the plugin referred to by ``name`` or return the default. :param str name: Name of the plugin to retrieve. :param default: Default value to return. :returns: Plugin object referred to by name, if it exists. :rtype: :class:`Plugin` """ if name in self: return self[name] return default @property def names(self): """Proxy attribute to underlying manager.""" return self.manager.names @property def plugins(self): """Proxy attribute to underlying manager.""" return self.manager.plugins @staticmethod def _generate_call_function(method_name, optmanager, *args, **kwargs): def generated_function(plugin): method = getattr(plugin, method_name, None) if method is not None and callable(method): return method(optmanager, *args, **kwargs) return generated_function def load_plugins(self): """Load all plugins of this type that are managed by this manager.""" if self.plugins_loaded: return def load_plugin(plugin): """Call each plugin's load_plugin method.""" return plugin.load_plugin() plugins = list(self.manager.map(load_plugin)) # Do not set plugins_loaded if we run into an exception self.plugins_loaded = True return plugins def register_plugin_versions(self, optmanager): """Register the plugins and their versions with the OptionManager.""" self.load_plugins() for (plugin_name, version) in self.manager.versions(): optmanager.register_plugin(name=plugin_name, version=version) def register_options(self, optmanager): """Register all of the checkers' options to the OptionManager.""" self.load_plugins() call_register_options = self._generate_call_function( "register_options", optmanager ) list(self.manager.map(call_register_options)) def provide_options(self, optmanager, options, extra_args): """Provide parsed options and extra arguments to the plugins.""" call_provide_options = self._generate_call_function( "provide_options", optmanager, options, extra_args ) list(self.manager.map(call_provide_options)) class Checkers(PluginTypeManager): """All of the checkers registered through entry-points or config.""" namespace = "flake8.extension" def checks_expecting(self, argument_name): """Retrieve checks that expect an argument with the specified name. Find all checker plugins that are expecting a specific argument. """ for plugin in self.plugins.values(): if argument_name == plugin.parameter_names[0]: yield plugin def to_dictionary(self): """Return a dictionary of AST and line-based plugins.""" return { "ast_plugins": [ plugin.to_dictionary() for plugin in self.ast_plugins ], "logical_line_plugins": [ plugin.to_dictionary() for plugin in self.logical_line_plugins ], "physical_line_plugins": [ plugin.to_dictionary() for plugin in self.physical_line_plugins ], } def register_options(self, optmanager): """Register all of the checkers' options to the OptionManager. This also ensures that plugins that are not part of a group and are enabled by default are enabled on the option manager. """ # NOTE(sigmavirus24) We reproduce a little of # PluginTypeManager.register_options to reduce the number of times # that we loop over the list of plugins. Instead of looping twice, # option registration and enabling the plugin, we loop once with one # function to map over the plugins. self.load_plugins() call_register_options = self._generate_call_function( "register_options", optmanager ) def register_and_enable(plugin): call_register_options(plugin) if plugin.group() is None and not plugin.off_by_default: plugin.enable(optmanager) list(self.manager.map(register_and_enable)) @property def ast_plugins(self): """List of plugins that expect the AST tree.""" plugins = getattr(self, "_ast_plugins", []) if not plugins: plugins = list(self.checks_expecting("tree")) self._ast_plugins = plugins return plugins @property def logical_line_plugins(self): """List of plugins that expect the logical lines.""" plugins = getattr(self, "_logical_line_plugins", []) if not plugins: plugins = list(self.checks_expecting("logical_line")) self._logical_line_plugins = plugins return plugins @property def physical_line_plugins(self): """List of plugins that expect the physical lines.""" plugins = getattr(self, "_physical_line_plugins", []) if not plugins: plugins = list(self.checks_expecting("physical_line")) self._physical_line_plugins = plugins return plugins class ReportFormatters(PluginTypeManager): """All of the report formatters registered through entry-points/config.""" namespace = "flake8.report" ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633909928.0 flake8-4.0.1/src/flake8/plugins/pyflakes.py0000644000175000017500000001442300000000000022177 0ustar00asottileasottile00000000000000"""Plugin built-in to Flake8 to treat pyflakes as a plugin.""" import os from typing import List import pyflakes.checker from flake8 import utils FLAKE8_PYFLAKES_CODES = { "UnusedImport": "F401", "ImportShadowedByLoopVar": "F402", "ImportStarUsed": "F403", "LateFutureImport": "F404", "ImportStarUsage": "F405", "ImportStarNotPermitted": "F406", "FutureFeatureNotDefined": "F407", "PercentFormatInvalidFormat": "F501", "PercentFormatExpectedMapping": "F502", "PercentFormatExpectedSequence": "F503", "PercentFormatExtraNamedArguments": "F504", "PercentFormatMissingArgument": "F505", "PercentFormatMixedPositionalAndNamed": "F506", "PercentFormatPositionalCountMismatch": "F507", "PercentFormatStarRequiresSequence": "F508", "PercentFormatUnsupportedFormatCharacter": "F509", "StringDotFormatInvalidFormat": "F521", "StringDotFormatExtraNamedArguments": "F522", "StringDotFormatExtraPositionalArguments": "F523", "StringDotFormatMissingArgument": "F524", "StringDotFormatMixingAutomatic": "F525", "FStringMissingPlaceholders": "F541", "MultiValueRepeatedKeyLiteral": "F601", "MultiValueRepeatedKeyVariable": "F602", "TooManyExpressionsInStarredAssignment": "F621", "TwoStarredExpressions": "F622", "AssertTuple": "F631", "IsLiteral": "F632", "InvalidPrintSyntax": "F633", "IfTuple": "F634", "BreakOutsideLoop": "F701", "ContinueOutsideLoop": "F702", "ContinueInFinally": "F703", "YieldOutsideFunction": "F704", "ReturnWithArgsInsideGenerator": "F705", "ReturnOutsideFunction": "F706", "DefaultExceptNotLast": "F707", "DoctestSyntaxError": "F721", "ForwardAnnotationSyntaxError": "F722", "CommentAnnotationSyntaxError": "F723", "RedefinedWhileUnused": "F811", "RedefinedInListComp": "F812", "UndefinedName": "F821", "UndefinedExport": "F822", "UndefinedLocal": "F823", "DuplicateArgument": "F831", "UnusedVariable": "F841", "RaiseNotImplemented": "F901", } class FlakesChecker(pyflakes.checker.Checker): """Subclass the Pyflakes checker to conform with the flake8 API.""" name = "pyflakes" version = pyflakes.__version__ with_doctest = False include_in_doctest: List[str] = [] exclude_from_doctest: List[str] = [] def __init__(self, tree, file_tokens, filename): """Initialize the PyFlakes plugin with an AST tree and filename.""" filename = utils.normalize_path(filename) with_doctest = self.with_doctest included_by = [ include for include in self.include_in_doctest if include != "" and filename.startswith(include) ] if included_by: with_doctest = True for exclude in self.exclude_from_doctest: if exclude != "" and filename.startswith(exclude): with_doctest = False overlaped_by = [ include for include in included_by if include.startswith(exclude) ] if overlaped_by: with_doctest = True super().__init__( tree, filename=filename, withDoctest=with_doctest, file_tokens=file_tokens, ) @classmethod def add_options(cls, parser): """Register options for PyFlakes on the Flake8 OptionManager.""" parser.add_option( "--builtins", parse_from_config=True, comma_separated_list=True, help="define more built-ins, comma separated", ) parser.add_option( "--doctests", default=False, action="store_true", parse_from_config=True, help="also check syntax of the doctests", ) parser.add_option( "--include-in-doctest", default="", dest="include_in_doctest", parse_from_config=True, comma_separated_list=True, normalize_paths=True, help="Run doctests only on these files", ) parser.add_option( "--exclude-from-doctest", default="", dest="exclude_from_doctest", parse_from_config=True, comma_separated_list=True, normalize_paths=True, help="Skip these files when running doctests", ) @classmethod def parse_options(cls, options): """Parse option values from Flake8's OptionManager.""" if options.builtins: cls.builtIns = cls.builtIns.union(options.builtins) cls.with_doctest = options.doctests included_files = [] for included_file in options.include_in_doctest: if included_file == "": continue if not included_file.startswith((os.sep, "./", "~/")): included_files.append(f"./{included_file}") else: included_files.append(included_file) cls.include_in_doctest = utils.normalize_paths(included_files) excluded_files = [] for excluded_file in options.exclude_from_doctest: if excluded_file == "": continue if not excluded_file.startswith((os.sep, "./", "~/")): excluded_files.append(f"./{excluded_file}") else: excluded_files.append(excluded_file) cls.exclude_from_doctest = utils.normalize_paths(excluded_files) inc_exc = set(cls.include_in_doctest).intersection( cls.exclude_from_doctest ) if inc_exc: raise ValueError( f"{inc_exc!r} was specified in both the " f"include-in-doctest and exclude-from-doctest " f"options. You are not allowed to specify it in " f"both for doctesting." ) def run(self): """Run the plugin.""" for message in self.messages: col = getattr(message, "col", 0) yield ( message.lineno, col, "{} {}".format( FLAKE8_PYFLAKES_CODES.get(type(message).__name__, "F999"), message.message % message.message_args, ), message.__class__, ) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633909928.0 flake8-4.0.1/src/flake8/processor.py0000644000175000017500000004101000000000000020707 0ustar00asottileasottile00000000000000"""Module containing our file processor that tokenizes a file for checks.""" import argparse import ast import contextlib import logging import tokenize from typing import Any from typing import Dict from typing import Generator from typing import List from typing import Optional from typing import Tuple import flake8 from flake8 import defaults from flake8 import utils LOG = logging.getLogger(__name__) PyCF_ONLY_AST = 1024 NEWLINE = frozenset([tokenize.NL, tokenize.NEWLINE]) SKIP_TOKENS = frozenset( [tokenize.NL, tokenize.NEWLINE, tokenize.INDENT, tokenize.DEDENT] ) _Token = Tuple[int, str, Tuple[int, int], Tuple[int, int], str] _LogicalMapping = List[Tuple[int, Tuple[int, int]]] _Logical = Tuple[List[str], List[str], _LogicalMapping] class FileProcessor: """Processes a file and holdes state. This processes a file by generating tokens, logical and physical lines, and AST trees. This also provides a way of passing state about the file to checks expecting that state. Any public attribute on this object can be requested by a plugin. The known public attributes are: - :attr:`blank_before` - :attr:`blank_lines` - :attr:`checker_state` - :attr:`indent_char` - :attr:`indent_level` - :attr:`line_number` - :attr:`logical_line` - :attr:`max_line_length` - :attr:`max_doc_length` - :attr:`multiline` - :attr:`noqa` - :attr:`previous_indent_level` - :attr:`previous_logical` - :attr:`previous_unindented_logical_line` - :attr:`tokens` - :attr:`file_tokens` - :attr:`total_lines` - :attr:`verbose` """ #: always ``False``, included for compatibility noqa = False def __init__( self, filename: str, options: argparse.Namespace, lines: Optional[List[str]] = None, ) -> None: """Initialice our file processor. :param str filename: Name of the file to process """ self.options = options self.filename = filename self.lines = lines if lines is not None else self.read_lines() self.strip_utf_bom() # Defaults for public attributes #: Number of preceding blank lines self.blank_before = 0 #: Number of blank lines self.blank_lines = 0 #: Checker states for each plugin? self._checker_states: Dict[str, Dict[Any, Any]] = {} #: Current checker state self.checker_state: Dict[Any, Any] = {} #: User provided option for hang closing self.hang_closing = options.hang_closing #: Character used for indentation self.indent_char: Optional[str] = None #: Current level of indentation self.indent_level = 0 #: Number of spaces used for indentation self.indent_size = options.indent_size #: String representing the space indentation (DEPRECATED) self.indent_size_str = str(self.indent_size) #: Line number in the file self.line_number = 0 #: Current logical line self.logical_line = "" #: Maximum line length as configured by the user self.max_line_length = options.max_line_length #: Maximum docstring / comment line length as configured by the user self.max_doc_length = options.max_doc_length #: Whether the current physical line is multiline self.multiline = False #: Previous level of indentation self.previous_indent_level = 0 #: Previous logical line self.previous_logical = "" #: Previous unindented (i.e. top-level) logical line self.previous_unindented_logical_line = "" #: Current set of tokens self.tokens: List[_Token] = [] #: Total number of lines in the file self.total_lines = len(self.lines) #: Verbosity level of Flake8 self.verbose = options.verbose #: Statistics dictionary self.statistics = {"logical lines": 0} self._file_tokens: Optional[List[_Token]] = None # map from line number to the line we'll search for `noqa` in self._noqa_line_mapping: Optional[Dict[int, str]] = None @property def file_tokens(self) -> List[_Token]: """Return the complete set of tokens for a file.""" if self._file_tokens is None: line_iter = iter(self.lines) self._file_tokens = list( tokenize.generate_tokens(lambda: next(line_iter)) ) return self._file_tokens @contextlib.contextmanager def inside_multiline( self, line_number: int ) -> Generator[None, None, None]: """Context-manager to toggle the multiline attribute.""" self.line_number = line_number self.multiline = True yield self.multiline = False def reset_blank_before(self) -> None: """Reset the blank_before attribute to zero.""" self.blank_before = 0 def delete_first_token(self) -> None: """Delete the first token in the list of tokens.""" del self.tokens[0] def visited_new_blank_line(self) -> None: """Note that we visited a new blank line.""" self.blank_lines += 1 def update_state(self, mapping: _LogicalMapping) -> None: """Update the indent level based on the logical line mapping.""" (start_row, start_col) = mapping[0][1] start_line = self.lines[start_row - 1] self.indent_level = expand_indent(start_line[:start_col]) if self.blank_before < self.blank_lines: self.blank_before = self.blank_lines def update_checker_state_for(self, plugin: Dict[str, Any]) -> None: """Update the checker_state attribute for the plugin.""" if "checker_state" in plugin["parameters"]: self.checker_state = self._checker_states.setdefault( plugin["name"], {} ) def next_logical_line(self) -> None: """Record the previous logical line. This also resets the tokens list and the blank_lines count. """ if self.logical_line: self.previous_indent_level = self.indent_level self.previous_logical = self.logical_line if not self.indent_level: self.previous_unindented_logical_line = self.logical_line self.blank_lines = 0 self.tokens = [] def build_logical_line_tokens(self) -> _Logical: """Build the mapping, comments, and logical line lists.""" logical = [] comments = [] mapping: _LogicalMapping = [] length = 0 previous_row = previous_column = None for token_type, text, start, end, line in self.tokens: if token_type in SKIP_TOKENS: continue if not mapping: mapping = [(0, start)] if token_type == tokenize.COMMENT: comments.append(text) continue if token_type == tokenize.STRING: text = mutate_string(text) if previous_row: (start_row, start_column) = start if previous_row != start_row: row_index = previous_row - 1 column_index = previous_column - 1 previous_text = self.lines[row_index][column_index] if previous_text == "," or ( previous_text not in "{[(" and text not in "}])" ): text = f" {text}" elif previous_column != start_column: text = line[previous_column:start_column] + text logical.append(text) length += len(text) mapping.append((length, end)) (previous_row, previous_column) = end return comments, logical, mapping def build_ast(self) -> ast.AST: """Build an abstract syntax tree from the list of lines.""" return ast.parse("".join(self.lines)) def build_logical_line(self) -> Tuple[str, str, _LogicalMapping]: """Build a logical line from the current tokens list.""" comments, logical, mapping_list = self.build_logical_line_tokens() joined_comments = "".join(comments) self.logical_line = "".join(logical) self.statistics["logical lines"] += 1 return joined_comments, self.logical_line, mapping_list def split_line(self, token: _Token) -> Generator[str, None, None]: """Split a physical line's line based on new-lines. This also auto-increments the line number for the caller. """ for line in token[1].split("\n")[:-1]: yield line self.line_number += 1 def keyword_arguments_for( self, parameters: Dict[str, bool], arguments: Optional[Dict[str, Any]] = None, ) -> Dict[str, Any]: """Generate the keyword arguments for a list of parameters.""" if arguments is None: arguments = {} for param, required in parameters.items(): if param in arguments: continue try: arguments[param] = getattr(self, param) except AttributeError as exc: if required: LOG.exception(exc) raise else: LOG.warning( 'Plugin requested optional parameter "%s" ' "but this is not an available parameter.", param, ) return arguments def generate_tokens(self) -> Generator[_Token, None, None]: """Tokenize the file and yield the tokens.""" for token in tokenize.generate_tokens(self.next_line): if token[2][0] > self.total_lines: break self.tokens.append(token) yield token def _noqa_line_range(self, min_line: int, max_line: int) -> Dict[int, str]: line_range = range(min_line, max_line + 1) joined = "".join(self.lines[min_line - 1 : max_line]) return dict.fromkeys(line_range, joined) def noqa_line_for(self, line_number: int) -> Optional[str]: """Retrieve the line which will be used to determine noqa.""" if self._noqa_line_mapping is None: try: file_tokens = self.file_tokens except (tokenize.TokenError, SyntaxError): # if we failed to parse the file tokens, we'll always fail in # the future, so set this so the code does not try again self._noqa_line_mapping = {} else: ret = {} min_line = len(self.lines) + 2 max_line = -1 for tp, _, (s_line, _), (e_line, _), _ in file_tokens: if tp == tokenize.ENDMARKER: break min_line = min(min_line, s_line) max_line = max(max_line, e_line) if tp in (tokenize.NL, tokenize.NEWLINE): ret.update(self._noqa_line_range(min_line, max_line)) min_line = len(self.lines) + 2 max_line = -1 # in newer versions of python, a `NEWLINE` token is inserted # at the end of the file even if it doesn't have one. # on old pythons, they will not have hit a `NEWLINE` if max_line != -1: ret.update(self._noqa_line_range(min_line, max_line)) self._noqa_line_mapping = ret # NOTE(sigmavirus24): Some plugins choose to report errors for empty # files on Line 1. In those cases, we shouldn't bother trying to # retrieve a physical line (since none exist). return self._noqa_line_mapping.get(line_number) def next_line(self) -> str: """Get the next line from the list.""" if self.line_number >= self.total_lines: return "" line = self.lines[self.line_number] self.line_number += 1 if self.indent_char is None and line[:1] in defaults.WHITESPACE: self.indent_char = line[0] return line def read_lines(self) -> List[str]: """Read the lines for this file checker.""" if self.filename is None or self.filename == "-": self.filename = self.options.stdin_display_name or "stdin" lines = self.read_lines_from_stdin() else: lines = self.read_lines_from_filename() return lines def read_lines_from_filename(self) -> List[str]: """Read the lines for a file.""" try: with tokenize.open(self.filename) as fd: return fd.readlines() except (SyntaxError, UnicodeError): # If we can't detect the codec with tokenize.detect_encoding, or # the detected encoding is incorrect, just fallback to latin-1. with open(self.filename, encoding="latin-1") as fd: return fd.readlines() def read_lines_from_stdin(self) -> List[str]: """Read the lines from standard in.""" return utils.stdin_get_lines() def should_ignore_file(self) -> bool: """Check if ``flake8: noqa`` is in the file to be ignored. :returns: True if a line matches :attr:`defaults.NOQA_FILE`, otherwise False :rtype: bool """ if not self.options.disable_noqa and any( defaults.NOQA_FILE.match(line) for line in self.lines ): return True elif any(defaults.NOQA_FILE.search(line) for line in self.lines): LOG.warning( "Detected `flake8: noqa` on line with code. To ignore an " "error on a line use `noqa` instead." ) return False else: return False def strip_utf_bom(self) -> None: """Strip the UTF bom from the lines of the file.""" if not self.lines: # If we have nothing to analyze quit early return first_byte = ord(self.lines[0][0]) if first_byte not in (0xEF, 0xFEFF): return # If the first byte of the file is a UTF-8 BOM, strip it if first_byte == 0xFEFF: self.lines[0] = self.lines[0][1:] elif self.lines[0][:3] == "\xEF\xBB\xBF": self.lines[0] = self.lines[0][3:] def is_eol_token(token: _Token) -> bool: """Check if the token is an end-of-line token.""" return token[0] in NEWLINE or token[4][token[3][1] :].lstrip() == "\\\n" def is_multiline_string(token: _Token) -> bool: """Check if this is a multiline string.""" return token[0] == tokenize.STRING and "\n" in token[1] def token_is_newline(token: _Token) -> bool: """Check if the token type is a newline token type.""" return token[0] in NEWLINE def count_parentheses(current_parentheses_count: int, token_text: str) -> int: """Count the number of parentheses.""" if token_text in "([{": # nosec return current_parentheses_count + 1 elif token_text in "}])": # nosec return current_parentheses_count - 1 return current_parentheses_count def log_token(log: logging.Logger, token: _Token) -> None: """Log a token to a provided logging object.""" if token[2][0] == token[3][0]: pos = "[{}:{}]".format(token[2][1] or "", token[3][1]) else: pos = f"l.{token[3][0]}" log.log( flake8._EXTRA_VERBOSE, "l.%s\t%s\t%s\t%r" % (token[2][0], pos, tokenize.tok_name[token[0]], token[1]), ) def expand_indent(line: str) -> int: r"""Return the amount of indentation. Tabs are expanded to the next multiple of 8. >>> expand_indent(' ') 4 >>> expand_indent('\t') 8 >>> expand_indent(' \t') 8 >>> expand_indent(' \t') 16 """ return len(line.expandtabs(8)) # NOTE(sigmavirus24): This was taken wholesale from # https://github.com/PyCQA/pycodestyle. The in-line comments were edited to be # more descriptive. def mutate_string(text: str) -> str: """Replace contents with 'xxx' to prevent syntax matching. >>> mutate_string('"abc"') '"xxx"' >>> mutate_string("'''abc'''") "'''xxx'''" >>> mutate_string("r'abc'") "r'xxx'" """ # NOTE(sigmavirus24): If there are string modifiers (e.g., b, u, r) # use the last "character" to determine if we're using single or double # quotes and then find the first instance of it start = text.index(text[-1]) + 1 end = len(text) - 1 # Check for triple-quoted strings if text[-3:] in ('"""', "'''"): start += 2 end -= 2 return text[:start] + "x" * (end - start) + text[end:] ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633909928.0 flake8-4.0.1/src/flake8/statistics.py0000644000175000017500000001105200000000000021065 0ustar00asottileasottile00000000000000"""Statistic collection logic for Flake8.""" import collections from typing import Dict from typing import Generator from typing import List from typing import Optional from typing import TYPE_CHECKING if TYPE_CHECKING: from flake8.style_guide import Violation class Statistics: """Manager of aggregated statistics for a run of Flake8.""" def __init__(self) -> None: """Initialize the underlying dictionary for our statistics.""" self._store: Dict[Key, "Statistic"] = {} def error_codes(self) -> List[str]: """Return all unique error codes stored. :returns: Sorted list of error codes. :rtype: list(str) """ return sorted({key.code for key in self._store}) def record(self, error: "Violation") -> None: """Add the fact that the error was seen in the file. :param error: The Violation instance containing the information about the violation. :type error: flake8.style_guide.Violation """ key = Key.create_from(error) if key not in self._store: self._store[key] = Statistic.create_from(error) self._store[key].increment() def statistics_for( self, prefix: str, filename: Optional[str] = None ) -> Generator["Statistic", None, None]: """Generate statistics for the prefix and filename. If you have a :class:`Statistics` object that has recorded errors, you can generate the statistics for a prefix (e.g., ``E``, ``E1``, ``W50``, ``W503``) with the optional filter of a filename as well. .. code-block:: python >>> stats = Statistics() >>> stats.statistics_for('E12', filename='src/flake8/statistics.py') >>> stats.statistics_for('W') :param str prefix: The error class or specific error code to find statistics for. :param str filename: (Optional) The filename to further filter results by. :returns: Generator of instances of :class:`Statistic` """ matching_errors = sorted( key for key in self._store if key.matches(prefix, filename) ) for error_code in matching_errors: yield self._store[error_code] class Key(collections.namedtuple("Key", ["filename", "code"])): """Simple key structure for the Statistics dictionary. To make things clearer, easier to read, and more understandable, we use a namedtuple here for all Keys in the underlying dictionary for the Statistics object. """ __slots__ = () @classmethod def create_from(cls, error: "Violation") -> "Key": """Create a Key from :class:`flake8.style_guide.Violation`.""" return cls(filename=error.filename, code=error.code) def matches(self, prefix: str, filename: Optional[str]) -> bool: """Determine if this key matches some constraints. :param str prefix: The error code prefix that this key's error code should start with. :param str filename: The filename that we potentially want to match on. This can be None to only match on error prefix. :returns: True if the Key's code starts with the prefix and either filename is None, or the Key's filename matches the value passed in. :rtype: bool """ return self.code.startswith(prefix) and ( filename is None or self.filename == filename ) class Statistic: """Simple wrapper around the logic of each statistic. Instead of maintaining a simple but potentially hard to reason about tuple, we create a namedtuple which has attributes and a couple convenience methods on it. """ def __init__( self, error_code: str, filename: str, message: str, count: int ) -> None: """Initialize our Statistic.""" self.error_code = error_code self.filename = filename self.message = message self.count = count @classmethod def create_from(cls, error: "Violation") -> "Statistic": """Create a Statistic from a :class:`flake8.style_guide.Violation`.""" return cls( error_code=error.code, filename=error.filename, message=error.text, count=0, ) def increment(self) -> None: """Increment the number of times we've seen this error in this file.""" self.count += 1 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633909928.0 flake8-4.0.1/src/flake8/style_guide.py0000644000175000017500000005307000000000000021216 0ustar00asottileasottile00000000000000"""Implementation of the StyleGuide used by Flake8.""" import argparse import collections import contextlib import copy import enum import functools import itertools import linecache import logging from typing import Dict from typing import Generator from typing import List from typing import Match from typing import Optional from typing import Sequence from typing import Set from typing import Tuple from typing import Union from flake8 import defaults from flake8 import statistics from flake8 import utils from flake8.formatting import base as base_formatter __all__ = ("StyleGuide",) LOG = logging.getLogger(__name__) class Selected(enum.Enum): """Enum representing an explicitly or implicitly selected code.""" Explicitly = "explicitly selected" Implicitly = "implicitly selected" class Ignored(enum.Enum): """Enum representing an explicitly or implicitly ignored code.""" Explicitly = "explicitly ignored" Implicitly = "implicitly ignored" class Decision(enum.Enum): """Enum representing whether a code should be ignored or selected.""" Ignored = "ignored error" Selected = "selected error" @functools.lru_cache(maxsize=512) def find_noqa(physical_line: str) -> Optional[Match[str]]: return defaults.NOQA_INLINE_REGEXP.search(physical_line) class Violation( collections.namedtuple( "Violation", [ "code", "filename", "line_number", "column_number", "text", "physical_line", ], ) ): """Class representing a violation reported by Flake8.""" def is_inline_ignored(self, disable_noqa: bool) -> bool: """Determine if a comment has been added to ignore this line. :param bool disable_noqa: Whether or not users have provided ``--disable-noqa``. :returns: True if error is ignored in-line, False otherwise. :rtype: bool """ physical_line = self.physical_line # TODO(sigmavirus24): Determine how to handle stdin with linecache if disable_noqa: return False if physical_line is None: physical_line = linecache.getline(self.filename, self.line_number) noqa_match = find_noqa(physical_line) if noqa_match is None: LOG.debug("%r is not inline ignored", self) return False codes_str = noqa_match.groupdict()["codes"] if codes_str is None: LOG.debug("%r is ignored by a blanket ``# noqa``", self) return True codes = set(utils.parse_comma_separated_list(codes_str)) if self.code in codes or self.code.startswith(tuple(codes)): LOG.debug( "%r is ignored specifically inline with ``# noqa: %s``", self, codes_str, ) return True LOG.debug( "%r is not ignored inline with ``# noqa: %s``", self, codes_str ) return False def is_in(self, diff: Dict[str, Set[int]]) -> bool: """Determine if the violation is included in a diff's line ranges. This function relies on the parsed data added via :meth:`~StyleGuide.add_diff_ranges`. If that has not been called and we are not evaluating files in a diff, then this will always return True. If there are diff ranges, then this will return True if the line number in the error falls inside one of the ranges for the file (and assuming the file is part of the diff data). If there are diff ranges, this will return False if the file is not part of the diff data or the line number of the error is not in any of the ranges of the diff. :returns: True if there is no diff or if the error is in the diff's line number ranges. False if the error's line number falls outside the diff's line number ranges. :rtype: bool """ if not diff: return True # NOTE(sigmavirus24): The parsed diff will be a defaultdict with # a set as the default value (if we have received it from # flake8.utils.parse_unified_diff). In that case ranges below # could be an empty set (which is False-y) or if someone else # is using this API, it could be None. If we could guarantee one # or the other, we would check for it more explicitly. line_numbers = diff.get(self.filename) if not line_numbers: return False return self.line_number in line_numbers class DecisionEngine: """A class for managing the decision process around violations. This contains the logic for whether a violation should be reported or ignored. """ def __init__(self, options: argparse.Namespace) -> None: """Initialize the engine.""" self.cache: Dict[str, Decision] = {} self.selected = tuple(options.select) self.extended_selected = tuple( sorted(options.extended_default_select, reverse=True) ) self.enabled_extensions = tuple(options.enable_extensions) self.all_selected = tuple( sorted( itertools.chain( self.selected, options.extend_select, self.enabled_extensions, ), reverse=True, ) ) self.ignored = tuple( sorted( itertools.chain(options.ignore, options.extend_ignore), reverse=True, ) ) self.using_default_ignore = set(self.ignored) == set( defaults.IGNORE ).union(options.extended_default_ignore) self.using_default_select = set(self.selected) == set(defaults.SELECT) def _in_all_selected(self, code: str) -> bool: return bool(self.all_selected) and code.startswith(self.all_selected) def _in_extended_selected(self, code: str) -> bool: return bool(self.extended_selected) and code.startswith( self.extended_selected ) def was_selected(self, code: str) -> Union[Selected, Ignored]: """Determine if the code has been selected by the user. :param str code: The code for the check that has been run. :returns: Selected.Implicitly if the selected list is empty, Selected.Explicitly if the selected list is not empty and a match was found, Ignored.Implicitly if the selected list is not empty but no match was found. """ if self._in_all_selected(code): return Selected.Explicitly if not self.all_selected and self._in_extended_selected(code): # If it was not explicitly selected, it may have been implicitly # selected because the check comes from a plugin that is enabled by # default return Selected.Implicitly return Ignored.Implicitly def was_ignored(self, code: str) -> Union[Selected, Ignored]: """Determine if the code has been ignored by the user. :param str code: The code for the check that has been run. :returns: Selected.Implicitly if the ignored list is empty, Ignored.Explicitly if the ignored list is not empty and a match was found, Selected.Implicitly if the ignored list is not empty but no match was found. """ if self.ignored and code.startswith(self.ignored): return Ignored.Explicitly return Selected.Implicitly def more_specific_decision_for(self, code: str) -> Decision: select = find_first_match(code, self.all_selected) extra_select = find_first_match(code, self.extended_selected) ignore = find_first_match(code, self.ignored) if select and ignore: # If the violation code appears in both the select and ignore # lists (in some fashion) then if we're using the default ignore # list and a custom select list we should select the code. An # example usage looks like this: # A user has a code that would generate an E126 violation which # is in our default ignore list and they specify select=E. # We should be reporting that violation. This logic changes, # however, if they specify select and ignore such that both match. # In that case we fall through to our find_more_specific call. # If, however, the user hasn't specified a custom select, and # we're using the defaults for both select and ignore then the # more specific rule must win. In most cases, that will be to # ignore the violation since our default select list is very # high-level and our ignore list is highly specific. if self.using_default_ignore and not self.using_default_select: return Decision.Selected return find_more_specific(select, ignore) if extra_select and ignore: # At this point, select is false-y. Now we need to check if the # code is in our extended select list and our ignore list. This is # a *rare* case as we see little usage of the extended select list # that plugins can use, so I suspect this section may change to # look a little like the block above in which we check if we're # using our default ignore list. return find_more_specific(extra_select, ignore) if select or (extra_select and self.using_default_select): # Here, ignore was false-y and the user has either selected # explicitly the violation or the violation is covered by # something in the extended select list and we're using the # default select list. In either case, we want the violation to be # selected. return Decision.Selected if select is None and ( extra_select is None or not self.using_default_ignore ): return Decision.Ignored if (select is None and not self.using_default_select) and ( ignore is None and self.using_default_ignore ): return Decision.Ignored return Decision.Selected def make_decision(self, code: str) -> Decision: """Decide if code should be ignored or selected.""" LOG.debug('Deciding if "%s" should be reported', code) selected = self.was_selected(code) ignored = self.was_ignored(code) LOG.debug( 'The user configured "%s" to be "%s", "%s"', code, selected, ignored, ) if ( selected is Selected.Explicitly or selected is Selected.Implicitly ) and ignored is Selected.Implicitly: decision = Decision.Selected elif ( selected is Selected.Explicitly and ignored is Ignored.Explicitly ) or ( selected is Ignored.Implicitly and ignored is Selected.Implicitly ): decision = self.more_specific_decision_for(code) elif selected is Ignored.Implicitly or ignored is Ignored.Explicitly: decision = Decision.Ignored # pylint: disable=R0204 return decision def decision_for(self, code: str) -> Decision: """Return the decision for a specific code. This method caches the decisions for codes to avoid retracing the same logic over and over again. We only care about the select and ignore rules as specified by the user in their configuration files and command-line flags. This method does not look at whether the specific line is being ignored in the file itself. :param str code: The code for the check that has been run. """ decision = self.cache.get(code) if decision is None: decision = self.make_decision(code) self.cache[code] = decision LOG.debug('"%s" will be "%s"', code, decision) return decision class StyleGuideManager: """Manage multiple style guides for a single run.""" def __init__( self, options: argparse.Namespace, formatter: base_formatter.BaseFormatter, decider: Optional[DecisionEngine] = None, ) -> None: """Initialize our StyleGuide. .. todo:: Add parameter documentation. """ self.options = options self.formatter = formatter self.stats = statistics.Statistics() self.decider = decider or DecisionEngine(options) self.style_guides: List[StyleGuide] = [] self.default_style_guide = StyleGuide( options, formatter, self.stats, decider=decider ) self.style_guides = list( itertools.chain( [self.default_style_guide], self.populate_style_guides_with(options), ) ) def populate_style_guides_with( self, options: argparse.Namespace ) -> Generator["StyleGuide", None, None]: """Generate style guides from the per-file-ignores option. :param options: The original options parsed from the CLI and config file. :type options: :class:`~argparse.Namespace` :returns: A copy of the default style guide with overridden values. :rtype: :class:`~flake8.style_guide.StyleGuide` """ per_file = utils.parse_files_to_codes_mapping(options.per_file_ignores) for filename, violations in per_file: yield self.default_style_guide.copy( filename=filename, extend_ignore_with=violations ) @functools.lru_cache(maxsize=None) def style_guide_for(self, filename: str) -> "StyleGuide": """Find the StyleGuide for the filename in particular.""" guides = sorted( (g for g in self.style_guides if g.applies_to(filename)), key=lambda g: len(g.filename or ""), ) if len(guides) > 1: return guides[-1] return guides[0] @contextlib.contextmanager def processing_file( self, filename: str ) -> Generator["StyleGuide", None, None]: """Record the fact that we're processing the file's results.""" guide = self.style_guide_for(filename) with guide.processing_file(filename): yield guide def handle_error( self, code: str, filename: str, line_number: int, column_number: Optional[int], text: str, physical_line: Optional[str] = None, ) -> int: """Handle an error reported by a check. :param str code: The error code found, e.g., E123. :param str filename: The file in which the error was found. :param int line_number: The line number (where counting starts at 1) at which the error occurs. :param int column_number: The column number (where counting starts at 1) at which the error occurs. :param str text: The text of the error message. :param str physical_line: The actual physical line causing the error. :returns: 1 if the error was reported. 0 if it was ignored. This is to allow for counting of the number of errors found that were not ignored. :rtype: int """ guide = self.style_guide_for(filename) return guide.handle_error( code, filename, line_number, column_number, text, physical_line ) def add_diff_ranges(self, diffinfo: Dict[str, Set[int]]) -> None: """Update the StyleGuides to filter out information not in the diff. This provides information to the underlying StyleGuides so that only the errors in the line number ranges are reported. :param dict diffinfo: Dictionary mapping filenames to sets of line number ranges. """ for guide in self.style_guides: guide.add_diff_ranges(diffinfo) class StyleGuide: """Manage a Flake8 user's style guide.""" def __init__( self, options: argparse.Namespace, formatter: base_formatter.BaseFormatter, stats: statistics.Statistics, filename: Optional[str] = None, decider: Optional[DecisionEngine] = None, ): """Initialize our StyleGuide. .. todo:: Add parameter documentation. """ self.options = options self.formatter = formatter self.stats = stats self.decider = decider or DecisionEngine(options) self.filename = filename if self.filename: self.filename = utils.normalize_path(self.filename) self._parsed_diff: Dict[str, Set[int]] = {} def __repr__(self) -> str: """Make it easier to debug which StyleGuide we're using.""" return f"" def copy( self, filename: Optional[str] = None, extend_ignore_with: Optional[Sequence[str]] = None, ) -> "StyleGuide": """Create a copy of this style guide with different values.""" filename = filename or self.filename options = copy.deepcopy(self.options) options.ignore.extend(extend_ignore_with or []) return StyleGuide( options, self.formatter, self.stats, filename=filename ) @contextlib.contextmanager def processing_file( self, filename: str ) -> Generator["StyleGuide", None, None]: """Record the fact that we're processing the file's results.""" self.formatter.beginning(filename) yield self self.formatter.finished(filename) def applies_to(self, filename: str) -> bool: """Check if this StyleGuide applies to the file. :param str filename: The name of the file with violations that we're potentially applying this StyleGuide to. :returns: True if this applies, False otherwise :rtype: bool """ if self.filename is None: return True return utils.matches_filename( filename, patterns=[self.filename], log_message=f'{self!r} does %(whether)smatch "%(path)s"', logger=LOG, ) def should_report_error(self, code: str) -> Decision: """Determine if the error code should be reported or ignored. This method only cares about the select and ignore rules as specified by the user in their configuration files and command-line flags. This method does not look at whether the specific line is being ignored in the file itself. :param str code: The code for the check that has been run. """ return self.decider.decision_for(code) def handle_error( self, code: str, filename: str, line_number: int, column_number: Optional[int], text: str, physical_line: Optional[str] = None, ) -> int: """Handle an error reported by a check. :param str code: The error code found, e.g., E123. :param str filename: The file in which the error was found. :param int line_number: The line number (where counting starts at 1) at which the error occurs. :param int column_number: The column number (where counting starts at 1) at which the error occurs. :param str text: The text of the error message. :param str physical_line: The actual physical line causing the error. :returns: 1 if the error was reported. 0 if it was ignored. This is to allow for counting of the number of errors found that were not ignored. :rtype: int """ disable_noqa = self.options.disable_noqa # NOTE(sigmavirus24): Apparently we're provided with 0-indexed column # numbers so we have to offset that here. Also, if a SyntaxError is # caught, column_number may be None. if not column_number: column_number = 0 error = Violation( code, filename, line_number, column_number + 1, text, physical_line, ) error_is_selected = ( self.should_report_error(error.code) is Decision.Selected ) is_not_inline_ignored = error.is_inline_ignored(disable_noqa) is False is_included_in_diff = error.is_in(self._parsed_diff) if error_is_selected and is_not_inline_ignored and is_included_in_diff: self.formatter.handle(error) self.stats.record(error) return 1 return 0 def add_diff_ranges(self, diffinfo: Dict[str, Set[int]]) -> None: """Update the StyleGuide to filter out information not in the diff. This provides information to the StyleGuide so that only the errors in the line number ranges are reported. :param dict diffinfo: Dictionary mapping filenames to sets of line number ranges. """ self._parsed_diff = diffinfo def find_more_specific(selected: str, ignored: str) -> Decision: if selected.startswith(ignored) and selected != ignored: return Decision.Selected return Decision.Ignored def find_first_match( error_code: str, code_list: Tuple[str, ...] ) -> Optional[str]: startswith = error_code.startswith for code in code_list: if startswith(code): break else: return None return code ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633909928.0 flake8-4.0.1/src/flake8/utils.py0000644000175000017500000003333600000000000020044 0ustar00asottileasottile00000000000000"""Utility methods for flake8.""" import collections import fnmatch as _fnmatch import functools import inspect import io import logging import os import platform import re import sys import textwrap import tokenize from typing import Callable from typing import Dict from typing import Generator from typing import List from typing import Optional from typing import Pattern from typing import Sequence from typing import Set from typing import Tuple from typing import TYPE_CHECKING from typing import Union from flake8 import exceptions if TYPE_CHECKING: from flake8.plugins.manager import Plugin DIFF_HUNK_REGEXP = re.compile(r"^@@ -\d+(?:,\d+)? \+(\d+)(?:,(\d+))? @@.*$") COMMA_SEPARATED_LIST_RE = re.compile(r"[,\s]") LOCAL_PLUGIN_LIST_RE = re.compile(r"[,\t\n\r\f\v]") def parse_comma_separated_list( value: str, regexp: Pattern[str] = COMMA_SEPARATED_LIST_RE ) -> List[str]: """Parse a comma-separated list. :param value: String to be parsed and normalized. :param regexp: Compiled regular expression used to split the value when it is a string. :type regexp: _sre.SRE_Pattern :returns: List of values with whitespace stripped. :rtype: list """ assert isinstance(value, str), value separated = regexp.split(value) item_gen = (item.strip() for item in separated) return [item for item in item_gen if item] _Token = collections.namedtuple("_Token", ("tp", "src")) _CODE, _FILE, _COLON, _COMMA, _WS = "code", "file", "colon", "comma", "ws" _EOF = "eof" _FILE_LIST_TOKEN_TYPES = [ (re.compile(r"[A-Z]+[0-9]*(?=$|\s|,)"), _CODE), (re.compile(r"[^\s:,]+"), _FILE), (re.compile(r"\s*:\s*"), _COLON), (re.compile(r"\s*,\s*"), _COMMA), (re.compile(r"\s+"), _WS), ] def _tokenize_files_to_codes_mapping(value: str) -> List[_Token]: tokens = [] i = 0 while i < len(value): for token_re, token_name in _FILE_LIST_TOKEN_TYPES: match = token_re.match(value, i) if match: tokens.append(_Token(token_name, match.group().strip())) i = match.end() break else: raise AssertionError("unreachable", value, i) tokens.append(_Token(_EOF, "")) return tokens def parse_files_to_codes_mapping( # noqa: C901 value_: Union[Sequence[str], str] ) -> List[Tuple[str, List[str]]]: """Parse a files-to-codes mapping. A files-to-codes mapping a sequence of values specified as `filenames list:codes list ...`. Each of the lists may be separated by either comma or whitespace tokens. :param value: String to be parsed and normalized. :type value: str """ if not isinstance(value_, str): value = "\n".join(value_) else: value = value_ ret: List[Tuple[str, List[str]]] = [] if not value.strip(): return ret class State: seen_sep = True seen_colon = False filenames: List[str] = [] codes: List[str] = [] def _reset() -> None: if State.codes: for filename in State.filenames: ret.append((filename, State.codes)) State.seen_sep = True State.seen_colon = False State.filenames = [] State.codes = [] def _unexpected_token() -> exceptions.ExecutionError: return exceptions.ExecutionError( f"Expected `per-file-ignores` to be a mapping from file exclude " f"patterns to ignore codes.\n\n" f"Configured `per-file-ignores` setting:\n\n" f"{textwrap.indent(value.strip(), ' ')}" ) for token in _tokenize_files_to_codes_mapping(value): # legal in any state: separator sets the sep bit if token.tp in {_COMMA, _WS}: State.seen_sep = True # looking for filenames elif not State.seen_colon: if token.tp == _COLON: State.seen_colon = True State.seen_sep = True elif State.seen_sep and token.tp == _FILE: State.filenames.append(token.src) State.seen_sep = False else: raise _unexpected_token() # looking for codes else: if token.tp == _EOF: _reset() elif State.seen_sep and token.tp == _CODE: State.codes.append(token.src) State.seen_sep = False elif State.seen_sep and token.tp == _FILE: _reset() State.filenames.append(token.src) State.seen_sep = False else: raise _unexpected_token() return ret def normalize_paths( paths: Sequence[str], parent: str = os.curdir ) -> List[str]: """Normalize a list of paths relative to a parent directory. :returns: The normalized paths. :rtype: [str] """ assert isinstance(paths, list), paths return [normalize_path(p, parent) for p in paths] def normalize_path(path: str, parent: str = os.curdir) -> str: """Normalize a single-path. :returns: The normalized path. :rtype: str """ # NOTE(sigmavirus24): Using os.path.sep and os.path.altsep allow for # Windows compatibility with both Windows-style paths (c:\\foo\bar) and # Unix style paths (/foo/bar). separator = os.path.sep # NOTE(sigmavirus24): os.path.altsep may be None alternate_separator = os.path.altsep or "" if separator in path or ( alternate_separator and alternate_separator in path ): path = os.path.abspath(os.path.join(parent, path)) return path.rstrip(separator + alternate_separator) @functools.lru_cache(maxsize=1) def stdin_get_value() -> str: """Get and cache it so plugins can use it.""" stdin_value = sys.stdin.buffer.read() fd = io.BytesIO(stdin_value) try: coding, _ = tokenize.detect_encoding(fd.readline) fd.seek(0) return io.TextIOWrapper(fd, coding).read() except (LookupError, SyntaxError, UnicodeError): return stdin_value.decode("utf-8") def stdin_get_lines() -> List[str]: """Return lines of stdin split according to file splitting.""" return list(io.StringIO(stdin_get_value())) def parse_unified_diff(diff: Optional[str] = None) -> Dict[str, Set[int]]: """Parse the unified diff passed on stdin. :returns: dictionary mapping file names to sets of line numbers :rtype: dict """ # Allow us to not have to patch out stdin_get_value if diff is None: diff = stdin_get_value() number_of_rows = None current_path = None parsed_paths: Dict[str, Set[int]] = collections.defaultdict(set) for line in diff.splitlines(): if number_of_rows: if not line or line[0] != "-": number_of_rows -= 1 # We're in the part of the diff that has lines starting with +, -, # and ' ' to show context and the changes made. We skip these # because the information we care about is the filename and the # range within it. # When number_of_rows reaches 0, we will once again start # searching for filenames and ranges. continue # NOTE(sigmavirus24): Diffs that we support look roughly like: # diff a/file.py b/file.py # ... # --- a/file.py # +++ b/file.py # Below we're looking for that last line. Every diff tool that # gives us this output may have additional information after # ``b/file.py`` which it will separate with a \t, e.g., # +++ b/file.py\t100644 # Which is an example that has the new file permissions/mode. # In this case we only care about the file name. if line[:3] == "+++": current_path = line[4:].split("\t", 1)[0] # NOTE(sigmavirus24): This check is for diff output from git. if current_path[:2] == "b/": current_path = current_path[2:] # We don't need to do anything else. We have set up our local # ``current_path`` variable. We can skip the rest of this loop. # The next line we will see will give us the hung information # which is in the next section of logic. continue hunk_match = DIFF_HUNK_REGEXP.match(line) # NOTE(sigmavirus24): pep8/pycodestyle check for: # line[:3] == '@@ ' # But the DIFF_HUNK_REGEXP enforces that the line start with that # So we can more simply check for a match instead of slicing and # comparing. if hunk_match: (row, number_of_rows) = ( 1 if not group else int(group) for group in hunk_match.groups() ) assert current_path is not None parsed_paths[current_path].update(range(row, row + number_of_rows)) # We have now parsed our diff into a dictionary that looks like: # {'file.py': set(range(10, 16), range(18, 20)), ...} return parsed_paths def is_windows() -> bool: """Determine if we're running on Windows. :returns: True if running on Windows, otherwise False :rtype: bool """ return os.name == "nt" def is_using_stdin(paths: List[str]) -> bool: """Determine if we're going to read from stdin. :param list paths: The paths that we're going to check. :returns: True if stdin (-) is in the path, otherwise False :rtype: bool """ return "-" in paths def _default_predicate(*args: str) -> bool: return False def filenames_from( arg: str, predicate: Optional[Callable[[str], bool]] = None ) -> Generator[str, None, None]: """Generate filenames from an argument. :param str arg: Parameter from the command-line. :param callable predicate: Predicate to use to filter out filenames. If the predicate returns ``True`` we will exclude the filename, otherwise we will yield it. By default, we include every filename generated. :returns: Generator of paths """ if predicate is None: predicate = _default_predicate if predicate(arg): return if os.path.isdir(arg): for root, sub_directories, files in os.walk(arg): if predicate(root): sub_directories[:] = [] continue # NOTE(sigmavirus24): os.walk() will skip a directory if you # remove it from the list of sub-directories. for directory in sub_directories: joined = os.path.join(root, directory) if predicate(joined): sub_directories.remove(directory) for filename in files: joined = os.path.join(root, filename) if not predicate(joined): yield joined else: yield arg def fnmatch(filename: str, patterns: Sequence[str]) -> bool: """Wrap :func:`fnmatch.fnmatch` to add some functionality. :param str filename: Name of the file we're trying to match. :param list patterns: Patterns we're using to try to match the filename. :param bool default: The default value if patterns is empty :returns: True if a pattern matches the filename, False if it doesn't. ``default`` if patterns is empty. """ if not patterns: return True return any(_fnmatch.fnmatch(filename, pattern) for pattern in patterns) def parameters_for(plugin: "Plugin") -> Dict[str, bool]: """Return the parameters for the plugin. This will inspect the plugin and return either the function parameters if the plugin is a function or the parameters for ``__init__`` after ``self`` if the plugin is a class. :param plugin: The internal plugin object. :type plugin: flake8.plugins.manager.Plugin :returns: A dictionary mapping the parameter name to whether or not it is required (a.k.a., is positional only/does not have a default). :rtype: dict([(str, bool)]) """ func = plugin.plugin is_class = not inspect.isfunction(func) if is_class: # The plugin is a class func = plugin.plugin.__init__ parameters = { parameter.name: parameter.default is parameter.empty for parameter in inspect.signature(func).parameters.values() if parameter.kind == parameter.POSITIONAL_OR_KEYWORD } if is_class: parameters.pop("self", None) return parameters def matches_filename( path: str, patterns: Sequence[str], log_message: str, logger: logging.Logger, ) -> bool: """Use fnmatch to discern if a path exists in patterns. :param str path: The path to the file under question :param patterns: The patterns to match the path against. :type patterns: list[str] :param str log_message: The message used for logging purposes. :returns: True if path matches patterns, False otherwise :rtype: bool """ if not patterns: return False basename = os.path.basename(path) if basename not in {".", ".."} and fnmatch(basename, patterns): logger.debug(log_message, {"path": basename, "whether": ""}) return True absolute_path = os.path.abspath(path) match = fnmatch(absolute_path, patterns) logger.debug( log_message, {"path": absolute_path, "whether": "" if match else "not "}, ) return match def get_python_version() -> str: """Find and format the python implementation and version. :returns: Implementation name, version, and platform as a string. :rtype: str """ return "{} {} on {}".format( platform.python_implementation(), platform.python_version(), platform.system(), ) ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1633956150.3494692 flake8-4.0.1/src/flake8.egg-info/0000755000175000017500000000000000000000000020014 5ustar00asottileasottile00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633956150.0 flake8-4.0.1/src/flake8.egg-info/PKG-INFO0000644000175000017500000000726600000000000021124 0ustar00asottileasottile00000000000000Metadata-Version: 2.1 Name: flake8 Version: 4.0.1 Summary: the modular source code checker: pep8 pyflakes and co Home-page: https://github.com/pycqa/flake8 Author: Tarek Ziade Author-email: tarek@ziade.org Maintainer: Ian Stapleton Cordasco Maintainer-email: graffatcolmingov@gmail.com License: MIT Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: Environment :: Console Classifier: Framework :: Flake8 Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: MIT License Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3 :: Only Classifier: Programming Language :: Python :: 3.6 Classifier: Programming Language :: Python :: 3.7 Classifier: Programming Language :: Python :: 3.8 Classifier: Programming Language :: Python :: 3.9 Classifier: Programming Language :: Python :: 3.10 Classifier: Programming Language :: Python :: Implementation :: CPython Classifier: Programming Language :: Python :: Implementation :: PyPy Classifier: Topic :: Software Development :: Libraries :: Python Modules Classifier: Topic :: Software Development :: Quality Assurance Requires-Python: >=3.6 Description-Content-Type: text/x-rst License-File: LICENSE .. image:: https://github.com/PyCQA/flake8/workflows/main/badge.svg :target: https://github.com/PyCQA/flake8/actions?query=workflow%3Amain :alt: build status .. image:: https://results.pre-commit.ci/badge/github/PyCQA/flake8/main.svg :target: https://results.pre-commit.ci/latest/github/PyCQA/flake8/main :alt: pre-commit.ci status ======== Flake8 ======== Flake8 is a wrapper around these tools: - PyFlakes - pycodestyle - Ned Batchelder's McCabe script Flake8 runs all the tools by launching the single ``flake8`` command. It displays the warnings in a per-file, merged output. It also adds a few features: - files that contain this line are skipped:: # flake8: noqa - lines that contain a ``# noqa`` comment at the end will not issue warnings. - you can ignore specific errors on a line with ``# noqa: ``, e.g., ``# noqa: E234``. Multiple codes can be given, separated by comma. The ``noqa`` token is case insensitive, the colon before the list of codes is required otherwise the part after ``noqa`` is ignored - Git and Mercurial hooks - extendable through ``flake8.extension`` and ``flake8.formatting`` entry points Quickstart ========== See our `quickstart documentation `_ for how to install and get started with Flake8. Frequently Asked Questions ========================== Flake8 maintains an `FAQ `_ in its documentation. Questions or Feedback ===================== If you have questions you'd like to ask the developers, or feedback you'd like to provide, feel free to use the mailing list: code-quality@python.org We would love to hear from you. Additionally, if you have a feature you'd like to suggest, the mailing list would be the best place for it. Links ===== * `Flake8 Documentation `_ * `GitHub Project `_ * `All (Open and Closed) Issues `_ * `Code-Quality Archives `_ * `Code of Conduct `_ * `Getting Started Contributing `_ Maintenance =========== Flake8 was created by Tarek Ziadé and is currently maintained by `Ian Cordasco `_ ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633956150.0 flake8-4.0.1/src/flake8.egg-info/SOURCES.txt0000644000175000017500000001462600000000000021711 0ustar00asottileasottile00000000000000CONTRIBUTING.rst CONTRIBUTORS.txt LICENSE MANIFEST.in README.rst pytest.ini setup.cfg setup.py tox.ini docs/source/conf.py docs/source/faq.rst docs/source/glossary.rst docs/source/index.rst docs/source/manpage.rst docs/source/internal/checker.rst docs/source/internal/cli.rst docs/source/internal/contributing.rst docs/source/internal/formatters.rst docs/source/internal/index.rst docs/source/internal/option_handling.rst docs/source/internal/plugin_handling.rst docs/source/internal/releases.rst docs/source/internal/start-to-finish.rst docs/source/internal/utils.rst docs/source/internal/writing-code.rst docs/source/internal/writing-documentation.rst docs/source/plugin-development/cross-compatibility.rst docs/source/plugin-development/formatters.rst docs/source/plugin-development/index.rst docs/source/plugin-development/plugin-parameters.rst docs/source/plugin-development/registering-plugins.rst docs/source/release-notes/0.6.0.rst docs/source/release-notes/0.7.0.rst docs/source/release-notes/0.8.0.rst docs/source/release-notes/0.9.0.rst docs/source/release-notes/1.0.0.rst docs/source/release-notes/1.1.0.rst docs/source/release-notes/1.2.0.rst docs/source/release-notes/1.3.0.rst docs/source/release-notes/1.3.1.rst docs/source/release-notes/1.4.0.rst docs/source/release-notes/1.5.0.rst docs/source/release-notes/1.6.0.rst docs/source/release-notes/1.6.1.rst docs/source/release-notes/1.6.2.rst docs/source/release-notes/1.7.0.rst docs/source/release-notes/2.0.0.rst docs/source/release-notes/2.1.0.rst docs/source/release-notes/2.2.0.rst docs/source/release-notes/2.2.1.rst docs/source/release-notes/2.2.2.rst docs/source/release-notes/2.2.3.rst docs/source/release-notes/2.2.4.rst docs/source/release-notes/2.2.5.rst docs/source/release-notes/2.3.0.rst docs/source/release-notes/2.4.0.rst docs/source/release-notes/2.4.1.rst docs/source/release-notes/2.5.0.rst docs/source/release-notes/2.5.1.rst docs/source/release-notes/2.5.2.rst docs/source/release-notes/2.5.3.rst docs/source/release-notes/2.5.4.rst docs/source/release-notes/2.5.5.rst docs/source/release-notes/2.6.0.rst docs/source/release-notes/2.6.1.rst docs/source/release-notes/2.6.2.rst docs/source/release-notes/3.0.0.rst docs/source/release-notes/3.0.1.rst docs/source/release-notes/3.0.2.rst docs/source/release-notes/3.0.3.rst docs/source/release-notes/3.0.4.rst docs/source/release-notes/3.1.0.rst docs/source/release-notes/3.1.1.rst docs/source/release-notes/3.2.0.rst docs/source/release-notes/3.2.1.rst docs/source/release-notes/3.3.0.rst docs/source/release-notes/3.4.0.rst docs/source/release-notes/3.4.1.rst docs/source/release-notes/3.5.0.rst docs/source/release-notes/3.6.0.rst docs/source/release-notes/3.7.0.rst docs/source/release-notes/3.7.1.rst docs/source/release-notes/3.7.2.rst docs/source/release-notes/3.7.3.rst docs/source/release-notes/3.7.4.rst docs/source/release-notes/3.7.5.rst docs/source/release-notes/3.7.6.rst docs/source/release-notes/3.7.7.rst docs/source/release-notes/3.7.8.rst docs/source/release-notes/3.7.9.rst docs/source/release-notes/3.8.0.rst docs/source/release-notes/3.8.1.rst docs/source/release-notes/3.8.2.rst docs/source/release-notes/3.8.3.rst docs/source/release-notes/3.8.4.rst docs/source/release-notes/3.9.0.rst docs/source/release-notes/3.9.1.rst docs/source/release-notes/3.9.2.rst docs/source/release-notes/4.0.0.rst docs/source/release-notes/4.0.1.rst docs/source/release-notes/index.rst docs/source/user/configuration.rst docs/source/user/error-codes.rst docs/source/user/index.rst docs/source/user/invocation.rst docs/source/user/options.rst docs/source/user/python-api.rst docs/source/user/using-hooks.rst docs/source/user/using-plugins.rst docs/source/user/violations.rst src/flake8/__init__.py src/flake8/__main__.py src/flake8/_compat.py src/flake8/checker.py src/flake8/defaults.py src/flake8/exceptions.py src/flake8/processor.py src/flake8/statistics.py src/flake8/style_guide.py src/flake8/utils.py src/flake8.egg-info/PKG-INFO src/flake8.egg-info/SOURCES.txt src/flake8.egg-info/dependency_links.txt src/flake8.egg-info/entry_points.txt src/flake8.egg-info/requires.txt src/flake8.egg-info/top_level.txt src/flake8/api/__init__.py src/flake8/api/legacy.py src/flake8/formatting/__init__.py src/flake8/formatting/base.py src/flake8/formatting/default.py src/flake8/main/__init__.py src/flake8/main/application.py src/flake8/main/cli.py src/flake8/main/debug.py src/flake8/main/options.py src/flake8/options/__init__.py src/flake8/options/aggregator.py src/flake8/options/config.py src/flake8/options/manager.py src/flake8/plugins/__init__.py src/flake8/plugins/manager.py src/flake8/plugins/pyflakes.py tests/__init__.py tests/conftest.py tests/fixtures/config_files/README.rst tests/fixtures/config_files/broken.ini tests/fixtures/config_files/cli-specified-with-inline-comments.ini tests/fixtures/config_files/cli-specified-without-inline-comments.ini tests/fixtures/config_files/cli-specified.ini tests/fixtures/config_files/config-with-hyphenated-options.ini tests/fixtures/config_files/local-config.ini tests/fixtures/config_files/local-plugin-path.ini tests/fixtures/config_files/local-plugin.ini tests/fixtures/config_files/no-flake8-section.ini tests/fixtures/config_files/user-config.ini tests/fixtures/diffs/multi_file_diff tests/fixtures/diffs/single_file_diff tests/fixtures/diffs/two_file_diff tests/fixtures/example-code/empty.py tests/fixtures/example-code/invalid-syntax.py tests/fixtures/example-code/inline-ignores/E501.py tests/fixtures/example-code/inline-ignores/E731.py tests/integration/test_aggregator.py tests/integration/test_api_legacy.py tests/integration/test_checker.py tests/integration/test_main.py tests/integration/test_plugins.py tests/integration/subdir/aplugin.py tests/unit/conftest.py tests/unit/test_application.py tests/unit/test_base_formatter.py tests/unit/test_checker_manager.py tests/unit/test_config_file_finder.py tests/unit/test_config_parser.py tests/unit/test_debug.py tests/unit/test_decision_engine.py tests/unit/test_exceptions.py tests/unit/test_file_checker.py tests/unit/test_file_processor.py tests/unit/test_filenameonly_formatter.py tests/unit/test_get_local_plugins.py tests/unit/test_legacy_api.py tests/unit/test_nothing_formatter.py tests/unit/test_option.py tests/unit/test_option_manager.py tests/unit/test_plugin.py tests/unit/test_plugin_manager.py tests/unit/test_plugin_type_manager.py tests/unit/test_pyflakes_codes.py tests/unit/test_statistics.py tests/unit/test_style_guide.py tests/unit/test_utils.py tests/unit/test_violation.py././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633956150.0 flake8-4.0.1/src/flake8.egg-info/dependency_links.txt0000644000175000017500000000000100000000000024062 0ustar00asottileasottile00000000000000 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633956150.0 flake8-4.0.1/src/flake8.egg-info/entry_points.txt0000644000175000017500000000560600000000000023321 0ustar00asottileasottile00000000000000[console_scripts] flake8 = flake8.main.cli:main [flake8.extension] F = flake8.plugins.pyflakes:FlakesChecker pycodestyle.ambiguous_identifier = pycodestyle:ambiguous_identifier pycodestyle.bare_except = pycodestyle:bare_except pycodestyle.blank_lines = pycodestyle:blank_lines pycodestyle.break_after_binary_operator = pycodestyle:break_after_binary_operator pycodestyle.break_before_binary_operator = pycodestyle:break_before_binary_operator pycodestyle.comparison_negative = pycodestyle:comparison_negative pycodestyle.comparison_to_singleton = pycodestyle:comparison_to_singleton pycodestyle.comparison_type = pycodestyle:comparison_type pycodestyle.compound_statements = pycodestyle:compound_statements pycodestyle.continued_indentation = pycodestyle:continued_indentation pycodestyle.explicit_line_join = pycodestyle:explicit_line_join pycodestyle.extraneous_whitespace = pycodestyle:extraneous_whitespace pycodestyle.imports_on_separate_lines = pycodestyle:imports_on_separate_lines pycodestyle.indentation = pycodestyle:indentation pycodestyle.maximum_doc_length = pycodestyle:maximum_doc_length pycodestyle.maximum_line_length = pycodestyle:maximum_line_length pycodestyle.missing_whitespace = pycodestyle:missing_whitespace pycodestyle.missing_whitespace_after_import_keyword = pycodestyle:missing_whitespace_after_import_keyword pycodestyle.missing_whitespace_around_operator = pycodestyle:missing_whitespace_around_operator pycodestyle.module_imports_on_top_of_file = pycodestyle:module_imports_on_top_of_file pycodestyle.python_3000_async_await_keywords = pycodestyle:python_3000_async_await_keywords pycodestyle.python_3000_backticks = pycodestyle:python_3000_backticks pycodestyle.python_3000_has_key = pycodestyle:python_3000_has_key pycodestyle.python_3000_invalid_escape_sequence = pycodestyle:python_3000_invalid_escape_sequence pycodestyle.python_3000_not_equal = pycodestyle:python_3000_not_equal pycodestyle.python_3000_raise_comma = pycodestyle:python_3000_raise_comma pycodestyle.tabs_obsolete = pycodestyle:tabs_obsolete pycodestyle.tabs_or_spaces = pycodestyle:tabs_or_spaces pycodestyle.trailing_blank_lines = pycodestyle:trailing_blank_lines pycodestyle.trailing_whitespace = pycodestyle:trailing_whitespace pycodestyle.whitespace_around_comma = pycodestyle:whitespace_around_comma pycodestyle.whitespace_around_keywords = pycodestyle:whitespace_around_keywords pycodestyle.whitespace_around_named_parameter_equals = pycodestyle:whitespace_around_named_parameter_equals pycodestyle.whitespace_around_operator = pycodestyle:whitespace_around_operator pycodestyle.whitespace_before_comment = pycodestyle:whitespace_before_comment pycodestyle.whitespace_before_parameters = pycodestyle:whitespace_before_parameters [flake8.report] default = flake8.formatting.default:Default pylint = flake8.formatting.default:Pylint quiet-filename = flake8.formatting.default:FilenameOnly quiet-nothing = flake8.formatting.default:Nothing ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633956150.0 flake8-4.0.1/src/flake8.egg-info/requires.txt0000644000175000017500000000017000000000000022412 0ustar00asottileasottile00000000000000mccabe<0.7.0,>=0.6.0 pycodestyle<2.9.0,>=2.8.0 pyflakes<2.5.0,>=2.4.0 [:python_version < "3.8"] importlib-metadata<4.3 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633956150.0 flake8-4.0.1/src/flake8.egg-info/top_level.txt0000644000175000017500000000000700000000000022543 0ustar00asottileasottile00000000000000flake8 ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1633956150.3494692 flake8-4.0.1/tests/0000755000175000017500000000000000000000000015523 5ustar00asottileasottile00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633909928.0 flake8-4.0.1/tests/__init__.py0000644000175000017500000000007400000000000017635 0ustar00asottileasottile00000000000000"""This is here because mypy doesn't understand PEP 420.""" ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633909928.0 flake8-4.0.1/tests/conftest.py0000644000175000017500000000021400000000000017717 0ustar00asottileasottile00000000000000"""Test configuration for py.test.""" import sys import flake8 flake8.configure_logging(2, "test-logs-%s.%s.log" % sys.version_info[0:2]) ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1633956150.3334746 flake8-4.0.1/tests/fixtures/0000755000175000017500000000000000000000000017374 5ustar00asottileasottile00000000000000././@PaxHeader0000000000000000000000000000003300000000000011451 xustar000000000000000027 mtime=1633956150.353468 flake8-4.0.1/tests/fixtures/config_files/0000755000175000017500000000000000000000000022023 5ustar00asottileasottile00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633909928.0 flake8-4.0.1/tests/fixtures/config_files/README.rst0000644000175000017500000000251500000000000023515 0ustar00asottileasottile00000000000000About this directory ==================== The files in this directory are test fixtures for unit and integration tests. Their purpose is described below. Please note the list of file names that can not be created as they are already used by tests. New fixtures are preferred over updating existing features unless existing tests will fail. Files that should not be created -------------------------------- - ``tests/fixtures/config_files/missing.ini`` Purposes of existing fixtures ----------------------------- ``tests/fixtures/config_files/cli-specified.ini`` This should only be used when providing config file(s) specified by the user on the command-line. ``tests/fixtures/config_files/local-config.ini`` This should be used when providing config files that would have been found by looking for config files in the current working project directory. ``tests/fixtures/config_files/local-plugin.ini`` This is for testing configuring a plugin via flake8 config file instead of setuptools entry-point. ``tests/fixtures/config_files/no-flake8-section.ini`` This should be used when parsing an ini file without a ``[flake8]`` section. ``tests/fixtures/config_files/user-config.ini`` This is an example configuration file that would be found in the user's home directory (or XDG Configuration Directory). ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633909928.0 flake8-4.0.1/tests/fixtures/config_files/broken.ini0000644000175000017500000000022200000000000024000 0ustar00asottileasottile00000000000000[flake8] exclude = <<<<<<< 642f88cb1b6027e184d9a662b255f7fea4d9eacc tests/fixtures/, ======= tests/, >>>>>>> HEAD docs/ ignore = D203 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633909928.0 flake8-4.0.1/tests/fixtures/config_files/cli-specified-with-inline-comments.ini0000644000175000017500000000044700000000000031301 0ustar00asottileasottile00000000000000[flake8] # This is a flake8 config, there are many like it, but this is mine ignore = # Disable E123 E123, # Disable W234 W234, # Also disable E111 E111 exclude = # Exclude foo/ foo/, # Exclude bar/ while we're at it bar/, # Exclude bogus/ bogus/ ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633909928.0 flake8-4.0.1/tests/fixtures/config_files/cli-specified-without-inline-comments.ini0000644000175000017500000000041700000000000032026 0ustar00asottileasottile00000000000000[flake8] # This is a flake8 config, there are many like it, but this is mine # Disable E123 # Disable W234 # Also disable E111 ignore = E123, W234, E111 # Exclude foo/ # Exclude bar/ while we're at it # Exclude bogus/ exclude = foo/, bar/, bogus/ ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633909928.0 flake8-4.0.1/tests/fixtures/config_files/cli-specified.ini0000644000175000017500000000014200000000000025221 0ustar00asottileasottile00000000000000[flake8] ignore = E123, W234, E111 exclude = foo/, bar/, bogus/ quiet = 1 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633909928.0 flake8-4.0.1/tests/fixtures/config_files/config-with-hyphenated-options.ini0000644000175000017500000000010600000000000030557 0ustar00asottileasottile00000000000000[flake8] max-line-length = 110 enable_extensions = H101, H235 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633909928.0 flake8-4.0.1/tests/fixtures/config_files/local-config.ini0000644000175000017500000000005000000000000025054 0ustar00asottileasottile00000000000000[flake8] exclude = docs/ select = E,W,F ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633909928.0 flake8-4.0.1/tests/fixtures/config_files/local-plugin-path.ini0000644000175000017500000000015300000000000026043 0ustar00asottileasottile00000000000000[flake8:local-plugins] extension = XE = aplugin:ExtensionTestPlugin2 paths = ../../integration/subdir/ ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633909928.0 flake8-4.0.1/tests/fixtures/config_files/local-plugin.ini0000644000175000017500000000017100000000000025111 0ustar00asottileasottile00000000000000[flake8:local-plugins] extension = XE = test_plugins:ExtensionTestPlugin report = XR = test_plugins:ReportTestPlugin ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633909928.0 flake8-4.0.1/tests/fixtures/config_files/no-flake8-section.ini0000644000175000017500000000042600000000000025754 0ustar00asottileasottile00000000000000[tox] minversion=2.3.1 envlist = py26,py27,py32,py33,py34,py35,flake8 [testenv] deps = mock pytest commands = py.test {posargs} [testenv:flake8] skipsdist = true skip_install = true use_develop = false deps = flake8 flake8-docstrings commands = flake8 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633909928.0 flake8-4.0.1/tests/fixtures/config_files/user-config.ini0000644000175000017500000000010000000000000024734 0ustar00asottileasottile00000000000000[flake8] exclude = tests/fixtures/, docs/ ignore = D203 ././@PaxHeader0000000000000000000000000000003300000000000011451 xustar000000000000000027 mtime=1633956150.353468 flake8-4.0.1/tests/fixtures/diffs/0000755000175000017500000000000000000000000020467 5ustar00asottileasottile00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633909928.0 flake8-4.0.1/tests/fixtures/diffs/multi_file_diff0000644000175000017500000000762100000000000023541 0ustar00asottileasottile00000000000000diff --git a/flake8/utils.py b/flake8/utils.py index f6ce384..7cd12b0 100644 --- a/flake8/utils.py +++ b/flake8/utils.py @@ -75,8 +75,8 @@ def stdin_get_value(): return cached_value.getvalue() -def parse_unified_diff(): - # type: () -> List[str] +def parse_unified_diff(diff=None): + # type: (str) -> List[str] """Parse the unified diff passed on stdin. :returns: @@ -84,7 +84,10 @@ def parse_unified_diff(): :rtype: dict """ - diff = stdin_get_value() + # Allow us to not have to patch out stdin_get_value + if diff is None: + diff = stdin_get_value() + number_of_rows = None current_path = None parsed_paths = collections.defaultdict(set) diff --git a/tests/fixtures/diffs/single_file_diff b/tests/fixtures/diffs/single_file_diff new file mode 100644 index 0000000..77ca534 --- /dev/null +++ b/tests/fixtures/diffs/single_file_diff @@ -0,0 +1,27 @@ +diff --git a/flake8/utils.py b/flake8/utils.py +index f6ce384..7cd12b0 100644 +--- a/flake8/utils.py ++++ b/flake8/utils.py +@@ -75,8 +75,8 @@ def stdin_get_value(): + return cached_value.getvalue() + + +-def parse_unified_diff(): +- # type: () -> List[str] ++def parse_unified_diff(diff=None): ++ # type: (str) -> List[str] + """Parse the unified diff passed on stdin. + + :returns: +@@ -84,7 +84,10 @@ def parse_unified_diff(): + :rtype: + dict + """ +- diff = stdin_get_value() ++ # Allow us to not have to patch out stdin_get_value ++ if diff is None: ++ diff = stdin_get_value() ++ + number_of_rows = None + current_path = None + parsed_paths = collections.defaultdict(set) diff --git a/tests/fixtures/diffs/two_file_diff b/tests/fixtures/diffs/two_file_diff new file mode 100644 index 0000000..5bd35cd --- /dev/null +++ b/tests/fixtures/diffs/two_file_diff @@ -0,0 +1,45 @@ +diff --git a/flake8/utils.py b/flake8/utils.py +index f6ce384..7cd12b0 100644 +--- a/flake8/utils.py ++++ b/flake8/utils.py +@@ -75,8 +75,8 @@ def stdin_get_value(): + return cached_value.getvalue() + + +-def parse_unified_diff(): +- # type: () -> List[str] ++def parse_unified_diff(diff=None): ++ # type: (str) -> List[str] + """Parse the unified diff passed on stdin. + + :returns: +@@ -84,7 +84,10 @@ def parse_unified_diff(): + :rtype: + dict + """ +- diff = stdin_get_value() ++ # Allow us to not have to patch out stdin_get_value ++ if diff is None: ++ diff = stdin_get_value() ++ + number_of_rows = None + current_path = None + parsed_paths = collections.defaultdict(set) +diff --git a/tests/unit/test_utils.py b/tests/unit/test_utils.py +index d69d939..21482ce 100644 +--- a/tests/unit/test_utils.py ++++ b/tests/unit/test_utils.py +@@ -115,3 +115,13 @@ def test_parameters_for_function_plugin(): + plugin = plugin_manager.Plugin('plugin-name', object()) + plugin._plugin = fake_plugin + assert utils.parameters_for(plugin) == ['physical_line', 'self', 'tree'] ++ ++ ++def read_diff_file(filename): ++ """Read the diff file in its entirety.""" ++ with open(filename, 'r') as fd: ++ content = fd.read() ++ return content ++ ++ ++SINGLE_FILE_DIFF = read_diff_file('tests/fixtures/diffs/single_file_diff') diff --git a/tests/unit/test_utils.py b/tests/unit/test_utils.py index d69d939..1461369 100644 --- a/tests/unit/test_utils.py +++ b/tests/unit/test_utils.py @@ -115,3 +115,14 @@ def test_parameters_for_function_plugin(): plugin = plugin_manager.Plugin('plugin-name', object()) plugin._plugin = fake_plugin assert utils.parameters_for(plugin) == ['physical_line', 'self', 'tree'] + + +def read_diff_file(filename): + """Read the diff file in its entirety.""" + with open(filename, 'r') as fd: + content = fd.read() + return content + + +SINGLE_FILE_DIFF = read_diff_file('tests/fixtures/diffs/single_file_diff') +TWO_FILE_DIFF = read_diff_file('tests/fixtures/diffs/two_file_diff') ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633909928.0 flake8-4.0.1/tests/fixtures/diffs/single_file_diff0000644000175000017500000000131300000000000023660 0ustar00asottileasottile00000000000000diff --git a/flake8/utils.py b/flake8/utils.py index f6ce384..7cd12b0 100644 --- a/flake8/utils.py +++ b/flake8/utils.py @@ -75,8 +75,8 @@ def stdin_get_value(): return cached_value.getvalue() -def parse_unified_diff(): - # type: () -> List[str] +def parse_unified_diff(diff=None): + # type: (str) -> List[str] """Parse the unified diff passed on stdin. :returns: @@ -84,7 +84,10 @@ def parse_unified_diff(): :rtype: dict """ - diff = stdin_get_value() + # Allow us to not have to patch out stdin_get_value + if diff is None: + diff = stdin_get_value() + number_of_rows = None current_path = None parsed_paths = collections.defaultdict(set) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633909928.0 flake8-4.0.1/tests/fixtures/diffs/two_file_diff0000644000175000017500000000252000000000000023211 0ustar00asottileasottile00000000000000diff --git a/flake8/utils.py b/flake8/utils.py index f6ce384..7cd12b0 100644 --- a/flake8/utils.py +++ b/flake8/utils.py @@ -75,8 +75,8 @@ def stdin_get_value(): return cached_value.getvalue() -def parse_unified_diff(): - # type: () -> List[str] +def parse_unified_diff(diff=None): + # type: (str) -> List[str] """Parse the unified diff passed on stdin. :returns: @@ -84,7 +84,10 @@ def parse_unified_diff(): :rtype: dict """ - diff = stdin_get_value() + # Allow us to not have to patch out stdin_get_value + if diff is None: + diff = stdin_get_value() + number_of_rows = None current_path = None parsed_paths = collections.defaultdict(set) diff --git a/tests/unit/test_utils.py b/tests/unit/test_utils.py index d69d939..21482ce 100644 --- a/tests/unit/test_utils.py +++ b/tests/unit/test_utils.py @@ -115,3 +115,13 @@ def test_parameters_for_function_plugin(): plugin = plugin_manager.Plugin('plugin-name', object()) plugin._plugin = fake_plugin assert utils.parameters_for(plugin) == ['physical_line', 'self', 'tree'] + + +def read_diff_file(filename): + """Read the diff file in its entirety.""" + with open(filename, 'r') as fd: + content = fd.read() + return content + + +SINGLE_FILE_DIFF = read_diff_file('tests/fixtures/diffs/single_file_diff') ././@PaxHeader0000000000000000000000000000003300000000000011451 xustar000000000000000027 mtime=1633956150.353468 flake8-4.0.1/tests/fixtures/example-code/0000755000175000017500000000000000000000000021737 5ustar00asottileasottile00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633909928.0 flake8-4.0.1/tests/fixtures/example-code/empty.py0000644000175000017500000000000000000000000023435 0ustar00asottileasottile00000000000000././@PaxHeader0000000000000000000000000000003300000000000011451 xustar000000000000000027 mtime=1633956150.353468 flake8-4.0.1/tests/fixtures/example-code/inline-ignores/0000755000175000017500000000000000000000000024661 5ustar00asottileasottile00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633909928.0 flake8-4.0.1/tests/fixtures/example-code/inline-ignores/E501.py0000644000175000017500000000022600000000000025645 0ustar00asottileasottile00000000000000from some.module.that.has.nested.sub.modules import \ ClassWithVeryVeryVeryVeryLongName # noqa: E501,F401 # ClassWithVeryVeryVeryVeryLongName() ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633909928.0 flake8-4.0.1/tests/fixtures/example-code/inline-ignores/E731.py0000644000175000017500000000005200000000000025647 0ustar00asottileasottile00000000000000example = lambda: 'example' # noqa: E731 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633909928.0 flake8-4.0.1/tests/fixtures/example-code/invalid-syntax.py0000644000175000017500000000000500000000000025256 0ustar00asottileasottile00000000000000foo( ././@PaxHeader0000000000000000000000000000003300000000000011451 xustar000000000000000027 mtime=1633956150.353468 flake8-4.0.1/tests/integration/0000755000175000017500000000000000000000000020046 5ustar00asottileasottile00000000000000././@PaxHeader0000000000000000000000000000003300000000000011451 xustar000000000000000027 mtime=1633956150.353468 flake8-4.0.1/tests/integration/subdir/0000755000175000017500000000000000000000000021336 5ustar00asottileasottile00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633909928.0 flake8-4.0.1/tests/integration/subdir/aplugin.py0000644000175000017500000000053400000000000023351 0ustar00asottileasottile00000000000000"""Module that is off sys.path by default, for testing local-plugin-paths.""" class ExtensionTestPlugin2: """Extension test plugin in its own directory.""" name = "ExtensionTestPlugin2" version = "1.0.0" def __init__(self, tree): """Construct an instance of test plugin.""" def run(self): """Do nothing.""" ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633909928.0 flake8-4.0.1/tests/integration/test_aggregator.py0000644000175000017500000000413700000000000023606 0ustar00asottileasottile00000000000000"""Test aggregation of config files and command-line options.""" import argparse import os import pytest from flake8.main import options from flake8.options import aggregator from flake8.options import config from flake8.options import manager CLI_SPECIFIED_CONFIG = "tests/fixtures/config_files/cli-specified.ini" @pytest.fixture def optmanager(): """Create a new OptionManager.""" prelim_parser = argparse.ArgumentParser(add_help=False) options.register_preliminary_options(prelim_parser) option_manager = manager.OptionManager( prog="flake8", version="3.0.0", parents=[prelim_parser], ) options.register_default_options(option_manager) return option_manager def test_aggregate_options_with_config(optmanager): """Verify we aggregate options and config values appropriately.""" arguments = [ "flake8", "--select", "E11,E34,E402,W,F", "--exclude", "tests/*", ] config_finder = config.ConfigFileFinder( "flake8", config_file=CLI_SPECIFIED_CONFIG ) options, args = aggregator.aggregate_options( optmanager, config_finder, arguments ) assert options.select == ["E11", "E34", "E402", "W", "F"] assert options.ignore == ["E123", "W234", "E111"] assert options.exclude == [os.path.abspath("tests/*")] def test_aggregate_options_when_isolated(optmanager): """Verify we aggregate options and config values appropriately.""" arguments = [ "flake8", "--select", "E11,E34,E402,W,F", "--exclude", "tests/*", ] config_finder = config.ConfigFileFinder("flake8", ignore_config_files=True) optmanager.extend_default_ignore(["E8"]) options, args = aggregator.aggregate_options( optmanager, config_finder, arguments ) assert options.select == ["E11", "E34", "E402", "W", "F"] assert sorted(options.ignore) == [ "E121", "E123", "E126", "E226", "E24", "E704", "E8", "W503", "W504", ] assert options.exclude == [os.path.abspath("tests/*")] ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633909928.0 flake8-4.0.1/tests/integration/test_api_legacy.py0000644000175000017500000000066100000000000023557 0ustar00asottileasottile00000000000000"""Integration tests for the legacy api.""" from flake8.api import legacy def test_legacy_api(tmpdir): """A basic end-to-end test for the legacy api reporting errors.""" with tmpdir.as_cwd(): t_py = tmpdir.join("t.py") t_py.write("import os # unused import\n") style_guide = legacy.get_style_guide() report = style_guide.check_files([t_py.strpath]) assert report.total_errors == 1 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633909928.0 flake8-4.0.1/tests/integration/test_checker.py0000644000175000017500000002652500000000000023075 0ustar00asottileasottile00000000000000"""Integration tests for the checker submodule.""" import sys from unittest import mock import pytest from flake8 import checker from flake8._compat import importlib_metadata from flake8.plugins import manager from flake8.processor import FileProcessor PHYSICAL_LINE = "# Physical line content" EXPECTED_REPORT = (1, 1, "T000 Expected Message") EXPECTED_REPORT_PHYSICAL_LINE = (1, "T000 Expected Message") EXPECTED_RESULT_PHYSICAL_LINE = ( "T000", 0, 1, "Expected Message", None, ) class PluginClass: """Simple file plugin class yielding the expected report.""" name = "test" version = "1.0.0" def __init__(self, tree): """Construct a dummy object to provide mandatory parameter.""" pass def run(self): """Run class yielding one element containing the expected report.""" yield EXPECTED_REPORT + (type(self),) def plugin_func(func): """Decorate file plugins which are implemented as functions.""" func.name = "test" func.version = "1.0.0" return func @plugin_func def plugin_func_gen(tree): """Yield the expected report.""" yield EXPECTED_REPORT + (type(plugin_func_gen),) @plugin_func def plugin_func_list(tree): """Return a list of expected reports.""" return [EXPECTED_REPORT + (type(plugin_func_list),)] @plugin_func def plugin_func_physical_ret(physical_line): """Expect report from a physical_line. Single return.""" return EXPECTED_REPORT_PHYSICAL_LINE @plugin_func def plugin_func_physical_none(physical_line): """Expect report from a physical_line. No results.""" return None @plugin_func def plugin_func_physical_list_single(physical_line): """Expect report from a physical_line. List of single result.""" return [EXPECTED_REPORT_PHYSICAL_LINE] @plugin_func def plugin_func_physical_list_multiple(physical_line): """Expect report from a physical_line. List of multiple results.""" return [EXPECTED_REPORT_PHYSICAL_LINE] * 2 @plugin_func def plugin_func_physical_gen_single(physical_line): """Expect report from a physical_line. Generator of single result.""" yield EXPECTED_REPORT_PHYSICAL_LINE @plugin_func def plugin_func_physical_gen_multiple(physical_line): """Expect report from a physical_line. Generator of multiple results.""" for _ in range(3): yield EXPECTED_REPORT_PHYSICAL_LINE def mock_file_checker_with_plugin(plugin_target): """Get a mock FileChecker class with plugin_target registered. Useful as a starting point for mocking reports/results. """ # Mock an entry point returning the plugin target entry_point = mock.Mock(spec=["load"]) entry_point.name = plugin_target.name entry_point.load.return_value = plugin_target entry_point.value = "mocked:value" # Load the checker plugins using the entry point mock with mock.patch.object( importlib_metadata, "entry_points", return_value={"flake8.extension": [entry_point]}, ): checks = manager.Checkers() # Prevent it from reading lines from stdin or somewhere else with mock.patch( "flake8.processor.FileProcessor.read_lines", return_value=["Line 1"] ): file_checker = checker.FileChecker( "-", checks.to_dictionary(), mock.MagicMock() ) return file_checker @pytest.mark.parametrize( "plugin_target", [ PluginClass, plugin_func_gen, plugin_func_list, ], ) def test_handle_file_plugins(plugin_target): """Test the FileChecker class handling different file plugin types.""" file_checker = mock_file_checker_with_plugin(plugin_target) # Do not actually build an AST file_checker.processor.build_ast = lambda: True # Forward reports to this mock report = mock.Mock() file_checker.report = report file_checker.run_ast_checks() report.assert_called_once_with( error_code=None, line_number=EXPECTED_REPORT[0], column=EXPECTED_REPORT[1], text=EXPECTED_REPORT[2], ) @pytest.mark.parametrize( "plugin_target,len_results", [ (plugin_func_physical_ret, 1), (plugin_func_physical_none, 0), (plugin_func_physical_list_single, 1), (plugin_func_physical_list_multiple, 2), (plugin_func_physical_gen_single, 1), (plugin_func_physical_gen_multiple, 3), ], ) def test_line_check_results(plugin_target, len_results): """Test the FileChecker class handling results from line checks.""" file_checker = mock_file_checker_with_plugin(plugin_target) # Results will be stored in an internal array file_checker.run_physical_checks(PHYSICAL_LINE) expected = [EXPECTED_RESULT_PHYSICAL_LINE] * len_results assert file_checker.results == expected def test_logical_line_offset_out_of_bounds(): """Ensure that logical line offsets that are out of bounds do not crash.""" @plugin_func def _logical_line_out_of_bounds(logical_line): yield 10000, "L100 test" file_checker = mock_file_checker_with_plugin(_logical_line_out_of_bounds) logical_ret = ( "", 'print("xxxxxxxxxxx")', [(0, (1, 0)), (5, (1, 5)), (6, (1, 6)), (19, (1, 19)), (20, (1, 20))], ) with mock.patch.object( FileProcessor, "build_logical_line", return_value=logical_ret, ): file_checker.run_logical_checks() assert file_checker.results == [("L100", 0, 0, "test", None)] PLACEHOLDER_CODE = 'some_line = "of" * code' @pytest.mark.parametrize( "results, expected_order", [ # No entries should be added ([], []), # Results are correctly ordered ( [ ("A101", 1, 1, "placeholder error", PLACEHOLDER_CODE), ("A101", 2, 1, "placeholder error", PLACEHOLDER_CODE), ], [0, 1], ), # Reversed order of lines ( [ ("A101", 2, 1, "placeholder error", PLACEHOLDER_CODE), ("A101", 1, 1, "placeholder error", PLACEHOLDER_CODE), ], [1, 0], ), # Columns are not ordered correctly # (when reports are ordered correctly) ( [ ("A101", 1, 2, "placeholder error", PLACEHOLDER_CODE), ("A101", 1, 1, "placeholder error", PLACEHOLDER_CODE), ("A101", 2, 1, "placeholder error", PLACEHOLDER_CODE), ], [1, 0, 2], ), ( [ ("A101", 2, 1, "placeholder error", PLACEHOLDER_CODE), ("A101", 1, 1, "placeholder error", PLACEHOLDER_CODE), ("A101", 1, 2, "placeholder error", PLACEHOLDER_CODE), ], [1, 2, 0], ), ( [ ("A101", 1, 2, "placeholder error", PLACEHOLDER_CODE), ("A101", 2, 2, "placeholder error", PLACEHOLDER_CODE), ("A101", 2, 1, "placeholder error", PLACEHOLDER_CODE), ], [0, 2, 1], ), ( [ ("A101", 1, 3, "placeholder error", PLACEHOLDER_CODE), ("A101", 2, 2, "placeholder error", PLACEHOLDER_CODE), ("A101", 3, 1, "placeholder error", PLACEHOLDER_CODE), ], [0, 1, 2], ), ( [ ("A101", 1, 1, "placeholder error", PLACEHOLDER_CODE), ("A101", 1, 3, "placeholder error", PLACEHOLDER_CODE), ("A101", 2, 2, "placeholder error", PLACEHOLDER_CODE), ], [0, 1, 2], ), # Previously sort column and message (so reversed) (see bug 196) ( [ ("A101", 1, 1, "placeholder error", PLACEHOLDER_CODE), ("A101", 2, 1, "charlie error", PLACEHOLDER_CODE), ], [0, 1], ), ], ) def test_report_order(results, expected_order): """ Test in which order the results will be reported. It gets a list of reports from the file checkers and verifies that the result will be ordered independent from the original report. """ def count_side_effect(name, sorted_results): """Side effect for the result handler to tell all are reported.""" return len(sorted_results) # To simplify the parameters (and prevent copy & pasting) reuse report # tuples to create the expected result lists from the indexes expected_results = [results[index] for index in expected_order] file_checker = mock.Mock(spec=["results", "display_name"]) file_checker.results = results file_checker.display_name = "placeholder" style_guide = mock.MagicMock(spec=["options", "processing_file"]) # Create a placeholder manager without arguments or plugins # Just add one custom file checker which just provides the results manager = checker.Manager(style_guide, [], []) manager.checkers = manager._all_checkers = [file_checker] # _handle_results is the first place which gets the sorted result # Should something non-private be mocked instead? handler = mock.Mock(side_effect=count_side_effect) with mock.patch.object(manager, "_handle_results", handler): assert manager.report() == (len(results), len(results)) handler.assert_called_once_with("placeholder", expected_results) def test_acquire_when_multiprocessing_pool_can_initialize(): """Verify successful importing of hardware semaphore support. Mock the behaviour of a platform that has a hardware sem_open implementation, and then attempt to initialize a multiprocessing Pool object. This simulates the behaviour on most common platforms. """ with mock.patch("multiprocessing.Pool") as pool: result = checker._try_initialize_processpool(2) pool.assert_called_once_with(2, checker._pool_init) assert result is pool.return_value def test_acquire_when_multiprocessing_pool_can_not_initialize(): """Verify unsuccessful importing of hardware semaphore support. Mock the behaviour of a platform that has not got a hardware sem_open implementation, and then attempt to initialize a multiprocessing Pool object. This scenario will occur on platforms such as Termux and on some more exotic devices. https://github.com/python/cpython/blob/4e02981de0952f54bf87967f8e10d169d6946b40/Lib/multiprocessing/synchronize.py#L30-L33 """ with mock.patch("multiprocessing.Pool", side_effect=ImportError) as pool: result = checker._try_initialize_processpool(2) pool.assert_called_once_with(2, checker._pool_init) assert result is None def test_handling_syntaxerrors_across_pythons(): """Verify we properly handle exception argument tuples. Python 3.10 added more information to the SyntaxError parse token tuple. We need to handle that correctly to avoid crashing. https://github.com/PyCQA/flake8/issues/1372 """ if sys.version_info < (3, 10): # pragma: no cover (<3.10) # Python 3.9 or older err = SyntaxError( "invalid syntax", ("", 2, 5, "bad python:\n") ) expected = (2, 4) else: # pragma: no cover (3.10+) err = SyntaxError( "invalid syntax", ("", 2, 1, "bad python:\n", 2, 11) ) expected = (2, 1) file_checker = checker.FileChecker("-", {}, mock.MagicMock()) actual = file_checker._extract_syntax_information(err) assert actual == expected ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633909928.0 flake8-4.0.1/tests/integration/test_main.py0000644000175000017500000002402200000000000022403 0ustar00asottileasottile00000000000000"""Integration tests for the main entrypoint of flake8.""" import json import os import sys from unittest import mock import pytest from flake8 import utils from flake8.main import cli def _call_main(argv, retv=0): with pytest.raises(SystemExit) as excinfo: cli.main(argv) assert excinfo.value.code == retv def test_diff_option(tmpdir, capsys): """Ensure that `flake8 --diff` works.""" t_py_contents = """\ import os import sys # unused but not part of diff print('(to avoid trailing whitespace in test)') print('(to avoid trailing whitespace in test)') print(os.path.join('foo', 'bar')) y # part of the diff and an error """ diff = """\ diff --git a/t.py b/t.py index d64ac39..7d943de 100644 --- a/t.py +++ b/t.py @@ -4,3 +4,5 @@ import sys # unused but not part of diff print('(to avoid trailing whitespace in test)') print('(to avoid trailing whitespace in test)') print(os.path.join('foo', 'bar')) + +y # part of the diff and an error """ with mock.patch.object(utils, "stdin_get_value", return_value=diff): with tmpdir.as_cwd(): tmpdir.join("t.py").write(t_py_contents) _call_main(["--diff"], retv=1) out, err = capsys.readouterr() assert out == "t.py:8:1: F821 undefined name 'y'\n" assert err == "" def test_form_feed_line_split(tmpdir, capsys): """Test that form feed is treated the same for stdin.""" src = "x=1\n\f\ny=1\n" expected_out = """\ t.py:1:2: E225 missing whitespace around operator t.py:3:2: E225 missing whitespace around operator """ with tmpdir.as_cwd(): tmpdir.join("t.py").write(src) with mock.patch.object(utils, "stdin_get_value", return_value=src): _call_main(["-", "--stdin-display-name=t.py"], retv=1) out, err = capsys.readouterr() assert out == expected_out assert err == "" _call_main(["t.py"], retv=1) out, err = capsys.readouterr() assert out == expected_out assert err == "" def test_e101_indent_char_does_not_reset(tmpdir, capsys): """Ensure that E101 with an existing indent_char does not reset it.""" t_py_contents = """\ if True: print('space indented') s = '''\ \ttab indented ''' # noqa: E101 if True: print('space indented') """ with tmpdir.as_cwd(): tmpdir.join("t.py").write(t_py_contents) _call_main(["t.py"]) def test_statistics_option(tmpdir, capsys): """Ensure that `flake8 --statistics` works.""" with tmpdir.as_cwd(): tmpdir.join("t.py").write("import os\nimport sys\n") _call_main(["--statistics", "t.py"], retv=1) expected = """\ t.py:1:1: F401 'os' imported but unused t.py:2:1: F401 'sys' imported but unused 2 F401 'os' imported but unused """ out, err = capsys.readouterr() assert out == expected assert err == "" def test_show_source_option(tmpdir, capsys): """Ensure that --show-source and --no-show-source work.""" with tmpdir.as_cwd(): tmpdir.join("tox.ini").write("[flake8]\nshow_source = true\n") tmpdir.join("t.py").write("import os\n") _call_main(["t.py"], retv=1) expected = """\ t.py:1:1: F401 'os' imported but unused import os ^ """ out, err = capsys.readouterr() assert out == expected assert err == "" with tmpdir.as_cwd(): _call_main(["t.py", "--no-show-source"], retv=1) expected = """\ t.py:1:1: F401 'os' imported but unused """ out, err = capsys.readouterr() assert out == expected assert err == "" def test_extend_exclude(tmpdir, capsys): """Ensure that `flake8 --extend-exclude` works.""" for d in ["project", "vendor", "legacy", ".git", ".tox", ".hg"]: tmpdir.mkdir(d).join("t.py").write("import os\nimport sys\n") with tmpdir.as_cwd(): _call_main(["--extend-exclude=vendor,legacy/"], retv=1) out, err = capsys.readouterr() expected_out = """\ ./project/t.py:1:1: F401 'os' imported but unused ./project/t.py:2:1: F401 'sys' imported but unused """ assert out == expected_out.replace("/", os.sep) assert err == "" def test_malformed_per_file_ignores_error(tmpdir, capsys): """Test the error message for malformed `per-file-ignores`.""" setup_cfg = """\ [flake8] per-file-ignores = incorrect/* values/* """ expected = """\ There was a critical error during execution of Flake8: Expected `per-file-ignores` to be a mapping from file exclude patterns to ignore codes. Configured `per-file-ignores` setting: incorrect/* values/* """ # noqa: E501 with tmpdir.as_cwd(): tmpdir.join("setup.cfg").write(setup_cfg) _call_main(["."], retv=1) out, err = capsys.readouterr() assert out == expected def test_tokenization_error_but_not_syntax_error(tmpdir, capsys): """Test that flake8 does not crash on tokenization errors.""" with tmpdir.as_cwd(): # this is a crash in the tokenizer, but not in the ast tmpdir.join("t.py").write("b'foo' \\\n") _call_main(["t.py"], retv=1) if hasattr(sys, "pypy_version_info"): # pragma: no cover (pypy) expected = "t.py:2:1: E999 SyntaxError: end of file (EOF) in multi-line statement\n" # noqa: E501 elif sys.version_info < (3, 8): # pragma: no cover ( 5 assert lines[0].strip() == '"""Tests for the FileProcessor class."""' def _lines_from_file(tmpdir, contents, options): f = tmpdir.join("f.py") # be careful to write the bytes exactly to avoid newline munging f.write_binary(contents) return processor.FileProcessor(f.strpath, options).lines def test_read_lines_universal_newlines(tmpdir, default_options): r"""Verify that line endings are translated to \n.""" lines = _lines_from_file( tmpdir, b"# coding: utf-8\r\nx = 1\r\n", default_options ) assert lines == ["# coding: utf-8\n", "x = 1\n"] def test_read_lines_incorrect_utf_16(tmpdir, default_options): """Verify that an incorrectly encoded file is read as latin-1.""" lines = _lines_from_file( tmpdir, b"# coding: utf16\nx = 1\n", default_options ) assert lines == ["# coding: utf16\n", "x = 1\n"] def test_read_lines_unknown_encoding(tmpdir, default_options): """Verify that an unknown encoding is still read as latin-1.""" lines = _lines_from_file( tmpdir, b"# coding: fake-encoding\nx = 1\n", default_options ) assert lines == ["# coding: fake-encoding\n", "x = 1\n"] @pytest.mark.parametrize( "first_line", [ '\xEF\xBB\xBF"""Module docstring."""\n', '\uFEFF"""Module docstring."""\n', ], ) def test_strip_utf_bom(first_line, default_options): r"""Verify that we strip '\xEF\xBB\xBF' from the first line.""" lines = [first_line] file_processor = processor.FileProcessor("-", default_options, lines[:]) assert file_processor.lines != lines assert file_processor.lines[0] == '"""Module docstring."""\n' @pytest.mark.parametrize( "lines, expected", [ (['\xEF\xBB\xBF"""Module docstring."""\n'], False), (['\uFEFF"""Module docstring."""\n'], False), (["#!/usr/bin/python", "# flake8 is great", "a = 1"], False), (["#!/usr/bin/python", "# flake8: noqa", "a = 1"], True), (["#!/usr/bin/python", "# flake8:noqa", "a = 1"], True), (["# flake8: noqa", "#!/usr/bin/python", "a = 1"], True), (["# flake8:noqa", "#!/usr/bin/python", "a = 1"], True), (["#!/usr/bin/python", "a = 1", "# flake8: noqa"], True), (["#!/usr/bin/python", "a = 1", "# flake8:noqa"], True), (["#!/usr/bin/python", "a = 1 # flake8: noqa"], False), (["#!/usr/bin/python", "a = 1 # flake8:noqa"], False), ], ) def test_should_ignore_file(lines, expected, default_options): """Verify that we ignore a file if told to.""" file_processor = processor.FileProcessor("-", default_options, lines) assert file_processor.should_ignore_file() is expected def test_should_ignore_file_to_handle_disable_noqa(default_options): """Verify that we ignore a file if told to.""" lines = ["# flake8: noqa"] file_processor = processor.FileProcessor("-", default_options, lines) assert file_processor.should_ignore_file() is True default_options.disable_noqa = True file_processor = processor.FileProcessor("-", default_options, lines) assert file_processor.should_ignore_file() is False @mock.patch("flake8.utils.stdin_get_value") def test_read_lines_from_stdin(stdin_get_value, default_options): """Verify that we use our own utility function to retrieve stdin.""" stdin_get_value.return_value = "" processor.FileProcessor("-", default_options) stdin_get_value.assert_called_once_with() @mock.patch("flake8.utils.stdin_get_value") def test_stdin_filename_attribute(stdin_get_value, default_options): """Verify that we update the filename attribute.""" stdin_get_value.return_value = "" file_processor = processor.FileProcessor("-", default_options) assert file_processor.filename == "stdin" @mock.patch("flake8.utils.stdin_get_value") def test_read_lines_uses_display_name(stdin_get_value, default_options): """Verify that when processing stdin we use a display name if present.""" default_options.stdin_display_name = "display_name.py" stdin_get_value.return_value = "" file_processor = processor.FileProcessor("-", default_options) assert file_processor.filename == "display_name.py" @mock.patch("flake8.utils.stdin_get_value") def test_read_lines_ignores_empty_display_name( stdin_get_value, default_options, ): """Verify that when processing stdin we use a display name if present.""" stdin_get_value.return_value = "" default_options.stdin_display_name = "" file_processor = processor.FileProcessor("-", default_options) assert file_processor.filename == "stdin" def test_noqa_line_for(default_options): """Verify we grab the correct line from the cached lines.""" file_processor = processor.FileProcessor( "-", default_options, lines=[ "Line 1\n", "Line 2\n", "Line 3\n", ], ) for i in range(1, 4): assert file_processor.noqa_line_for(i) == f"Line {i}\n" def test_noqa_line_for_continuation(default_options): """Verify that the correct "line" is retrieved for continuation.""" src = '''\ from foo \\ import bar # 2 x = """ hello world """ # 7 ''' lines = src.splitlines(True) file_processor = processor.FileProcessor("-", default_options, lines=lines) assert file_processor.noqa_line_for(0) is None l_1_2 = "from foo \\\n import bar # 2\n" assert file_processor.noqa_line_for(1) == l_1_2 assert file_processor.noqa_line_for(2) == l_1_2 assert file_processor.noqa_line_for(3) == "\n" l_4_7 = 'x = """\nhello\nworld\n""" # 7\n' for i in (4, 5, 6, 7): assert file_processor.noqa_line_for(i) == l_4_7 assert file_processor.noqa_line_for(8) is None def test_noqa_line_for_no_eol_at_end_of_file(default_options): """Verify that we properly handle noqa line at the end of the file.""" src = "from foo \\\nimport bar" # no end of file newline lines = src.splitlines(True) file_processor = processor.FileProcessor("-", default_options, lines=lines) l_1_2 = "from foo \\\nimport bar" assert file_processor.noqa_line_for(1) == l_1_2 assert file_processor.noqa_line_for(2) == l_1_2 def test_next_line(default_options): """Verify we update the file_processor state for each new line.""" file_processor = processor.FileProcessor( "-", default_options, lines=[ "Line 1", "Line 2", "Line 3", ], ) for i in range(1, 4): assert file_processor.next_line() == f"Line {i}" assert file_processor.line_number == i @pytest.mark.parametrize( "params, args, expected_kwargs", [ ( {"blank_before": True, "blank_lines": True}, None, {"blank_before": 0, "blank_lines": 0}, ), ( {"noqa": True, "fake": True}, {"fake": "foo"}, {"noqa": False, "fake": "foo"}, ), ( {"blank_before": True, "blank_lines": True, "noqa": True}, {"blank_before": 10, "blank_lines": 5, "noqa": True}, {"blank_before": 10, "blank_lines": 5, "noqa": True}, ), ({}, {"fake": "foo"}, {"fake": "foo"}), ({"non-existent": False}, {"fake": "foo"}, {"fake": "foo"}), ], ) def test_keyword_arguments_for(params, args, expected_kwargs, default_options): """Verify the keyword args are generated properly.""" file_processor = processor.FileProcessor( "-", default_options, lines=[ "Line 1", ], ) kwargs_for = file_processor.keyword_arguments_for assert kwargs_for(params, args) == expected_kwargs def test_keyword_arguments_for_does_not_handle_attribute_errors( default_options, ): """Verify we re-raise AttributeErrors.""" file_processor = processor.FileProcessor( "-", default_options, lines=[ "Line 1", ], ) with pytest.raises(AttributeError): file_processor.keyword_arguments_for({"fake": True}) @pytest.mark.parametrize( "unsplit_line, expected_lines", [ ("line", []), ("line 1\n", ["line 1"]), ("line 1\nline 2\n", ["line 1", "line 2"]), ("line 1\n\nline 2\n", ["line 1", "", "line 2"]), ], ) def test_split_line(unsplit_line, expected_lines, default_options): """Verify the token line splitting.""" file_processor = processor.FileProcessor( "-", default_options, lines=[ "Line 1", ], ) token = (1, unsplit_line, (0, 0), (0, 0), "") actual_lines = list(file_processor.split_line(token)) assert expected_lines == actual_lines assert len(actual_lines) == file_processor.line_number def test_build_ast(default_options): """Verify the logic for how we build an AST for plugins.""" file_processor = processor.FileProcessor( "-", default_options, lines=["a = 1\n"] ) module = file_processor.build_ast() assert isinstance(module, ast.Module) def test_next_logical_line_updates_the_previous_logical_line(default_options): """Verify that we update our tracking of the previous logical line.""" file_processor = processor.FileProcessor( "-", default_options, lines=["a = 1\n"] ) file_processor.indent_level = 1 file_processor.logical_line = "a = 1" assert file_processor.previous_logical == "" assert file_processor.previous_indent_level == 0 file_processor.next_logical_line() assert file_processor.previous_logical == "a = 1" assert file_processor.previous_indent_level == 1 def test_visited_new_blank_line(default_options): """Verify we update the number of blank lines seen.""" file_processor = processor.FileProcessor( "-", default_options, lines=["a = 1\n"] ) assert file_processor.blank_lines == 0 file_processor.visited_new_blank_line() assert file_processor.blank_lines == 1 def test_inside_multiline(default_options): """Verify we update the line number and reset multiline.""" file_processor = processor.FileProcessor( "-", default_options, lines=["a = 1\n"] ) assert file_processor.multiline is False assert file_processor.line_number == 0 with file_processor.inside_multiline(10): assert file_processor.multiline is True assert file_processor.line_number == 10 assert file_processor.multiline is False @pytest.mark.parametrize( "string, expected", [ ('""', '""'), ("''", "''"), ('"a"', '"x"'), ("'a'", "'x'"), ('"x"', '"x"'), ("'x'", "'x'"), ('"abcdef"', '"xxxxxx"'), ("'abcdef'", "'xxxxxx'"), ('""""""', '""""""'), ("''''''", "''''''"), ('"""a"""', '"""x"""'), ("'''a'''", "'''x'''"), ('"""x"""', '"""x"""'), ("'''x'''", "'''x'''"), ('"""abcdef"""', '"""xxxxxx"""'), ("'''abcdef'''", "'''xxxxxx'''"), ('"""xxxxxx"""', '"""xxxxxx"""'), ("'''xxxxxx'''", "'''xxxxxx'''"), ], ) def test_mutate_string(string, expected, default_options): """Verify we appropriately mutate the string to sanitize it.""" actual = processor.mutate_string(string) assert expected == actual @pytest.mark.parametrize( "string, expected", [ (" ", 4), (" ", 6), ("\t", 8), ("\t\t", 16), (" \t", 8), (" \t", 16), ], ) def test_expand_indent(string, expected): """Verify we correctly measure the amount of indentation.""" actual = processor.expand_indent(string) assert expected == actual @pytest.mark.parametrize( "token, log_string", [ [ ( tokenize.COMMENT, "# this is a comment", (1, 0), # (start_row, start_column) (1, 19), # (end_ro, end_column) "# this is a comment", ), "l.1\t[:19]\tCOMMENT\t'# this is a comment'", ], [ ( tokenize.COMMENT, "# this is a comment", (1, 5), # (start_row, start_column) (1, 19), # (end_ro, end_column) "# this is a comment", ), "l.1\t[5:19]\tCOMMENT\t'# this is a comment'", ], [ ( tokenize.COMMENT, "# this is a comment", (1, 0), # (start_row, start_column) (2, 19), # (end_ro, end_column) "# this is a comment", ), "l.1\tl.2\tCOMMENT\t'# this is a comment'", ], ], ) def test_log_token(token, log_string): """Verify we use the log object passed in.""" log = mock.Mock() processor.log_token(log, token) log.log.assert_called_once_with( 5, # flake8._EXTRA_VERBOSE log_string, ) @pytest.mark.parametrize( "current_count, token_text, expected", [ (0, "(", 1), (0, "[", 1), (0, "{", 1), (1, ")", 0), (1, "]", 0), (1, "}", 0), (10, "+", 10), ], ) def test_count_parentheses(current_count, token_text, expected): """Verify our arithmetic is correct.""" assert processor.count_parentheses(current_count, token_text) == expected def test_nonexistent_file(default_options): """Verify that FileProcessor raises IOError when a file does not exist.""" with pytest.raises(IOError): processor.FileProcessor("foobar.py", default_options) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633909928.0 flake8-4.0.1/tests/unit/test_filenameonly_formatter.py0000644000175000017500000000241400000000000024661 0ustar00asottileasottile00000000000000"""Tests for the FilenameOnly formatter object.""" import argparse from flake8 import style_guide from flake8.formatting import default def options(**kwargs): """Create an argparse.Namespace instance.""" kwargs.setdefault("output_file", None) kwargs.setdefault("tee", False) return argparse.Namespace(**kwargs) def test_caches_filenames_already_printed(): """Verify we cache filenames when we format them.""" formatter = default.FilenameOnly(options()) assert formatter.filenames_already_printed == set() formatter.format( style_guide.Violation("code", "file.py", 1, 1, "text", "l") ) assert formatter.filenames_already_printed == {"file.py"} def test_only_returns_a_string_once_from_format(): """Verify format ignores the second error with the same filename.""" formatter = default.FilenameOnly(options()) error = style_guide.Violation("code", "file.py", 1, 1, "text", "1") assert formatter.format(error) == "file.py" assert formatter.format(error) is None def test_show_source_returns_nothing(): """Verify show_source returns nothing.""" formatter = default.FilenameOnly(options()) error = style_guide.Violation("code", "file.py", 1, 1, "text", "1") assert formatter.show_source(error) is None ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633913940.0 flake8-4.0.1/tests/unit/test_get_local_plugins.py0000644000175000017500000000315300000000000023607 0ustar00asottileasottile00000000000000"""Tests for get_local_plugins.""" from unittest import mock from flake8.options import config def test_get_local_plugins_respects_isolated(): """Verify behaviour of get_local_plugins with isolated=True.""" config_finder = mock.MagicMock() config_finder.ignore_config_files = True local_plugins = config.get_local_plugins(config_finder) assert local_plugins.extension == [] assert local_plugins.report == [] assert config_finder.local_configs.called is False assert config_finder.user_config.called is False def test_get_local_plugins_uses_cli_config(): """Verify behaviour of get_local_plugins with a specified config.""" config_obj = mock.Mock() config_finder = mock.MagicMock() config_finder.cli_config.return_value = config_obj config_finder.ignore_config_files = False config_obj.get.return_value = "" config_file_value = "foo.ini" config_finder.config_file = config_file_value config.get_local_plugins(config_finder) config_finder.cli_config.assert_called_once_with(config_file_value) def test_get_local_plugins(): """Verify get_local_plugins returns expected plugins.""" config_fixture_path = "tests/fixtures/config_files/local-plugin.ini" config_finder = config.ConfigFileFinder("flake8") with mock.patch.object(config_finder, "local_config_files") as localcfs: localcfs.return_value = [config_fixture_path] local_plugins = config.get_local_plugins(config_finder) assert local_plugins.extension == ["XE = test_plugins:ExtensionTestPlugin"] assert local_plugins.report == ["XR = test_plugins:ReportTestPlugin"] ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633909928.0 flake8-4.0.1/tests/unit/test_legacy_api.py0000644000175000017500000001256300000000000022217 0ustar00asottileasottile00000000000000"""Tests for Flake8's legacy API.""" import argparse import os.path from unittest import mock import pytest from flake8.api import legacy as api from flake8.formatting import base as formatter from flake8.options.config import ConfigFileFinder def test_get_style_guide(): """Verify the methods called on our internal Application.""" prelim_opts = argparse.Namespace( append_config=[], config=None, isolated=False, output_file=None, verbose=0, ) mockedapp = mock.Mock() mockedapp.parse_preliminary_options.return_value = (prelim_opts, []) mockedapp.program = "flake8" with mock.patch( "flake8.api.legacy.config.ConfigFileFinder" ) as mock_config_finder: # noqa: E501 config_finder = ConfigFileFinder(mockedapp.program) mock_config_finder.return_value = config_finder with mock.patch("flake8.main.application.Application") as application: application.return_value = mockedapp style_guide = api.get_style_guide() application.assert_called_once_with() mockedapp.parse_preliminary_options.assert_called_once_with([]) mockedapp.find_plugins.assert_called_once_with(config_finder) mockedapp.register_plugin_options.assert_called_once_with() mockedapp.parse_configuration_and_cli.assert_called_once_with( config_finder, [] ) mockedapp.make_formatter.assert_called_once_with() mockedapp.make_guide.assert_called_once_with() mockedapp.make_file_checker_manager.assert_called_once_with() assert isinstance(style_guide, api.StyleGuide) def test_styleguide_options(): """Show that we proxy the StyleGuide.options attribute.""" app = mock.Mock() app.options = "options" style_guide = api.StyleGuide(app) assert style_guide.options == "options" def test_styleguide_paths(): """Show that we proxy the StyleGuide.paths attribute.""" app = mock.Mock() app.paths = "paths" style_guide = api.StyleGuide(app) assert style_guide.paths == "paths" def test_styleguide_check_files(): """Verify we call the right application methods.""" paths = ["foo", "bar"] app = mock.Mock() style_guide = api.StyleGuide(app) report = style_guide.check_files(paths) app.run_checks.assert_called_once_with(paths) app.report_errors.assert_called_once_with() assert isinstance(report, api.Report) def test_styleguide_excluded(): """Verify we delegate to our file checker manager. We also want to ensure that if we don't specify a parent, is_path_excluded is called exactly once. """ app = mock.Mock() file_checker_manager = app.file_checker_manager = mock.Mock() style_guide = api.StyleGuide(app) style_guide.excluded("file.py") file_checker_manager.is_path_excluded.assert_called_once_with("file.py") def test_styleguide_excluded_with_parent(): """Verify we delegate to our file checker manager. When we add the parent argument, we don't check that is_path_excluded was called only once. """ app = mock.Mock() file_checker_manager = app.file_checker_manager = mock.Mock() file_checker_manager.is_path_excluded.return_value = False style_guide = api.StyleGuide(app) style_guide.excluded("file.py", "parent") assert file_checker_manager.is_path_excluded.call_args_list == [ mock.call("file.py"), mock.call(os.path.join("parent", "file.py")), ] def test_styleguide_init_report_does_nothing(): """Verify if we use None that we don't call anything.""" app = mock.Mock() style_guide = api.StyleGuide(app) style_guide.init_report() assert app.make_formatter.called is False assert app.make_guide.called is False def test_styleguide_init_report_with_non_subclass(): """Verify we raise a ValueError with non BaseFormatter subclasses.""" app = mock.Mock() style_guide = api.StyleGuide(app) with pytest.raises(ValueError): style_guide.init_report(object) assert app.make_formatter.called is False assert app.make_guide.called is False def test_styleguide_init_report(): """Verify we do the right incantation for the Application.""" app = mock.Mock(guide="fake") style_guide = api.StyleGuide(app) class FakeFormatter(formatter.BaseFormatter): def format(self, *args): raise NotImplementedError style_guide.init_report(FakeFormatter) app.make_formatter.assert_called_once_with(FakeFormatter) assert app.guide is None app.make_guide.assert_called_once_with() def test_styleguide_input_file(): """Verify we call StyleGuide.check_files with the filename.""" app = mock.Mock() style_guide = api.StyleGuide(app) with mock.patch.object(style_guide, "check_files") as check_files: style_guide.input_file("file.py") check_files.assert_called_once_with(["file.py"]) def test_report_total_errors(): """Verify total errors is just a proxy attribute.""" app = mock.Mock(result_count="Fake count") report = api.Report(app) assert report.total_errors == "Fake count" def test_report_get_statistics(): """Verify that we use the statistics object.""" stats = mock.Mock() stats.statistics_for.return_value = [] style_guide = mock.Mock(stats=stats) app = mock.Mock(guide=style_guide) report = api.Report(app) assert report.get_statistics("E") == [] stats.statistics_for.assert_called_once_with("E") ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633909928.0 flake8-4.0.1/tests/unit/test_nothing_formatter.py0000644000175000017500000000147100000000000023647 0ustar00asottileasottile00000000000000"""Tests for the Nothing formatter obbject.""" import argparse from flake8 import style_guide from flake8.formatting import default def options(**kwargs): """Create an argparse.Namespace instance.""" kwargs.setdefault("output_file", None) kwargs.setdefault("tee", False) return argparse.Namespace(**kwargs) def test_format_returns_nothing(): """Verify Nothing.format returns None.""" formatter = default.Nothing(options()) error = style_guide.Violation("code", "file.py", 1, 1, "text", "1") assert formatter.format(error) is None def test_show_source_returns_nothing(): """Verify Nothing.show_source returns None.""" formatter = default.Nothing(options()) error = style_guide.Violation("code", "file.py", 1, 1, "text", "1") assert formatter.show_source(error) is None ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633909928.0 flake8-4.0.1/tests/unit/test_option.py0000644000175000017500000000364400000000000021432 0ustar00asottileasottile00000000000000"""Unit tests for flake8.options.manager.Option.""" import functools from unittest import mock import pytest from flake8.options import manager def test_to_argparse(): """Test conversion to an argparse arguments.""" opt = manager.Option( short_option_name="-t", long_option_name="--test", action="count", parse_from_config=True, normalize_paths=True, ) assert opt.normalize_paths is True assert opt.parse_from_config is True args, kwargs = opt.to_argparse() assert args == ["-t", "--test"] assert kwargs == {"action": "count", "type": mock.ANY} assert isinstance(kwargs["type"], functools.partial) def test_to_optparse(): """Test that .to_optparse() produces a useful error message.""" with pytest.raises(AttributeError) as excinfo: manager.Option("--foo").to_optparse (msg,) = excinfo.value.args assert msg == "to_optparse: flake8 now uses argparse" def test_to_argparse_creates_an_option_as_we_expect(): """Show that we pass all keyword args to argparse.""" opt = manager.Option("-t", "--test", action="count") args, kwargs = opt.to_argparse() assert args == ["-t", "--test"] assert kwargs == {"action": "count"} def test_config_name_generation(): """Show that we generate the config name deterministically.""" opt = manager.Option( long_option_name="--some-very-long-option-name", parse_from_config=True, ) assert opt.config_name == "some_very_long_option_name" def test_config_name_needs_long_option_name(): """Show that we error out if the Option should be parsed from config.""" with pytest.raises(ValueError): manager.Option("-s", parse_from_config=True) def test_dest_is_not_overridden(): """Show that we do not override custom destinations.""" opt = manager.Option("-s", "--short", dest="something_not_short") assert opt.dest == "something_not_short" ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633909928.0 flake8-4.0.1/tests/unit/test_option_manager.py0000644000175000017500000003075000000000000023122 0ustar00asottileasottile00000000000000"""Unit tests for flake.options.manager.OptionManager.""" import argparse import os from unittest import mock import pytest from flake8 import utils from flake8.main.options import JobsArgument from flake8.options import manager TEST_VERSION = "3.0.0b1" @pytest.fixture def optmanager(): """Generate a simple OptionManager with default test arguments.""" return manager.OptionManager(prog="flake8", version=TEST_VERSION) def test_option_manager_creates_option_parser(optmanager): """Verify that a new manager creates a new parser.""" assert isinstance(optmanager.parser, argparse.ArgumentParser) def test_option_manager_including_parent_options(): """Verify parent options are included in the parsed options.""" # GIVEN parent_parser = argparse.ArgumentParser(add_help=False) parent_parser.add_argument("--parent") # WHEN optmanager = manager.OptionManager( prog="flake8", version=TEST_VERSION, parents=[parent_parser] ) option, _ = optmanager.parse_args(["--parent", "foo"]) # THEN assert option.parent == "foo" def test_parse_args_forwarding_default_values(optmanager): """Verify default provided values are present in the final result.""" namespace = argparse.Namespace(foo="bar") options, args = optmanager.parse_args([], namespace) assert options.foo == "bar" def test_parse_args_forwarding_type_coercion(optmanager): """Verify default provided values are type converted from add_option.""" optmanager.add_option("--foo", type=int) namespace = argparse.Namespace(foo="5") options, args = optmanager.parse_args([], namespace) assert options.foo == 5 def test_add_option_short_option_only(optmanager): """Verify the behaviour of adding a short-option only.""" assert optmanager.options == [] assert optmanager.config_options_dict == {} optmanager.add_option("-s", help="Test short opt") assert optmanager.options[0].short_option_name == "-s" def test_add_option_long_option_only(optmanager): """Verify the behaviour of adding a long-option only.""" assert optmanager.options == [] assert optmanager.config_options_dict == {} optmanager.add_option("--long", help="Test long opt") assert optmanager.options[0].short_option_name is manager._ARG.NO assert optmanager.options[0].long_option_name == "--long" def test_add_short_and_long_option_names(optmanager): """Verify the behaviour of using both short and long option names.""" assert optmanager.options == [] assert optmanager.config_options_dict == {} optmanager.add_option("-b", "--both", help="Test both opts") assert optmanager.options[0].short_option_name == "-b" assert optmanager.options[0].long_option_name == "--both" def test_add_option_with_custom_args(optmanager): """Verify that add_option handles custom Flake8 parameters.""" assert optmanager.options == [] assert optmanager.config_options_dict == {} optmanager.add_option("--parse", parse_from_config=True) optmanager.add_option("--commas", comma_separated_list=True) optmanager.add_option("--files", normalize_paths=True) attrs = ["parse_from_config", "comma_separated_list", "normalize_paths"] for option, attr in zip(optmanager.options, attrs): assert getattr(option, attr) is True def test_parse_args_normalize_path(optmanager): """Show that parse_args handles path normalization.""" assert optmanager.options == [] assert optmanager.config_options_dict == {} optmanager.add_option("--config", normalize_paths=True) options, args = optmanager.parse_args(["--config", "../config.ini"]) assert options.config == os.path.abspath("../config.ini") def test_parse_args_handles_comma_separated_defaults(optmanager): """Show that parse_args handles defaults that are comma-separated.""" assert optmanager.options == [] assert optmanager.config_options_dict == {} optmanager.add_option( "--exclude", default="E123,W234", comma_separated_list=True ) options, args = optmanager.parse_args([]) assert options.exclude == ["E123", "W234"] def test_parse_args_handles_comma_separated_lists(optmanager): """Show that parse_args handles user-specified comma-separated lists.""" assert optmanager.options == [] assert optmanager.config_options_dict == {} optmanager.add_option( "--exclude", default="E123,W234", comma_separated_list=True ) options, args = optmanager.parse_args(["--exclude", "E201,W111,F280"]) assert options.exclude == ["E201", "W111", "F280"] def test_parse_args_normalize_paths(optmanager): """Verify parse_args normalizes a comma-separated list of paths.""" assert optmanager.options == [] assert optmanager.config_options_dict == {} optmanager.add_option( "--extra-config", normalize_paths=True, comma_separated_list=True ) options, args = optmanager.parse_args( ["--extra-config", "../config.ini,tox.ini,flake8/some-other.cfg"] ) assert options.extra_config == [ os.path.abspath("../config.ini"), "tox.ini", os.path.abspath("flake8/some-other.cfg"), ] def test_generate_versions(optmanager): """Verify a comma-separated string is generated of registered plugins.""" optmanager.registered_plugins = [ manager.PluginVersion("Testing 100", "0.0.0", False), manager.PluginVersion("Testing 101", "0.0.0", False), manager.PluginVersion("Testing 300", "0.0.0", True), ] assert ( optmanager.generate_versions() == "Testing 100: 0.0.0, Testing 101: 0.0.0, Testing 300: 0.0.0" ) def test_plugins_are_sorted_in_generate_versions(optmanager): """Verify we sort before joining strings in generate_versions.""" optmanager.registered_plugins = [ manager.PluginVersion("pyflakes", "1.5.0", False), manager.PluginVersion("mccabe", "0.7.0", False), manager.PluginVersion("pycodestyle", "2.2.0", False), manager.PluginVersion("flake8-docstrings", "0.6.1", False), manager.PluginVersion("flake8-bugbear", "2016.12.1", False), ] assert ( optmanager.generate_versions() == "flake8-bugbear: 2016.12.1, " "flake8-docstrings: 0.6.1, " "mccabe: 0.7.0, " "pycodestyle: 2.2.0, " "pyflakes: 1.5.0" ) def test_generate_versions_with_format_string(optmanager): """Verify a comma-separated string is generated of registered plugins.""" optmanager.registered_plugins.update( [ manager.PluginVersion("Testing", "0.0.0", False), manager.PluginVersion("Testing", "0.0.0", False), manager.PluginVersion("Testing", "0.0.0", False), ] ) assert optmanager.generate_versions() == "Testing: 0.0.0" def test_update_version_string(optmanager): """Verify we update the version string idempotently.""" assert optmanager.version == TEST_VERSION assert optmanager.version_action.version == TEST_VERSION optmanager.registered_plugins = [ manager.PluginVersion("Testing 100", "0.0.0", False), manager.PluginVersion("Testing 101", "0.0.0", False), manager.PluginVersion("Testing 300", "0.0.0", False), ] optmanager.update_version_string() assert optmanager.version == TEST_VERSION assert ( optmanager.version_action.version == TEST_VERSION + " (Testing 100: 0.0.0, Testing 101: 0.0.0, Testing 300: 0.0.0) " + utils.get_python_version() ) def test_generate_epilog(optmanager): """Verify how we generate the epilog for help text.""" assert optmanager.parser.epilog is None optmanager.registered_plugins = [ manager.PluginVersion("Testing 100", "0.0.0", False), manager.PluginVersion("Testing 101", "0.0.0", False), manager.PluginVersion("Testing 300", "0.0.0", False), ] expected_value = ( "Installed plugins: Testing 100: 0.0.0, Testing 101: 0.0.0, Testing" " 300: 0.0.0" ) optmanager.generate_epilog() assert optmanager.parser.epilog == expected_value def test_extend_default_ignore(optmanager): """Verify that we update the extended default ignore list.""" assert optmanager.extended_default_ignore == set() optmanager.extend_default_ignore(["T100", "T101", "T102"]) assert optmanager.extended_default_ignore == {"T100", "T101", "T102"} def test_parse_known_args(optmanager): """Verify we ignore unknown options.""" with mock.patch("sys.exit") as sysexit: optmanager.parse_known_args(["--max-complexity", "5"]) assert sysexit.called is False def test_optparse_normalize_callback_option_legacy(optmanager): """Test the optparse shim for `callback=`.""" callback_foo = mock.Mock() optmanager.add_option( "--foo", action="callback", callback=callback_foo, callback_args=(1, 2), callback_kwargs={"a": "b"}, ) callback_bar = mock.Mock() optmanager.add_option( "--bar", action="callback", type="string", callback=callback_bar, ) callback_baz = mock.Mock() optmanager.add_option( "--baz", action="callback", type="string", nargs=2, callback=callback_baz, ) optmanager.parse_args(["--foo", "--bar", "bararg", "--baz", "1", "2"]) callback_foo.assert_called_once_with( mock.ANY, # the option / action instance "--foo", None, mock.ANY, # the OptionParser / ArgumentParser 1, 2, a="b", ) callback_bar.assert_called_once_with( mock.ANY, # the option / action instance "--bar", "bararg", mock.ANY, # the OptionParser / ArgumentParser ) callback_baz.assert_called_once_with( mock.ANY, # the option / action instance "--baz", ("1", "2"), mock.ANY, # the OptionParser / ArgumentParser ) @pytest.mark.parametrize( ("type_s", "input_val", "expected"), ( ("int", "5", 5), ("long", "6", 6), ("string", "foo", "foo"), ("float", "1.5", 1.5), ("complex", "1+5j", 1 + 5j), # optparse allows this but does not document it ("str", "foo", "foo"), ), ) def test_optparse_normalize_types(optmanager, type_s, input_val, expected): """Test the optparse shim for type="typename".""" optmanager.add_option("--foo", type=type_s) opts, args = optmanager.parse_args(["--foo", input_val]) assert opts.foo == expected def test_optparse_normalize_choice_type(optmanager): """Test the optparse shim for type="choice".""" optmanager.add_option("--foo", type="choice", choices=("1", "2", "3")) opts, args = optmanager.parse_args(["--foo", "1"]) assert opts.foo == "1" # fails to parse with pytest.raises(SystemExit): optmanager.parse_args(["--foo", "4"]) def test_optparse_normalize_help(optmanager, capsys): """Test the optparse shim for %default in help text.""" optmanager.add_option("--foo", default="bar", help="default: %default") with pytest.raises(SystemExit): optmanager.parse_args(["--help"]) out, err = capsys.readouterr() output = out + err assert "default: bar" in output def test_optmanager_group(optmanager, capsys): """Test that group(...) causes options to be assigned to a group.""" with optmanager.group("groupname"): optmanager.add_option("--foo") with pytest.raises(SystemExit): optmanager.parse_args(["--help"]) out, err = capsys.readouterr() output = out + err assert "\ngroupname:\n" in output @pytest.mark.parametrize( ("s", "is_auto", "n_jobs"), ( ("auto", True, -1), ("4", False, 4), ), ) def test_parse_valid_jobs_argument(s, is_auto, n_jobs): """Test that --jobs properly parses valid arguments.""" jobs_opt = JobsArgument(s) assert is_auto == jobs_opt.is_auto assert n_jobs == jobs_opt.n_jobs def test_parse_invalid_jobs_argument(optmanager, capsys): """Test that --jobs properly rejects invalid arguments.""" namespace = argparse.Namespace() optmanager.add_option("--jobs", type=JobsArgument) with pytest.raises(SystemExit): optmanager.parse_args(["--jobs=foo"], namespace) out, err = capsys.readouterr() output = out + err expected = ( "\nflake8: error: argument --jobs: " "'foo' must be 'auto' or an integer.\n" ) assert expected in output def test_jobs_argument_str(): """Test that JobsArgument has a correct __str__.""" assert str(JobsArgument("auto")) == "auto" assert str(JobsArgument("123")) == "123" ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633909928.0 flake8-4.0.1/tests/unit/test_plugin.py0000644000175000017500000001261500000000000021416 0ustar00asottileasottile00000000000000"""Tests for flake8.plugins.manager.Plugin.""" import argparse from unittest import mock import pytest from flake8 import exceptions from flake8.options import manager as options_manager from flake8.plugins import manager def test_load_plugin_fallsback_on_old_setuptools(): """Verify we fallback gracefully to on old versions of setuptools.""" entry_point = mock.Mock(spec=["load"]) plugin = manager.Plugin("T000", entry_point) plugin.load_plugin() entry_point.load.assert_called_once_with() def test_load_plugin_is_idempotent(): """Verify we use the preferred methods on new versions of setuptools.""" entry_point = mock.Mock(spec=["load"]) plugin = manager.Plugin("T000", entry_point) plugin.load_plugin() plugin.load_plugin() plugin.load_plugin() entry_point.load.assert_called_once_with() def test_load_plugin_catches_and_reraises_exceptions(): """Verify we raise our own FailedToLoadPlugin.""" entry_point = mock.Mock(spec=["load"]) entry_point.load.side_effect = ValueError("Test failure") plugin = manager.Plugin("T000", entry_point) with pytest.raises(exceptions.FailedToLoadPlugin): plugin.load_plugin() def test_load_noncallable_plugin(): """Verify that we do not load a non-callable plugin.""" entry_point = mock.Mock(spec=["load"]) entry_point.load.return_value = mock.NonCallableMock() plugin = manager.Plugin("T000", entry_point) with pytest.raises(exceptions.FailedToLoadPlugin): plugin.load_plugin() entry_point.load.assert_called_once_with() def test_plugin_property_loads_plugin_on_first_use(): """Verify that we load our plugin when we first try to use it.""" entry_point = mock.Mock(spec=["load"]) plugin = manager.Plugin("T000", entry_point) assert plugin.plugin is not None entry_point.load.assert_called_once_with() def test_execute_calls_plugin_with_passed_arguments(): """Verify that we pass arguments directly to the plugin.""" entry_point = mock.Mock(spec=["load"]) plugin_obj = mock.Mock() plugin = manager.Plugin("T000", entry_point) plugin._plugin = plugin_obj plugin.execute("arg1", "arg2", kwarg1="value1", kwarg2="value2") plugin_obj.assert_called_once_with( "arg1", "arg2", kwarg1="value1", kwarg2="value2" ) # Extra assertions assert entry_point.load.called is False def test_version_proxies_to_the_plugin(): """Verify that we pass arguments directly to the plugin.""" entry_point = mock.Mock(spec=["load"]) plugin_obj = mock.Mock(spec_set=["version"]) plugin_obj.version = "a.b.c" plugin = manager.Plugin("T000", entry_point) plugin._plugin = plugin_obj assert plugin.version == "a.b.c" def test_register_options(): """Verify we call add_options on the plugin only if it exists.""" # Set up our mocks and Plugin object entry_point = mock.Mock(spec=["load"]) plugin_obj = mock.Mock( spec_set=["name", "version", "add_options", "parse_options"] ) option_manager = mock.MagicMock(spec=options_manager.OptionManager) plugin = manager.Plugin("T000", entry_point) plugin._plugin = plugin_obj # Call the method we're testing. plugin.register_options(option_manager) # Assert that we call add_options plugin_obj.add_options.assert_called_once_with(option_manager) def test_register_options_checks_plugin_for_method(): """Verify we call add_options on the plugin only if it exists.""" # Set up our mocks and Plugin object entry_point = mock.Mock(spec=["load"]) plugin_obj = mock.Mock(spec_set=["name", "version", "parse_options"]) option_manager = mock.Mock(spec=["register_plugin"]) plugin = manager.Plugin("T000", entry_point) plugin._plugin = plugin_obj # Call the method we're testing. plugin.register_options(option_manager) # Assert that we register the plugin assert option_manager.register_plugin.called is False def test_provide_options(): """Verify we call add_options on the plugin only if it exists.""" # Set up our mocks and Plugin object entry_point = mock.Mock(spec=["load"]) plugin_obj = mock.Mock( spec_set=["name", "version", "add_options", "parse_options"] ) option_values = argparse.Namespace(enable_extensions=[]) option_manager = mock.Mock() plugin = manager.Plugin("T000", entry_point) plugin._plugin = plugin_obj # Call the method we're testing. plugin.provide_options(option_manager, option_values, None) # Assert that we call add_options plugin_obj.parse_options.assert_called_once_with( option_manager, option_values, None ) @pytest.mark.parametrize( "ignore_list, code, expected_list", [ (["E", "W", "F", "C9"], "W", ["E", "F", "C9"]), (["E", "W", "F"], "C9", ["E", "W", "F"]), ], ) def test_enable(ignore_list, code, expected_list): """Verify that enabling a plugin removes it from the ignore list.""" options = mock.Mock(ignore=ignore_list) optmanager = mock.Mock() plugin = manager.Plugin(code, mock.Mock()) plugin.enable(optmanager, options) assert options.ignore == expected_list def test_enable_without_providing_parsed_options(): """Verify that enabling a plugin removes it from the ignore list.""" optmanager = mock.Mock() plugin = manager.Plugin("U4", mock.Mock()) plugin.enable(optmanager) optmanager.remove_from_default_ignore.assert_called_once_with(["U4"]) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633909928.0 flake8-4.0.1/tests/unit/test_plugin_manager.py0000644000175000017500000000440300000000000023104 0ustar00asottileasottile00000000000000"""Tests for flake8.plugins.manager.PluginManager.""" from unittest import mock from flake8._compat import importlib_metadata from flake8.plugins import manager @mock.patch.object(importlib_metadata, "entry_points") def test_calls_entrypoints_on_instantiation(entry_points_mck): """Verify that we call entry_points() when we create a manager.""" entry_points_mck.return_value = {} manager.PluginManager(namespace="testing.entrypoints") entry_points_mck.assert_called_once_with() @mock.patch.object(importlib_metadata, "entry_points") def test_calls_entrypoints_creates_plugins_automaticaly(entry_points_mck): """Verify that we create Plugins on instantiation.""" entry_points_mck.return_value = { "testing.entrypoints": [ importlib_metadata.EntryPoint("T100", "", "testing.entrypoints"), importlib_metadata.EntryPoint("T200", "", "testing.entrypoints"), ], } plugin_mgr = manager.PluginManager(namespace="testing.entrypoints") entry_points_mck.assert_called_once_with() assert "T100" in plugin_mgr.plugins assert "T200" in plugin_mgr.plugins assert isinstance(plugin_mgr.plugins["T100"], manager.Plugin) assert isinstance(plugin_mgr.plugins["T200"], manager.Plugin) @mock.patch.object(importlib_metadata, "entry_points") def test_handles_mapping_functions_across_plugins(entry_points_mck): """Verify we can use the PluginManager call functions on all plugins.""" entry_points_mck.return_value = { "testing.entrypoints": [ importlib_metadata.EntryPoint("T100", "", "testing.entrypoints"), importlib_metadata.EntryPoint("T200", "", "testing.entrypoints"), ], } plugin_mgr = manager.PluginManager(namespace="testing.entrypoints") plugins = [plugin_mgr.plugins[name] for name in plugin_mgr.names] assert list(plugin_mgr.map(lambda x: x)) == plugins @mock.patch.object(importlib_metadata, "entry_points") def test_local_plugins(entry_points_mck): """Verify PluginManager can load given local plugins.""" entry_points_mck.return_value = {} plugin_mgr = manager.PluginManager( namespace="testing.entrypoints", local_plugins=["X = path.to:Plugin"] ) assert plugin_mgr.plugins["X"].entry_point.value == "path.to:Plugin" ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633909928.0 flake8-4.0.1/tests/unit/test_plugin_type_manager.py0000644000175000017500000001532100000000000024146 0ustar00asottileasottile00000000000000"""Tests for flake8.plugins.manager.PluginTypeManager.""" from unittest import mock import pytest from flake8 import exceptions from flake8.plugins import manager TEST_NAMESPACE = "testing.plugin-type-manager" def create_plugin_mock(raise_exception=False): """Create an auto-spec'd mock of a flake8 Plugin.""" plugin = mock.create_autospec(manager.Plugin, instance=True) if raise_exception: plugin.load_plugin.side_effect = exceptions.FailedToLoadPlugin( plugin_name="T101", exception=ValueError("Test failure"), ) return plugin def create_mapping_manager_mock(plugins): """Create a mock for the PluginManager.""" # Have a function that will actually call the method underneath def fake_map(func): for plugin in plugins: yield func(plugin) # Mock out the PluginManager instance manager_mock = mock.Mock(spec=["map"]) # Replace the map method manager_mock.map = fake_map return manager_mock class FakeTestType(manager.PluginTypeManager): """Fake PluginTypeManager.""" namespace = TEST_NAMESPACE @mock.patch("flake8.plugins.manager.PluginManager", autospec=True) def test_instantiates_a_manager(PluginManager): # noqa: N803 """Verify we create a PluginManager on instantiation.""" FakeTestType() PluginManager.assert_called_once_with(TEST_NAMESPACE, local_plugins=None) @mock.patch("flake8.plugins.manager.PluginManager", autospec=True) def test_proxies_names_to_manager(PluginManager): # noqa: N803 """Verify we proxy the names attribute.""" PluginManager.return_value = mock.Mock(names=["T100", "T200", "T300"]) type_mgr = FakeTestType() assert type_mgr.names == ["T100", "T200", "T300"] @mock.patch("flake8.plugins.manager.PluginManager", autospec=True) def test_proxies_plugins_to_manager(PluginManager): # noqa: N803 """Verify we proxy the plugins attribute.""" PluginManager.return_value = mock.Mock(plugins=["T100", "T200", "T300"]) type_mgr = FakeTestType() assert type_mgr.plugins == ["T100", "T200", "T300"] def test_generate_call_function(): """Verify the function we generate.""" optmanager = object() plugin = mock.Mock(method_name=lambda x: x) func = manager.PluginTypeManager._generate_call_function( "method_name", optmanager, ) assert callable(func) assert func(plugin) is optmanager @mock.patch("flake8.plugins.manager.PluginManager", autospec=True) def test_load_plugins(PluginManager): # noqa: N803 """Verify load plugins loads *every* plugin.""" # Create a bunch of fake plugins plugins = [ create_plugin_mock(), create_plugin_mock(), create_plugin_mock(), create_plugin_mock(), create_plugin_mock(), create_plugin_mock(), create_plugin_mock(), create_plugin_mock(), ] # Return our PluginManager mock PluginManager.return_value = create_mapping_manager_mock(plugins) type_mgr = FakeTestType() # Load the tests (do what we're actually testing) assert len(type_mgr.load_plugins()) == 8 # Assert that our closure does what we think it does for plugin in plugins: plugin.load_plugin.assert_called_once_with() assert type_mgr.plugins_loaded is True @mock.patch("flake8.plugins.manager.PluginManager") def test_load_plugins_fails(PluginManager): # noqa: N803 """Verify load plugins bubbles up exceptions.""" plugins = [ create_plugin_mock(), create_plugin_mock(True), create_plugin_mock(), create_plugin_mock(), create_plugin_mock(), create_plugin_mock(), create_plugin_mock(), create_plugin_mock(), ] # Return our PluginManager mock PluginManager.return_value = create_mapping_manager_mock(plugins) type_mgr = FakeTestType() with pytest.raises(exceptions.FailedToLoadPlugin): type_mgr.load_plugins() # Assert we didn't finish loading plugins assert type_mgr.plugins_loaded is False # Assert the first two plugins had their load_plugin method called plugins[0].load_plugin.assert_called_once_with() plugins[1].load_plugin.assert_called_once_with() # Assert the rest of the plugins were not loaded for plugin in plugins[2:]: assert plugin.load_plugin.called is False @mock.patch("flake8.plugins.manager.PluginManager") def test_register_options(PluginManager): # noqa: N803 """Test that we map over every plugin to register options.""" plugins = [ create_plugin_mock(), create_plugin_mock(), create_plugin_mock(), create_plugin_mock(), create_plugin_mock(), create_plugin_mock(), create_plugin_mock(), create_plugin_mock(), ] # Return our PluginManager mock PluginManager.return_value = create_mapping_manager_mock(plugins) optmanager = object() type_mgr = FakeTestType() type_mgr.register_options(optmanager) for plugin in plugins: plugin.register_options.assert_called_with(optmanager) @mock.patch("flake8.plugins.manager.PluginManager") def test_provide_options(PluginManager): # noqa: N803 """Test that we map over every plugin to provide parsed options.""" plugins = [ create_plugin_mock(), create_plugin_mock(), create_plugin_mock(), create_plugin_mock(), create_plugin_mock(), create_plugin_mock(), create_plugin_mock(), create_plugin_mock(), ] # Return our PluginManager mock PluginManager.return_value = create_mapping_manager_mock(plugins) optmanager = object() options = object() type_mgr = FakeTestType() type_mgr.provide_options(optmanager, options, []) for plugin in plugins: plugin.provide_options.assert_called_with(optmanager, options, []) @mock.patch("flake8.plugins.manager.PluginManager", autospec=True) def test_proxy_contains_to_managers_plugins_dict(PluginManager): # noqa: N803 """Verify that we proxy __contains__ to the manager's dictionary.""" plugins = {"T10%i" % i: create_plugin_mock() for i in range(8)} # Return our PluginManager mock PluginManager.return_value.plugins = plugins type_mgr = FakeTestType() for i in range(8): key = "T10%i" % i assert key in type_mgr @mock.patch("flake8.plugins.manager.PluginManager") def test_proxies_getitem_to_managers_plugins_dict(PluginManager): # noqa: N803 """Verify that we can use the PluginTypeManager like a dictionary.""" plugins = {"T10%i" % i: create_plugin_mock() for i in range(8)} # Return our PluginManager mock PluginManager.return_value.plugins = plugins type_mgr = FakeTestType() for i in range(8): key = "T10%i" % i assert type_mgr[key] is plugins[key] ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633909928.0 flake8-4.0.1/tests/unit/test_pyflakes_codes.py0000644000175000017500000000171700000000000023114 0ustar00asottileasottile00000000000000"""Tests of pyflakes monkey patches.""" import ast import pyflakes from flake8.plugins import pyflakes as pyflakes_shim def test_all_pyflakes_messages_have_flake8_codes_assigned(): """Verify all PyFlakes messages have error codes assigned.""" messages = { name for name, obj in vars(pyflakes.messages).items() if name[0].isupper() and obj.message } assert messages == set(pyflakes_shim.FLAKE8_PYFLAKES_CODES) def test_undefined_local_code(): """In pyflakes 2.1.0 this code's string formatting was changed.""" src = """\ import sys def f(): sys = sys """ tree = ast.parse(src) checker = pyflakes_shim.FlakesChecker(tree, (), "t.py") message_texts = [s for _, _, s, _ in checker.run()] assert message_texts == [ "F823 local variable 'sys' defined in enclosing scope on line 1 referenced before assignment", # noqa: E501 "F841 local variable 'sys' is assigned to but never used", ] ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633909928.0 flake8-4.0.1/tests/unit/test_statistics.py0000644000175000017500000001061000000000000022303 0ustar00asottileasottile00000000000000"""Tests for the statistics module in Flake8.""" import pytest from flake8 import statistics as stats from flake8 import style_guide DEFAULT_ERROR_CODE = "E100" DEFAULT_FILENAME = "file.py" DEFAULT_TEXT = "Default text" def make_error(**kwargs): """Create errors with a bunch of default values.""" return style_guide.Violation( code=kwargs.pop("code", DEFAULT_ERROR_CODE), filename=kwargs.pop("filename", DEFAULT_FILENAME), line_number=kwargs.pop("line_number", 1), column_number=kwargs.pop("column_number", 1), text=kwargs.pop("text", DEFAULT_TEXT), physical_line=None, ) def test_key_creation(): """Verify how we create Keys from Errors.""" key = stats.Key.create_from(make_error()) assert key == (DEFAULT_FILENAME, DEFAULT_ERROR_CODE) assert key.filename == DEFAULT_FILENAME assert key.code == DEFAULT_ERROR_CODE @pytest.mark.parametrize( "code, filename, args, expected_result", [ # Error prefix matches ("E123", "file000.py", ("E", None), True), ("E123", "file000.py", ("E1", None), True), ("E123", "file000.py", ("E12", None), True), ("E123", "file000.py", ("E123", None), True), # Error prefix and filename match ("E123", "file000.py", ("E", "file000.py"), True), ("E123", "file000.py", ("E1", "file000.py"), True), ("E123", "file000.py", ("E12", "file000.py"), True), ("E123", "file000.py", ("E123", "file000.py"), True), # Error prefix does not match ("E123", "file000.py", ("W", None), False), # Error prefix matches but filename does not ("E123", "file000.py", ("E", "file001.py"), False), # Error prefix does not match but filename does ("E123", "file000.py", ("W", "file000.py"), False), # Neither error prefix match nor filename ("E123", "file000.py", ("W", "file001.py"), False), ], ) def test_key_matching(code, filename, args, expected_result): """Verify Key#matches behaves as we expect with fthe above input.""" key = stats.Key.create_from(make_error(code=code, filename=filename)) assert key.matches(*args) is expected_result def test_statistic_creation(): """Verify how we create Statistic objects from Errors.""" stat = stats.Statistic.create_from(make_error()) assert stat.error_code == DEFAULT_ERROR_CODE assert stat.message == DEFAULT_TEXT assert stat.filename == DEFAULT_FILENAME assert stat.count == 0 def test_statistic_increment(): """Verify we update the count.""" stat = stats.Statistic.create_from(make_error()) assert stat.count == 0 stat.increment() assert stat.count == 1 def test_recording_statistics(): """Verify that we appropriately create a new Statistic and store it.""" aggregator = stats.Statistics() assert list(aggregator.statistics_for("E")) == [] aggregator.record(make_error()) storage = aggregator._store for key, value in storage.items(): assert isinstance(key, stats.Key) assert isinstance(value, stats.Statistic) assert storage[stats.Key(DEFAULT_FILENAME, DEFAULT_ERROR_CODE)].count == 1 def test_statistics_for_single_record(): """Show we can retrieve the only statistic recorded.""" aggregator = stats.Statistics() assert list(aggregator.statistics_for("E")) == [] aggregator.record(make_error()) statistics = list(aggregator.statistics_for("E")) assert len(statistics) == 1 assert isinstance(statistics[0], stats.Statistic) def test_statistics_for_filters_by_filename(): """Show we can retrieve the only statistic recorded.""" aggregator = stats.Statistics() assert list(aggregator.statistics_for("E")) == [] aggregator.record(make_error()) aggregator.record(make_error(filename="example.py")) statistics = list(aggregator.statistics_for("E", DEFAULT_FILENAME)) assert len(statistics) == 1 assert isinstance(statistics[0], stats.Statistic) def test_statistic_for_retrieves_more_than_one_value(): """Show this works for more than a couple statistic values.""" aggregator = stats.Statistics() for i in range(50): aggregator.record(make_error(code=f"E1{i:02d}")) aggregator.record(make_error(code=f"W2{i:02d}")) statistics = list(aggregator.statistics_for("E")) assert len(statistics) == 50 statistics = list(aggregator.statistics_for("W22")) assert len(statistics) == 10 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633909928.0 flake8-4.0.1/tests/unit/test_style_guide.py0000644000175000017500000001160700000000000022435 0ustar00asottileasottile00000000000000"""Tests for the flake8.style_guide.StyleGuide class.""" import argparse from unittest import mock import pytest from flake8 import statistics from flake8 import style_guide from flake8 import utils from flake8.formatting import base def create_options(**kwargs): """Create and return an instance of argparse.Namespace.""" kwargs.setdefault("select", []) kwargs.setdefault("extended_default_select", []) kwargs.setdefault("extended_default_ignore", []) kwargs.setdefault("extend_select", []) kwargs.setdefault("ignore", []) kwargs.setdefault("extend_ignore", []) kwargs.setdefault("disable_noqa", False) kwargs.setdefault("enable_extensions", []) kwargs.setdefault("per_file_ignores", []) return argparse.Namespace(**kwargs) def test_handle_error_does_not_raise_type_errors(): """Verify that we handle our inputs better.""" formatter = mock.create_autospec(base.BaseFormatter, instance=True) guide = style_guide.StyleGuide( create_options(select=["T111"], ignore=[]), formatter=formatter, stats=statistics.Statistics(), ) assert 1 == guide.handle_error( "T111", "file.py", 1, None, "error found", "a = 1" ) def test_style_guide_manager(): """Verify how the StyleGuideManager creates a default style guide.""" formatter = mock.create_autospec(base.BaseFormatter, instance=True) options = create_options() guide = style_guide.StyleGuideManager(options, formatter=formatter) assert guide.default_style_guide.options is options assert len(guide.style_guides) == 1 PER_FILE_IGNORES_UNPARSED = [ "first_file.py:W9", "second_file.py:F4,F9", "third_file.py:E3", "sub_dir/*:F4", ] @pytest.mark.parametrize( "style_guide_file,filename,expected", [ ("first_file.py", "first_file.py", True), ("first_file.py", "second_file.py", False), ("sub_dir/*.py", "first_file.py", False), ("sub_dir/*.py", "sub_dir/file.py", True), ("sub_dir/*.py", "other_dir/file.py", False), ], ) def test_style_guide_applies_to(style_guide_file, filename, expected): """Verify that we match a file to its style guide.""" formatter = mock.create_autospec(base.BaseFormatter, instance=True) options = create_options() guide = style_guide.StyleGuide( options, formatter=formatter, stats=statistics.Statistics(), filename=style_guide_file, ) assert guide.applies_to(filename) is expected def test_style_guide_manager_pre_file_ignores_parsing(): """Verify how the StyleGuideManager creates a default style guide.""" formatter = mock.create_autospec(base.BaseFormatter, instance=True) options = create_options(per_file_ignores=PER_FILE_IGNORES_UNPARSED) guide = style_guide.StyleGuideManager(options, formatter=formatter) assert len(guide.style_guides) == 5 expected = [ utils.normalize_path(p) for p in [ "first_file.py", "second_file.py", "third_file.py", "sub_dir/*", ] ] assert expected == [g.filename for g in guide.style_guides[1:]] @pytest.mark.parametrize( "ignores,violation,filename,handle_error_return", [ (["E1", "E2"], "F401", "first_file.py", 1), (["E1", "E2"], "E121", "first_file.py", 0), (["E1", "E2"], "F401", "second_file.py", 0), (["E1", "E2"], "F401", "third_file.py", 1), (["E1", "E2"], "E311", "third_file.py", 0), (["E1", "E2"], "F401", "sub_dir/file.py", 0), ], ) def test_style_guide_manager_pre_file_ignores( ignores, violation, filename, handle_error_return ): """Verify how the StyleGuideManager creates a default style guide.""" formatter = mock.create_autospec(base.BaseFormatter, instance=True) options = create_options( ignore=ignores, select=["E", "F", "W"], per_file_ignores=PER_FILE_IGNORES_UNPARSED, ) guide = style_guide.StyleGuideManager(options, formatter=formatter) assert ( guide.handle_error(violation, filename, 1, 1, "Fake text") == handle_error_return ) @pytest.mark.parametrize( "filename,expected", [ ("first_file.py", utils.normalize_path("first_file.py")), ("second_file.py", utils.normalize_path("second_file.py")), ("third_file.py", utils.normalize_path("third_file.py")), ("fourth_file.py", None), ("sub_dir/__init__.py", utils.normalize_path("sub_dir/*")), ("other_dir/__init__.py", None), ], ) def test_style_guide_manager_style_guide_for(filename, expected): """Verify the style guide selection function.""" formatter = mock.create_autospec(base.BaseFormatter, instance=True) options = create_options(per_file_ignores=PER_FILE_IGNORES_UNPARSED) guide = style_guide.StyleGuideManager(options, formatter=formatter) file_guide = guide.style_guide_for(filename) assert file_guide.filename == expected ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633909928.0 flake8-4.0.1/tests/unit/test_utils.py0000644000175000017500000002551600000000000021264 0ustar00asottileasottile00000000000000"""Tests for flake8's utils module.""" import io import logging import os import sys from unittest import mock import pytest from flake8 import exceptions from flake8 import utils from flake8.plugins import manager as plugin_manager RELATIVE_PATHS = ["flake8", "pep8", "pyflakes", "mccabe"] @pytest.mark.parametrize( "value,expected", [ ("E123,\n\tW234,\n E206", ["E123", "W234", "E206"]), ("E123,W234,E206", ["E123", "W234", "E206"]), ("E123 W234 E206", ["E123", "W234", "E206"]), ("E123\nW234 E206", ["E123", "W234", "E206"]), ("E123\nW234\nE206", ["E123", "W234", "E206"]), ("E123,W234,E206,", ["E123", "W234", "E206"]), ("E123,W234,E206, ,\n", ["E123", "W234", "E206"]), ("E123,W234,,E206,,", ["E123", "W234", "E206"]), ("E123, W234,, E206,,", ["E123", "W234", "E206"]), ("E123,,W234,,E206,,", ["E123", "W234", "E206"]), ("", []), ], ) def test_parse_comma_separated_list(value, expected): """Verify that similar inputs produce identical outputs.""" assert utils.parse_comma_separated_list(value) == expected @pytest.mark.parametrize( ("value", "expected"), ( # empty option configures nothing ("", []), (" ", []), ("\n\n\n", []), # basic case ( "f.py:E123", [("f.py", ["E123"])], ), # multiple filenames, multiple codes ( "f.py,g.py:E,F", [("f.py", ["E", "F"]), ("g.py", ["E", "F"])], ), # demonstrate that whitespace is not important around tokens ( " f.py , g.py : E , F ", [("f.py", ["E", "F"]), ("g.py", ["E", "F"])], ), # whitespace can separate groups of configuration ( "f.py:E g.py:F", [("f.py", ["E"]), ("g.py", ["F"])], ), # newlines can separate groups of configuration ( "f.py: E\ng.py: F\n", [("f.py", ["E"]), ("g.py", ["F"])], ), # whitespace can be used in place of commas ( "f.py g.py: E F", [("f.py", ["E", "F"]), ("g.py", ["E", "F"])], ), # go ahead, indent your codes ( "f.py:\n E,F\ng.py:\n G,H", [("f.py", ["E", "F"]), ("g.py", ["G", "H"])], ), # capitalized filenames are ok too ( "F.py,G.py: F,G", [("F.py", ["F", "G"]), ("G.py", ["F", "G"])], ), # it's easier to allow zero filenames or zero codes than forbid it (":E", []), ("f.py:", []), (":E f.py:F", [("f.py", ["F"])]), ("f.py: g.py:F", [("g.py", ["F"])]), ("f.py:E:", []), ("f.py:E.py:", []), ("f.py:Eg.py:F", [("Eg.py", ["F"])]), # sequences are also valid (?) ( ["f.py:E,F", "g.py:G,H"], [("f.py", ["E", "F"]), ("g.py", ["G", "H"])], ), # six-digits codes are allowed ( "f.py: ABC123", [("f.py", ["ABC123"])], ), ), ) def test_parse_files_to_codes_mapping(value, expected): """Test parsing of valid files-to-codes mappings.""" assert utils.parse_files_to_codes_mapping(value) == expected @pytest.mark.parametrize( "value", ( # code while looking for filenames "E123", "f.py,E123", "f.py E123", # eof while looking for filenames "f.py", "f.py:E,g.py" # colon while looking for codes "f.py::", # no separator between "f.py:E1F1", ), ) def test_invalid_file_list(value): """Test parsing of invalid files-to-codes mappings.""" with pytest.raises(exceptions.ExecutionError): utils.parse_files_to_codes_mapping(value) @pytest.mark.parametrize( "value,expected", [ ("flake8", "flake8"), ("../flake8", os.path.abspath("../flake8")), ("flake8/", os.path.abspath("flake8")), ], ) def test_normalize_path(value, expected): """Verify that we normalize paths provided to the tool.""" assert utils.normalize_path(value) == expected @pytest.mark.parametrize( "value,expected", [ ( ["flake8", "pep8", "pyflakes", "mccabe"], ["flake8", "pep8", "pyflakes", "mccabe"], ), ( ["../flake8", "../pep8", "../pyflakes", "../mccabe"], [os.path.abspath(f"../{p}") for p in RELATIVE_PATHS], ), ], ) def test_normalize_paths(value, expected): """Verify we normalizes a sequence of paths provided to the tool.""" assert utils.normalize_paths(value) == expected def test_is_windows_checks_for_nt(): """Verify that we correctly detect Windows.""" with mock.patch.object(os, "name", "nt"): assert utils.is_windows() is True with mock.patch.object(os, "name", "posix"): assert utils.is_windows() is False @pytest.mark.parametrize( "filename,patterns,expected", [ ("foo.py", [], True), ("foo.py", ["*.pyc"], False), ("foo.pyc", ["*.pyc"], True), ("foo.pyc", ["*.swp", "*.pyc", "*.py"], True), ], ) def test_fnmatch(filename, patterns, expected): """Verify that our fnmatch wrapper works as expected.""" assert utils.fnmatch(filename, patterns) is expected @pytest.fixture def files_dir(tmpdir): """Create test dir for testing filenames_from.""" with tmpdir.as_cwd(): tmpdir.join("a/b/c.py").ensure() tmpdir.join("a/b/d.py").ensure() tmpdir.join("a/b/e/f.py").ensure() yield tmpdir def _normpath(s): return s.replace("/", os.sep) def _normpaths(pths): return {_normpath(pth) for pth in pths} @pytest.mark.usefixtures("files_dir") def test_filenames_from_a_directory(): """Verify that filenames_from walks a directory.""" filenames = set(utils.filenames_from(_normpath("a/b/"))) # should include all files expected = _normpaths(("a/b/c.py", "a/b/d.py", "a/b/e/f.py")) assert filenames == expected @pytest.mark.usefixtures("files_dir") def test_filenames_from_a_directory_with_a_predicate(): """Verify that predicates filter filenames_from.""" filenames = set( utils.filenames_from( arg=_normpath("a/b/"), predicate=lambda path: path.endswith(_normpath("b/c.py")), ) ) # should not include c.py expected = _normpaths(("a/b/d.py", "a/b/e/f.py")) assert filenames == expected @pytest.mark.usefixtures("files_dir") def test_filenames_from_a_directory_with_a_predicate_from_the_current_dir(): """Verify that predicates filter filenames_from.""" filenames = set( utils.filenames_from( arg=_normpath("./a/b"), predicate=lambda path: path == "c.py", ) ) # none should have matched the predicate so all returned expected = _normpaths(("./a/b/c.py", "./a/b/d.py", "./a/b/e/f.py")) assert filenames == expected @pytest.mark.usefixtures("files_dir") def test_filenames_from_a_single_file(): """Verify that we simply yield that filename.""" filenames = set(utils.filenames_from(_normpath("a/b/c.py"))) assert filenames == {_normpath("a/b/c.py")} def test_filenames_from_a_single_file_does_not_exist(): """Verify that a passed filename which does not exist is returned back.""" filenames = set(utils.filenames_from(_normpath("d/n/e.py"))) assert filenames == {_normpath("d/n/e.py")} def test_filenames_from_exclude_doesnt_exclude_directory_names(tmpdir): """Verify that we don't greedily exclude subdirs.""" tmpdir.join("1").ensure_dir().join("dont_return_me.py").ensure() tmpdir.join("2").join("1").ensure_dir().join("return_me.py").ensure() exclude = [tmpdir.join("1").strpath] # This acts similar to src.flake8.checker.is_path_excluded def predicate(pth): return utils.fnmatch(os.path.abspath(pth), exclude) with tmpdir.as_cwd(): filenames = list(utils.filenames_from(".", predicate)) assert filenames == [os.path.join(".", "2", "1", "return_me.py")] def test_parameters_for_class_plugin(): """Verify that we can retrieve the parameters for a class plugin.""" class FakeCheck: def __init__(self, tree): raise NotImplementedError plugin = plugin_manager.Plugin("plugin-name", object()) plugin._plugin = FakeCheck assert utils.parameters_for(plugin) == {"tree": True} def test_parameters_for_function_plugin(): """Verify that we retrieve the parameters for a function plugin.""" def fake_plugin(physical_line, self, tree, optional=None): raise NotImplementedError plugin = plugin_manager.Plugin("plugin-name", object()) plugin._plugin = fake_plugin assert utils.parameters_for(plugin) == { "physical_line": True, "self": True, "tree": True, "optional": False, } def read_diff_file(filename): """Read the diff file in its entirety.""" with open(filename) as fd: content = fd.read() return content SINGLE_FILE_DIFF = read_diff_file("tests/fixtures/diffs/single_file_diff") SINGLE_FILE_INFO = { "flake8/utils.py": set(range(75, 83)).union(set(range(84, 94))), } TWO_FILE_DIFF = read_diff_file("tests/fixtures/diffs/two_file_diff") TWO_FILE_INFO = { "flake8/utils.py": set(range(75, 83)).union(set(range(84, 94))), "tests/unit/test_utils.py": set(range(115, 128)), } MULTI_FILE_DIFF = read_diff_file("tests/fixtures/diffs/multi_file_diff") MULTI_FILE_INFO = { "flake8/utils.py": set(range(75, 83)).union(set(range(84, 94))), "tests/unit/test_utils.py": set(range(115, 129)), "tests/fixtures/diffs/single_file_diff": set(range(1, 28)), "tests/fixtures/diffs/two_file_diff": set(range(1, 46)), } @pytest.mark.parametrize( "diff, parsed_diff", [ (SINGLE_FILE_DIFF, SINGLE_FILE_INFO), (TWO_FILE_DIFF, TWO_FILE_INFO), (MULTI_FILE_DIFF, MULTI_FILE_INFO), ], ) def test_parse_unified_diff(diff, parsed_diff): """Verify that what we parse from a diff matches expectations.""" assert utils.parse_unified_diff(diff) == parsed_diff def test_matches_filename_for_excluding_dotfiles(): """Verify that `.` and `..` are not matched by `.*`.""" logger = logging.Logger(__name__) assert not utils.matches_filename(".", (".*",), "", logger) assert not utils.matches_filename("..", (".*",), "", logger) def test_stdin_get_value_crlf(): """Ensure that stdin is normalized from crlf to lf.""" stdin = io.TextIOWrapper(io.BytesIO(b"1\r\n2\r\n"), "UTF-8") with mock.patch.object(sys, "stdin", stdin): assert utils.stdin_get_value.__wrapped__() == "1\n2\n" def test_stdin_unknown_coding_token(): """Ensure we produce source even for unknown encodings.""" stdin = io.TextIOWrapper(io.BytesIO(b"# coding: unknown\n"), "UTF-8") with mock.patch.object(sys, "stdin", stdin): assert utils.stdin_get_value.__wrapped__() == "# coding: unknown\n" ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633909928.0 flake8-4.0.1/tests/unit/test_violation.py0000644000175000017500000000545100000000000022124 0ustar00asottileasottile00000000000000"""Tests for the flake8.style_guide.Violation class.""" from unittest import mock import pytest from flake8 import style_guide @pytest.mark.parametrize( "error_code,physical_line,expected_result", [ ("E111", "a = 1", False), ("E121", "a = 1 # noqa: E111", False), ("E121", "a = 1 # noqa: E111,W123,F821", False), ("E111", "a = 1 # noqa: E111,W123,F821", True), ("W123", "a = 1 # noqa: E111,W123,F821", True), ("W123", "a = 1 # noqa: E111, W123,F821", True), ("E111", "a = 1 # noqa: E11,W123,F821", True), ("E121", "a = 1 # noqa:E111,W123,F821", False), ("E111", "a = 1 # noqa:E111,W123,F821", True), ("W123", "a = 1 # noqa:E111,W123,F821", True), ("W123", "a = 1 # noqa:E111, W123,F821", True), ("E111", "a = 1 # noqa:E11,W123,F821", True), ("E111", "a = 1 # noqa, analysis:ignore", True), ("E111", "a = 1 # noqa analysis:ignore", True), ("E111", "a = 1 # noqa - We do not care", True), ("E111", "a = 1 # noqa: We do not care", True), ("E111", "a = 1 # noqa:We do not care", True), ("ABC123", "a = 1 # noqa: ABC123", True), ("E111", "a = 1 # noqa: ABC123", False), ("ABC123", "a = 1 # noqa: ABC124", False), ], ) def test_is_inline_ignored(error_code, physical_line, expected_result): """Verify that we detect inline usage of ``# noqa``.""" error = style_guide.Violation( error_code, "filename.py", 1, 1, "error text", None ) # We want `None` to be passed as the physical line so we actually use our # monkey-patched linecache.getline value. with mock.patch("linecache.getline", return_value=physical_line): assert error.is_inline_ignored(False) is expected_result def test_disable_is_inline_ignored(): """Verify that is_inline_ignored exits immediately if disabling NoQA.""" error = style_guide.Violation( "E121", "filename.py", 1, 1, "error text", "line" ) with mock.patch("linecache.getline") as getline: assert error.is_inline_ignored(True) is False assert getline.called is False @pytest.mark.parametrize( "violation_file,violation_line,diff,expected", [ ("file.py", 10, {}, True), ("file.py", 1, {"file.py": range(1, 2)}, True), ("file.py", 10, {"file.py": range(1, 2)}, False), ("file.py", 1, {"other.py": range(1, 2)}, False), ("file.py", 10, {"other.py": range(1, 2)}, False), ], ) def test_violation_is_in_diff(violation_file, violation_line, diff, expected): """Verify that we find violations within a diff.""" violation = style_guide.Violation( "E001", violation_file, violation_line, 1, "warning", "line", ) assert violation.is_in(diff) is expected ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633915242.0 flake8-4.0.1/tox.ini0000644000175000017500000000545200000000000015702 0ustar00asottileasottile00000000000000[tox] minversion=2.3.1 envlist = py36,py37,py38,flake8,linters,docs [testenv] deps = pytest!=3.0.5,!=5.2.3 coverage commands = coverage run -m pytest {posargs} coverage combine coverage report # ensure 100% coverage of tests coverage report --fail-under 100 --include tests/* # Dogfood our current main version [testenv:dogfood] skip_install = true deps = wheel commands = python setup.py -qq bdist_wheel pip install --force-reinstall -U --pre --find-links ./dist/ flake8 flake8 --version flake8 src/flake8/ tests/ setup.py # Linters [testenv:flake8] skip_install = true deps = flake8 flake8-bugbear flake8-docstrings>=1.3.1 flake8-typing-imports>=1.1 pep8-naming commands = flake8 src/flake8/ tests/ setup.py [testenv:pylint] skip_install = true deps = pyflakes pylint!=2.5.0 commands = pylint src/flake8 [testenv:doc8] skip_install = true deps = sphinx doc8 commands = doc8 docs/source/ [testenv:pre-commit] skip_install = true deps = pre-commit commands = pre-commit run --all-files --show-diff-on-failure [testenv:bandit] skip_install = true deps = bandit commands = bandit -r src/flake8/ -c .bandit.yml [testenv:linters] skip_install = true deps = {[testenv:flake8]deps} {[testenv:pylint]deps} {[testenv:doc8]deps} {[testenv:readme]deps} {[testenv:bandit]deps} commands = {[testenv:flake8]commands} {[testenv:pylint]commands} {[testenv:doc8]commands} {[testenv:readme]commands} {[testenv:bandit]commands} # Documentation [testenv:docs] deps = -rdocs/source/requirements.txt commands = sphinx-build -E -W -c docs/source/ -b html docs/source/ docs/build/html sphinx-build -E -W -c docs/source/ -b man docs/source/ docs/build/man [testenv:serve-docs] skip_install = true changedir = docs/build/html deps = commands = python -m http.server {posargs} [testenv:readme] deps = readme_renderer commands = python setup.py check -r -s # Release tooling [testenv:build] skip_install = true deps = wheel setuptools commands = python setup.py -q sdist bdist_wheel [testenv:release] skip_install = true deps = {[testenv:build]deps} twine >= 1.5.0 commands = {[testenv:build]commands} twine upload --skip-existing dist/* # Flake8 Configuration [flake8] # Ignore some flake8-docstrings errors # NOTE(sigmavirus24): While we're still using flake8 2.x, this ignore line # defaults to selecting all other errors so we do not need select=E,F,W,I,D # Once Flake8 3.0 is released and in a good state, we can use both and it will # work well \o/ ignore = D203, W503, E203, N818 exclude = .tox, .git, __pycache__, docs/source/conf.py, build, dist, tests/fixtures/*, *.pyc, *.egg-info, .cache, .eggs max-complexity = 10