././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1659957992.323301 pylama-8.4.1/0000755000175100001710000000000014274171350012412 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1659957987.0 pylama-8.4.1/Changelog0000644000175100001710000001437614274171343014241 0ustar00runnerdocker2022-08-08 k.klenov * Version 8.4.1 * Support TOML configuration (thank you https://github.com/villainy) * Fix pylint integration * Fix linting for empty files 2022-03-11 k.klenov * Version 8.3.8 * Better pytest integration 2021-12-15 k.klenov * Version 8.3.6 * Fixed processing of linters params 2021-12-02 k.klenov * Version 8.3.0 * Added support for default config file `~/.pylama.ini` 2021-11-28 k.klenov * Version 8.2.0 * Added `--max-line-length` to setup max line length for pycodestyle and pylint * Support for linters options in command line 2021-11-27 k.klenov * Version 8.1.4 * Support json format * Support `--from-stdin` option * Changed: pylama only allows to check python files (--force is removed) * Changed: mccabe argument `complexity` -> `max-complexity` 2021-11-26 k.klenov * Version 8.0.5 * Drop support for python 2 * Support python 3.7, 3.8, 3.9 * Support mccabe==0.6.1, pycodestyle==2.8.0, pydocstyle==6.1.1, pyflakes==2.4.0, eradicate==2.0.0, radon==5.1.0, pylint==2.11.1 * Support vulture * Support 'convention' for pydocstyle * License changed to MIT (see LICENSE file for details) * Fix Radon message format 2019-04-10 k.klenov * Version 7.7.1 * Fix CI by removing eradicate from linters 2019-04-10 k.klenov * Version 7.7.0 * Add note about configuration option names * Added eradicate as a requirement #144 * Adds mypy linter #150 * Remove eradicate from default linters. 2018-11-02 k.klenov * Version 7.6.6 * Avoid reference usage for linter specific ignore/select * Update Python requirements description * Update the command help message description * Add eradicate to tools references 2018-10-10 k.klenov * Version 7.6.5 * Fix build 2018-10-09 k.klenov * Version 7.6.4 * No changes other than version number 2018-10-09 k.klenov * Version 7.6.3 * Respect linters params 2018-10-09 k.klenov * Version 7.6.2 * No changes other than version number 2018-10-09 k.klenov * Version 7.6.1 * Merge #131 2018-10-09 k.klenov * Version 7.6.0 * Log errors in linters with ERROR logging level * Include pylint support into pylama by default. 2018-10-02 k.klenov * Version 7.5.5 * Take advantage of startswith accepting a tuple #119 2018-10-02 k.klenov * Version 7.5.4 * Fix build with ASCII locale #116 * Respect tools own config without pylama #117 2018-10-02 k.klenov * Version 7.5.3 * Fix Travis CI 2018-10-02 k.klenov * Version 7.5.2 * Fix Travis CI 2018-10-02 k.klenov * Version 7.5.1 * Fix tests & update authors 2018-10-02 k.klenov * Version 7.5.0 * 2017-09-13 k.klenov * Version 7.4.2 * Fix Git hook with Python 3.6 #111 2017-09-13 k.klenov * Version 7.4.2 2017-09-04 horneds * Version 7.4.1 * Fix Windows encoding problem #108 2016-10-25 horneds * Version 7.2.0 * Replace PEP8 with pycodestyle (c) Serg Baburin 2015-08-17 k.klenov * --abspath 2015-06-30 horneds * Pyflakes 0.9.2 2015-06-03 horneds * Pyflakes 0.9.1-pre 2015-03-25 horneds * Version 6.2.0 * Pep257 0.5.0 * PEP8 1.6.3a0 * Pyflakes 0.8.2a0 2014-10-26 horneds * Version 6.1.0 2014-07-23 horneds * Fix mercurial hook installation (c) MrShark * Version 6.0.1 2014-07-01 horneds * Add sorting (--sort) * Version 6.0.0 2014-06-15 horneds * Better handling pylint properties 2014-06-11 horneds * Pytest support (as plugin) 2014-06-08 horneds * WARNING: Change format INI-options. See README for details. * INI configurations could be read from `pylama.ini`, `setup.cfg`, `pytest.ini`, `tox.ini` files. 2014-06-07 horneds * Reduce duplicate messages #3 * Update pep8 to version 1.6.0a0 2014-05-07 horneds * Update pep8 to version 1.5.7a0 * Update pyflakes to version 0.8.2a0 2014-05-04 horneds * Version 3.1.2 * Parse numbers from ini correctly (c) Grzegorz Śliwiński 2014-03-26 horneds * Version 3.1.1 * Update PEP8 to version 1.5.0 2014-03-24 horneds * File options (and modeline) 'lint_ignore' -> 'ignore', 'lint_select' -> 'select', 'lint' -> 'skip' * Update pep257 * Update pyflakes * Added frosted * Version 3.0.2 2013-11-12 horneds * Version 2.0.4 * Bugfix release 2013-10-27 horneds * Version 2.0.2 2013-10-13 horneds * Version 2.0.1 * Append JavaScript code checker (c) lukaszpiotr * Create plugin structure (move pylint, gjslint to plugins) 2013-09-16 horneds * Version 1.5.4 * fix default liners value for parsing options (c) Grzegorz Śliwiński 2013-09-05 horneds * Version 1.5.3 * Hotfix release 2013-08-30 horneds * Version 1.5.1 * Remove ordereddict requirement for python 2.6 * pep257 0.2.4 * pep8 1.4.7a0 2013-08-07 horneds * Version 1.4.0 * Pylint 1.0.0 * Pep257 0.2.3 * mccabe 0.2.1 2013-07-25 horneds * Version 1.3.3 2013-07-08 horneds * Merge settings from command lines, ini files and modelines * Version 1.3.1 2013-07-03 horneds * PEP8 1.4.6 * Pyflakes 0.7.3 2013-06-25 horneds * Fix file paths 2013-06-20 horneds * Version 1.1.0 * File's sections in `pylama.ini` now supports a filemasks 2013-06-17 horneds * WARNING: Change skipline pattern 'nolint' -> 'noqa' for better compatibility 2013-06-07 horneds * Version 1.0.4 * Added PEP257 checker * Experemental async support 2013-05-31 horneds * Version 1.0.2 * Fix release 1.0.0 2013-05-30 horneds * Beta release 1.0.0 2013-05-29 horneds * Version 0.3.8 * Added docs 2013-05-22 horneds * Version 0.3.6 * Fix release 0.3.5 2013-05-21 horneds * Version 0.3.5 * Now pylama can parse global and file-related options from file. 2013-05-15 horneds * Version 0.3.2 * Fix PEP8 UTF bug 2013-05-03 horneds * Version 0.3.1 * pylint 0.28.0 * pyflakes 0.7.3a0 2013-03-31 klen * Version 0.3.0; * Python3 support; 2013-03-29 klen * Added git and mercurial hooks; * Version 0.2.8 2013-03-22 klen * Version 0.2.7; * Added 'skipline' flag. See `# nolint`; * Added pylint parseable format; 2013-03-15 klen * Version 0.2.3 * Update pyflakes to 0.6.1 2013-03-14 klen * Version 0.2.2 * PEP8 to version 1.4.5; * Added Pylint 0.27.0 (disabled by default) 2013-02-15 klen * Version 0.1.4 * Update pep8 and pyflakes * `skip` option allowed to use unix file masks * `skip` option allowed to use many patterns (split by comma) * Added `report` option for file reports 2012-08-17 klen * Initial release ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1659957987.0 pylama-8.4.1/LICENSE0000644000175100001710000000212214274171343013416 0ustar00runnerdockerThe MIT License (MIT) ===================== Copyright (c) 2021 Pylama Developers 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. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1659957987.0 pylama-8.4.1/MANIFEST.in0000644000175100001710000000040414274171343014150 0ustar00runnerdockerinclude AUTHORS include Changelog include LICENSE include MANIFEST.in include README.rst include dummy.py recursive-include requirements *.txt recursive-include pylama * recursive-exclude * __pycache__ recursive-exclude * *.py[co] recursive-exclude * *.orig ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1659957992.323301 pylama-8.4.1/PKG-INFO0000644000175100001710000003402114274171350013507 0ustar00runnerdockerMetadata-Version: 2.1 Name: pylama Version: 8.4.1 Summary: Code audit tool for python Home-page: https://github.com/klen/pylama Author: Kirill Klenov Author-email: horneds@gmail.com License: MIT Project-URL: Documentation, https://klen.github.io/pylama Project-URL: Source code, https://github.com/klen/pylama Project-URL: Issue tracker, https://github.com/klen/pylama/issues Keywords: qa,linter,pydocstyle,pycodestyle,mccabe,pylint Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: Environment :: Console Classifier: Intended Audience :: Developers Classifier: Intended Audience :: System Administrators Classifier: License :: OSI Approved :: MIT License Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.10 Classifier: Programming Language :: Python :: 3.7 Classifier: Programming Language :: Python :: 3.8 Classifier: Programming Language :: Python :: 3.9 Classifier: Topic :: Software Development :: Quality Assurance Classifier: Topic :: Software Development :: Testing Requires-Python: >=3.7 Provides-Extra: tests Provides-Extra: all Provides-Extra: pylint Provides-Extra: eradicate Provides-Extra: radon Provides-Extra: mypy Provides-Extra: vulture Provides-Extra: toml License-File: LICENSE |logo| Pylama ############# .. _badges: .. image:: https://github.com/klen/pylama/workflows/tests/badge.svg :target: https://github.com/klen/pylama/actions/workflows/tests.yml :alt: Tests Status .. image:: https://github.com/klen/pylama/workflows/docs/badge.svg :target: https://klen.github.io/pylama :alt: Documentation Status .. image:: https://img.shields.io/pypi/v/pylama :target: https://pypi.org/project/pylama/ :alt: PYPI Version .. image:: https://img.shields.io/pypi/pyversions/pylama :target: https://pypi.org/project/pylama/ :alt: Python Versions .. _description: Code audit tool for Python. Pylama wraps these tools: * pycodestyle_ (formerly pep8) © 2012-2013, Florent Xicluna; * pydocstyle_ (formerly pep257 by Vladimir Keleshev) © 2014, Amir Rachum; * PyFlakes_ © 2005-2013, Kevin Watters; * Mccabe_ © Ned Batchelder; * Pylint_ © 2013, Logilab; * Radon_ © Michele Lacchia * eradicate_ © Steven Myint; * Mypy_ © Jukka Lehtosalo and contributors; * Vulture_ © Jendrik Seipp and contributors; .. _documentation: Docs are available at https://klen.github.io/pylama/. Pull requests with documentation enhancements and/or fixes are awesome and most welcome. .. _contents: .. contents:: .. _requirements: Requirements: ============= - Python (3.7, 3.8, 3.9, 3.10) - If your tests are failing on Win platform you are missing: ``curses`` - http://www.lfd.uci.edu/~gohlke/pythonlibs/ (The curses library supplies a terminal-independent screen-painting and keyboard-handling facility for text-based terminals) For python versions < 3.7 install pylama 7.7.1 .. _installation: Installation: ============= **Pylama** can be installed using pip: :: $ pip install pylama TOML configuration can be enabled optionally: :: $ pip install pylama[toml] You may optionally install the requirements with the library: :: $ pip install pylama[mypy] $ pip install pylama[pylint] $ pip install pylama[eradicate] $ pip install pylama[radon] $ pip install pylama[vulture] Or install them all: :: $ pip install pylama[all] .. _quickstart: Quickstart ========== **Pylama** is easy to use and really fun for checking code quality. Just run `pylama` and get common output from all pylama plugins (pycodestyle_, PyFlakes_, etc.) Recursively check the current directory. :: $ pylama Recursively check a path. :: $ pylama Ignore errors :: $ pylama -i W,E501 .. note:: You can choose a group of errors like `D`, `E1`, etc, or special errors like `C0312` Choose code checkers :: $ pylama -l "pycodestyle,mccabe" .. _options: Set Pylama (checkers) options ============================= Command line options -------------------- :: $ pylama --help usage: pylama [-h] [--version] [--verbose] [--options FILE] [--linters LINTERS] [--from-stdin] [--concurrent] [--format {pydocstyle,pycodestyle,pylint,parsable,json}] [--abspath] [--max-line-length MAX_LINE_LENGTH] [--select SELECT] [--ignore IGNORE] [--skip SKIP] [--sort SORT] [--report REPORT] [--hook] [--max-complexity MAX_COMPLEXITY] [--pydocstyle-convention {pep257,numpy,google}] [--pylint-confidence {HIGH,INFERENCE,INFERENCE_FAILURE,UNDEFINED}] [paths ...] Code audit tool for python. positional arguments: paths Paths to files or directories for code check. optional arguments: -h, --help show this help message and exit --version show program's version number and exit --verbose, -v Verbose mode. --options FILE, -o FILE Specify configuration file. Looks for pylama.ini, setup.cfg, tox.ini, or pytest.ini in the current directory (default: None) --linters LINTERS, -l LINTERS Select linters. (comma-separated). Choices are eradicate,mccabe,mypy,pycodestyle,pydocstyle,pyflakes,pylint,isort. --from-stdin Interpret the stdin as a python script, whose filename needs to be passed as the path argument. --concurrent, --async Enable async mode. Useful for checking a lot of files. --format {pydocstyle,pycodestyle,pylint,parsable,json}, -f {pydocstyle,pycodestyle,pylint,parsable,json} Choose output format. --abspath, -a Use absolute paths in output. --max-line-length MAX_LINE_LENGTH, -m MAX_LINE_LENGTH Maximum allowed line length --select SELECT, -s SELECT Select errors and warnings. (comma-separated list) --ignore IGNORE, -i IGNORE Ignore errors and warnings. (comma-separated) --skip SKIP Skip files by masks (comma-separated, Ex. */messages.py) --sort SORT Sort result by error types. Ex. E,W,D --report REPORT, -r REPORT Send report to file [REPORT] --hook Install Git (Mercurial) hook. --max-complexity MAX_COMPLEXITY Max complexity threshold .. note:: additional options may be available depending on installed linters .. _modeline: File modelines -------------- You can set options for **Pylama** inside a source file. Use a pylama *modeline* for this, anywhere in the file. Format: :: # pylama:{name1}={value1}:{name2}={value2}:... For example, ignore warnings except W301: :: # pylama:ignore=W:select=W301 Disable code checking for current file: :: # pylama:skip=1 Those options have a higher priority. .. _skiplines: Skip lines (noqa) ----------------- Just add ``# noqa`` at the end of a line to ignore: :: def urgent_fuction(): unused_var = 'No errors here' # noqa .. _config: Configuration file ================== **Pylama** looks for a configuration file in the current directory. You can use a “global” configuration, stored in `.pylama.ini` in your home directory. This will be used as a fallback configuration. The program searches for the first matching configuration file in the directories of command line argument. Pylama looks for the configuration in this order: :: ./pylama.ini ./pyproject.toml ./setup.cfg ./tox.ini ./pytest.ini ~/.pylama.ini The ``--option`` / ``-o`` argument can be used to specify a configuration file. INI-style configuration ----------------------- Pylama searches for sections whose names start with `pylama`. The `pylama` section configures global options like `linters` and `skip`. :: [pylama] format = pylint skip = */.tox/*,*/.env/* linters = pylint,mccabe ignore = F0401,C0111,E731 Set code-checkers' options ^^^^^^^^^^^^^^^^^^^^^^^^^^ You can set options for a special code checkers with pylama configurations. :: [pylama:pyflakes] builtins = _ [pylama:pycodestyle] max_line_length = 100 [pylama:pylint] max_line_length = 100 disable = R See code-checkers' documentation for more info. Note that dashes are replaced by underscores (e.g. Pylint's ``max-line-length`` becomes ``max_line_length``). Set options for file (group of files) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ You can set options for special file (group of files) with sections: The options have a higher priority than in the `pylama` section. :: [pylama:*/pylama/main.py] ignore = C901,R0914,W0212 select = R [pylama:*/tests.py] ignore = C0110 [pylama:*/setup.py] skip = 1 TOML configuration ----------------------- Pylama searches for sections whose names start with `tool.pylama`. The `tool.pylama` section configures global options like `linters` and `skip`. :: [tool.pylama] format = "pylint" skip = "*/.tox/*,*/.env/*" linters = "pylint,mccabe" ignore = "F0401,C0111,E731" Set code-checkers' options ^^^^^^^^^^^^^^^^^^^^^^^^^^ You can set options for a special code checkers with pylama configurations. :: [tool.pylama.linter.pyflakes] builtins = "_" [tool.pylama.linter.pycodestyle] max_line_length = 100 [tool.pylama.linter.pylint] max_line_length = 100 disable = "R" See code-checkers' documentation for more info. Note that dashes are replaced by underscores (e.g. Pylint's ``max-line-length`` becomes ``max_line_length``). Set options for file (group of files) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ You can set options for special file (group of files) with sections: The options have a higher priority than in the `tool.pylama` section. :: [[tool.pylama.files]] path = "*/pylama/main.py" ignore = "C901,R0914,W0212" select = "R" [[tool.pylama.files]] path = "pylama:*/tests.py" ignore = "C0110" [[tool.pylama.files]] path = "pylama:*/setup.py" skip = 1 Pytest integration ================== Pylama has Pytest_ support. The package automatically registers itself as a pytest plugin during installation. Pylama also supports the `pytest_cache` plugin. Check files with pylama :: pytest --pylama ... The recommended way to set pylama options when using pytest — configuration files (see below). Writing a linter ================ You can write a custom extension for Pylama. The custom linter should be a python module. Its name should be like 'pylama_'. In 'setup.py', 'pylama.linter' entry point should be defined. :: setup( # ... entry_points={ 'pylama.linter': ['lintername = pylama_lintername.main:Linter'], } # ... ) 'Linter' should be an instance of 'pylama.lint.Linter' class. It must implement two methods: 1. ``allow`` takes a `path` argument and returns true if the linter can check this file for errors. 2. ``run`` takes a `path` argument and `meta` keyword arguments and returns a list of errors. Example: -------- Just a virtual 'WOW' checker. setup.py: :: setup( name='pylama_wow', install_requires=[ 'setuptools' ], entry_points={ 'pylama.linter': ['wow = pylama_wow.main:Linter'], } # ... ) pylama_wow.py: :: from pylama.lint import Linter as BaseLinter class Linter(BaseLinter): def allow(self, path): return 'wow' in path def run(self, path, **meta): with open(path) as f: if 'wow' in f.read(): return [{ lnum: 0, col: 0, text: '"wow" has been found.', type: 'WOW' }] Run pylama from python code --------------------------- :: from pylama.main import check_paths, parse_options # Use and/or modify 0 or more of the options defined as keys in the variable my_redefined_options below. # To use defaults for any option, remove that key completely. my_redefined_options = { 'linters': ['pep257', 'pydocstyle', 'pycodestyle', 'pyflakes' ...], 'ignore': ['D203', 'D213', 'D406', 'D407', 'D413' ...], 'select': ['R1705' ...], 'sort': 'F,E,W,C,D,...', 'skip': '*__init__.py,*/test/*.py,...', 'async': True, 'force': True ... } # relative path of the directory in which pylama should check my_path = '...' options = parse_options([my_path], **my_redefined_options) errors = check_paths(my_path, options, rootdir='.') .. _bagtracker: Bug tracker ----------- If you have any suggestions, bug reports or annoyances please report them to the issue tracker at https://github.com/klen/pylama/issues .. _contributing: Contributing ------------ Development of `pylama` happens at GitHub: https://github.com/klen/pylama Contributors ^^^^^^^^^^^^ See CONTRIBUTORS_. .. _license: License ------- This is free software. You are permitted to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of it, under the terms of the MIT License. See LICENSE file for the complete license. This software is provided WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE file for the complete disclaimer. .. _links: .. _CONTRIBUTORS: https://github.com/klen/pylama/graphs/contributors .. _Mccabe: http://nedbatchelder.com/blog/200803/python_code_complexity_microtool.html .. _pydocstyle: https://github.com/PyCQA/pydocstyle/ .. _pycodestyle: https://github.com/PyCQA/pycodestyle .. _PyFlakes: https://github.com/pyflakes/pyflakes .. _Pylint: http://pylint.org .. _Pytest: http://pytest.org .. _klen: http://klen.github.io/ .. _eradicate: https://github.com/myint/eradicate .. _Mypy: https://github.com/python/mypy .. _Vulture: https://github.com/jendrikseipp/vulture .. |logo| image:: https://raw.github.com/klen/pylama/develop/docs/_static/logo.png :width: 100 .. _Radon: https://github.com/rubik/radon ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1659957987.0 pylama-8.4.1/README.rst0000644000175100001710000003134214274171343014106 0ustar00runnerdocker|logo| Pylama ############# .. _badges: .. image:: https://github.com/klen/pylama/workflows/tests/badge.svg :target: https://github.com/klen/pylama/actions/workflows/tests.yml :alt: Tests Status .. image:: https://github.com/klen/pylama/workflows/docs/badge.svg :target: https://klen.github.io/pylama :alt: Documentation Status .. image:: https://img.shields.io/pypi/v/pylama :target: https://pypi.org/project/pylama/ :alt: PYPI Version .. image:: https://img.shields.io/pypi/pyversions/pylama :target: https://pypi.org/project/pylama/ :alt: Python Versions .. _description: Code audit tool for Python. Pylama wraps these tools: * pycodestyle_ (formerly pep8) © 2012-2013, Florent Xicluna; * pydocstyle_ (formerly pep257 by Vladimir Keleshev) © 2014, Amir Rachum; * PyFlakes_ © 2005-2013, Kevin Watters; * Mccabe_ © Ned Batchelder; * Pylint_ © 2013, Logilab; * Radon_ © Michele Lacchia * eradicate_ © Steven Myint; * Mypy_ © Jukka Lehtosalo and contributors; * Vulture_ © Jendrik Seipp and contributors; .. _documentation: Docs are available at https://klen.github.io/pylama/. Pull requests with documentation enhancements and/or fixes are awesome and most welcome. .. _contents: .. contents:: .. _requirements: Requirements: ============= - Python (3.7, 3.8, 3.9, 3.10) - If your tests are failing on Win platform you are missing: ``curses`` - http://www.lfd.uci.edu/~gohlke/pythonlibs/ (The curses library supplies a terminal-independent screen-painting and keyboard-handling facility for text-based terminals) For python versions < 3.7 install pylama 7.7.1 .. _installation: Installation: ============= **Pylama** can be installed using pip: :: $ pip install pylama TOML configuration can be enabled optionally: :: $ pip install pylama[toml] You may optionally install the requirements with the library: :: $ pip install pylama[mypy] $ pip install pylama[pylint] $ pip install pylama[eradicate] $ pip install pylama[radon] $ pip install pylama[vulture] Or install them all: :: $ pip install pylama[all] .. _quickstart: Quickstart ========== **Pylama** is easy to use and really fun for checking code quality. Just run `pylama` and get common output from all pylama plugins (pycodestyle_, PyFlakes_, etc.) Recursively check the current directory. :: $ pylama Recursively check a path. :: $ pylama Ignore errors :: $ pylama -i W,E501 .. note:: You can choose a group of errors like `D`, `E1`, etc, or special errors like `C0312` Choose code checkers :: $ pylama -l "pycodestyle,mccabe" .. _options: Set Pylama (checkers) options ============================= Command line options -------------------- :: $ pylama --help usage: pylama [-h] [--version] [--verbose] [--options FILE] [--linters LINTERS] [--from-stdin] [--concurrent] [--format {pydocstyle,pycodestyle,pylint,parsable,json}] [--abspath] [--max-line-length MAX_LINE_LENGTH] [--select SELECT] [--ignore IGNORE] [--skip SKIP] [--sort SORT] [--report REPORT] [--hook] [--max-complexity MAX_COMPLEXITY] [--pydocstyle-convention {pep257,numpy,google}] [--pylint-confidence {HIGH,INFERENCE,INFERENCE_FAILURE,UNDEFINED}] [paths ...] Code audit tool for python. positional arguments: paths Paths to files or directories for code check. optional arguments: -h, --help show this help message and exit --version show program's version number and exit --verbose, -v Verbose mode. --options FILE, -o FILE Specify configuration file. Looks for pylama.ini, setup.cfg, tox.ini, or pytest.ini in the current directory (default: None) --linters LINTERS, -l LINTERS Select linters. (comma-separated). Choices are eradicate,mccabe,mypy,pycodestyle,pydocstyle,pyflakes,pylint,isort. --from-stdin Interpret the stdin as a python script, whose filename needs to be passed as the path argument. --concurrent, --async Enable async mode. Useful for checking a lot of files. --format {pydocstyle,pycodestyle,pylint,parsable,json}, -f {pydocstyle,pycodestyle,pylint,parsable,json} Choose output format. --abspath, -a Use absolute paths in output. --max-line-length MAX_LINE_LENGTH, -m MAX_LINE_LENGTH Maximum allowed line length --select SELECT, -s SELECT Select errors and warnings. (comma-separated list) --ignore IGNORE, -i IGNORE Ignore errors and warnings. (comma-separated) --skip SKIP Skip files by masks (comma-separated, Ex. */messages.py) --sort SORT Sort result by error types. Ex. E,W,D --report REPORT, -r REPORT Send report to file [REPORT] --hook Install Git (Mercurial) hook. --max-complexity MAX_COMPLEXITY Max complexity threshold .. note:: additional options may be available depending on installed linters .. _modeline: File modelines -------------- You can set options for **Pylama** inside a source file. Use a pylama *modeline* for this, anywhere in the file. Format: :: # pylama:{name1}={value1}:{name2}={value2}:... For example, ignore warnings except W301: :: # pylama:ignore=W:select=W301 Disable code checking for current file: :: # pylama:skip=1 Those options have a higher priority. .. _skiplines: Skip lines (noqa) ----------------- Just add ``# noqa`` at the end of a line to ignore: :: def urgent_fuction(): unused_var = 'No errors here' # noqa .. _config: Configuration file ================== **Pylama** looks for a configuration file in the current directory. You can use a “global” configuration, stored in `.pylama.ini` in your home directory. This will be used as a fallback configuration. The program searches for the first matching configuration file in the directories of command line argument. Pylama looks for the configuration in this order: :: ./pylama.ini ./pyproject.toml ./setup.cfg ./tox.ini ./pytest.ini ~/.pylama.ini The ``--option`` / ``-o`` argument can be used to specify a configuration file. INI-style configuration ----------------------- Pylama searches for sections whose names start with `pylama`. The `pylama` section configures global options like `linters` and `skip`. :: [pylama] format = pylint skip = */.tox/*,*/.env/* linters = pylint,mccabe ignore = F0401,C0111,E731 Set code-checkers' options ^^^^^^^^^^^^^^^^^^^^^^^^^^ You can set options for a special code checkers with pylama configurations. :: [pylama:pyflakes] builtins = _ [pylama:pycodestyle] max_line_length = 100 [pylama:pylint] max_line_length = 100 disable = R See code-checkers' documentation for more info. Note that dashes are replaced by underscores (e.g. Pylint's ``max-line-length`` becomes ``max_line_length``). Set options for file (group of files) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ You can set options for special file (group of files) with sections: The options have a higher priority than in the `pylama` section. :: [pylama:*/pylama/main.py] ignore = C901,R0914,W0212 select = R [pylama:*/tests.py] ignore = C0110 [pylama:*/setup.py] skip = 1 TOML configuration ----------------------- Pylama searches for sections whose names start with `tool.pylama`. The `tool.pylama` section configures global options like `linters` and `skip`. :: [tool.pylama] format = "pylint" skip = "*/.tox/*,*/.env/*" linters = "pylint,mccabe" ignore = "F0401,C0111,E731" Set code-checkers' options ^^^^^^^^^^^^^^^^^^^^^^^^^^ You can set options for a special code checkers with pylama configurations. :: [tool.pylama.linter.pyflakes] builtins = "_" [tool.pylama.linter.pycodestyle] max_line_length = 100 [tool.pylama.linter.pylint] max_line_length = 100 disable = "R" See code-checkers' documentation for more info. Note that dashes are replaced by underscores (e.g. Pylint's ``max-line-length`` becomes ``max_line_length``). Set options for file (group of files) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ You can set options for special file (group of files) with sections: The options have a higher priority than in the `tool.pylama` section. :: [[tool.pylama.files]] path = "*/pylama/main.py" ignore = "C901,R0914,W0212" select = "R" [[tool.pylama.files]] path = "pylama:*/tests.py" ignore = "C0110" [[tool.pylama.files]] path = "pylama:*/setup.py" skip = 1 Pytest integration ================== Pylama has Pytest_ support. The package automatically registers itself as a pytest plugin during installation. Pylama also supports the `pytest_cache` plugin. Check files with pylama :: pytest --pylama ... The recommended way to set pylama options when using pytest — configuration files (see below). Writing a linter ================ You can write a custom extension for Pylama. The custom linter should be a python module. Its name should be like 'pylama_'. In 'setup.py', 'pylama.linter' entry point should be defined. :: setup( # ... entry_points={ 'pylama.linter': ['lintername = pylama_lintername.main:Linter'], } # ... ) 'Linter' should be an instance of 'pylama.lint.Linter' class. It must implement two methods: 1. ``allow`` takes a `path` argument and returns true if the linter can check this file for errors. 2. ``run`` takes a `path` argument and `meta` keyword arguments and returns a list of errors. Example: -------- Just a virtual 'WOW' checker. setup.py: :: setup( name='pylama_wow', install_requires=[ 'setuptools' ], entry_points={ 'pylama.linter': ['wow = pylama_wow.main:Linter'], } # ... ) pylama_wow.py: :: from pylama.lint import Linter as BaseLinter class Linter(BaseLinter): def allow(self, path): return 'wow' in path def run(self, path, **meta): with open(path) as f: if 'wow' in f.read(): return [{ lnum: 0, col: 0, text: '"wow" has been found.', type: 'WOW' }] Run pylama from python code --------------------------- :: from pylama.main import check_paths, parse_options # Use and/or modify 0 or more of the options defined as keys in the variable my_redefined_options below. # To use defaults for any option, remove that key completely. my_redefined_options = { 'linters': ['pep257', 'pydocstyle', 'pycodestyle', 'pyflakes' ...], 'ignore': ['D203', 'D213', 'D406', 'D407', 'D413' ...], 'select': ['R1705' ...], 'sort': 'F,E,W,C,D,...', 'skip': '*__init__.py,*/test/*.py,...', 'async': True, 'force': True ... } # relative path of the directory in which pylama should check my_path = '...' options = parse_options([my_path], **my_redefined_options) errors = check_paths(my_path, options, rootdir='.') .. _bagtracker: Bug tracker ----------- If you have any suggestions, bug reports or annoyances please report them to the issue tracker at https://github.com/klen/pylama/issues .. _contributing: Contributing ------------ Development of `pylama` happens at GitHub: https://github.com/klen/pylama Contributors ^^^^^^^^^^^^ See CONTRIBUTORS_. .. _license: License ------- This is free software. You are permitted to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of it, under the terms of the MIT License. See LICENSE file for the complete license. This software is provided WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE file for the complete disclaimer. .. _links: .. _CONTRIBUTORS: https://github.com/klen/pylama/graphs/contributors .. _Mccabe: http://nedbatchelder.com/blog/200803/python_code_complexity_microtool.html .. _pydocstyle: https://github.com/PyCQA/pydocstyle/ .. _pycodestyle: https://github.com/PyCQA/pycodestyle .. _PyFlakes: https://github.com/pyflakes/pyflakes .. _Pylint: http://pylint.org .. _Pytest: http://pytest.org .. _klen: http://klen.github.io/ .. _eradicate: https://github.com/myint/eradicate .. _Mypy: https://github.com/python/mypy .. _Vulture: https://github.com/jendrikseipp/vulture .. |logo| image:: https://raw.github.com/klen/pylama/develop/docs/_static/logo.png :width: 100 .. _Radon: https://github.com/rubik/radon ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1659957987.0 pylama-8.4.1/dummy.py0000644000175100001710000000740014274171343014122 0ustar00runnerdocker#!/usr/bin/env python # coding: utf-8 # (c) 2005 Divmod, Inc. See LICENSE file for details # commented code #import os # from foo import junk # a = 3 a = 4 #foo(1, 2, 3) class Message(object): message = '' message_args = () def __init__(self, filename, loc, use_column=True): self.filename = filename self.lineno = loc.lineno self.col = getattr(loc, 'col_offset', None) if use_column else None test = 1 if test == 1: if test == 1: return 28 elif test == 2: return 28 return 28 elif test == 2: return 28 def __str__(self): return '%s:%s: %s' % (self.filename, self.lineno, self.message % self.message_args) class UnusedImport(Message): message = 'W402 %r imported but unused' def __init__(self, filename, lineno, name): Message.__init__(self, filename, lineno) self.message_args = (name,) class RedefinedWhileUnused(Message): message = 'W801 redefinition of unused %r from line %r' def __init__(self, filename, lineno, name, orig_lineno): Message.__init__(self, filename, lineno) self.message_args = (name, orig_lineno) class ImportShadowedByLoopVar(Message): message = 'W403 import %r from line %r shadowed by loop variable' def __init__(self, filename, lineno, name, orig_lineno): Message.__init__(self, filename, lineno) self.message_args = (name, orig_lineno) class ImportStarUsed(Message): message = "W404 'from %s import *' used; unable to detect undefined names" def __init__(self, filename, lineno, modname): Message.__init__(self, filename, lineno) self.message_args = (modname,) class UndefinedName(Message): message = 'W802 undefined name %r' def __init__(self, filename, lineno, name): Message.__init__(self, filename, lineno) self.message_args = (name,) class UndefinedExport(Message): message = 'W803 undefined name %r in __all__' def __init__(self, filename, lineno, name): Message.__init__(self, filename, lineno) self.message_args = (name,) class UndefinedLocal(Message): message = "W804 local variable %r (defined in enclosing scope on line " \ "%r) referenced before assignment" def __init__(self, filename, lineno, name, orig_lineno): Message.__init__(self, filename, lineno) self.message_args = (name, orig_lineno) class DuplicateArgument(Message): message = 'W805 duplicate argument %r in function definition' def __init__(self, filename, lineno, name): Message.__init__(self, filename, lineno) self.message_args = (name,) class RedefinedFunction(Message): message = 'W806 redefinition of function %r from line %r' def __init__(self, filename, lineno, name, orig_lineno): Message.__init__(self, filename, lineno) self.message_args = (name, orig_lineno) class LateFutureImport(Message): message = 'W405 future import(s) %r after other statements' def __init__(self, filename, lineno, names): Message.__init__(self, filename, lineno) self.message_args = (names,) class UnusedVariable(Message): """ Indicates that a variable has been explicitly assigned to but not actually used. """ message = 'W806 local variable %r is assigned to but never used' def __init__(self, filename, lineno, names): Message.__init__(self, filename, lineno) self.message_args = (names,) error = 1 # noQa and some comments another = 42 class BadTyping(Message): """Test the MyPy linting.""" message = 'error: No return value expected' def bad_method(self): # type: () -> None """Return type mismatch.""" return 1 ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1659957992.319301 pylama-8.4.1/pylama/0000755000175100001710000000000014274171350013675 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1659957987.0 pylama-8.4.1/pylama/__init__.py0000644000175100001710000000025014274171343016005 0ustar00runnerdocker"""Code audit tool for python. :copyright: 2013 by Kirill Klenov. """ import logging __version__ = "8.4.1" LOGGER = logging.getLogger("pylama") # pylama:ignore=D ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1659957987.0 pylama-8.4.1/pylama/__main__.py0000644000175100001710000000015314274171343015770 0ustar00runnerdocker"""Support the module execution.""" from pylama.main import shell if __name__ == "__main__": shell() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1659957987.0 pylama-8.4.1/pylama/check_async.py0000644000175100001710000000201214274171343016516 0ustar00runnerdocker"""Support for checking code asynchronously.""" import logging from concurrent.futures import ProcessPoolExecutor from pathlib import Path from typing import List from pylama.config import Namespace from pylama.errors import Error try: import multiprocessing CPU_COUNT = multiprocessing.cpu_count() except (ImportError, NotImplementedError): CPU_COUNT = 1 from pylama.core import run LOGGER = logging.getLogger("pylama") def worker(params): """Do work.""" path, code, options, rootdir = params return run(path, code=code, rootdir=rootdir, options=options) def check_async( paths: List[str], code: str = None, options: Namespace = None, rootdir: Path = None ) -> List[Error]: """Check given paths asynchronously.""" with ProcessPoolExecutor(CPU_COUNT) as pool: return [ err for res in pool.map( worker, [(path, code, options, rootdir) for path in paths] ) for err in res ] # pylama:ignore=W0212,D210,F0001 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1659957987.0 pylama-8.4.1/pylama/config.py0000644000175100001710000002160314274171343015520 0ustar00runnerdocker"""Parse arguments from command line and configuration files.""" import fnmatch import logging import os import re import sys from argparse import ArgumentParser, Namespace from pathlib import Path from typing import Any, Collection, Dict, List, Optional, Set, Union from pylama import LOGGER, __version__ from pylama.libs import inirama from pylama.lint import LINTERS try: from pylama import config_toml CONFIG_FILES = ["pylama.ini", "pyproject.toml", "setup.cfg", "tox.ini", "pytest.ini"] except ImportError: CONFIG_FILES = ["pylama.ini", "setup.cfg", "tox.ini", "pytest.ini"] #: A default checkers DEFAULT_LINTERS = "pycodestyle", "pyflakes", "mccabe" CURDIR = Path.cwd() HOMECFG = Path.home() / ".pylama.ini" DEFAULT_SECTION = "pylama" # Setup a logger LOGGER.propagate = False STREAM = logging.StreamHandler(sys.stdout) LOGGER.addHandler(STREAM) class _Default: def __init__(self, value=None): self.value = value def __str__(self): return str(self.value) def __repr__(self): return f"<_Default [{self.value}]>" def split_csp_str(val: Union[Collection[str], str]) -> Set[str]: """Split comma separated string into unique values, keeping their order.""" if isinstance(val, str): val = val.strip().split(",") return set(x for x in val if x) def prepare_sorter(val: Union[Collection[str], str]) -> Optional[Dict[str, int]]: """Parse sort value.""" if val: types = split_csp_str(val) return dict((v, n) for n, v in enumerate(types, 1)) return None def parse_linters(linters: str) -> List[str]: """Initialize choosen linters.""" return [name for name in split_csp_str(linters) if name in LINTERS] def get_default_config_file(rootdir: Path = None) -> Optional[str]: """Search for configuration file.""" if rootdir is None: return DEFAULT_CONFIG_FILE for filename in CONFIG_FILES: path = rootdir / filename if path.is_file() and os.access(path, os.R_OK): return path.as_posix() return None DEFAULT_CONFIG_FILE = get_default_config_file(CURDIR) def setup_parser() -> ArgumentParser: """Create and setup parser for command line.""" parser = ArgumentParser(description="Code audit tool for python.") parser.add_argument( "paths", nargs="*", default=_Default([CURDIR.as_posix()]), help="Paths to files or directories for code check.", ) parser.add_argument( "--version", action="version", version="%(prog)s " + __version__ ) parser.add_argument("--verbose", "-v", action="store_true", help="Verbose mode.") parser.add_argument( "--options", "-o", default=DEFAULT_CONFIG_FILE, metavar="FILE", help=( "Specify configuration file. " f"Looks for {', '.join(CONFIG_FILES[:-1])}, or {CONFIG_FILES[-1]}" f" in the current directory (default: {DEFAULT_CONFIG_FILE})" ), ) parser.add_argument( "--linters", "-l", default=_Default(",".join(DEFAULT_LINTERS)), type=parse_linters, help=( f"Select linters. (comma-separated). Choices are {','.join(s for s in LINTERS)}." ), ) parser.add_argument( "--from-stdin", action="store_true", help="Interpret the stdin as a python script, " "whose filename needs to be passed as the path argument.", ) parser.add_argument( "--concurrent", "--async", action="store_true", help="Enable async mode. Useful for checking a lot of files. ", ) parser.add_argument( "--format", "-f", default=_Default("pycodestyle"), choices=["pydocstyle", "pycodestyle", "pylint", "parsable", "json"], help="Choose output format.", ) parser.add_argument( "--abspath", "-a", action="store_true", default=_Default(False), help="Use absolute paths in output.", ) parser.add_argument( "--max-line-length", "-m", default=_Default(100), type=int, help="Maximum allowed line length", ) parser.add_argument( "--select", "-s", default=_Default(""), type=split_csp_str, help="Select errors and warnings. (comma-separated list)", ) parser.add_argument( "--ignore", "-i", default=_Default(""), type=split_csp_str, help="Ignore errors and warnings. (comma-separated)", ) parser.add_argument( "--skip", default=_Default(""), type=lambda s: [re.compile(fnmatch.translate(p)) for p in s.split(",") if p], help="Skip files by masks (comma-separated, Ex. */messages.py)", ) parser.add_argument( "--sort", default=_Default(), type=prepare_sorter, help="Sort result by error types. Ex. E,W,D", ) parser.add_argument("--report", "-r", help="Send report to file [REPORT]") parser.add_argument( "--hook", action="store_true", help="Install Git (Mercurial) hook." ) for linter_type in LINTERS.values(): linter_type.add_args(parser) return parser def parse_options( # noqa args: List[str] = None, config: bool = True, rootdir: Path = CURDIR, **overrides ) -> Namespace: """Parse options from command line and configuration files.""" # Parse args from command string parser = setup_parser() actions = dict( (a.dest, a) for a in parser._actions ) # pylint: disable=protected-access options = parser.parse_args(args or []) options.file_params = {} options.linters_params = {} # Compile options from ini if config: cfg = get_config(options.options, rootdir=rootdir) for opt, val in cfg.default.items(): LOGGER.info("Find option %s (%s)", opt, val) passed_value = getattr(options, opt, _Default()) if isinstance(passed_value, _Default): if opt == "paths": val = val.split() if opt == "skip": val = fix_pathname_sep(val) setattr(options, opt, _Default(val)) # Parse file related options for name, opts in cfg.sections.items(): if name == cfg.default_section: continue if name.startswith("pylama"): name = name[7:] if name in LINTERS: options.linters_params[name] = dict(opts) continue mask = re.compile(fnmatch.translate(fix_pathname_sep(name))) options.file_params[mask] = dict(opts) # Override options for opt, val in overrides.items(): setattr(options, opt, process_value(actions, opt, val)) # Postprocess options for name in options.__dict__: value = getattr(options, name) if isinstance(value, _Default): setattr(options, name, process_value(actions, name, value.value)) if options.concurrent and "pylint" in options.linters: LOGGER.warning("Can't parse code asynchronously with pylint enabled.") options.concurrent = False return options def process_value(actions: Dict, name: str, value: Any) -> Any: """Compile option value.""" action = actions.get(name) if not action: return value if callable(action.type): return action.type(value) if action.const: return bool(int(value)) return value def get_config(user_path: str = None, rootdir: Path = None) -> inirama.Namespace: """Load configuration from files.""" cfg_path = user_path or get_default_config_file(rootdir) if not cfg_path and HOMECFG.exists(): cfg_path = HOMECFG.as_posix() if cfg_path: LOGGER.info("Read config: %s", cfg_path) if cfg_path.endswith(".toml"): return get_config_toml(cfg_path) else: return get_config_ini(cfg_path) return inirama.Namespace() def get_config_ini(ini_path: str) -> inirama.Namespace: """Load configuration from INI.""" config = inirama.Namespace() config.default_section = DEFAULT_SECTION config.read(ini_path) return config def get_config_toml(toml_path: str) -> inirama.Namespace: """Load configuration from TOML.""" config = config_toml.Namespace() config.default_section = DEFAULT_SECTION config.read(toml_path) return config def setup_logger(options: Namespace): """Do the logger setup with options.""" LOGGER.setLevel(logging.INFO if options.verbose else logging.WARN) if options.report: LOGGER.removeHandler(STREAM) LOGGER.addHandler(logging.FileHandler(options.report, mode="w")) if options.options: LOGGER.info("Try to read configuration from: %r", options.options) def fix_pathname_sep(val: str) -> str: """Fix pathnames for Win.""" return val.replace(os.altsep or "\\", os.sep) # pylama:ignore=W0212,D210,F0001 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1659957987.0 pylama-8.4.1/pylama/config_toml.py0000644000175100001710000000166614274171343016562 0ustar00runnerdocker"""Pylama TOML configuration.""" import toml from pylama.libs.inirama import Namespace as _Namespace class Namespace(_Namespace): """Inirama-style wrapper for TOML config.""" def parse(self, source: str, update: bool = True, **params): """Parse TOML source as string.""" content = toml.loads(source) tool = content.get("tool", {}) pylama = tool.get("pylama", {}) linters = pylama.pop("linter", {}) files = pylama.pop("files", []) for name, value in pylama.items(): self["pylama"][name] = value for linter, options in linters.items(): for name, value in options.items(): self[f"pylama:{linter}"][name] = value for file in files: path = file.pop("path", None) if path is None: continue for name, value in file.items(): self[f"pylama:{path}"][name] = value ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1659957987.0 pylama-8.4.1/pylama/context.py0000644000175100001710000001354014274171343015740 0ustar00runnerdocker"""Manage resources.""" import ast import os.path as op import re from argparse import Namespace from copy import copy from functools import lru_cache from pathlib import Path from tempfile import NamedTemporaryFile, mkdtemp from typing import Dict, List, Set from pylama.errors import Error from pylama.utils import read # Parse modeline MODELINE_RE = re.compile( r"^\s*#\s+(?:pylama:)\s*((?:[\w_]*=[^:\n\s]+:?)+)", re.I | re.M ).search SKIP_PATTERN = re.compile(r"# *noqa\b", re.I).search class RunContext: # pylint: disable=R0902 """Manage resources.""" __slots__ = ( "errors", "options", "skip", "ignore", "select", "linters", "linters_params", "filename", "_ast", "_from_stdin", "_source", "_tempfile", "_lines", ) def __init__(self, filename: str, source: str = None, options: Namespace = None): """Initialize the class.""" self.errors: List[Error] = [] self.options = options self.skip = False self.ignore = set() self.select = set() self.linters = [] self.linters_params = {} self._ast = None self._from_stdin = source is not None self._source = source self._tempfile = None self._lines = None if options: if options.abspath: filename = op.abspath(filename) self.skip = options.skip and any( ptrn.match(filename) for ptrn in options.skip ) self.linters.extend(options.linters) self.ignore |= options.ignore self.select |= options.select self.linters_params.update(options.linters_params) for mask in options.file_params: if mask.match(filename): fparams = options.file_params[mask] self.update_params(**fparams) self.filename = filename # Read/parse modeline if not self.skip: modeline = MODELINE_RE(self.source) if modeline: values = modeline.group(1).split(":") self.update_params(**dict(v.split("=", 1) for v in values)) # type: ignore def __enter__(self): """Enter to context.""" return self def __exit__(self, etype, evalue, _): """Exit from the context.""" if self._tempfile is not None: tmpfile = Path(self._tempfile) tmpfile.unlink() tmpfile.parent.rmdir() if evalue is not None: if etype is IOError: self.push(text=f"{evalue}", number="E001") elif etype is UnicodeDecodeError: self.push(text=f"UnicodeError: {self.filename}", number="E001") elif etype is SyntaxError: self.push( lnum=evalue.lineno, col=evalue.offset, text=f"SyntaxError: {evalue.args[0]}", ) else: self.push(lnum=1, col=1, text=str(evalue)) return False return True @property def source(self): """Get the current source code.""" if self._source is None: self._source = read(self.filename) return self._source @property def lines(self): """Split source to lines.""" if self._lines is None: self._lines = self.source.splitlines(True) return self._lines @property def ast(self): """Get the AST for the source.""" if self._ast is None: self._ast = compile(self.source, self.filename, "exec", ast.PyCF_ONLY_AST) return self._ast @property def temp_filename(self): """Get a filename for run external command.""" if not self._from_stdin: return self.filename if self._tempfile is None: file = NamedTemporaryFile( # noqa "w", encoding="utf8", suffix=".py", dir=mkdtemp(prefix="pylama_"), delete=False, ) file.write(self.source) file.close() self._tempfile = file.name return self._tempfile def update_params(self, ignore=None, select=None, linters=None, skip=None, **_): """Update general params (from file configs or modeline).""" if select: self.select |= set(select.split(",")) if ignore: self.ignore |= set(ignore.split(",")) if linters: self.linters = linters.split(",") if skip is not None: self.skip = bool(int(skip)) @lru_cache(42) def get_params(self, name: str) -> Dict: """Get params for a linter with the given name.""" lparams = copy(self.linters_params.get(name, {})) for key in ("ignore", "select"): if key in lparams and not isinstance(lparams[key], set): lparams[key] = set(lparams[key].split(",")) return lparams @lru_cache(42) def get_filter(self, name: str, key: str) -> Set: """Get select/ignore from linter params.""" lparams = self.get_params(name) return lparams.get(key, set()) def push(self, filtrate: bool = True, **params): """Record an error.""" err = Error(filename=self.filename, **params) number = err.number if len(self.lines) >= err.lnum and SKIP_PATTERN(self.lines[err.lnum - 1]): return None if filtrate: for rule in self.select | self.get_filter(err.source, "select"): if number.startswith(rule): return self.errors.append(err) for rule in self.ignore | self.get_filter(err.source, "ignore"): if number.startswith(rule): return None return self.errors.append(err) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1659957987.0 pylama-8.4.1/pylama/core.py0000644000175100001710000000324414274171343015204 0ustar00runnerdocker"""Pylama's core functionality. Prepare params, check a modeline and run the checkers. """ import os.path as op from pathlib import Path from typing import List from pylama.config import CURDIR, LOGGER, Namespace from pylama.context import RunContext from pylama.errors import Error, default_sorter, remove_duplicates from pylama.lint import LINTERS, LinterV2 def run( path: str, code: str = None, rootdir: Path = CURDIR, options: Namespace = None ) -> List[Error]: """Run code checkers with the given params. :param path: (str) A file's path. """ path = op.relpath(path, rootdir) with RunContext(path, code, options) as ctx: if ctx.skip: LOGGER.info("Skip checking for path: %s", path) else: for lname in ctx.linters or LINTERS: linter_cls = LINTERS.get(lname) if not linter_cls: continue linter = linter_cls() LOGGER.info("Run [%s] %s", lname, path) if isinstance(linter, LinterV2): linter.run_check(ctx) else: for err_info in linter.run( ctx.temp_filename, code=ctx.source, params=ctx.get_params(lname) ): ctx.push(source=lname, **err_info) if not ctx.errors: return ctx.errors errors = list(remove_duplicates(ctx.errors)) sorter = default_sorter if options and options.sort: sort = options.sort sorter = lambda err: (sort.get(err.etype, 999), err.lnum) # pylint: disable=C3001 return sorted(errors, key=sorter) # pylama:ignore=R0912,D210,F0001,C3001 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1659957987.0 pylama-8.4.1/pylama/errors.py0000644000175100001710000000756614274171343015603 0ustar00runnerdocker""" Don't duplicate same errors from different linters. """ from __future__ import annotations import re from collections import defaultdict from typing import Any, DefaultDict, Dict, Generator, List, Set, Tuple PATTERN_NUMBER = re.compile(r"^\s*([A-Z]\d+)\s*", re.I) DUPLICATES: Dict[Tuple[str, str], Set] = { key: values # type: ignore for values in ( # multiple statements on one line {("pycodestyle", "E701"), ("pylint", "C0321")}, # unused variable {("pylint", "W0612"), ("pyflakes", "W0612")}, # undefined variable {("pylint", "E0602"), ("pyflakes", "E0602")}, # unused import {("pylint", "W0611"), ("pyflakes", "W0611")}, # whitespace before ')' {("pylint", "C0326"), ("pycodestyle", "E202")}, # whitespace before '(' {("pylint", "C0326"), ("pycodestyle", "E211")}, # multiple spaces after operator {("pylint", "C0326"), ("pycodestyle", "E222")}, # missing whitespace around operator {("pylint", "C0326"), ("pycodestyle", "E225")}, # unexpected spaces {("pylint", "C0326"), ("pycodestyle", "E251")}, # long lines {("pylint", "C0301"), ("pycodestyle", "E501")}, # statement ends with a semicolon {("pylint", "W0301"), ("pycodestyle", "E703")}, # multiple statements on one line {('pylint", "C0321'), ("pycodestyle", "E702")}, # bad indentation {("pylint", "W0311"), ("pycodestyle", "E111")}, # wildcart import {("pylint", "W00401"), ("pyflakes", "W0401")}, # module docstring {("pydocstyle", "D100"), ("pylint", "C0111")}, ) for key in values # type: ignore } class Error: """Store an error's information.""" __slots__ = "source", "col", "lnum", "etype", "message", "filename", "number" def __init__( self, source="pylama", col=1, lnum=1, type=None, # pylint: disable=R0913 text="unknown error", filename="", number="", **_, ): """Init error information with default values.""" text = str(text).strip().replace("\n", " ") if number: self.number = number else: number = PATTERN_NUMBER.match(text) self.number = number.group(1).upper() if number else "" self.etype = type[:1] if type else (number[0] if number else "E") self.col = max(col, 1) self.filename = filename self.source = source self.lnum = int(lnum) self.message = text def __repr__(self): return f"" def format(self, pattern: str) -> str: """Format the error with the given pattern.""" return pattern.format( filename=self.filename, lnum=self.lnum, col=self.col, message=self.message, etype=self.etype, source=self.source, number=self.number, ) def to_dict(self) -> Dict[str, Any]: """Return the error as a dict.""" return { "source": self.source, "col": self.col, "lnum": self.lnum, "etype": self.etype, "message": self.message, "filename": self.filename, "number": self.number, } def remove_duplicates(errors: List[Error]) -> Generator[Error, None, None]: """Filter duplicates from given error's list.""" passed: DefaultDict[int, Set] = defaultdict(set) for error in errors: key = error.source, error.number if key in DUPLICATES: if key in passed[error.lnum]: continue passed[error.lnum] = DUPLICATES[key] yield error def default_sorter(err: Error) -> Any: """Sort by line number.""" return err.lnum # pylama:ignore=W0622,D,R0924 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1659957987.0 pylama-8.4.1/pylama/hook.py0000644000175100001710000000652714274171343015223 0ustar00runnerdocker"""SCM hooks. Integration with git and mercurial.""" from __future__ import absolute_import import sys from configparser import ConfigParser # noqa from os import chmod, getcwd from os import path as op from subprocess import PIPE, Popen from typing import List, Tuple from pylama.config import parse_options, setup_logger from pylama.main import LOGGER, check_paths, display_errors def run(command: str) -> Tuple[int, List[bytes], List[bytes]]: """Run a shell command.""" with Popen(command.split(), stdout=PIPE, stderr=PIPE) as pipe: (stdout, stderr) = pipe.communicate() return ( pipe.returncode, [line.strip() for line in stdout.splitlines()], [line.strip() for line in stderr.splitlines()], ) def git_hook(error=True): """Run pylama after git commit.""" _, files_modified, _ = run("git diff-index --cached --name-only HEAD") options = parse_options() setup_logger(options) candidates = [f.decode("utf-8") for f in files_modified] if candidates: errors = check_paths(candidates, options, rootdir=getcwd()) display_errors(errors, options) sys.exit(int(error and bool(errors))) def hg_hook(_, repo, node=None, **kwargs): # noqa """Run pylama after mercurial commit.""" seen = set() candidates = [] if len(repo): for rev in range(repo[node], len(repo)): for file_ in repo[rev].files(): file_ = op.join(repo.root, file_) if file_ in seen or not op.exists(file_): continue seen.add(file_) candidates.append(file_) options = parse_options() setup_logger(options) if candidates: errors = check_paths(candidates, options) display_errors(errors, options) sys.exit(int(bool(errors))) def install_git(path): """Install hook in Git repository.""" hook = op.join(path, "pre-commit") with open(hook, "w", encoding="utf-8") as target: target.write( """#!/usr/bin/env python import sys from pylama.hook import git_hook if __name__ == '__main__': sys.exit(git_hook()) """ ) chmod(hook, 484) def install_hg(path): """Install hook in Mercurial repository.""" hook = op.join(path, "hgrc") if not op.isfile(hook): open(hook, "w+", encoding="utf-8").close() cfgp = ConfigParser() with open(hook, "r", encoding="utf-8") as source: cfgp.read_file(source) if not cfgp.has_section("hooks"): cfgp.add_section("hooks") if not cfgp.has_option("hooks", "commit"): cfgp.set("hooks", "commit", "python:pylama.hooks.hg_hook") if not cfgp.has_option("hooks", "qrefresh"): cfgp.set("hooks", "qrefresh", "python:pylama.hooks.hg_hook") with open(hook, "w+", encoding="utf-8") as target: cfgp.write(target) def install_hook(path): """Auto definition of SCM and hook installation.""" is_git = op.join(path, ".git", "hooks") is_hg = op.join(path, ".hg") if op.exists(is_git): install_git(is_git) LOGGER.warning("Git hook has been installed.") elif op.exists(is_hg): install_hg(is_hg) LOGGER.warning("Mercurial hook has been installed.") else: LOGGER.error("VCS has not found. Check your path.") sys.exit(1) # pylama:ignore=F0401,E1103,D210,F0001 ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1659957992.319301 pylama-8.4.1/pylama/libs/0000755000175100001710000000000014274171350014626 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1659957987.0 pylama-8.4.1/pylama/libs/__init__.py0000644000175100001710000000002614274171343016737 0ustar00runnerdocker""" Support libs. """ ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1659957987.0 pylama-8.4.1/pylama/libs/inirama.py0000644000175100001710000002201514274171343016622 0ustar00runnerdocker""" Inirama is a python module that parses INI files. .. _badges: .. include:: ../README.rst :start-after: .. _badges: :end-before: .. _contents: .. _description: .. include:: ../README.rst :start-after: .. _description: :end-before: .. _badges: :copyright: 2013 by Kirill Klenov. :license: BSD, see LICENSE for more details. """ from __future__ import unicode_literals, print_function __version__ = "0.8.0" __project__ = "Inirama" __author__ = "Kirill Klenov " __license__ = "BSD" import io import re import logging from collections import OrderedDict NS_LOGGER = logging.getLogger('inirama') class Scanner: """ Split a code string on tokens. """ def __init__(self, source, ignore=None, patterns=None): """ Init Scanner instance. :param patterns: List of token patterns [(token, regexp)] :param ignore: List of ignored tokens """ self.reset(source) if patterns: self.patterns = [] for k, r in patterns: self.patterns.append((k, re.compile(r))) if ignore: self.ignore = ignore def reset(self, source): """ Reset scanner's state. :param source: Source for parsing """ self.tokens = [] self.source = source self.pos = 0 def scan(self): """ Scan source and grab tokens. """ self.pre_scan() token = None end = len(self.source) while self.pos < end: best_pat = None best_pat_len = 0 # Check patterns for p, regexp in self.patterns: m = regexp.match(self.source, self.pos) if m: best_pat = p best_pat_len = len(m.group(0)) break if best_pat is None: raise SyntaxError( "SyntaxError[@char {0}: {1}]".format( self.pos, "Bad token.")) # Ignore patterns if best_pat in self.ignore: self.pos += best_pat_len continue # Create token token = ( best_pat, self.source[self.pos:self.pos + best_pat_len], self.pos, self.pos + best_pat_len, ) self.pos = token[-1] self.tokens.append(token) def pre_scan(self): """ Prepare source. """ pass def __repr__(self): """ Print the last 5 tokens that have been scanned in. :return str: """ return '" class INIScanner(Scanner): """ Get tokens for INI. """ patterns = [ ('SECTION', re.compile(r'\[[^]]+\]')), ('IGNORE', re.compile(r'[ \r\t\n]+')), ('COMMENT', re.compile(r'[;#].*')), ('KEY_VALUE', re.compile(r'[^=\s]+\s*[:=].*')), ('CONTINUATION', re.compile(r'.*')) ] ignore = ['IGNORE'] def pre_scan(self): """ Prepare string for scanning. """ escape_re = re.compile(r'\\\n[\t ]+') self.source = escape_re.sub('', self.source) undefined = object() class Section(OrderedDict): """ Representation of INI section. """ def __init__(self, namespace, *args, **kwargs): super(Section, self).__init__(*args, **kwargs) self.namespace = namespace def __setitem__(self, name, value): value = str(value) if value.isdigit(): value = int(value) super(Section, self).__setitem__(name, value) class InterpolationSection(Section): """ INI section with interpolation support. """ var_re = re.compile('{([^}]+)}') def get(self, name, default=None): """ Get item by name. :return object: value or None if name not exists """ if name in self: return self[name] return default def __interpolate__(self, math): try: key = math.group(1).strip() return self.namespace.default.get(key) or self[key] except KeyError: return '' def __getitem__(self, name, raw=False): value = super(InterpolationSection, self).__getitem__(name) if not raw: sample = undefined while sample != value: try: sample, value = value, self.var_re.sub( self.__interpolate__, value) except RuntimeError: message = "Interpolation failed: {0}".format(name) NS_LOGGER.error(message) raise ValueError(message) return value def iteritems(self, raw=False): """ Iterate self items. """ for key in self: yield key, self.__getitem__(key, raw=raw) items = iteritems class Namespace(object): """ Default class for parsing INI. :param **default_items: Default items for default section. Usage ----- :: from inirama import Namespace ns = Namespace() ns.read('config.ini') print ns['section']['key'] ns['other']['new'] = 'value' ns.write('new_config.ini') """ #: Name of default section (:attr:`~inirama.Namespace.default`) default_section = 'DEFAULT' #: Dont raise any exception on file reading errors silent_read = True #: Class for generating sections section_type = Section def __init__(self, **default_items): self.sections = OrderedDict() for k, v in default_items.items(): self[self.default_section][k] = v @property def default(self): """ Return default section or empty dict. :return :class:`inirama.Section`: section """ return self.sections.get(self.default_section, dict()) def read(self, *files, **params): """ Read and parse INI files. :param *files: Files for reading :param **params: Params for parsing Set `update=False` for prevent values redefinition. """ for f in files: try: with io.open(f, encoding='utf-8') as ff: NS_LOGGER.info('Read from `{0}`'.format(ff.name)) self.parse(ff.read(), **params) except (IOError, TypeError, SyntaxError, io.UnsupportedOperation): if not self.silent_read: NS_LOGGER.error('Reading error `{0}`'.format(ff.name)) raise def write(self, f): """ Write namespace as INI file. :param f: File object or path to file. """ if isinstance(f, str): f = io.open(f, 'w', encoding='utf-8') if not hasattr(f, 'read'): raise AttributeError("Wrong type of file: {0}".format(type(f))) NS_LOGGER.info('Write to `{0}`'.format(f.name)) for section in self.sections.keys(): f.write('[{0}]\n'.format(section)) for k, v in self[section].items(): f.write('{0:15}= {1}\n'.format(k, v)) f.write('\n') f.close() def parse(self, source, update=True, **params): """ Parse INI source as string. :param source: Source of INI :param update: Replace already defined items """ scanner = INIScanner(source) scanner.scan() section = self.default_section name = None for token in scanner.tokens: if token[0] == 'KEY_VALUE': name, value = re.split('[=:]', token[1], 1) name, value = name.strip(), value.strip() if not update and name in self[section]: continue self[section][name] = value elif token[0] == 'SECTION': section = token[1].strip('[]') elif token[0] == 'CONTINUATION': if not name: raise SyntaxError( "SyntaxError[@char {0}: {1}]".format( token[2], "Bad continuation.")) self[section][name] += '\n' + token[1].strip() def __getitem__(self, name): """ Look name in self sections. :return :class:`inirama.Section`: section """ if name not in self.sections: self.sections[name] = self.section_type(self) return self.sections[name] def __contains__(self, name): return name in self.sections def __repr__(self): return "".format(self.sections) class InterpolationNamespace(Namespace): """ That implements the interpolation feature. :: from inirama import InterpolationNamespace ns = InterpolationNamespace() ns.parse(''' [main] test = value foo = bar {test} more_deep = wow {foo} ''') print ns['main']['more_deep'] # wow bar value """ section_type = InterpolationSection # pylama:ignore=D,W02,E731,W0621 ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1659957992.323301 pylama-8.4.1/pylama/lint/0000755000175100001710000000000014274171350014643 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1659957987.0 pylama-8.4.1/pylama/lint/__init__.py0000644000175100001710000000316314274171343016761 0ustar00runnerdocker"""Custom module loader.""" from __future__ import annotations from argparse import ArgumentParser from importlib import import_module from pathlib import Path from pkgutil import walk_packages from typing import TYPE_CHECKING, Any, Dict, List, Optional, Type from pkg_resources import iter_entry_points LINTERS: Dict[str, Type[LinterV2]] = {} if TYPE_CHECKING: from pylama.context import RunContext class LinterMeta(type): """Register linters.""" def __new__(mcs, name, bases, params): """Register linters.""" cls: Type[LinterV2] = super().__new__(mcs, name, bases, params) if cls.name is not None: LINTERS[cls.name] = cls return cls class Linter(metaclass=LinterMeta): """Abstract class for linter plugin.""" name: Optional[str] = None @classmethod def add_args(cls, _: ArgumentParser): """Add options from linters. The method has to be a classmethod. """ def run(self, _path: str, **_meta) -> List[Dict[str, Any]]: # noqa """Legacy method (support old extenstions).""" return [] class LinterV2(Linter): """A new linter class.""" def run_check(self, ctx: RunContext): """Check code.""" # Import default linters for _, pname, _ in walk_packages([str(Path(__file__).parent)]): # type: ignore try: import_module(f"{__name__}.{pname}") except ImportError: pass # Import installed linters for entry in iter_entry_points("pylama.linter"): if entry.name not in LINTERS: try: LINTERS[entry.name] = entry.load() except ImportError: pass ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1659957987.0 pylama-8.4.1/pylama/lint/pylama_eradicate.py0000644000175100001710000000134114274171343020502 0ustar00runnerdocker"""Commented-out code checking.""" from eradicate import Eradicator from pylama.context import RunContext from pylama.lint import LinterV2 as Abstract class Linter(Abstract): """Run commented-out code checking.""" name = "eradicate" def run_check(self, ctx: RunContext): """Eradicate code checking. TODO: Support params """ eradicator = Eradicator() line_numbers = eradicator.commented_out_code_line_numbers(ctx.source) for line_number in line_numbers: ctx.push( lnum=line_number, source="eradicate", text=str("Found commented out code"), number="E800", type="E", ) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1659957987.0 pylama-8.4.1/pylama/lint/pylama_fake.py0000644000175100001710000000055614274171343017476 0ustar00runnerdocker"""A fake linter which one never be loaded.""" from typing import Any, Dict, List import unknown_module # noqa from pylama.lint import Linter as Abstract class Linter(Abstract): """Just a fake.""" name = "fake" def run(self, _path: str, **_) -> List[Dict[str, Any]]: """Run the unknown module.""" return unknown_module.run(_path) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1659957987.0 pylama-8.4.1/pylama/lint/pylama_mccabe.py0000644000175100001710000000251314274171343017775 0ustar00runnerdocker"""Code complexity checking.""" from argparse import ArgumentError from mccabe import McCabeChecker from pylama.context import RunContext from pylama.lint import ArgumentParser from pylama.lint import LinterV2 as Abstract class Linter(Abstract): """Run complexity checking.""" name = "mccabe" @classmethod def add_args(cls, parser: ArgumentParser): """Add --max-complexity option.""" try: parser.add_argument( "--max-complexity", default=10, type=int, help="Max complexity threshold" ) except ArgumentError: pass def run_check(self, ctx: RunContext): """Run Mccabe code checker.""" params = ctx.get_params("mccabe") options = ctx.options if options: params.setdefault("max-complexity", options.max_complexity) McCabeChecker.max_complexity = int(params.get("max-complexity", 10)) McCabeChecker._error_tmpl = "%r is too complex (%d)" number = McCabeChecker._code for lineno, offset, text, _ in McCabeChecker(ctx.ast, ctx.filename).run(): ctx.push( col=offset + 1, lnum=lineno, number=number, text=text, type="C", source="mccabe", ) # pylama:ignore=W0212 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1659957987.0 pylama-8.4.1/pylama/lint/pylama_mypy.py0000644000175100001710000000310614274171343017560 0ustar00runnerdocker"""MyPy support. TODO: Error codes """ from __future__ import annotations from mypy import api from pylama.context import RunContext from pylama.lint import LinterV2 as Abstract class Linter(Abstract): """MyPy runner.""" name = "mypy" def run_check(self, ctx: RunContext): """Check code with mypy.""" # Support stdin args = [ctx.temp_filename, "--follow-imports=skip", "--show-column-numbers"] stdout, _, _ = api.run(args) # noqa for line in stdout.splitlines(): if not line: continue message = _MyPyMessage(line) if message.valid: ctx.push( source="mypy", lnum=message.line_num, col=message.column, text=message.text, type=message.types.get(message.message_type.strip(), "W"), ) class _MyPyMessage: """Parser for a single MyPy output line.""" types = {"error": "E", "warning": "W", "note": "N"} valid = False def __init__(self, line): self.filename = None self.line_num = None self.column = None try: result = line.split(":", maxsplit=4) self.filename, line_num_txt, column_txt, self.message_type, text = result except ValueError: return try: self.line_num = int(line_num_txt.strip()) self.column = int(column_txt.strip()) except ValueError: return self.text = text.strip() self.valid = True ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1659957987.0 pylama-8.4.1/pylama/lint/pylama_pycodestyle.py0000644000175100001710000000272614274171343021135 0ustar00runnerdocker"""pycodestyle support.""" from pycodestyle import BaseReport, Checker, StyleGuide, get_parser from pylama.context import RunContext from pylama.lint import LinterV2 as Abstract class Linter(Abstract): """pycodestyle runner.""" name = "pycodestyle" def run_check(self, ctx: RunContext): # noqa """Check code with pycodestyle.""" params = ctx.get_params("pycodestyle") options = ctx.options if options: params.setdefault("max_line_length", options.max_line_length) if params: parser = get_parser() for option in parser.option_list: if option.dest and option.dest in params: value = params[option.dest] if isinstance(value, str): params[option.dest] = option.convert_value(option, value) style = StyleGuide(reporter=_PycodestyleReport, **params) options = style.options options.report.ctx = ctx # type: ignore checker = Checker(ctx.filename, lines=ctx.lines, options=options) checker.check_all() class _PycodestyleReport(BaseReport): ctx: RunContext def error(self, line_number, offset, text, _): """Save errors.""" code, _, text = text.partition(" ") self.ctx.push( text=text, type=code[0], number=code, col=offset + 1, lnum=line_number, source="pycodestyle", ) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1659957987.0 pylama-8.4.1/pylama/lint/pylama_pydocstyle.py0000644000175100001710000000300614274171343020760 0ustar00runnerdocker"""pydocstyle support.""" from argparse import ArgumentParser from pydocstyle import ConventionChecker as PyDocChecker from pydocstyle.violations import conventions from pylama.context import RunContext from pylama.lint import LinterV2 as Abstract class Linter(Abstract): """Check pydocstyle errors.""" name = "pydocstyle" @classmethod def add_args(cls, parser: ArgumentParser): """Add --max-complexity option.""" parser.add_argument( "--pydocstyle-convention", choices=list(conventions.keys()), help="choose the basic list of checked errors by specifying an existing convention.", ) def run_check(self, ctx: RunContext): # noqa """Check code with pydocstyle.""" params = ctx.get_params("pydocstyle") options = ctx.options if options and options.pydocstyle_convention: params.setdefault("convention", options.pydocstyle_convention) convention_codes = conventions.get(params.get("convention")) for err in PyDocChecker().check_source( ctx.source, ctx.filename, params.get("ignore_decorators"), params.get("ignore_inline_noqa", False), ): if convention_codes is None or err.code in convention_codes: ctx.push( lnum=err.line, text=err.short_desc, type="D", number=err.code, source="pydocstyle", ) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1659957987.0 pylama-8.4.1/pylama/lint/pylama_pyflakes.py0000644000175100001710000000341414274171343020402 0ustar00runnerdocker"""Pyflakes support.""" from pyflakes import checker from pylama.context import RunContext from pylama.lint import LinterV2 as Abstract m = checker.messages CODES = { m.UnusedImport.message: "W0611", m.RedefinedWhileUnused.message: "W0404", m.ImportShadowedByLoopVar.message: "W0621", m.ImportStarUsed.message: "W0401", m.ImportStarUsage.message: "W0401", m.UndefinedName.message: "E0602", m.DoctestSyntaxError.message: "W0511", m.UndefinedExport.message: "E0603", m.UndefinedLocal.message: "E0602", m.DuplicateArgument.message: "E1122", m.LateFutureImport.message: "W0410", m.UnusedVariable.message: "W0612", m.ReturnOutsideFunction.message: "E0104", } # RedefinedInListComp and ReturnWithArgsInsideGenerator were removed at pyflakes 2.5.0: # https://github.com/PyCQA/pyflakes/commit/2246217295dc8cb30ef4a7b9d8dc449ce32e603a if hasattr(m, "RedefinedInListComp"): CODES[m.RedefinedInListComp.message] = "W0621" if hasattr(m, "ReturnWithArgsInsideGenerator"): CODES[m.ReturnWithArgsInsideGenerator.message] = "E0106" class Linter(Abstract): """Pyflakes runner.""" name = "pyflakes" def run_check(self, context: RunContext): # noqa """Check code with pyflakes.""" params = context.get_params("pyflakes") builtins = params.get("builtins", "") if builtins: builtins = builtins.split(",") check = checker.Checker(context.ast, context.filename, builtins=builtins) for msg in check.messages: context.push( lnum=msg.lineno, col=msg.col + 1, text=msg.message % msg.message_args, number=CODES.get(msg.message, ""), source="pyflakes", ) # pylama:ignore=E501,C0301 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1659957987.0 pylama-8.4.1/pylama/lint/pylama_pylint.py0000644000175100001710000000620614274171343020105 0ustar00runnerdocker"""Pylint integration to Pylama.""" import logging from argparse import ArgumentParser from os import environ from pathlib import Path from typing import Dict from pylint.interfaces import CONFIDENCE_LEVELS from pylint.lint import Run from pylint.reporters import BaseReporter from pylama.context import RunContext from pylama.lint import LinterV2 as BaseLinter HOME_RCFILE = Path(environ.get("HOME", "")) / ".pylintrc" logger = logging.getLogger("pylama") class Linter(BaseLinter): """Check code with Pylint.""" name = "pylint" @classmethod def add_args(cls, parser: ArgumentParser): """Add --max-complexity option.""" parser.add_argument( "--pylint-confidence", choices=[cc.name for cc in CONFIDENCE_LEVELS], help="Only show warnings with the listed confidence levels.", ) def run_check(self, ctx: RunContext): """Pylint code checking.""" logger.debug("Start pylint") params = ctx.get_params("pylint") options = ctx.options if options: params.setdefault("max_line_length", options.max_line_length) params.setdefault("confidence", options.pylint_confidence) params.setdefault("enable", ctx.select | ctx.get_filter("pylint", "select")) params.setdefault("disable", ctx.ignore | ctx.get_filter("pylint", "ignore")) # if params.get("disable"): # params["disable"].add("W0012") class Reporter(BaseReporter): """Handle messages.""" def _display(self, _): pass def handle_message(self, msg): msg_id = msg.msg_id ctx.push( filtrate=False, col=msg.column + 1, lnum=msg.line, number=msg_id, text=msg.msg, type=msg_id[0], source="pylint", ) logger.debug(params) reporter = Reporter() args = _Params(params).to_attrs() Run([ctx.temp_filename] + args, reporter=reporter, exit=False) class _Params: """Store pylint params.""" def __init__(self, params: Dict): attrs = { name.replace("_", "-"): self.prepare_value(value) for name, value in params.items() if value } if HOME_RCFILE.exists(): attrs["rcfile"] = HOME_RCFILE.as_posix() if attrs.get("disable"): attrs["disable"] += ",W0012" else: attrs["disable"] = "W0012" self.attrs = attrs @staticmethod def prepare_value(value): """Prepare value to pylint.""" if isinstance(value, (list, tuple, set)): return ",".join(value) if isinstance(value, bool): return "y" if value else "n" return str(value) def to_attrs(self): """Convert to argument list.""" return [f"--{key}={value}" for key, value in self.attrs.items()] # noqa def __str__(self): return " ".join(self.to_attrs()) def __repr__(self): return f"" # pylama:ignore=W0403 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1659957987.0 pylama-8.4.1/pylama/lint/pylama_radon.py0000644000175100001710000000422014274171343017663 0ustar00runnerdocker"""Support Radon. Supports stdin. """ from argparse import ArgumentError, ArgumentParser from radon.complexity import add_inner_blocks from radon.visitors import ComplexityVisitor from pylama.context import RunContext from pylama.lint import LinterV2 as Abstract class Linter(Abstract): """Radon runner.""" name = "radon" @classmethod def add_args(cls, parser: ArgumentParser): """Add --max-complexity option.""" parser.add_argument( "--radon-no-assert", default=False, action="store_true", help="Ignore `assert` statements.", ) parser.add_argument( "--radon-show-closures", default=False, action="store_true", help="Increase complexity on closures.", ) try: parser.add_argument( "--max-complexity", default=10, type=int, help="Max complexity threshold", ) except ArgumentError: pass def run_check(self, ctx: RunContext): # noqa # noqa """Check code with Radon.""" params = ctx.get_params("radon") options = ctx.options if options: params.setdefault("complexity", options.max_complexity) params.setdefault("no_assert", options.radon_no_assert) params.setdefault("show_closures", options.radon_show_closures) complexity = params.get("complexity", 10) no_assert = params.get("no_assert", False) show_closures = params.get("show_closures", False) visitor = ComplexityVisitor.from_code(ctx.source, no_assert=no_assert) blocks = visitor.blocks if show_closures: blocks = add_inner_blocks(blocks) for block in visitor.blocks: if block.complexity > complexity: ctx.push( lnum=block.lineno, col=block.col_offset + 1, source="radon", type="R", number="R901", text=f"{block.name} is too complex {block.complexity}", ) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1659957987.0 pylama-8.4.1/pylama/lint/pylama_vulture.py0000644000175100001710000000430014274171343020265 0ustar00runnerdocker"""Support Vulture.""" from argparse import ArgumentParser from vulture.core import ERROR_CODES, Vulture, make_config from pylama.context import RunContext from pylama.lint import LinterV2 as BaseLinter class Linter(BaseLinter): """vulture runner.""" name = "vulture" @classmethod def add_args(cls, parser: ArgumentParser): """Add --max-complexity option.""" parser.add_argument( "--vulture-min-confidence", type=int, help="Minimum confidence (between 0 and 100) for code to be reported as unused.", ) parser.add_argument( "--vulture-ignore-names", help="Comma-separated list of names to ignore", ) parser.add_argument( "--vulture-ignore-decorators", help="Comma-separated list of decorators to ignore", ) def run_check(self, ctx: RunContext): # noqa """Check code with vulture.""" params = ctx.get_params("vulture") options = ctx.options if options: params.setdefault("min-confidence", options.vulture_min_confidence) params.setdefault("ignore-names", options.vulture_ignore_names) params.setdefault("ignore-decorators", options.vulture_ignore_decorators) config = make_config(parse_params(ctx.filename, params)) vulture = Vulture( verbose=config["verbose"], ignore_names=config["ignore_names"], ignore_decorators=config["ignore_decorators"], ) vulture.scan(ctx.source, filename=ctx.filename) unused_code_items = vulture.get_unused_code( min_confidence=config["min_confidence"], sort_by_size=config["sort_by_size"] ) for item in unused_code_items: error_code = ERROR_CODES[item.typ] ctx.push( source="vulture", type="R", lnum=item.first_lineno, number=error_code, text=f"{item.message} ({item.confidence}% confidence)", ) def parse_params(path, params=None): """Convert params from pylama.""" return [f"--{key}={value}" for key, value in params.items() if value] + [path] ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1659957987.0 pylama-8.4.1/pylama/main.py0000644000175100001710000000725114274171343015202 0ustar00runnerdocker"""Pylama's shell support.""" import sys import warnings from json import dumps from os import path as op from os import walk from pathlib import Path from typing import List, Optional from pylama.check_async import check_async from pylama.config import CURDIR, Namespace, parse_options, setup_logger from pylama.core import LOGGER, run from pylama.errors import Error from pylama.utils import read_stdin DEFAULT_FORMAT = "{filename}:{lnum}:{col} [{etype}] {number} {message} [{source}]" MESSAGE_FORMATS = { "pylint": "{filename}:{lnum}: [{etype}] {number} {message} [{source}]", "pycodestyle": "{filename}:{lnum}:{col} {number} {message} [{source}]", "parsable": DEFAULT_FORMAT, } def check_paths( paths: Optional[List[str]], options: Namespace, code: str = None, rootdir: Path = None, ) -> List[Error]: """Check the given paths. :param rootdir: Root directory (for making relative file paths) :param options: Parsed pylama options (from pylama.config.parse_options) """ paths = paths or options.paths if not paths: return [] if code is None: candidates = [] for path in paths or options.paths: if not op.exists(path): continue if not op.isdir(path): candidates.append(op.abspath(path)) for root, _, files in walk(path): candidates += [op.relpath(op.join(root, f), CURDIR) for f in files] else: candidates = [paths[0]] if not candidates: return [] if rootdir is None: path = candidates[0] rootdir = Path(path if op.isdir(path) else op.dirname(path)) candidates = [path for path in candidates if path.endswith(".py")] if options.concurrent: return check_async(candidates, code=code, options=options, rootdir=rootdir) errors = [] for path in candidates: errors += run(path=path, code=code, rootdir=rootdir, options=options) return errors def check_path( options: Namespace, rootdir: str = None, candidates: List[str] = None, code: str = None, # noqa ) -> List[Error]: """Support legacy code.""" warnings.warn( "pylama.main.check_path is depricated and will be removed in pylama 9", DeprecationWarning, ) return check_paths( candidates, code=code, options=options, rootdir=rootdir and Path(rootdir) or None, ) def shell(args: List[str] = None, error: bool = True): """Endpoint for console. Parse a command arguments, configuration files and run a checkers. """ if args is None: args = sys.argv[1:] options = parse_options(args) setup_logger(options) LOGGER.info(options) # Install VSC hook if options.hook: from .hook import install_hook # noqa for path in options.paths: return install_hook(path) if options.from_stdin and not options.paths: LOGGER.error("--from-stdin requires a filename") return sys.exit(1) errors = check_paths( options.paths, code=read_stdin() if options.from_stdin else None, options=options, rootdir=CURDIR, ) display_errors(errors, options) if error: sys.exit(int(bool(errors))) return errors def display_errors(errors: List[Error], options: Namespace): """Format and display the given errors.""" if options.format == "json": LOGGER.warning(dumps([err.to_dict() for err in errors])) else: pattern = MESSAGE_FORMATS.get(options.format, DEFAULT_FORMAT) for err in errors: LOGGER.warning(err.format(pattern)) if __name__ == "__main__": shell() # pylama:ignore=F0001 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1659957987.0 pylama-8.4.1/pylama/pytest.py0000644000175100001710000000555214274171343015610 0ustar00runnerdocker""" py.test plugin for checking files with pylama. """ from __future__ import absolute_import import pathlib from os import path as op import pytest from pylama.config import CURDIR from pylama.main import DEFAULT_FORMAT, check_paths, parse_options HISTKEY = "pylama/mtimes" def pytest_load_initial_conftests(early_config, *_): # Marks have to be registered before usage # to not fail with --strict command line argument early_config.addinivalue_line( "markers", "pycodestyle: Mark test as using pylama code audit tool." ) def pytest_addoption(parser): group = parser.getgroup("general") group.addoption( "--pylama", action="store_true", help="perform some pylama code checks on .py files", ) def pytest_sessionstart(session): config = session.config if config.option.pylama and getattr(config, "cache", None): config._pylamamtimes = config.cache.get(HISTKEY, {}) def pytest_sessionfinish(session): config = session.config if hasattr(config, "_pylamamtimes"): config.cache.set(HISTKEY, config._pylamamtimes) def pytest_collect_file(path, parent): config = parent.config if config.option.pylama and path.ext == ".py": return PylamaFile.from_parent(parent, path=pathlib.Path(path)) return None class PylamaError(Exception): """indicates an error during pylama checks.""" class PylamaFile(pytest.File): def collect(self): return [PylamaItem.from_parent(self, name="pylama")] class PylamaItem(pytest.Item): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.add_marker("pycodestyle") self.cache = None self._pylamamtimes = None def setup(self): if not getattr(self.config, "cache", None): return False self.cache = True self._pylamamtimes = self.fspath.mtime() pylamamtimes = self.config._pylamamtimes old = pylamamtimes.get(str(self.fspath), 0) if old == self._pylamamtimes: pytest.skip("file(s) previously passed Pylama checks") return True def runtest(self): errors = check_file(self.fspath) if errors: out = "\n".join(err.format(DEFAULT_FORMAT) for err in errors) raise PylamaError(out) # update mtime only if test passed # otherwise failures would not be re-run next time if self.cache: self.config._pylamamtimes[str(self.fspath)] = self._pylamamtimes def repr_failure(self, excinfo, style=None): if excinfo.errisinstance(PylamaError): return excinfo.value.args[0] return super().repr_failure(excinfo, style) def check_file(path): options = parse_options() path = op.relpath(str(path), CURDIR) return check_paths([path], options, rootdir=CURDIR) # pylama:ignore=D,E1002,W0212,F0001,C0115,C0116 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1659957987.0 pylama-8.4.1/pylama/utils.py0000644000175100001710000000075114274171343015414 0ustar00runnerdocker"""Pylama utils.""" from io import StringIO from sys import stdin from typing import List def get_lines(value: str) -> List[str]: """Return lines from the given string.""" return StringIO(value).readlines() def read(filename: str) -> str: """Read the given filename.""" with open(filename, encoding="utf-8") as file: return file.read() def read_stdin() -> str: """Get value from stdin.""" value = stdin.buffer.read() return value.decode("utf-8") ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1659957992.319301 pylama-8.4.1/pylama.egg-info/0000755000175100001710000000000014274171350015367 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1659957992.0 pylama-8.4.1/pylama.egg-info/PKG-INFO0000644000175100001710000003402114274171350016464 0ustar00runnerdockerMetadata-Version: 2.1 Name: pylama Version: 8.4.1 Summary: Code audit tool for python Home-page: https://github.com/klen/pylama Author: Kirill Klenov Author-email: horneds@gmail.com License: MIT Project-URL: Documentation, https://klen.github.io/pylama Project-URL: Source code, https://github.com/klen/pylama Project-URL: Issue tracker, https://github.com/klen/pylama/issues Keywords: qa,linter,pydocstyle,pycodestyle,mccabe,pylint Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: Environment :: Console Classifier: Intended Audience :: Developers Classifier: Intended Audience :: System Administrators Classifier: License :: OSI Approved :: MIT License Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.10 Classifier: Programming Language :: Python :: 3.7 Classifier: Programming Language :: Python :: 3.8 Classifier: Programming Language :: Python :: 3.9 Classifier: Topic :: Software Development :: Quality Assurance Classifier: Topic :: Software Development :: Testing Requires-Python: >=3.7 Provides-Extra: tests Provides-Extra: all Provides-Extra: pylint Provides-Extra: eradicate Provides-Extra: radon Provides-Extra: mypy Provides-Extra: vulture Provides-Extra: toml License-File: LICENSE |logo| Pylama ############# .. _badges: .. image:: https://github.com/klen/pylama/workflows/tests/badge.svg :target: https://github.com/klen/pylama/actions/workflows/tests.yml :alt: Tests Status .. image:: https://github.com/klen/pylama/workflows/docs/badge.svg :target: https://klen.github.io/pylama :alt: Documentation Status .. image:: https://img.shields.io/pypi/v/pylama :target: https://pypi.org/project/pylama/ :alt: PYPI Version .. image:: https://img.shields.io/pypi/pyversions/pylama :target: https://pypi.org/project/pylama/ :alt: Python Versions .. _description: Code audit tool for Python. Pylama wraps these tools: * pycodestyle_ (formerly pep8) © 2012-2013, Florent Xicluna; * pydocstyle_ (formerly pep257 by Vladimir Keleshev) © 2014, Amir Rachum; * PyFlakes_ © 2005-2013, Kevin Watters; * Mccabe_ © Ned Batchelder; * Pylint_ © 2013, Logilab; * Radon_ © Michele Lacchia * eradicate_ © Steven Myint; * Mypy_ © Jukka Lehtosalo and contributors; * Vulture_ © Jendrik Seipp and contributors; .. _documentation: Docs are available at https://klen.github.io/pylama/. Pull requests with documentation enhancements and/or fixes are awesome and most welcome. .. _contents: .. contents:: .. _requirements: Requirements: ============= - Python (3.7, 3.8, 3.9, 3.10) - If your tests are failing on Win platform you are missing: ``curses`` - http://www.lfd.uci.edu/~gohlke/pythonlibs/ (The curses library supplies a terminal-independent screen-painting and keyboard-handling facility for text-based terminals) For python versions < 3.7 install pylama 7.7.1 .. _installation: Installation: ============= **Pylama** can be installed using pip: :: $ pip install pylama TOML configuration can be enabled optionally: :: $ pip install pylama[toml] You may optionally install the requirements with the library: :: $ pip install pylama[mypy] $ pip install pylama[pylint] $ pip install pylama[eradicate] $ pip install pylama[radon] $ pip install pylama[vulture] Or install them all: :: $ pip install pylama[all] .. _quickstart: Quickstart ========== **Pylama** is easy to use and really fun for checking code quality. Just run `pylama` and get common output from all pylama plugins (pycodestyle_, PyFlakes_, etc.) Recursively check the current directory. :: $ pylama Recursively check a path. :: $ pylama Ignore errors :: $ pylama -i W,E501 .. note:: You can choose a group of errors like `D`, `E1`, etc, or special errors like `C0312` Choose code checkers :: $ pylama -l "pycodestyle,mccabe" .. _options: Set Pylama (checkers) options ============================= Command line options -------------------- :: $ pylama --help usage: pylama [-h] [--version] [--verbose] [--options FILE] [--linters LINTERS] [--from-stdin] [--concurrent] [--format {pydocstyle,pycodestyle,pylint,parsable,json}] [--abspath] [--max-line-length MAX_LINE_LENGTH] [--select SELECT] [--ignore IGNORE] [--skip SKIP] [--sort SORT] [--report REPORT] [--hook] [--max-complexity MAX_COMPLEXITY] [--pydocstyle-convention {pep257,numpy,google}] [--pylint-confidence {HIGH,INFERENCE,INFERENCE_FAILURE,UNDEFINED}] [paths ...] Code audit tool for python. positional arguments: paths Paths to files or directories for code check. optional arguments: -h, --help show this help message and exit --version show program's version number and exit --verbose, -v Verbose mode. --options FILE, -o FILE Specify configuration file. Looks for pylama.ini, setup.cfg, tox.ini, or pytest.ini in the current directory (default: None) --linters LINTERS, -l LINTERS Select linters. (comma-separated). Choices are eradicate,mccabe,mypy,pycodestyle,pydocstyle,pyflakes,pylint,isort. --from-stdin Interpret the stdin as a python script, whose filename needs to be passed as the path argument. --concurrent, --async Enable async mode. Useful for checking a lot of files. --format {pydocstyle,pycodestyle,pylint,parsable,json}, -f {pydocstyle,pycodestyle,pylint,parsable,json} Choose output format. --abspath, -a Use absolute paths in output. --max-line-length MAX_LINE_LENGTH, -m MAX_LINE_LENGTH Maximum allowed line length --select SELECT, -s SELECT Select errors and warnings. (comma-separated list) --ignore IGNORE, -i IGNORE Ignore errors and warnings. (comma-separated) --skip SKIP Skip files by masks (comma-separated, Ex. */messages.py) --sort SORT Sort result by error types. Ex. E,W,D --report REPORT, -r REPORT Send report to file [REPORT] --hook Install Git (Mercurial) hook. --max-complexity MAX_COMPLEXITY Max complexity threshold .. note:: additional options may be available depending on installed linters .. _modeline: File modelines -------------- You can set options for **Pylama** inside a source file. Use a pylama *modeline* for this, anywhere in the file. Format: :: # pylama:{name1}={value1}:{name2}={value2}:... For example, ignore warnings except W301: :: # pylama:ignore=W:select=W301 Disable code checking for current file: :: # pylama:skip=1 Those options have a higher priority. .. _skiplines: Skip lines (noqa) ----------------- Just add ``# noqa`` at the end of a line to ignore: :: def urgent_fuction(): unused_var = 'No errors here' # noqa .. _config: Configuration file ================== **Pylama** looks for a configuration file in the current directory. You can use a “global” configuration, stored in `.pylama.ini` in your home directory. This will be used as a fallback configuration. The program searches for the first matching configuration file in the directories of command line argument. Pylama looks for the configuration in this order: :: ./pylama.ini ./pyproject.toml ./setup.cfg ./tox.ini ./pytest.ini ~/.pylama.ini The ``--option`` / ``-o`` argument can be used to specify a configuration file. INI-style configuration ----------------------- Pylama searches for sections whose names start with `pylama`. The `pylama` section configures global options like `linters` and `skip`. :: [pylama] format = pylint skip = */.tox/*,*/.env/* linters = pylint,mccabe ignore = F0401,C0111,E731 Set code-checkers' options ^^^^^^^^^^^^^^^^^^^^^^^^^^ You can set options for a special code checkers with pylama configurations. :: [pylama:pyflakes] builtins = _ [pylama:pycodestyle] max_line_length = 100 [pylama:pylint] max_line_length = 100 disable = R See code-checkers' documentation for more info. Note that dashes are replaced by underscores (e.g. Pylint's ``max-line-length`` becomes ``max_line_length``). Set options for file (group of files) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ You can set options for special file (group of files) with sections: The options have a higher priority than in the `pylama` section. :: [pylama:*/pylama/main.py] ignore = C901,R0914,W0212 select = R [pylama:*/tests.py] ignore = C0110 [pylama:*/setup.py] skip = 1 TOML configuration ----------------------- Pylama searches for sections whose names start with `tool.pylama`. The `tool.pylama` section configures global options like `linters` and `skip`. :: [tool.pylama] format = "pylint" skip = "*/.tox/*,*/.env/*" linters = "pylint,mccabe" ignore = "F0401,C0111,E731" Set code-checkers' options ^^^^^^^^^^^^^^^^^^^^^^^^^^ You can set options for a special code checkers with pylama configurations. :: [tool.pylama.linter.pyflakes] builtins = "_" [tool.pylama.linter.pycodestyle] max_line_length = 100 [tool.pylama.linter.pylint] max_line_length = 100 disable = "R" See code-checkers' documentation for more info. Note that dashes are replaced by underscores (e.g. Pylint's ``max-line-length`` becomes ``max_line_length``). Set options for file (group of files) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ You can set options for special file (group of files) with sections: The options have a higher priority than in the `tool.pylama` section. :: [[tool.pylama.files]] path = "*/pylama/main.py" ignore = "C901,R0914,W0212" select = "R" [[tool.pylama.files]] path = "pylama:*/tests.py" ignore = "C0110" [[tool.pylama.files]] path = "pylama:*/setup.py" skip = 1 Pytest integration ================== Pylama has Pytest_ support. The package automatically registers itself as a pytest plugin during installation. Pylama also supports the `pytest_cache` plugin. Check files with pylama :: pytest --pylama ... The recommended way to set pylama options when using pytest — configuration files (see below). Writing a linter ================ You can write a custom extension for Pylama. The custom linter should be a python module. Its name should be like 'pylama_'. In 'setup.py', 'pylama.linter' entry point should be defined. :: setup( # ... entry_points={ 'pylama.linter': ['lintername = pylama_lintername.main:Linter'], } # ... ) 'Linter' should be an instance of 'pylama.lint.Linter' class. It must implement two methods: 1. ``allow`` takes a `path` argument and returns true if the linter can check this file for errors. 2. ``run`` takes a `path` argument and `meta` keyword arguments and returns a list of errors. Example: -------- Just a virtual 'WOW' checker. setup.py: :: setup( name='pylama_wow', install_requires=[ 'setuptools' ], entry_points={ 'pylama.linter': ['wow = pylama_wow.main:Linter'], } # ... ) pylama_wow.py: :: from pylama.lint import Linter as BaseLinter class Linter(BaseLinter): def allow(self, path): return 'wow' in path def run(self, path, **meta): with open(path) as f: if 'wow' in f.read(): return [{ lnum: 0, col: 0, text: '"wow" has been found.', type: 'WOW' }] Run pylama from python code --------------------------- :: from pylama.main import check_paths, parse_options # Use and/or modify 0 or more of the options defined as keys in the variable my_redefined_options below. # To use defaults for any option, remove that key completely. my_redefined_options = { 'linters': ['pep257', 'pydocstyle', 'pycodestyle', 'pyflakes' ...], 'ignore': ['D203', 'D213', 'D406', 'D407', 'D413' ...], 'select': ['R1705' ...], 'sort': 'F,E,W,C,D,...', 'skip': '*__init__.py,*/test/*.py,...', 'async': True, 'force': True ... } # relative path of the directory in which pylama should check my_path = '...' options = parse_options([my_path], **my_redefined_options) errors = check_paths(my_path, options, rootdir='.') .. _bagtracker: Bug tracker ----------- If you have any suggestions, bug reports or annoyances please report them to the issue tracker at https://github.com/klen/pylama/issues .. _contributing: Contributing ------------ Development of `pylama` happens at GitHub: https://github.com/klen/pylama Contributors ^^^^^^^^^^^^ See CONTRIBUTORS_. .. _license: License ------- This is free software. You are permitted to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of it, under the terms of the MIT License. See LICENSE file for the complete license. This software is provided WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE file for the complete disclaimer. .. _links: .. _CONTRIBUTORS: https://github.com/klen/pylama/graphs/contributors .. _Mccabe: http://nedbatchelder.com/blog/200803/python_code_complexity_microtool.html .. _pydocstyle: https://github.com/PyCQA/pydocstyle/ .. _pycodestyle: https://github.com/PyCQA/pycodestyle .. _PyFlakes: https://github.com/pyflakes/pyflakes .. _Pylint: http://pylint.org .. _Pytest: http://pytest.org .. _klen: http://klen.github.io/ .. _eradicate: https://github.com/myint/eradicate .. _Mypy: https://github.com/python/mypy .. _Vulture: https://github.com/jendrikseipp/vulture .. |logo| image:: https://raw.github.com/klen/pylama/develop/docs/_static/logo.png :width: 100 .. _Radon: https://github.com/rubik/radon ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1659957992.0 pylama-8.4.1/pylama.egg-info/SOURCES.txt0000644000175100001710000000160314274171350017253 0ustar00runnerdockerChangelog LICENSE MANIFEST.in README.rst dummy.py setup.cfg setup.py pylama/__init__.py pylama/__main__.py pylama/check_async.py pylama/config.py pylama/config_toml.py pylama/context.py pylama/core.py pylama/errors.py pylama/hook.py pylama/main.py pylama/pytest.py pylama/utils.py pylama.egg-info/PKG-INFO pylama.egg-info/SOURCES.txt pylama.egg-info/dependency_links.txt pylama.egg-info/entry_points.txt pylama.egg-info/requires.txt pylama.egg-info/top_level.txt pylama/libs/__init__.py pylama/libs/inirama.py pylama/lint/__init__.py pylama/lint/pylama_eradicate.py pylama/lint/pylama_fake.py pylama/lint/pylama_mccabe.py pylama/lint/pylama_mypy.py pylama/lint/pylama_pycodestyle.py pylama/lint/pylama_pydocstyle.py pylama/lint/pylama_pyflakes.py pylama/lint/pylama_pylint.py pylama/lint/pylama_radon.py pylama/lint/pylama_vulture.py requirements/requirements-tests.txt requirements/requirements.txt././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1659957992.0 pylama-8.4.1/pylama.egg-info/dependency_links.txt0000644000175100001710000000000114274171350021435 0ustar00runnerdocker ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1659957992.0 pylama-8.4.1/pylama.egg-info/entry_points.txt0000644000175100001710000000012114274171350020657 0ustar00runnerdocker[console_scripts] pylama = pylama.main:shell [pytest11] pylama = pylama.pytest ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1659957992.0 pylama-8.4.1/pylama.egg-info/requires.txt0000644000175100001710000000054614274171350017774 0ustar00runnerdockermccabe>=0.7.0 pycodestyle>=2.9.1 pydocstyle>=6.1.1 pyflakes>=2.5.0 [all] pylint eradicate radon mypy vulture [eradicate] eradicate [mypy] mypy [pylint] pylint [radon] radon [tests] pytest>=7.1.2 pytest-mypy eradicate>=2.0.0 radon>=5.1.0 mypy pylint>=2.11.1 pylama-quotes toml vulture types-setuptools types-toml [toml] toml>=0.10.2 [vulture] vulture ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1659957992.0 pylama-8.4.1/pylama.egg-info/top_level.txt0000644000175100001710000000000714274171350020116 0ustar00runnerdockerpylama ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1659957992.323301 pylama-8.4.1/requirements/0000755000175100001710000000000014274171350015135 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1659957987.0 pylama-8.4.1/requirements/requirements-tests.txt0000644000175100001710000000023614274171343021564 0ustar00runnerdockerpytest >= 7.1.2 pytest-mypy eradicate >= 2.0.0 radon >= 5.1.0 mypy pylint >= 2.11.1 pylama-quotes toml vulture types-setuptools types-toml ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1659957987.0 pylama-8.4.1/requirements/requirements.txt0000644000175100001710000000015114274171343020420 0ustar00runnerdocker# Test requirements mccabe >= 0.7.0 pycodestyle >= 2.9.1 pydocstyle >= 6.1.1 pyflakes >= 2.5.0 ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1659957992.323301 pylama-8.4.1/setup.cfg0000644000175100001710000000366114274171350014241 0ustar00runnerdocker[wheel] python-tag = py3 [metadata] name = pylama version = attr: pylama.__version__ url = https://github.com/klen/pylama description = Code audit tool for python long_description = file: README.rst author = Kirill Klenov author_email = horneds@gmail.com license = MIT license_files = LICENSE keywords = qa, linter, pydocstyle, pycodestyle, mccabe, pylint project_urls = Documentation = https://klen.github.io/pylama Source code = https://github.com/klen/pylama Issue tracker = https://github.com/klen/pylama/issues classifiers = Development Status :: 5 - Production/Stable Environment :: Console Intended Audience :: Developers Intended Audience :: System Administrators License :: OSI Approved :: MIT License Programming Language :: Python Programming Language :: Python :: 3 Programming Language :: Python :: 3.10 Programming Language :: Python :: 3.7 Programming Language :: Python :: 3.8 Programming Language :: Python :: 3.9 Topic :: Software Development :: Quality Assurance Topic :: Software Development :: Testing [options] packages = pylama python_requires = >= 3.7 include_package_data = True [options.package_data] pylama = py.typed [options.entry_points] console_scripts = pylama = pylama.main:shell pytest11 = pylama = pylama.pytest [tool:pytest] addopts = -xsv [pylama] async = 1 ignore = D203,D213,F0401,C0111,E731,I0011 linters = pycodestyle,pyflakes,mccabe,pydocstyle,pylint,mypy skip = pylama/inirama.py,pylama/libs/* verbose = 0 max_line_length = 100 [pylama:pyflakes] builtins = _ [pylama:pylint] ignore = R,E1002,W0511,C0103,C0204,W0012 [pylama:pylama/core.py] ignore = C901,R0914 [pylama:pylama/main.py] ignore = R0914,W0212,C901,E1103 [pylama:tests/*] ignore = D,C,W,E1103 [mypy] ignore_missing_imports = True [tox:tox] envlist = py37,py38,py39,py310 [testenv] deps = -e .[tests] commands = pylama pylama pytest --pylama pylama {posargs} pytest tests -s {posargs} [egg_info] tag_build = tag_date = 0 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1659957987.0 pylama-8.4.1/setup.py0000644000175100001710000000127614274171343014134 0ustar00runnerdocker#!/usr/bin/env python """Setup pylama installation.""" import pathlib import pkg_resources from setuptools import setup def parse_requirements(path: str) -> "list[str]": with pathlib.Path(path).open(encoding='utf-8') as requirements: return [str(req) for req in pkg_resources.parse_requirements(requirements)] OPTIONAL_LINTERS = ['pylint', 'eradicate', 'radon', 'mypy', 'vulture'] setup( install_requires=parse_requirements("requirements/requirements.txt"), extras_require=dict( tests=parse_requirements("requirements/requirements-tests.txt"), all=OPTIONAL_LINTERS, **{linter: [linter] for linter in OPTIONAL_LINTERS}, toml="toml>=0.10.2", ), )