pax_global_header00006660000000000000000000000064145136746040014524gustar00rootroot0000000000000052 comment=65fb6458624157ca2e732863d25822f031c0bf76 prospector-1.10.3/000077500000000000000000000000001451367460400140065ustar00rootroot00000000000000prospector-1.10.3/.coveragerc000066400000000000000000000000311451367460400161210ustar00rootroot00000000000000 [run] source=prospector prospector-1.10.3/.github/000077500000000000000000000000001451367460400153465ustar00rootroot00000000000000prospector-1.10.3/.github/ISSUE_TEMPLATE/000077500000000000000000000000001451367460400175315ustar00rootroot00000000000000prospector-1.10.3/.github/ISSUE_TEMPLATE/bug_report.md000066400000000000000000000014441451367460400222260ustar00rootroot00000000000000--- name: Bug report about: Create a report to help us improve title: "[BUG]" labels: investigate assignees: "" --- **Describe the bug** A clear and concise description of what the bug is. **To Reproduce** Steps to reproduce the behavior: 1. Go to '...' 2. Call prospector the following way '....' 3. See error **Expected behavior** A clear and concise description of what you expected to happen. **Screenshots** If applicable, add screenshots to help explain your problem. **Environment (please complete the following information):** - OS: [e.g. macOS] - Tool [e.g. pylint] - Prospector version [e.g. 1.1.7] - Python version [e.g. 3.6.8] **Additional context** Add any other context about the problem here. Putting the list of dependencies installed, e.g. the output of `pip freeze` also helps. prospector-1.10.3/.github/ISSUE_TEMPLATE/feature_request.md000066400000000000000000000011541451367460400232570ustar00rootroot00000000000000--- name: Feature request about: Suggest an idea for this project title: "[FEATURE REQUEST]" labels: enhancement assignees: "" --- **Is your feature request related to a problem? Please describe.** A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] **Describe the solution you'd like** A clear and concise description of what you want to happen. **Describe alternatives you've considered** A clear and concise description of any alternative solutions or features you've considered. **Additional context** Add any other context or screenshots about the feature request here. prospector-1.10.3/.github/PULL_REQUEST_TEMPLATE.md000066400000000000000000000032521451367460400211510ustar00rootroot00000000000000 ## Description ## Related Issue ## Motivation and Context ## How Has This Been Tested? ## Types of changes - [ ] Bug fix (non-breaking change which fixes an issue) - [ ] New feature (non-breaking change which adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality to change) ## Checklist: - [ ] My code follows the code style of this project. - [ ] My change requires a change to the documentation. - [ ] I have updated the documentation accordingly. - [ ] My change requires a change to the dependencies - [ ] I have updated the dependencies accordingly - [ ] I have read the **CONTRIBUTING** document. - [ ] I have added tests to cover my changes. - [ ] All new and existing tests passed. prospector-1.10.3/.github/SECURITY.md000066400000000000000000000000731451367460400171370ustar00rootroot00000000000000Coordinated Disclosure Plan: https://tidelift.com/security prospector-1.10.3/.github/workflows/000077500000000000000000000000001451367460400174035ustar00rootroot00000000000000prospector-1.10.3/.github/workflows/codeql-analysis.yml000066400000000000000000000046701451367460400232250ustar00rootroot00000000000000# For most projects, this workflow file will not need changing; you simply need # to commit it to your repository. # # You may wish to alter this file to override the set of languages analyzed, # or to provide custom queries or build logic. # # ******** NOTE ******** # We have attempted to detect the languages in your repository. Please check # the `language` matrix defined below to confirm you have the correct set of # supported CodeQL languages. # name: "CodeQL" on: push: branches: [main] pull_request: # The branches below must be a subset of the branches above branches: [main] schedule: - cron: "44 16 * * 4" jobs: analyze: name: Analyze runs-on: ubuntu-latest permissions: actions: read contents: read security-events: write strategy: fail-fast: false matrix: language: ["python"] # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ] # Learn more: # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed steps: - name: Checkout repository uses: actions/checkout@v2 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL uses: github/codeql-action/init@v1 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. # By default, queries listed here will override any specified in a config file. # Prefix the list here with "+" to use these queries and those in the config file. # queries: ./path/to/local/query, your-org/your-repo/queries@main # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild uses: github/codeql-action/autobuild@v1 # â„šī¸ Command-line programs to run using the OS shell. # 📚 https://git.io/JvXDl # âœī¸ If the Autobuild fails above, remove it and uncomment the following three lines # and modify them (or add more) to build your code if your project # uses a compiled language #- run: | # make bootstrap # make release - name: Perform CodeQL Analysis uses: github/codeql-action/analyze@v1 prospector-1.10.3/.github/workflows/release.yml000066400000000000000000000017601451367460400215520ustar00rootroot00000000000000name: Release on: release: types: - published env: DEFAULT_PYTHON: 3.9 jobs: release-pypi: name: Upload release to PyPI runs-on: ubuntu-latest steps: - name: Check out code from Github uses: actions/checkout@v2.3.4 - name: Set up Python ${{ env.DEFAULT_PYTHON }} id: python uses: actions/setup-python@v2.2.2 with: python-version: ${{ env.DEFAULT_PYTHON }} - name: Install requirements run: | python -m pip install --disable-pip-version-check -U pip twine poetry "poetry-core<1.3.0" - name: Build distributions run: | poetry build -f wheel poetry build -f sdist - name: Upload to PyPI if: github.event_name == 'release' && startsWith(github.ref, 'refs/tags') env: TWINE_REPOSITORY: pypi TWINE_USERNAME: __token__ TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }} run: | twine upload --verbose dist/* prospector-1.10.3/.github/workflows/tests.yml000066400000000000000000000024771451367460400213020ustar00rootroot00000000000000name: tests on: push: branches: - master pull_request: branches: - master jobs: tests: runs-on: ubuntu-latest strategy: fail-fast: false matrix: python-version: ["3.7", "3.8", "3.9", "3.10", "3.11"] steps: - uses: actions/checkout@v3 - run: pipx install poetry - uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} cache: "poetry" - run: poetry install -E with_everything # - run: poetry show --all # - run: poetry show --outdated # - run: poetry show --tree - name: Install test profile used in the tests run: poetry run pip install tests/prospector-profile-test/ - run: poetry run pytest --benchmark-disable --cov --cov-report= tests/ - name: Check that Prospector can run run: | echo "Checking prospector can run; don't care about messages found, only that it runs successfully" poetry run prospector --quiet --zero-exit echo "Prospector ran successfully." - name: Check packaging run: | echo "Building packages" rm -rf dist/ poetry build echo "Validating packages" poetry run twine check dist/* echo "Packages build and valdiate successfully." prospector-1.10.3/.gitignore000066400000000000000000000012431451367460400157760ustar00rootroot00000000000000*.py[cod] # C extensions *.so # Packages *.egg *.egg-info dist build eggs parts var sdist develop-eggs .installed.cfg # Installer logs pip-log.txt # Unit test / coverage reports .coverage .tox nosetests.xml .noseids .benchmarks # Translations *.mo # Mr Developer .mr.developer.cfg .project .pydevproject .idea .vscode .vscode-server .devcontainer.json .vs-liveshare-keychain .local .gitconfig .cache .bash_history # Sphinx docs/_build docs/html # testing/linting .mypy_cache .pytest_cache # emacs TAGS flycheck* # local configuration .python-version .env .venv* env.txt venv # Pylint .pylint.d pip-wheel-metadata # local testing for now .drone.* TODO.txt *.swp prospector-1.10.3/.pre-commit-config.yaml000066400000000000000000000033771451367460400203010ustar00rootroot00000000000000fail_fast: true repos: - repo: https://github.com/pre-commit/pre-commit-hooks rev: v4.4.0 hooks: - id: trailing-whitespace - id: end-of-file-fixer - id: mixed-line-ending args: [--fix=lf] - id: debug-statements - id: check-added-large-files - repo: https://github.com/asottile/pyupgrade rev: v3.4.0 hooks: - id: pyupgrade args: [--py37-plus] - repo: https://github.com/psf/black rev: 23.3.0 hooks: - id: black args: - --exclude=/(tests)/ - --line-length=120 - repo: https://github.com/codespell-project/codespell rev: v2.2.4 hooks: - id: codespell args: - --ignore-words-list=nin,astroid - --skip=poetry.lock - repo: https://github.com/PyCQA/prospector rev: 1.10.1 hooks: - id: prospector additional_dependencies: - "prospector[with_everything]" # the following are needed to run mypy successfully in the pre-commit virtualenv - types-setuptools - types-PyYAML args: - --summary-only - --zero-exit - repo: https://github.com/PyCQA/flake8 rev: 6.0.0 hooks: - id: flake8 exclude: "testdata" args: - --max-line-length=120 - repo: https://github.com/PyCQA/isort rev: 5.12.0 hooks: - id: isort args: ["--profile", "black", "--filter-files"] - repo: https://github.com/regebro/pyroma rev: "4.2" hooks: - id: pyroma # Must be specified because of the default value in pyroma always_run: false additional_dependencies: - poetry files: | (?x)^( README.rst| pyproject.toml| )$ prospector-1.10.3/.pre-commit-hooks.yaml000066400000000000000000000002501451367460400201420ustar00rootroot00000000000000- id: prospector name: prospector description: Analyze Python code using Prospector entry: prospector language: python types: [python] require_serial: true prospector-1.10.3/.prospector.yml000066400000000000000000000014211451367460400170050ustar00rootroot00000000000000output-format: grouped # strictness: veryhigh # doc-warnings: no # test-warnings: no max-line-length: 120 # pep8: full ignore-paths: - docs/ mypy: run: true options: ignore-missing-imports: true follow-imports: skip pylint: options: extension-pkg-allow-list: mypy disable: - too-few-public-methods - missing-docstring - star-args - consider-using-f-string - too-many-locals # TODO: clean up these complexity problems - too-many-branches - too-many-arguments - too-many-instance-attributes - too-many-function-args - too-many-statements mccabe: run: false pycodestyle: disable: - E126 pydocstyle: run: false disable: - D100 - D101 - D102 - D103 - D107 - D205 - D400 - D401 prospector-1.10.3/.readthedocs.yaml000066400000000000000000000003731451367460400172400ustar00rootroot00000000000000version: 2 build: os: ubuntu-22.04 tools: python: "3.11" sphinx: configuration: docs/conf.py python: install: - requirements: docs/requirements.txt - method: pip path: . extra_requirements: - with_everything prospector-1.10.3/CHANGELOG.rst000066400000000000000000001240001451367460400160240ustar00rootroot00000000000000######### Changelog ######### Version 1.10.3 -------------- **New**: * Dependencies versions were bumped following multiple moderate CVE declared in transitive dependencies. Version 1.10.1 and 1.10.2 ------------------------- **Bugfixes**: 1.10.1 was released to fix a packaging and installation problem (see `this issue `_ 1.10.2 fixes both issues Version 1.10.0 -------------- **New**: * Prospector profiles can now be loaded from external packages, meaning that behaviour can be packaged and re-used across projects - `#604 `_ * Added pyright as an optional additional tool - `#612 `_ * `use-dmypy` option now passed through to MyPy `#611 `_ Version 1.9.0 ------------- * https://github.com/PyCQA/prospector/pull/577 * https://github.com/PyCQA/prospector/pull/580 * https://github.com/PyCQA/prospector/pull/592 Version 1.8.4 ------------- * https://github.com/PyCQA/prospector/issues/566 * https://github.com/PyCQA/prospector/issues/575 * https://github.com/PyCQA/prospector/issues/578 Version 1.8.3 ------------- * `#560 `_ * `#559 `_ * `#555 `_ * `#554 `_ * `#552 `_ * `#551 `_ Version 1.8.2 ------------- * `#547 `_ Version 1.8.1 ------------- Let's test faster. **Add support for Python 3.11**: Python 3.11 is between 10-60% faster than Python 3.10. Version 1.8.0 ------------- **File discovery fixes**: Finding paths and files to check has been replaced with a new version using ``pathlib`` - this should not result in any changes, except fixing an issue where ``pylint`` and ``pydocstyle`` were inspecting the same file or directory twice sometimes. However it may cause slightly different orders or reduce these duplicate warnings. The behavior of prospector should be unchanged, apart from some bugfixes related to the old file discovery mechanism. Related bugs and PRs: * `#480 `_ * `#417 `_ * `#199 `_ **Other bugfixes**: * `#106 `_ * Running prospector on a path not in the CWD (eg, 'prospector /some/where/else') will not cause exceptions, and will instead use absolute paths for message output * Autodetction of libraries, to automatically use pylint plugins, will no work on projects using a pyproject.toml ; also it has been turned on by default, it seemed to have accidentally been set to off by default some time ago. * `#529 `_ **Misc**: * Prospector now runs on itself without generating errors after all linting warnings were fixed Version 1.7.6 ------------- It's a bugs life. **Fixes**: * Fixed a problem where pylint was reporting the same message multiple times, because it was given a path to the file multiple times * The blending fix mentioned in the 1.7.5 release was actually not checked in by accident, this is there now. Version 1.7.5 ------------- Just say no to bugs. **New**: * Profile inheritance is now optional - appending a profile name with a ``?`` means that if it is not found, prospector will simply continue. `Read the documentation here `_. Closes `#161 `_ **Fixes**: * Stopped the ProfileValidator tool raising errors about ``pep8`` and ``pep257`` sections being unknown. Instead, they raise deprecated warnings. * Blending works again - for example, pylint and pycodestyle errors representing the same thing are combined. After renaming pep8 to pycodestyle, this only worked when using legacy names. * Unrecognised Mypy options now raise an exception instead of silently carrying on - `#455 `_ **Tidyup**: * Lots of warnings fixed from running prospector on itself Version 1.7.4 ------------- Mea culpa release **Fix** The effort to allow pylint configuration in ``pyproject.toml`` to be used as an external config source (`issue here `_) had the unintended side effect where any project using poetry would now use that configuration and thus would ignore the pylint configuration in the profile. This was true even if the ``pyproject.toml`` had no pylint directives in it. The behaviour has now been fixed where pylint will be configured using configuration from the profile *first* and then if any additional settings are found in a ``pylintrc`` or ``pyproject.toml`` or ``setup.cfg`` then these will override the profile configuration, instead of replacing it entirely. This also has the benefit of fixing `#227 `_. Version 1.7.3 ------------- The war on bugs. **Fixes**: * Autodetect now does not die if a user does not have permissions (related to `#271 `_ and `#487 `_) * Fixed that some pylint documentation warning messages were not correctly included in the list of documentation warnings to squash if doc warnings are not desired. * Fixed the exit code for prospector - it was always ``0`` after the move to using poetry for packaging instead of ``1`` if errors were found (unless ``--zero-exit``) was used. This now exits with the correct code based on the documented (and previous) behaviour. * Fix that ``pep8`` would overwrite instead of inherit from previous ``pycodestyle`` blocks, same with pep257 - `#491 (comment) `_ * Fix the pre-commit hook, as it could not run without being installed ``[with_everything]``, due to the "NotAvailableTool" class not properly implementing the abstract base class. * Improved documentation about the pre-commit hook as well to clarify its use better - `#484 `_ Version 1.7.2 ------------- More bugfixes! **Fixes**: * Fix that ``pep8`` and ``pep257`` sections were renamed but the old deprecated values were not properly used to configure ``pycodestyle`` and ``pydocstyle`` - `#491 `_ * Better handling for when the user running prospector is not able to read a file or directory - `#271 `_ and `#487 `_ Version 1.7.1 ------------- Lots of smaller bugfixes. **Fixes**: * Prospector now configures pylint using settings found in ``pyproject.toml`` or ``setup.cfg``, not only ``.pylintrc`` - `#485 `_ * Fixed ``--no-style-warnings`` command line argument no longer warning after renaming ``pep8`` to ``pycodestyle`` - `#488 `_ * Documentation is building again - `#473 `_ * ``--with-tool`` flag now respects - but overrides - tools disabled in profiles - `#447 `_ * Fixed crash with merging multiple import warnings - `#477 `_ * Fixed segfault when analysing code using cartopy - `#403 `_ Version 1.7.0 ------------- This is mostly a "tidying up" release but some things have changed which may cause differences to output, hence the bump of the major version. **New**: * Added a ``--quiet`` command line option to suppress all output. Useful if you just want to know the exit code of prospector for scripting. * Removed the prospector "indent checker" since this is now no longer in pylint `#482 `_ **Fixes**: `Deprecation warning:` * Tools ``pep8`` and ``pep257`` have been renamed to ``pycodestyle`` and ``pydocstyle`` respectively. This is because the tools themselves were renamed years ago - See `#222 `_. Note that this means that prospector profiles and message output uses this new name instead of the old name, so you will need to update your configuration. The old names will still work, but this legacy behaviour will be removed in prospector 2.0 * There is now a ``--legacy-tool-names`` flag for outputting pep8 or pep257 as the tool name when outputting errors. This is to be backwards compatible with any parsing logic; this flag is also deprecated and will go away in prospector 2.0 **Tidying up internals** These are all internal prospector code quality improvements. Ideally, they should not be noticed by anybody as they are internal refactorings. * `#467 `_ - Removed nosetests, as nose is not compatible with Python 3.10 yet and the pytest tests were already doing the same thing * Tidied up the tox testing * Started adding some type hints to methods * Fixed lots of warnings raised by prospector when running prospector on itself... * Removed some old python2 compatibility code which is no longer needed now python2 is not supported at all * Fixed hyperlink formatting in this CHANGELOG to be RST (was never updated after converting from markdown) * Replaced `os.path` with `pathlib.Path` everywhere in prospector internals, to improve and simplify finding files to inspect. Theoretically this behaves in the same way as far as the user will see (please open a ticket if you notice anything obviously different) Version 1.6.1 ------------- - Update pyflakes to 2.* `#454 `_ Version 1.6.0 ------------- - Fixed incompatible version specification of pylint-plugin-utils. This now requires pylint-django of at least 2.5. `#478 `_ *note* This release drops support for python ``3.6.1`` Version 1.5.3 and 1.5.3dev0 and 1.5.3.1 --------------------------------------- - `#465 `_ Remove unnecessary configuration reset to fix pylint>=2.12 compatibility - Version 1.5.3.1 was needed to unpin the pylint dependency to actually use the fix for compatibility. Version 1.5.2 ------------- - `#465 `_ Bugfix release to pin pylint<2.12 because prospector's internals were not compatible with it Version 1.5.1 ------------- - `#438 `_ Promoting pre-release to release as it appears to work Version 1.5.0.1 --------------- - `#433 `_ Attempted fix of flake8 dependency versioning conflict Version 1.5.0 ------------- - `#436 `_ Swapped out packaging to use poetry instead of setup.py and setuptools Version 1.4.1 ------------- - `#373 `_ Permits to raise pylint's useless-suppression - `#414 `_ Loosen pycodestyle requirement - `#408 `_ Fix filenames if they are PosixPath - `#412 `_ Fix unclosed file warning - `#399 `_ Fix fatal error on running mypy when duplicate module names Version 1.4.0 ------------- - `#424 `_ GitHub Action to discover typos with codespell - `#421 `_ Loosen pylint requirement - `#427 `_ Fix prospector for latest pylint version and add Github actions Version 1.3.1 ------------- - `#390 `_ Updating Vulture API usage for newer versions of Vulture - `#394 `_ Update pylint and pylint-django Version 1.3.0 ------------- - Update pylint support to 2.5.2 - Update pylint-django to 2.0.15 - Update pyflakes support to 2.2.0 - Update pycodestyle support to 2.6.0 - Update pep8-naming support to 0.10.0 - Update pyflakes to <2.3.0 and >=2.2.0 - Update pycodestyle to <2.7.0 and >=2.6.0 - Update vulture to 1.5 - Drop Python 2 support - Add output-target field when merging profiles - Add support for [pycodestyle] external config section - Fix AttributeExceptionError being raised when ignore_paths is an integer - Use black on entire project - Add new pylint option: `use_pylint_default_path_finder` to make sure there's an option to preserve pylint default behavior. - Update pyflakes error code list to the recent version Version 1.2.0 ------------- - Drop Python 3.4 support - `#308 `_ Update pyflakes support to < 2.1.0 - `#324 `_ Add bandit support - `#344 `_ Ignore __pycache__ and node_modules - `#349 `_ and `#355 `_ Fix compatibility issues with mypy >= 0.730 - `#356 `_ Add support for Python 3.8 Version 1.1.7 ------------- - `#299 `_ Output path tests and abspaths for windows - `#300 `_ Fix `check_paths` definition for pep8tool - `#318 `_ Add support pylint --load-plugins option in profile - `#336 `_ Pylint fix for message definitions usage - `#340 `_ Bump pylint django - `#343 `_ Support more kinds of mypy messages - `@5ea0e95 `_ Pin astroid to 2.2.5 Version 1.1.6.4 --------------- - `#333 `_ Hotfix for pylint module run - `#309 `_ Remove the pylint locally-enabled message suppression Version 1.1.6.2 --------------- - `#304 `_ Pin pylint to 2.1.1 for now as prospector is not compatible with 2.2.0 - `#302 `_ Pin astroid to 2.0.4 as pylint-django and pylint-flask need fixes to be compatible with newer versions Version 1.1.6.1 --------------- - `#292 `_ Adding pylint plugin dependencies back and fixing autodetect behaviour. - (note: .1 added as 1.1.6 upload to PyPI was broken) Version 1.1.5 ------------- - `#283 `_ Remove unexpected argument from read_config_file - Remove quiet argument - `#291 `_ Update pycodestyle support until 2.4.0 - `#280 `_ Add strict option and fixed emacs output format for mypy tool - `#282 `_ Fix working dir detection Version 1.1.4 --------------- - `#285 `_ Fix dependency tree resolution - now insists on `pep8-naming<=0.4.1` as later versions cause conflicting versions of flake8 to be installed. Version 1.1.3 --------------- - `#279 `_ Fix --show-profile crash Version 1.1.2 --------------- - `#276 `_ Updating required Pyroma version and removing some warnings which were removed from Pyroma - thanks `@volans- `_ for PR `#277 `_ Version 1.1.1 --------------- - Removing `pylint-common `_ as a direct dependency as it does not add a lot of utility and is not kept up to date as much as other plugins Version 1.1 --------------- - `#267 `_ Fix read_config_file using quiet keyword with older pylint versions - `#262 `_ Bugfix report different behavior based on path(includes KeyError on FORMATTERS fix) Version 1.0 --------------- - `#228 `_ Add mypy support - `#249 `_ Add option to point to pylintrc inside prospector configuration file - `#250 `_ Add option to redirect prospector output to files - `#261 `_ Drop Python 3.3 support - `#261 `_ Use Pylint >= 2 for Python 3 Version 0.12.11 --------------- - `#256 `_ Match relative paths that giving different results when using `--absolute-paths` flag - Pin vulture version < 0.25 Version 0.12.10 --------------- - Force pyroma >= 2.3 - `#236 `_ Fix typo and update URLs in docs Version 0.12.9 --------------- - `#237 `_ Load pylint plugins before pylint config - `#253 `_ Relaxing pyroma constraint - `#229 `_ prospector crashes on startup if a recent pyroma is installed Version 0.12.8 --------------- * Enforece pylint, pyflakes and pycodestyle versions to avoid breaking other dependent tools * `#242 `_ Fix absolute path issue with pylint * `#234 `_ Added Python 3.5/3.6 support on build Version 0.12.7 --------------- * Enforcing pydocstyle >= 2.0.0 for API compatibility reliability Version 0.12.6 --------------- * `#210 `_ `#212 `_ Removing debug output accidentally left in (@souliane) * `#211 `_ Added VSCode extension to docs (@DonJayamanne) * `#215 `_ Support `pydocstyle>=2.0` (@samspillaz) * `#217 `_ Updating links to supported tools in docs (@mbeacom) * `#219 `_ Added a `__main__.py` to allow calling `python -m prospector` (@cprogrammer1994) Version 0.12.5 --------------- * `#207 `_ Fixed missing 'UnknownMessage' exception caused by recent pylint submodule changes * Minor documentation formatting updates * `#202 `_ Ignoring .tox directories to avoid accidentally checking the code in there * `#205 `_ Fixes for compatibility with pylint 1.7+ * `#193 `_ Fixes for compatibility with pylint 1.6+ * `#194 `_ Fixes for compatibility with vulture 0.9+ * `#191 `_ Fixes for compatibility with pydocstyle 1.1+ Version 0.12.4 --------------- * Panicky stapling of pyroma dependency until prospector is fixed to not break with the new pyroma release Version 0.12.3 --------------- * `#190 `_ Pinning pydocstyle version for now until API compatibility with newer versions can be written * `#184 `_ Including the LICENCE file when building dists * Fixed a crash in the profile_validator tool if an empty profile was found * (Version 0.12.2 does not exist due to a counting error...) Version 0.12.1 --------------- * `#178 `_ Long paths no longer cause crash in Windows. * `#173 `_ Changed from using pep8 to pycodestyle (which is what pep8 was renamed to) * `#172 `_ Fixed non-ascii file handling for mccabe tool and simplified all python source file reading Version 0.12 --------------- * `#170 `_ Changed from using pep257 to pydocstyle (which is what pep257 is now called) * `#162 `_ Properly warning about optional tools which are not installed * `#166 `_ Added vscode formatter * `#153 `_ Better pep257 support * `#156 `_ Better pyroma logging hack for when pyroma is not installed * `#158 `_ Fixed max-line-length command line option Version 0.11.7 --------------- * Wrapping all tools so that none can directly write to stdout/stderr, as this breaks the output format for things like json. Instead, it is captured and optionally included as a regular message. Version 0.11.6 --------------- * Yet more 'dodgy' encoding problem avoidance Version 0.11.5 --------------- * Including forgotten 'python-targets' value in profile serialization Version 0.11.4 --------------- * Prevented 'dodgy' tool from trying to analyse compressed text data Version 0.11.3 --------------- * Fixed encoding of file contents handling by tool "dodgy" under Python3 Version 0.11.2 --------------- * Fixed a file encoding detection issue when running under Python3 * If a pylint plugin is specified in a .pylintrc file which cannot be loaded, prospector will now carry on with a warning rather than simply crash Version 0.11.1 --------------- * `#147 `_ Fixed crash when trying to load pylint configuration files in pylint 1.5 Version 0.11 --------------- * Compatibility fixes to work with pylint>=1.5 * McCabe tool now reports correct line and character number for syntax errors (and therefore gets blended if pylint etc detects such an error) * Autodetect of libraries will now not search inside virtualenvironments * `#142 `_ better installation documentation in README (thanks `@ExcaliburZero `_) * `#141 `_ profile-validator no longer complains about member-warnings (thanks `@alefteris `_) * `#140 `_ emacs formatter includes character position (thanks `@philroberts `_) * `#138 `_ docs fixed for 'output-format' profile option (thanks `@faulkner `_) * `#137 `_ fixed various formatting issues in docs (thanks `@danstender `_) * `#132 `_ Added support for custom flask linting thanks to the awesome [pylint-flask](https://github.com/jschaf/pylint-flask) plugin by [jschaf](https://github.com/jschaf) * `#131 `_, `#134 `_ Custom pylint plugins are now loaded from existing .pylintrc files if present (thanks `@kaidokert `_ and `@antoviaque `_) Version 0.10.2 --------------- * Added information to summary to explain what external configuration was used (if any) to configure the underlying tools * Fixed supression-token search to use (or at least guess) correct file encoding Version 0.10.1 --------------- * `#116 `_ Comparison failed between messages with numeric values for character and those with a `None` value (thanks @smspillaz) * `#118 `_ Unified output of formatters to have correct output of str rather than bytes (thanks @prophile) * `#115 `_ Removed argparse as an explicit dependency as only Python 2.7+ is supported now Version 0.10 --------------- * `#112 `_ Profiles will now also be autoloaded from directories named `.prospector`. * `#32 `_ and `#108 `_ Added a new 'xunit' output formatter for tools and services which integrate with this format (thanks to [lfrodrigues](https://github.com/lfrodrigues)) * Added a new built-in profile called 'flake8' for people who want to mimic the behaviour of 'flake8' using prospector. Version 0.9.10 --------------- * The profile validator would load any file whose name was a subset of '.prospector.yaml' due to using the incorrect comparison operator. * Fixing a crash when using an empty `ignore-patterns` list in a profile. * Fixing a crash when a profile is not valid YAML at all. * `#105 `_ pyflakes was not correctly ignoring errors. Version 0.9.9 --------------- * pep8.py 1.6.0 added new messages, which are now in prospector's built-in profiles Version 0.9.8 --------------- * Fixing a crash when using pep8 1.6.0 due to the pep8 tool renaming something that Prospector uses Version 0.9.7 --------------- * `#104 `_ The previous attempt at normalising bytestrings and unicode in Python 2 was clumsily done and a bit broken. It is hopefully now using the correct voodoo incantations to get characters from one place to another. * The blender combinations were not updated to use the new PyFlakes error codes; this is now fixed. Version 0.9.6 --------------- * The profile validator tool was always outputting absolute paths in messages. This is now fixed. * The "# NOQA" checking was using absolute paths incorrectly, which meant the message locations (with relative paths) did not match up and no messages were suppressed. Version 0.9.5 --------------- * Fixed a problem with profile serialising where it was using the incorrect dict value for strictness Version 0.9.4 --------------- * The previous PEP257 hack was not compatible with older versions of pep257. Version 0.9.3 --------------- * The PEP257 tool sets a logging level of DEBUG globally when imported as of version 0.4.1, and this causes huge amounts of tokenzing debug to be output. Prospector now has a hacky workaround until that is fixed. * Extra profile information (mainly the shorthand information) is kept when parsing and serializing profiles. Version 0.9.2 --------------- * There were some problems related to absolute paths when loading profiles that were not in the current working directory. Version 0.9.1 --------------- * Mandating version 0.2.3 of pylint-plugin-utils, as the earlier ones don't work with the add_message API changes made in pylint 1.4+ Version 0.9 --------------- * `#102 `_ By default, prospector will hide pylint's "no-member" warnings, because more often than not they are simply incorrect. They can be re-enabled with the '--member-warnings' command line flag or the 'member-warnings: true' profile option. * `#101 `_ Code annotated with pep8/flake8 style "# noqa" comments is now understood by prospector and will lead to messages from other tools being suppressed too. * `#100 `_ Pyflakes error codes have been replaced with the same as those used in flake8, for consistency. Profiles with the old values will still work, and the profile-validator will warn you to upgrade. * Messages now use Pylint error symbols ('star-args') instead of codes ('W0142'). This makes it much more obvious what each message means and what is happening when errors are suppressed or ignored in profiles. The old error codes will continue to work in profiles. * The way that profiles are handled and parsed has completely been rewritten to avoid several bugs and introduce 'shorthand' options to profiles. This allows profiles to specify simple options like 'doc-warnings: true' inside profiles and configure anything that can be configured as a command line argument. Profiles can now use options like 'strictness: high' or 'doc-warnings: true' as a shortcut for inheriting the built-in prospector profiles. * A new `--show-profile` option is available to dump the calculated profile, which is helpful for figuring out what prospector thinks it is doing. * Profiles now have separate `ignore-paths` and `ignore-patterns` directives to match the command line arguments. The old `ignore` directive remains in place for backwards compatibility and will be deprecated in the future. * A new tool, `profile-validator`, has been added. It simply checks prospector profiles and validates the settings, providing warnings if any are incorrect. * `#89 `_ and `#40 `_ - profile merging was not behaving exactly as intended, with later profiles not overriding earlier profiles. This is now fixed as part of the aforementioned rewrite. * pep257 is now included by default; however it will not run unless the '--doc-warnings' flag is used. * pep257 messages are now properly blended with other tools' documentation warnings * Path and output character encoding is now handled much better (which is to say, it is handled; previously it wasn't at all). Version 0.8.3 --------------- * `#96 `_ and `#97 `_ - disabling messages in profiles now works for pep8 Version 0.8.2 --------------- * Version loading in setup.py no longer imports the prospector module (which could lead to various weirdnesses when installing on different platforms) * `#82 `_ resolves regression in adapter library detection raising, ``ValueError: too many values to unpack``. provided by `@jquast `_ * `#83 `_ resolves regression when adapter library detects django, ``TypeError: '_sre.SRE_Pattern' object is not iterable``. provided by `@jquast `_ Version 0.8.1 --------------- * Strictness now also changes which pep257 messages are output * pep257 and vulture messages are now combined and 'blended' with other tools * `#80 `_ Fix for Python3 issue when detecting libraries, provided by `@smspillaz `_ Version 0.8 --------------- * Demoted frosted to be an optional tool - this is because development seems to have slowed and pyflakes has picked up again, and frosted how has several issues which are solved by pyflakes and is no longer a useful addition. * `#78 `_ Prospector can now take multiple files as a path argument, thus providing errors for several files at a time. This helps when integrating with IDEs, for example. * Upgrading to newer versions of Pylint and related dependencies resolves `#73 `_, `#75 `_, `#76 `_ and `#79 `_ * `#74 `_, `#10 `_ Tools will now use any configuration specific to them by default. That is to say, if a `.pylintrc` file exists, then that will be used in preference to prospector's own opinions of how to use pylint. * Added centralised configuration management, with an abstraction away from how prospector and each tool is actually configured. * Removed the "adaptors" concept. This was a sort of visitor pattern in which each tool's configuration could be updated by an adaptor, which 'visited' the tool to tweak settings based on what the adaptor represented. In practise this was not useful and a confusing way to tweak behaviour - tools now configure themselves based on configuration options directly. * Changed the default output format to be 'grouped' rather than 'text' * Support for Python 2.6 has been dropped, following Pylint's lead. * Using pylint 1.4's 'unsafe' mode, which allows it to load any C extensions (this was the behaviour for 1.3 and below). Not loading them causes many many inference errors. * `#65 `_ Resolve UnicodeDecodeErrors thrown while attempting to auto-discover modules of interest by discovering target python source file encoding (PEP263), and issuing only a warning if it fails (thanks to [Jeff Quast](https://github.com/jquast)). Version 0.7.3 --------------- * Pylint dependency version restricted to 1.3, as 1.4 drops support for Python 2.6. Prospector will drop support for Python 2.6 in a 0.8 release. * File names ending in 'tests.py' will now be ignored if prospector is set to ignore tests (previously, the regular expression only ignored files ending in 'test.py') * `#70 `_ Profiles starting with a `.yml` extension can now be autoloaded * `#62 `_ For human readable output, the summary of messages will now be printed at the end rather than at the start, so the summary will be what users see when running prospector (without piping into `less` etc) Version 0.7.2 --------------- * The E265 error from PEP8 - "Block comment should start with '# '" - has been disabled for anything except veryhigh strictness. Version 0.7.1 --------------- * `#60 `_ Prospector did not work with Python2.6 due to timedelta.total_seconds() not being available. * Restored the behaviour where std_out/std_err from pylint is suppressed Version 0.7 --------------- * `#48 `_ If a folder is detected to be a virtualenvironment, then prospector will not check the files inside. * `#31 `_ Prospector can now check single files if passed a module as the path argument. * `#50 `_ Prospector now uses an exit code of 1 to indicate that messages were found, to make it easier for bash scripts and so on to fail if any messages are found. A new flag, `-0` or `--zero-exit`, turns off this behaviour so that a non-zero exit code indicates that prospector failed to run. * Profiles got an update to make them easier to understand and use. They are mostly the same as before, but `the documentation `_ and command line arguments have improved so that they can be reliably used. * If a directive inline in code disables a pylint message, equivalent messages from other tools will now also be disabled. * Added optional tools - additional tools which are not enabled by default but can be activated if the user chooses to. * Added pyroma, a tool for validating packaging metadata, as an optional tool. * `#29 `_ Added support for pep257, a docstring format checker * `#45 `_ Added vulture, a tool for finding dead code, as an optional tool. * `#24 `_ Added Sphinx documentation, which is now also `available on ReadTheDocs `_ Version 0.6.4 --------------- * Fixed pylint system path munging again again Version 0.6.3 --------------- * Fixed dodgy tool's use of new file finder Version 0.6.2 --------------- * Fixed pylint system path munging again Version 0.6.1 --------------- * Fixed pylint system path munging Version 0.6 --------------- * Module and package finding has been centralised into a `finder.py` module, from which all tools take the list of files to be inspected. This helps unify which files get inspected, as previously there were several times when tools were not correctly ignoring files. * Frosted [cannot handle non-utf-8 encoded files](https://github.com/timothycrosley/frosted/issues/56) so a workaround has been added to simply ignore encoding errors raised by Frosted until the bug is fixed. This was deemed okay as it is very similar to pyflakes in terms of what it finds, and pyflakes does not have this problem. * `#43 `_ - the blender is now smarter, and considers that a message may be part of more than one 'blend'. This means that some messages are no longer duplicated. * `#42 `_ - a few more message pairs were cleaned up, reducing ambiguity and redundancy * `#33 `_ - there is now an output format called `pylint` which mimics the pylint `--parseable` output format, with the slight difference that it includes the name of the tool as well as the code of the message. * `#37 `_ - profiles can now use the extension `.yml` as well as `.yaml` * `#34 `_ - south migrations are ignored if in the new south name of `south_migrations` (ie, this is compatible with the post-Django-1.7 world) Version 0.5.6 / 0.5.5 --------------------- * The pylint path handling was slightly incorrect when multiple python modules were in the same directory and importing from each other, but no `__init__.py` package was present. If modules in such a directory imported from each other, pylint would crash, as the modules would not be in the `sys.path`. Note that 0.5.5 was released but this bugfix was not correctly merged before releasing. 0.5.6 contains this bugfix. Version 0.5.4 --------------- * Fixing a bug in the handling of relative/absolute paths in the McCabe tool Version 0.5.3 --------------- ##### New Features * Python 3.4 is now tested for and supported ##### Bug Fixes * Module-level attributes can now be documented with a string without triggering a "String statement has no effect" warning * `#28 `_ Fixed absolute path bug with Frosted tool Version 0.5.2 --------------- ##### New Features * Support for new error messages introduced in recent versions of `pep8` and `pylint` was included. Version 0.5.1 --------------- ##### New Features * All command line arguments can now also be specified in a `tox.ini` and `setup.cfg` (thanks to [Jason Simeone](https://github.com/jayclassless)) * `--max-line-length` option can be used to override the maximum line length specified by the chosen strictness ##### Bug Fixes * `#17 `_ Prospector generates messages if in a path containing a directory beginning with a `.` - ignore patterns were previously incorrectly being applied to the absolute path rather than the relative path. * `#12 `_ Library support for Django now extends to all tools rather than just pylint * Some additional bugs related to ignore paths were squashed. Version 0.5 --------------- * Files and paths can now be ignored using the `--ignore-paths` and `--ignore-patterns` arguments. * Full PEP8 compliance can be turned on using the `--full-pep8` flag, which overrides the defaults in the strictness profile. * The PEP8 tool will now use existing config if any is found in `.pep8`, `tox.ini`, `setup.cfg` in the path to check, or `~/.config/pep8`. These will override any other configuration specified by Prospector. If none are present, Prospector will fall back on the defaults specified by the strictness. * A new flag, `--external-config`, can be used to tweak how PEP8 treats external config. `only`, the default, means that external configuration will be preferred to Prospector configuration. `merge` means that Prospector will combine external configuration and its own values. `none` means that Prospector will ignore external config. * The `--path` command line argument is no longer required, and Prospector can be called with `prospector path_to_check`. * Pylint version 1.1 is now used. * Prospector will now run under Python3. Version 0.4.1 --------------- * Additional blending of messages - more messages indicating the same problem from different tools are now merged together * Fixed the maximum line length to 160 for medium strictness, 100 for high and 80 for very high. This affects both the pep8 tool and pylint. Version 0.4 --------------- * Added a changelog * Added support for the `dodgy `_ codebase checker * Added support for pep8 (thanks to `Jason Simeone `_) * Added support for pyflakes (thanks to `Jason Simeone `_) * Added support for mccabe (thanks to `Jason Simeone `_) * Replaced Pylint W0312 with a custom checker. This means that warnings are only generated for inconsistent indentation characters, rather than warning if spaces were not used. * Some messages will now be combined if Pylint generates multiple warnings per line for what is the same cause. For example, 'unused import from wildcard import' messages are now combined rather than having one message per unused import from that line. * Messages from multiple tools will be merged if they represent the same problem. * Tool failure no longer kills the Prospector process but adds a message instead. * Tools can be enabled or disabled from profiles. * All style warnings can be suppressed using the ``--no-style-warnings`` command line switch. * Uses a newer version of `pylint-django `_ for improved analysis of Django-based code. prospector-1.10.3/CONTRIBUTING.rst000066400000000000000000000031361451367460400164520ustar00rootroot00000000000000Development and Contributing ============================ All contributions are very welcome! You can contribute in many ways: * Join the `python code quality`_ mailing list and answer questions. * Report bugs on the GitHub `issue tracker`_. * Submit pull requests on the GitHub `repository`_. Ideally make a pull request to the *develop* branch, as I prefer to keep the master branch the same as the most recent release on PyPI. If you do this, be sure to add yourself to the CONTRIBUTORS.rst file too! .. _python code quality: https://mail.python.org/mailman/listinfo/code-quality .. _issue tracker: https://github.com/PyCQA/prospector/issues .. _repository: https://github.com/PyCQA/prospector Code Quality ------------ As a code quality testing tool, it makes sense to strive to be a good example of good code! To that end, prospector is checked by `Landscape _` and ideally when making a pull request, please take note of any decreases in quality. Additionally, there is a `pre-commit `_ configuration to prevent various small problems before they are committed. Check the site for more information but the short version is:: pip install pre-commit pre-commit install Tests ----- There are not a huge number of tests right now, as most of the code in Prospector is handling the output of other tools. However, please do run them before submitting a pull request:: pytest Prospector targets Python 3.6, 3.7, 3.8 and 3.9. You can use `tox`_ to test this locally, and all tests are run with Github Actions. .. _tox: https://tox.readthedocs.io/en/latest/ prospector-1.10.3/CONTRIBUTORS.rst000066400000000000000000000042561451367460400165040ustar00rootroot00000000000000Contributors ------------ * Abdullah Hilson (`@abumalick `_) * Adam Hodges (`@ajhodges `_) * Amir Rachum (`@Nurdok `_) * Angel Blasco (`@alblasco `_) * Anthony Ricaud (`@rik `_) * Bram (`@Psycojoker `_) * Bryce Guinta (`@brycepg `_) * Carl Crowder (`@carlio `_) * Carlos CoÃĒlho (`@chocoelho `_) * Carlos Cruz (`@ccruz09 `_) * Claudiu Popa (`@PCManticore `_) * D Morgan (`@morgangraphics `_) * David J Pugh (`@djpugh `_) * Eric Brown (`@ericwb `_) * Florian Bruhin (`@The-Compiler `_) * Ian Lee (`@IanLee1521 `_) * Ian Stapleton Cordasco (`@sigmavirus24 `_) * iderr (`@IDerr `_) * Joe Wallis (`@Peilonrayz `_) * Jon Parise (`@jparise `_) * Kristian Glass (`@doismellburning `_) * Lukasz Piatkowski (`@lukaspiatkowski `_) * Luke Hinds (`@lukehinds `_) * Matt Seymour (`@mattseymour `_) * Michael Tinsley (`@michaeltinsley `_) * Michal Petrucha (`@koniiiik `_) * Phil Frost (`@bitglue `_) * Phil Jones (`@pgjones `_) * Pierre Sassoulas (`@Pierre-Sassoulas `_) * SergeyKosarchuk (`@SergeyKosarchuk `_) * Shachar Ohana (`@shacharoo `_) * StÊphane Brunner (`@sbrunner `_) * Steven Myint (`@myint `_) * Sushobhit (`@sushobhit27 `_) * Yaroslav Kurlaev (`@ykurlaev `_) prospector-1.10.3/LICENSE000066400000000000000000000431741451367460400150240ustar00rootroot00000000000000GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Lesser General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Pylint plugin for improving code analysis for when using Django Copyright (C) 2013 This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. {signature of Ty Coon}, 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. prospector-1.10.3/README.rst000066400000000000000000000163401451367460400155010ustar00rootroot00000000000000prospector ========== .. image:: https://img.shields.io/pypi/v/prospector.svg :target: https://pypi.python.org/pypi/prospector :alt: Latest Version of Prospector .. image:: https://github.com/PyCQA/prospector/actions/workflows/tests.yml/badge.svg :target: https://github.com/PyCQA/prospector/actions/workflows/tests.yml :alt: Build Status .. image:: https://img.shields.io/coveralls/PyCQA/prospector.svg?style=flat :target: https://coveralls.io/r/PyCQA/prospector :alt: Test Coverage .. image:: https://readthedocs.org/projects/prospector/badge/?version=latest :target: https://prospector.readthedocs.io/ :alt: Documentation About ----- Prospector is a tool to analyse Python code and output information about errors, potential problems, convention violations and complexity. It brings together the functionality of other Python analysis tools such as `Pylint `_, `pycodestyle `_, and `McCabe complexity `_. See the `Supported Tools `_ documentation section for a complete list. The primary aim of Prospector is to be useful 'out of the box'. A common complaint of other Python analysis tools is that it takes a long time to filter through which errors are relevant or interesting to your own coding style. Prospector provides some default profiles, which hopefully will provide a good starting point and will be useful straight away, and adapts the output depending on the libraries your project uses. Installation ------------ Prospector can be installed from PyPI using ``pip`` by running the following command:: pip install prospector Optional dependencies for Prospector, such as ``pyroma`` can also be installed by running:: pip install prospector[with_pyroma] Some shells (such as ``Zsh``, the default shell of macOS Catalina) require brackets to be escaped:: pip install prospector\[with_pyroma\] For a list of all of the optional dependencies, see the optional extras section on the ReadTheDocs page on `Supported Tools Extras `_. For local development, `poetry `_ is used. Check out the code, then run:: poetry install And for extras:: poetry install -E with_everything For more detailed information on installing the tool, see the `installation section `_ of the tool's main page on ReadTheDocs. Documentation ------------- Full `documentation is available at ReadTheDocs `_. Usage ----- Simply run prospector from the root of your project:: prospector This will output a list of messages pointing out potential problems or errors, for example:: prospector.tools.base (prospector/tools/base.py): L5:0 ToolBase: pylint - R0922 Abstract class is only referenced 1 times Options ``````` Run ``prospector --help`` for a full list of options and their effects. Output Format ~~~~~~~~~~~~~ The default output format of ``prospector`` is designed to be human readable. For parsing (for example, for reporting), you can use the ``--output-format json`` flag to get JSON-formatted output. Profiles ~~~~~~~~ Prospector is configurable using "profiles". These are composable YAML files with directives to disable or enable tools or messages. For more information, read `the documentation about profiles `_. If your code uses frameworks and libraries ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Often tools such as pylint find errors in code which is not an error, for example due to attributes of classes being created at run time by a library or framework used by your project. For example, by default, pylint will generate an error for Django models when accessing ``objects``, as the ``objects`` attribute is not part of the ``Model`` class definition. Prospector mitigates this by providing an understanding of these frameworks to the underlying tools. Prospector will try to intuit which libraries your project uses by `detecting dependencies `_ and automatically turning on support for the requisite libraries. You can see which adaptors were run in the metadata section of the report. If Prospector does not correctly detect your project's dependencies, you can specify them manually from the commandline:: prospector --uses django celery Additionally, if Prospector is automatically detecting a library that you do not in fact use, you can turn off autodetection completely:: prospector --no-autodetect Note that as far as possible, these adaptors have been written as plugins or augmentations for the underlying tools so that they can be used without requiring Prospector. For example, the Django support is available as a pylint plugin. Strictness ~~~~~~~~~~ Prospector has a configurable 'strictness' level which will determine how harshly it searches for errors:: prospector --strictness high Possible values are ``verylow``, ``low``, ``medium``, ``high``, ``veryhigh``. Prospector does not include documentation warnings by default, but you can turn this on using the ``--doc-warnings`` flag. pre-commit ---------- If you'd like Prospector to be run automatically when making changes to files in your Git repository, you can install `pre-commit `_ and add the following text to your repositories' ``.pre-commit-config.yaml``:: repos: - repo: https://github.com/PyCQA/prospector rev: 1.10.0 # The version of Prospector to use, if not 'master' for latest hooks: - id: prospector This only installs base prospector - if you also use optional tools, for example bandit and/or mypy, then you can add them to the hook configuration like so:: repos: - repo: https://github.com/PyCQA/prospector rev: 1.10.0 hooks: - id: prospector additional_dependencies: - ".[with_mypy,with_bandit]" - args: [ '--with-tool=mypy', '--with-tool=bandit', ] Additional dependencies can be `individually configured `_ in your `prospector.yml` file :: # https://bandit.readthedocs.io/en/latest/config.html bandit: options: skips: - B201 - B601 - B610 - B611 - B703 # https://mypy.readthedocs.io/en/stable/command_line.html mypy: options: ignore-missing-imports: true For prospector options which affect display only - those which are not configurable using a profile - these can be added as command line arguments to the hook. For example:: repos: - repo: https://github.com/PyCQA/prospector rev: 1.10.0 hooks: - id: prospector additional_dependencies: - ".[with_mypy,with_bandit]" args: - --with-tool=mypy - --with-tool=bandit - --summary-only - --zero-exit License ------- Prospector is available under the GPLv2 License. prospector-1.10.3/docs/000077500000000000000000000000001451367460400147365ustar00rootroot00000000000000prospector-1.10.3/docs/Makefile000066400000000000000000000151761451367460400164100ustar00rootroot00000000000000# Makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = sphinx-build PAPER = BUILDDIR = _build # User-friendly check for sphinx-build ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) $(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://www.sphinx-doc.org/) endif # Internal variables. PAPEROPT_a4 = -D latex_paper_size=a4 PAPEROPT_letter = -D latex_paper_size=letter ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . # the i18n builder cannot share the environment and doctrees with the others I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext help: @echo "Please use \`make ' where is one of" @echo " html to make standalone HTML files" @echo " dirhtml to make HTML files named index.html in directories" @echo " singlehtml to make a single large HTML file" @echo " pickle to make pickle files" @echo " json to make JSON files" @echo " htmlhelp to make HTML files and a HTML help project" @echo " qthelp to make HTML files and a qthelp project" @echo " devhelp to make HTML files and a Devhelp project" @echo " epub to make an epub" @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" @echo " latexpdf to make LaTeX files and run them through pdflatex" @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" @echo " text to make text files" @echo " man to make manual pages" @echo " texinfo to make Texinfo files" @echo " info to make Texinfo files and run them through makeinfo" @echo " gettext to make PO message catalogs" @echo " changes to make an overview of all changed/added/deprecated items" @echo " xml to make Docutils-native XML files" @echo " pseudoxml to make pseudoxml-XML files for display purposes" @echo " linkcheck to check all external links for integrity" @echo " doctest to run all doctests embedded in the documentation (if enabled)" clean: rm -rf $(BUILDDIR)/* html: $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." dirhtml: $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." singlehtml: $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml @echo @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." pickle: $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle @echo @echo "Build finished; now you can process the pickle files." json: $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json @echo @echo "Build finished; now you can process the JSON files." htmlhelp: $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp @echo @echo "Build finished; now you can run HTML Help Workshop with the" \ ".hhp project file in $(BUILDDIR)/htmlhelp." qthelp: $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp @echo @echo "Build finished; now you can run "qcollectiongenerator" with the" \ ".qhcp project file in $(BUILDDIR)/qthelp, like this:" @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/Prospector.qhcp" @echo "To view the help file:" @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Prospector.qhc" devhelp: $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp @echo @echo "Build finished." @echo "To view the help file:" @echo "# mkdir -p $$HOME/.local/share/devhelp/Prospector" @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/Prospector" @echo "# devhelp" epub: $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub @echo @echo "Build finished. The epub file is in $(BUILDDIR)/epub." latex: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." @echo "Run \`make' in that directory to run these through (pdf)latex" \ "(use \`make latexpdf' here to do that automatically)." latexpdf: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through pdflatex..." $(MAKE) -C $(BUILDDIR)/latex all-pdf @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." latexpdfja: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through platex and dvipdfmx..." $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." text: $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text @echo @echo "Build finished. The text files are in $(BUILDDIR)/text." man: $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man @echo @echo "Build finished. The manual pages are in $(BUILDDIR)/man." texinfo: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." @echo "Run \`make' in that directory to run these through makeinfo" \ "(use \`make info' here to do that automatically)." info: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo "Running Texinfo files through makeinfo..." make -C $(BUILDDIR)/texinfo info @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." gettext: $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale @echo @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." changes: $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes @echo @echo "The overview file is in $(BUILDDIR)/changes." linkcheck: $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck @echo @echo "Link check complete; look for any errors in the above output " \ "or in $(BUILDDIR)/linkcheck/output.txt." doctest: $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest @echo "Testing of doctests in the sources finished, look at the " \ "results in $(BUILDDIR)/doctest/output.txt." xml: $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml @echo @echo "Build finished. The XML files are in $(BUILDDIR)/xml." pseudoxml: $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml @echo @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." prospector-1.10.3/docs/_templates/000077500000000000000000000000001451367460400170735ustar00rootroot00000000000000prospector-1.10.3/docs/_templates/globaltoc.html000066400000000000000000000014501451367460400217270ustar00rootroot00000000000000

{{ _('Table Of Contents') }}

{%- if display_toc %}
{{ toc }} {%- endif %}
Index
prospector-1.10.3/docs/_templates/layout.html000066400000000000000000000024161451367460400213010ustar00rootroot00000000000000{# Copied from: https://raw.githubusercontent.com/pallets/pallets-sphinx-themes/b0c6c41849b4e15cbf62cc1d95c05ef2b3e155c8/src/pallets_sphinx_themes/themes/pocoo/layout.html And removed the warning version (see #7331). #} {% extends "basic/layout.html" %} {% set metatags %} {{- metatags }} {%- endset %} {% block extrahead %} {%- if page_canonical_url %} {%- endif %} {{ super() }} {%- endblock %} {% block sidebarlogo %} {% if pagename != "index" or theme_index_sidebar_logo %} {{ super() }} {% endif %} {% endblock %} {% block relbar2 %}{% endblock %} {% block sidebar2 %} {{- super() }} {%- endblock %} {% block footer %} {{ super() }} {%- if READTHEDOCS and not readthedocs_docsearch %} {%- endif %} {{ js_tag("_static/version_warning_offset.js") }} {% endblock %} prospector-1.10.3/docs/_templates/links.html000066400000000000000000000005411451367460400211010ustar00rootroot00000000000000

Useful Links

prospector-1.10.3/docs/_templates/relations.html000066400000000000000000000011001451367460400217510ustar00rootroot00000000000000

Related Topics

prospector-1.10.3/docs/_templates/sidebarintro.html000066400000000000000000000001141451367460400224420ustar00rootroot00000000000000

About prospector

Prospector runs your favourite linters

prospector-1.10.3/docs/_templates/slim_searchbox.html000066400000000000000000000010171451367460400227620ustar00rootroot00000000000000{# basic/searchbox.html with heading removed. #} {%- if pagename != "search" and builder != "singlehtml" %} {%- endif %} prospector-1.10.3/docs/changelog.rst000066400000000000000000000000601451367460400174130ustar00rootroot00000000000000.. _`changelog`: .. include:: ../CHANGELOG.rst prospector-1.10.3/docs/conf.py000066400000000000000000000212141451367460400162350ustar00rootroot00000000000000# # Prospector documentation build configuration file, created by # sphinx-quickstart on Sun Sep 28 11:26:59 2014. # # This file is execfile()d with the current directory set to its # containing dir. # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. # import os # import sys import pkg_resources version = pkg_resources.get_distribution("prospector").version release = ".".join(version.split(".")[:2]) # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. # sys.path.insert(0, os.path.abspath('.')) # -- General configuration ------------------------------------------------ # If your documentation needs a minimal Sphinx version, state it here. # needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ "sphinxarg.ext", "sphinx.ext.autodoc", "sphinx.ext.viewcode", ] # Add any paths that contain templates here, relative to this directory. templates_path = ["_templates"] # The suffix of source filenames. source_suffix = ".rst" # The encoding of source files. # source_encoding = 'utf-8-sig' # The master toctree document. master_doc = "contents" # General information about the project. project = "Prospector" copyright = "2014-2020, Carl Crowder" # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # release = The short X.Y version. # version = The full version, including alpha/beta/rc tags. release = ".".join(version.split(".")[:2]) # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. # language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: # today = '' # Else, today_fmt is used as the format for a strftime call. # today_fmt = '%B %d, %Y' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. exclude_patterns = ["_build"] # The reST default role (used for this markup: `text`) to use for all # documents. # default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. # add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). add_module_names = False # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. # show_authors = False # The name of the Pygments (syntax highlighting) style to use. pygments_style = "sphinx" # A list of ignored prefixes for module index sorting. # modindex_common_prefix = [] # If true, keep warnings as "system message" paragraphs in the built documents. # keep_warnings = False # -- Options for HTML output ---------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. html_theme = "alabaster" # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. # html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. # html_theme_path = [] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". html_title = "prospector documentation" # A shorter title for the navigation bar. Default is the same as html_title. html_short_title = "prospector-%s" % version # The name of an image file (relative to this directory) to place at the top # of the sidebar. # html_logo = None # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. # html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". # html_static_path = ["_static"] # Add any extra paths that contain custom files (such as robots.txt or # .htaccess) here, relative to this directory. These files are copied # directly to the root of the documentation. # html_extra_path = [] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. # html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. # html_use_smartypants = True # Custom sidebar templates, maps document names to template names. html_sidebars = { "index": [ "slim_searchbox.html", "sidebarintro.html", "globaltoc.html", "links.html", "sourcelink.html", ], "**": [ "slim_searchbox.html", "globaltoc.html", "relations.html", "links.html", "sourcelink.html", ], } # Additional templates that should be rendered to pages, maps page names to # template names. # html_additional_pages = {} # If false, no module index is generated. html_domain_indices = True # If false, no index is generated. html_use_index = True # If true, the index is split into individual pages for each letter. # html_split_index = False # If true, links to the reST sources are added to the pages. # html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. # html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. # html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. # html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). # html_file_suffix = None # Output file base name for HTML help builder. htmlhelp_basename = "Prospectordoc" # -- Options for LaTeX output --------------------------------------------- latex_elements = { # The paper size ('letterpaper' or 'a4paper'). # 'papersize': 'letterpaper', # The font size ('10pt', '11pt' or '12pt'). # 'pointsize': '10pt', # Additional stuff for the LaTeX preamble. # 'preamble': '', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ ("index", "Prospector.tex", "Prospector Documentation", "Carl Crowder", "manual"), ] # The name of an image file (relative to this directory) to place at the top of # the title page. # latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. # latex_use_parts = False # If true, show page references after internal links. # latex_show_pagerefs = False # If true, show URL addresses after external links. # latex_show_urls = False # Documents to append as an appendix to all manuals. # latex_appendices = [] # If false, no module index is generated. # latex_domain_indices = True # -- Options for manual page output --------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [("index", "prospector", "Prospector Documentation", ["Carl Crowder"], 1)] # If true, show URL addresses after external links. # man_show_urls = False # -- Options for Texinfo output ------------------------------------------- # Grouping the document tree into Texinfo files. List of tuples # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ ( "index", "Prospector", "Prospector Documentation", "Carl Crowder", "Prospector", "One line description of project.", "Miscellaneous", ), ] # Documents to append as an appendix to all manuals. # texinfo_appendices = [] # If false, no module index is generated. # texinfo_domain_indices = True # How to display URL addresses: 'footnote', 'no', or 'inline'. # texinfo_show_urls = 'footnote' # If true, do not generate a @detailmenu in the "Top" node's menu. # texinfo_no_detailmenu = False prospector-1.10.3/docs/contents.rst000066400000000000000000000004001451367460400173170ustar00rootroot00000000000000Contents --------- .. toctree:: :caption: Table of Contents :maxdepth: 2 :numbered: 1 index usage profiles suppression supported_tools pre-commit contrib .. only:: html .. toctree:: :maxdepth: 1 changelog prospector-1.10.3/docs/contrib.rst000066400000000000000000000033031451367460400171270ustar00rootroot00000000000000Contributing ============ All contributions are very welcome! You can contribute in many ways: * Join the `python code quality`_ mailing list and answer questions. * Report bugs on the GitHub `issue tracker`_. * Submit pull requests on the GitHub `repository`_. Ideally make a pull request to the *develop* branch, as I prefer to keep the master branch the same as the most recent release on PyPI. If you do this, be sure to add yourself to the CONTRIBUTORS.md file too! .. _python code quality: https://mail.python.org/mailman/listinfo/code-quality .. _issue tracker: https://github.com/PyCQA/prospector/issues .. _repository: https://github.com/PyCQA/prospector Code Quality ------------ As a code quality testing tool, it makes sense to strive to be a good example of good code! To that end, prospector is checked by `Landscape `_ and ideally when making a pull request, please take note of any decreases in quality. Additionally, there is a `pre-commit `_ configuration to prevent various small problems before they are committed. Check the site for more information but the short version is to install it first before committing:: pre-commit install Tests ----- There are not a huge number of tests right now, as most of the code in Prospector is handling the output of other tools. However, please do run them before submitting a pull request:: pytest Prospector targets Python 3.6, 3.7, 3.8, 3.9 and 3.10. You can use `tox`_ to test this locally, and all tests are run on github Actions. .. _tox: https://tox.readthedocs.io/en/latest/ .. include:: ../CONTRIBUTORS.rst Developer Reference -------------------- .. toctree:: :maxdepth: 2 reference prospector-1.10.3/docs/index.rst000066400000000000000000000127441451367460400166070ustar00rootroot00000000000000.. Prospector documentation master file, created by sphinx-quickstart on Sun Sep 28 11:26:59 2014. Prospector - Python Static Analysis =================================== About ----- .. image:: https://img.shields.io/pypi/v/prospector.svg :target: https://pypi.python.org/pypi/prospector :alt: Latest Version of Prospector .. image:: https://github.com/PyCQA/prospector/actions/workflows/tests.yml/badge.svg :target: https://github.com/PyCQA/prospector/actions/workflows/tests.yml :alt: Build Status .. image:: https://img.shields.io/coveralls/PyCQA/prospector.svg?style=flat :target: https://coveralls.io/r/PyCQA/prospector :alt: Test Coverage Prospector is a tool to analyse Python code and output information about errors, potential problems, convention violations and complexity. It brings together the functionality of other Python analysis tools such as `Pylint`_, `pycodestyle`_, and `McCabe complexity`_. See the :doc:`Supported Tools` section for a complete list of default and optional extra tools. The primary aim of Prospector is to be useful 'out of the box'. A common complaint of other Python analysis tools is that it takes a long time to filter through which errors are relevant or interesting to your own coding style. Prospector provides some default profiles, which hopefully will provide a good starting point and will be useful straight away, and adapts the output depending on the libraries your project uses. .. _pylint: https://pylint.readthedocs.io/ .. _pycodestyle: https://pycodestyle.pycqa.org/ .. _McCabe complexity: https://pypi.python.org/pypi/mccabe Installation ------------ You can install default tools using ``pip`` command:: pip install prospector For a full list of optional extra tools, and specific examples to install each of them, see the :doc:`page on supported tools `. For example to install an optional tool such as ``pyroma``:: pip install prospector[with_pyroma] .. Note:: Some shells (such as ``Zsh``, the default shell of macOS Catalina) require brackets to be escaped:: pip install prospector\[with_pyroma\] To install two or more optional extra tools at the same time, they must be comma separated (and without spaces). For example to install mypy and bandit:: pip install prospector[with_mypy,with_bandit] And to install all optional extra tools at the same time, install prospector using the ``with_everything`` option:: pip install prospector[with_everything] For best results, you should install prospector to the same place as your project and its dependencies. That is, if you are using virtual environments, install prospector into that virtual environment alongside your code. This allows the underlying tools to give better results, as they can infer and use knowledge of libraries that you use. If you install prospector system-wide and use it on a project in a virtual environment, you will see several incorrect errors because prospector cannot access the libraries your project uses. Usage ----- Simply run prospector from the root of your project:: prospector This will output a list of messages pointing out potential problems or errors, for example:: prospector.tools.base (prospector/tools/base.py): L5:0 ToolBase: pylint - R0922 Abstract class is only referenced 1 times Read about the full list of options in the :doc:`usage ` page. It is also possible to use prospector as a :doc:`pre-commit hook `. Behaviour --------- Adapting to Dependencies ```````````````````````` Prospector will `try to detect `_ the libraries that your project uses, and adapt the output and filtering to those libraries. For example, if you use Django, the `pylint-django `_ plugin will be loaded to help Pylint inspect Django-specific code. There is currently support for the following frameworks: - `Celery `_ - `Django `_ - `Flask `_ If you have a suggestion for another framework or library which should be supported, please `add an issue `_ or :doc:`consider creating a pull request `. Strictness `````````` Prospector can be configured to be more or less strict. The more strict, the more errors and warnings it will find. At higher strictness levels, you may find that the output is a bit too picky. The default level is designed to give useful output and warnings but also to suppress messages which are not necessarily useful. To change the strictness level:: prospector --strictness high Valid levels are ``verylow``, ``low``, ``medium``, ``high`` and ``veryhigh``. Profiles ```````` A profile is a YAML file containing various directives about which messages and which tools to allow or disable. Profiles can inherit from each other, allowing you to adapt the behaviour of existing profiles or compose several smaller specialised profiles into one to suit your project. Note that the 'strictness' is implemented as a profile. There is more detail about profiles and how to use them on :doc:`the profiles documentation ` page. Pre-commit Hook ``````````````` Prospector can be configured as a `pre-commit `_ hook. For more information see :doc:`the pre-commit documentation `. License ------- Prospector is available under the GPLv2 License. prospector-1.10.3/docs/pre-commit.rst000066400000000000000000000030551451367460400175470ustar00rootroot00000000000000Pre-commit Hook =============== If you'd like Prospector to be run automatically when making changes to files in your Git repository, you can install `pre-commit`_ and add the following text to your repositories' ``.pre-commit-config.yaml``:: repos: - repo: https://github.com/PyCQA/prospector rev: 1.10.0 # The version of Prospector to use, if not 'master' for latest hooks: - id: prospector .. _pre-commit: https://pre-commit.com/ Commandline Arguments --------------------- Some controls for prospector, especially surrounding how the output is displayed, are not :doc:`configurable from a profile `, only from the commandline. To add command-line arguments to the pre-commit hook config file:: repos: - repo: https://github.com/PyCQA/prospector rev: 1.10.0 hooks: - id: prospector args: - --summary-only Optional Tools -------------- By default the configuration will only install :doc:`the base supported tools ` and not optional tools. If you also use optional tools, for example bandit or mypy, then you can add them to the hook configuration like so:: repos: - repo: https://github.com/PyCQA/prospector rev: 1.10.0 hooks: - id: prospector additional_dependencies: - ".[with-mypy,with-bandit]" - args: [ '--with-tool=mypy', '--with-tool=bandit', ] This is equivalent to running:: pip install prospector[with-bandit,with-mypy] prospector-1.10.3/docs/profiles.rst000066400000000000000000000440001451367460400173110ustar00rootroot00000000000000Profiles / Configuration ======================== The behaviour of prospector can be configured by creating a profile. A profile is a YAML file containing several sections as described below. Prospector will search for a ``.prospector.yaml`` file (and `several others`_) in the path that it is checking. If found, it will automatically be loaded. Otherwise, you can pass in the profile as an argument:: prospector --profile /path/to/your/profile.yaml You can also use a name instead of the path, if it is on the ``profile-path``:: prospector --profile my_profile In general, command-line arguments override and take precedence over values found in profiles. .. _several others: https://github.com/PyCQA/prospector/blob/master/prospector/profiles/__init__.py .. _profile_path: Profile Path ------------ The name of a profile is the filename without the ``.yaml`` extension. So if you create a profile called 'my_project.yaml', the name will be 'my_project'. Inheritance works by searching the ``profile-path`` for files matching the name in the inheritance list. The ``profile-path`` is where Prospector should search when looking for profiles. By default, it will look in the directory containing the `built-in profiles`_, as well as the directory where prospector is running, and a ``.prospector`` directory relative to that. To add additional places to search:: prospector --profile-path path/to/your/profiles .. _built-in profiles: https://github.com/PyCQA/prospector/tree/master/prospector/profiles/profiles Example ------- Here is an example profile:: output-format: json strictness: medium test-warnings: true doc-warnings: false inherits: - my/other/profile.yml ignore-paths: - docs ignore-patterns: - (^|/)skip(this)?(/|$) pycodestyle: disable: - W602 - W603 enable: - W601 options: max-line-length: 79 mccabe: run: false Builtin Profiles ---------------- Prospector comes with several built-in profiles, which power some of strictness and style options. You can see the `full list on GitHub `_. Global Configuration options ---------------------------- Global configuration options for tools are the following: * output-format_ * strictness_ * test-warnings_ * doc-warnings_ * member-warnings_ * inherits_ * ignore-paths_ * ignore-patterns_ * autodetect_ * uses_ * max-line-length_ .. _output-format: Output-format ............. Same command line options are available. See :doc:`Output format ` .. _strictness: Strictness .......... There is a command line argument to tweak how picky Prospector will be:: prospector --strictness This is implemented using profiles, and is simply a list of messages to disable at each level of strictness. If creating your own profile, you can use the ``strictness`` like so:: strictness: medium Valid values are 'verylow', 'low', 'medium' (the default), 'high' and 'veryhigh'. If you don't specify a strictness value, then the default of 'medium' will be used. To avoid using any of Prospector's default strictness profiles, set ``strictness: none``. There are some aspects of analysis which can be turned on or off separately from the strictness or individual tuning of the tools. Example: .. _doc-warnings: Documentation Warnings ...................... By default prospector will not produce warnings about missing documentation or `docstring styleguide violations `_. If you want to see these, use the ``--doc-warnings`` flag at runtime or include it in your profile:: doc-warnings: true This will turn on the otherwise disabled ``pycodestyle`` tool. .. _test-warnings: Test Warnings ............. Prospector will not inspect unit tests and test files by default. You can turn this on using the ``--test-warnings`` flag or in your profile:: test-warnings: true .. _member-warnings: Member Warnings ............... Pylint generates warnings when you try to access an attribute of a class that does not exist, or import a module that does not exist. Unfortunately it is not always accurate and in some projects, this message is a large amount of noise. Prospector therefore turns these messages off by default, but you can turn it on using the ``--member-warnings`` flag or in a profile:: member-warnings: true .. _uses: .. _autodetect: Libraries Used and Autodetect ............................. Prospector will adjust the behaviour of the underlying tools based on the libraries that your project uses. If you use Django, for example, the `pylint-django `_ plugin will be loaded. This will happen automatically. If prospector is not correctly determining which of its supported libraries you use, you can specify it manually in the profile:: uses: - django - celery - flask Currently, Django, Flask and Celery have plugins. If prospector is incorrectly deciding that you use one of these, you can turn off autodetection:: autodetect: false .. _inherits: Inheritance ........... Profiles can inherit from other profiles, and can inherit from more than one profile. Prospector merges together all of the options in each profile, starting at the top of the inheritance tree and overwriting values with those found lower. The example profile above inherits from another profile provided by the user, ``my/other/profile.yml``. This allows you to have, for example, a project wide default profile with specific overrides for each individual repository or library. It is possible to inherit from the built-in prospector profiles as well, although there are shortcuts for most of the built-ins, see below.:: inherits: - strictness_medium - full_pep8 For lists, such as the ``ignore`` section, they will be merged together rather than overwritten - so essentially, the ``ignore`` section will accumulate. The profile named in the ``inherits`` section must be on the :ref:`profile path `. Inheritance can also be optional - for example, if each developer might have a local prospector configuration but that's not guaranteed, then you can inherit from a profile with the ``?`` suffix and if it is not present, prospector will simply carry on. For example:: inherits: - project_config - local_config? In this case, if a developer has a local profile called 'local_config' it would append to the project-wide configuration, but if they don't, prospector won't error with a ``ProfileNotFound`` exception. Note that when using profiles, prospector does not automatically configure ``strictness``. The assumption is that if you provide a profile, you provide all the information about which messages to turn on or off. To keep the strictness functionality, simply inherit from the built-in prospector profiles:: inherits: - strictness_medium The ``inherits`` file can also be in an external package, if you specify a name Prospector will search for a tile named ``prospector.yaml`` or ``prospector.yml`` in the module ``prospector-profile-``. And if the name contains a ``:`` this mean that we use the syntax ``:`` to search the file named ``.yaml`` or ``.yml`` in the module name ``prospector-profile-``. For example:: inherits: - my_module - my_module:my_file .. _ignore-paths: .. _ignore-patterns: Ignoring Paths and patterns ........................... There are two ways to ignore paths or files. Firstly, with the ``ignore-paths`` section. This is a list of paths to ignore relative to the repository root. It can be a directory, in which case the directory contents and all subdirectories are ignored, or it can be a specific file. For example, ``docs`` would ignore a directory in the repository root called "docs", while ``mypackage/vendor`` would ignore anything in the directory at "mypackage/vendor". Secondly, ``ignore-patterns`` is a list of regular expressions. The relative path of files and directories is *searched* for each regular expression, and ignored if any matches are found. If the expression matches a directory, the directory contents and all subdirectories are ignored. For example, ``^example/doc_.*\.py$`` would ignore any files in the "example" directory beginning with "doc\_". Another example: ``(^|/)docs(/|$)`` would ignore all directories called "docs" in the entire repository. Note that a further option called ``ignore`` is available. This is equivalent to ``ignore-patterns``, and is from an older version of the configuration. It will continue working, but it is deprecated, and you should update your profile if you are using it. .. _max-line-length: Max Line Length ............... This general option, provides a way to select maximum line length allowed. .. Note:: This general option overrides and takes precedence over same option in a particular tool (pycodestyle or pylint) Individual Configuration Options -------------------------------- Each tool can be individually configured with a section beginning with the tool name (in lowercase). Valid values are ``bandit``, ``dodgy``, ``frosted``, ``mccabe``, ``mypy``, ``pydocstyle``, ``pycodestyle``, ``pyflakes``, ``pylint``, ``pyright``, ``pyroma`` and ``vulture``. Enabling and Disabling Tools ............................ There are :doc:`7 default and 5 optional `. Unless otherwise configured, the defaults are enabled and the optional tools are disabled. In a profile, you can enable or disable a tool using the boolean ``run``:: pyroma: run: true Note that the ``--tools`` :doc:`command line argument ` overrides profiles if used. Enabling and Disabling Messages ............................... Messages can be enabled or disabled using the tool's code for the output. These codes are either from the tool itself, or provided by prospector for those tools which do not have message codes. The list of tools and message codes can be found `in the tools package `_. The typical desired action is to disable messages:: pylint: disable: - method-hidden - access-member-before-definition However, you can also enable messages which were disabled by parent profiles:: pylint: enable: - method-hidden - access-member-before-definition Pylint Plugins .............. It is possible to specify list of plugins for Pylint. You can do this by using ``load-plugins`` option in ``pylint`` section:: pylint: load-plugins: - plugin - plugin Note that this option doesn't affect loading of :ref:`autodetected plugins `. PEP8 Control ............ The strictness will turn on or off different messages generated by the `pycodestyle `_ tool depending on how picky they are. However, if you want to have the standard 'medium' strictness but get either complete or zero pep8 style warnings, you can use a shorthand like below:: pep8: full: true Or:: pep8: none: true Note that this section is also the section for configuring the pycodestyle tool, see below. Therefore you can turn on all warnings from pep8 but turn off just one or two individually or otherwise tweak the tool like so:: pycodestyle: full: true disable: - E126 options: max-line-length: 120 Tool Options ............ Some tools can be further configured or tweaked using an options hash:: pycodestyle: options: max-line-length: 120 The available options are: +----------------+------------------------+----------------------------------------------+ | Tool + Option Name + Possible Values | +================+========================+==============================================+ | mccabe | max-complexity | Maximum number of paths allowed in a method | +----------------+------------------------+----------------------------------------------+ | pycodestyle | max-line-length | Maximum line length allowed (This option is | | | | overridden by global option max-line-length_)| +----------------+------------------------+----------------------------------------------+ | pylint | -anything- | Any of the `pylint options`_ | +----------------+------------------------+----------------------------------------------+ | mypy | strict | strict mode | +----------------+------------------------+----------------------------------------------+ | mypy | follow-imports | How to treat imports | +----------------+------------------------+----------------------------------------------+ | mypy | ignore-missing-import | Silently ignore imports of missing modules | +----------------+------------------------+----------------------------------------------+ | mypy | platform | Check for the given platform | +----------------+------------------------+----------------------------------------------+ | mypy | python-version | assume it will be running on Python x.y | +----------------+------------------------+----------------------------------------------+ | mypy | strict-optional | Checking of Optional types and None values | +----------------+------------------------+----------------------------------------------+ | mypy | namespace-packages | Import discovery uses namespace packages | +----------------+------------------------+----------------------------------------------+ | mypy | use-dmypy | Use mypy daemon (mypy server) for faster | | | | checks | +----------------+------------------------+----------------------------------------------+ | bandit | config | configuration filename | +----------------+------------------------+----------------------------------------------+ | bandit | profile | profile to use | +----------------+------------------------+----------------------------------------------+ | bandit | severity | report only issues of a given severity | +----------------+------------------------+----------------------------------------------+ | bandit | confidence | report only issues of a given confidence | +----------------+------------------------+----------------------------------------------+ | pyright | level | Minimum diagnostic level (error or warning) | +----------------+------------------------+----------------------------------------------+ | pyright | project | Path to location of configuration file | +----------------+------------------------+----------------------------------------------+ | pyright | pythonplatform | Analyze for a specific platform (Darwin, | | | | Linux, Windows) | +----------------+------------------------+----------------------------------------------+ | pyright | pythonversion | Analyze for a specific version | +----------------+------------------------+----------------------------------------------+ | pyright | skipunannotated | Skip analysis of functions with no type | | | | annotations | +----------------+------------------------+----------------------------------------------+ | pyright | typeshed-path | Path to location of typeshed type stubs | +----------------+------------------------+----------------------------------------------+ | pyright | venv-path | Directory that contains virtual environments | +----------------+------------------------+----------------------------------------------+ See `mypy options`_ for more details See `bandit options`_ for more details See `pyright options`_ for more details .. _pylint options: https://pylint.readthedocs.io/en/latest/user_guide/run.html .. _bandit options: https://bandit.readthedocs.io/en/latest/config.html .. _mypy options: https://mypy.readthedocs.io/en/stable/command_line.html .. _pyright options: https://microsoft.github.io/pyright/#/command-line Example ------- Next is another example using most options:: output-format: json strictness: medium test-warnings: true doc-warnings: false member-warnings: false inherits: - default ignore-paths: - docs ignore-patterns: - (^|/)skip(this)?(/|$) autodetect: true max-line-length: 88 bandit: run: true options: config: .bandit.yml dodgy: run: true frosted: disable: - E103 - E306 mccabe: run: false options: max-complexity: 10 pycodestyle: disable: - W602 - W603 enable: - W601 options: max-line-length: 79 pydocstyle: disable: - D100 - D101 pyflakes: disable: - F403 - F810 pylint: disable: - bad-builtin - too-few-public-methods options: max-locals: 15 max-returns: 6 max-branches: 15 max-statements: 60 max-parents: 7 max-attributes: 7 min-public-methods: 1 max-public-methods: 20 max-module-lines: 1000 max-line-length: 99 pyroma: disable: - PYR15 - PYR18 pyright: options: level: warning pythonversion: 3.7 skipunannotated: true mypy: run: true options: ignore-missing-imports: true follow-imports: skip vulture: run: true prospector-1.10.3/docs/reference.rst000066400000000000000000000027611451367460400174340ustar00rootroot00000000000000Tool classes ========================= *Contents are subject to change.* :class:`ToolBase` ------------------ .. autoclass:: prospector.tools.base.ToolBase :members: :class:`BanditTool` ------------------- .. autoclass:: prospector.tools.bandit.BanditTool :members: :class:`DodgyTool` ------------------- .. autoclass:: prospector.tools.dodgy.DodgyTool :members: :class:`McCabeTool` ------------------- .. autoclass:: prospector.tools.mccabe.McCabeTool :members: :class:`MypyTool` ------------------- .. autoclass:: prospector.tools.mypy.MypyTool :members: :class:`PycodestyleTool` ------------------------ .. autoclass:: prospector.tools.pycodestyle.PycodestyleTool :members: :class:`PydocstyleTool` ----------------------- .. autoclass:: prospector.tools.pydocstyle.PydocstyleTool :members: :class:`ProfileValidationTool` ------------------------------ .. autoclass:: prospector.tools.profile_validator.ProfileValidationTool :members: :class:`PyFlakesTool` --------------------- .. autoclass:: prospector.tools.pyflakes.PyFlakesTool :members: :class:`PylintTool` ------------------- .. autoclass:: prospector.tools.pylint.PylintTool :members: :class:`PyromaTool` ------------------- .. autoclass:: prospector.tools.pyroma.PyromaTool :members: :class:`PyrightTool` -------------------- .. autoclass:: prospector.tools.pyright.PyrightTool :members: :class:`VultureTool` -------------------- .. autoclass:: prospector.tools.vulture.VultureTool :members: prospector-1.10.3/docs/requirements.txt000066400000000000000000000000411451367460400202150ustar00rootroot00000000000000sphinx>=5.0.0,<8 sphinx-argparse prospector-1.10.3/docs/supported_tools.rst000066400000000000000000000136741451367460400207500ustar00rootroot00000000000000Supported Tools =============== Prospector currently supports 12 tools, of which 7 are defaults and 5 are optional extras. Enabling or Disabling Tools --------------------------- Prospector will run with defaults enabled and optional extras disabled unless configured otherwise. To specify an exact list of tools to run, for example, only pylint and pydocstyle:: prospector --tool pylint --tool pydocstyle Note that this command line option will override values set in :doc:`profiles`. To specify optional extras on top of the defaults, for example, to run defaults and also pyroma without needing to specify the complete list of defaults:: prospector --with-tool pyroma To run the default tools but turn off one or two defaults:: prospector --without-tool mccabe Defaults -------- `Pylint `_ ``````````````````````````````````` Pylint is the most comprehensive static analysis tool for Python. It is extremely thorough and is the source of most messages that prospector outputs. `pycodestyle `_ ````````````````````````````````````````````````````````` pycodestyle is a simple tool to warn about violations of the `PEP8 style guide `_. It produces messages for any divergence from the style guide. Prospector's concept of :doc:`strictness ` turns off various warnings depending on the strictness level. By default, several PEP8 errors will be suppressed. To adjust this without adjusting the strictness of other tools, you have some options:: # turn off pep8 checking completely: prospector --no-style-warnings # turn on complete pep8 checking: prospector --full-pep8 # change the maximum line length allowed # (the default varies by strictness): prospector --max-line-length 120 `Pyflakes `_ ```````````````````````````````````````````` Pyflakes analyzes programs and detects various errors. It is simpler and faster than pylint, but also not as thorough. `Mccabe `_ ``````````````````````````````````````````````` `McCabe or cyclomatic complexity `_ is a measurement of how many paths there are in a given function or method. It measures how complicated your functions are, and warns if they reach a certain threshold. Methods that are too complex are prone to logic errors, and should be refactored to a series of smaller methods. `Dodgy `_ ``````````````````````````````````````````````` Dodgy is a very simple tool designed to find 'dodgy' things which should not be in a public project, such as secret keys, passwords, AWS tokens or source control diffs. `Pydocstyle `_ ``````````````````````````````````````````````````` Pydocstyle is a simple tool to warn about violations of the `PEP257 Docstring Conventions `_. It produces messages for any divergence from the style guide. This tool is currently considered *experimental* due to some bugs in its ability to parse code. For example, modules that contain an ``__all__`` could end up producing bogus error messages if the ``__all__`` isn't formatted exactly as ``pydocstyle`` expects it. It will not run by default, and must be enabled explicitly (via ``--with-tool pep257`` or in a :doc:`profile `) or implicitly (using the ``--doc-warnings`` flag). `Profile-validator` ``````````````````` This is a simple tool built in to prospector which validates :doc:`prospector profiles `. Optional Extras --------------- These extras are integrated into prospector but are not activated by default. This is because their output is not necessarily useful for all projects. They are also not installed by default. The instructions for installing each tool is in the tool section below. For more detailed information on installing, see :doc:`install section`. `Pyroma `_ ````````````````````````````````````````````` Pyroma is a tool to check your `setup.py` to ensure it is following best practices of the Python packaging ecosystem. It will warn you if you are missing any package metadata which would improve the quality of your package. This is recommended if you intend to publish your code on PyPI. To install and use:: pip install prospector[with_pyroma] prospector --with-tool pyroma `Vulture `_ ```````````````````````````````````````````````````` Vulture finds unused classes, functions and variables in your code. This could be useful if your project is an application rather than a library, however, if you do a lot of dynamic access or metaprogramming, Vulture will likely warn about unused code that is in fact used. To install and use:: pip install prospector[with_vulture] prospector --with-tool vulture `Mypy `_ ```````````````````````````````````````` Mypy is an optional static type checker for Python that aims to combine the benefits of dynamic (or "duck") typing and static typing. Mypy combines the expressive power and convenience of Python with a powerful type system and compile-time type checking. To install and use:: pip install prospector[with_mypy] prospector --with-tool mypy `Bandit `_ ``````````````````````````````````````````` Bandit finds common security issues in Python code. To install and use:: pip install prospector[with_bandit] prospector --with-tool bandit `Pyright `_ ````````````````````````````````````````````````` Pyright is a full-featured, standards-based static type checker for Python. It is designed for high performance and can be used with large Python source bases. To install and use:: pip install prospector[with_pyright] prospector --with-tool pyright prospector-1.10.3/docs/suppression.rst000066400000000000000000000032641451367460400200670ustar00rootroot00000000000000Error Suppression ================= Prospector profiles configuration via :doc:`profiles` which includes the ability to tweak which errors are reported by the tools run by prospector. This is generally where to make changes which affect the project as a whole. It is also possible to suppress errors in specific places. In general the best place to find out how to do this will be on the documentation site of the tool which generates the error. This page contains additional information about behaviour which prospector adds. Suppressing Pylint Errors ------------------------- Pylint errors can be suppressed by adding a comment of the format:: # pylint:disable=redefined-builtin Although you can also use the numeric code (something like ``W1101``), pylint is moving towards using symbolic names so it is better to use the full name for the error. If Prospector finds that pylint would have emitted an error but a suppression comment disabled the error, then all equivalent errors from other tools will also be suppressed. This is Ignoring entire files --------------------- Although the ideal method of ignoring files is by using the ``ignore-patterns`` and ``ignore-paths`` in a :doc:`profile `, it is often the case that existing tools and configuration are already present in a repository. `flake8` includes the following directive to ignore an entire file, which is also honoured by prospector:: # flake8: noqa ``# noqa`` ---------- A comment of ``noqa`` is used by `pycodestyle` and `pyflakes` when ignoring all errors on a certain line. If Prospector encounters a ``# noqa`` comment it will suppress any error from any tool including ``pylint`` and others such as ``dodgy``. prospector-1.10.3/docs/usage.rst000066400000000000000000000125061451367460400166000ustar00rootroot00000000000000Command Line Usage ================== .. _issue_16: https://github.com/PyCQA/prospector/issues/16 .. _vscode_python_plugin: https://marketplace.visualstudio.com/items?itemName=donjayamanne.python The simplest way to run prospector is from the project root with no arguments. It will try to figure everything else out itself and provide sensible defaults:: prospector You can specify a path to check:: prospector path/to/my/package And you can specify a list of python modules:: prospector module/to/check.py prospector module/to/check.py other/module/to/check.py something/else.py See below for :ref:`a complete list of options and flags`. You can also run ``prospector --help`` for a full list of options and their effects. Alternatively, prospector can be executed as a module:: python3 -m prospector Output Format ''''''''''''' The default output format of ``prospector`` is designed to be human readable. You can change the output format using the ``--output-format`` or ``-o`` flags - for example, to get the output in JSON form you can use the ``--output-format json``. +-------------+----------------------------------------------------------------------------+ | Format Name | Notes | +=============+============================================================================+ | ``emacs`` | | Support for emacs compilation output mode, see `issue_16`_. | +-------------+----------------------------------------------------------------------------+ | ``vscode`` | | Support for `vscode_python_plugin`_ | +-------------+----------------------------------------------------------------------------+ | ``grouped`` | | Similar to ``text``, but groups all message on the same line together | | | | rather than having a separate entry per message. | +-------------+----------------------------------------------------------------------------+ | ``pylint`` | | Produces output in the same style as ``pylint --parseable``. This should | | | | allow ``prospector`` to be used as a drop-in replacement for any tools | | | | which parse ``pylint`` output. The one minor difference is that the | | | | output includes the name of the tool which generated the error as well | | | | as the error code. | +-------------+----------------------------------------------------------------------------+ | ``json`` | | Produces a structured, parseable output of the messages and summary. See | | | | below for more information about the structure. | +-------------+----------------------------------------------------------------------------+ | ``yaml`` | | Same as JSON except produces YAML output. | +-------------+----------------------------------------------------------------------------+ | ``xunit`` | | Same as JSON except produces xunit compatible XML output. | +-------------+----------------------------------------------------------------------------+ | ``text`` | | The default output format, a simple human readable format. | +-------------+----------------------------------------------------------------------------+ If your code uses frameworks and libraries '''''''''''''''''''''''''''''''''''''''''' Often tools such as pylint find errors in code which is not an error, due to attributes of classes being created at run time by a library or framework used by your project. For example, by default, pylint will generate an error for Django models when accessing ``objects``, as the ``objects`` attribute is not part of the ``Model`` class definition. Prospector mitigates this by providing an understanding of these frameworks to the underlying tools. Prospector will try to intuit which libraries your project uses by `detecting dependencies ` and automatically turning on support for the requisite libraries. You can see which adaptors were run in the metadata section of the report. If Prospector does not correctly detect your project's dependencies, you can specify them manually from the commandline:: prospector --uses django --uses celery --uses flask Additionally, if Prospector is automatically detecting a library that you do not in fact use, you can turn off autodetection completely:: prospector --no-autodetect Note that as far as possible, these adaptors have been written as plugins or augmentations for the underlying tools so that they can be used without requiring Prospector. For example, the Django support is available as a pylint plugin. See the "Supported frameworks and libraries" section for more information. Strictness '''''''''' Prospector has a configurable 'strictness' level which will determine how harshly it searches for errors:: prospector --strictness high Possible values are ``verylow``, ``low``, ``medium``, ``high``, ``veryhigh``. Prospector does not include documentation warnings by default, but you can turn this on using the ``--doc-warnings`` flag. .. _full_options: All Options ''''''''''' .. argparse:: :module: prospector.run :func: get_parser :prog: prospector prospector-1.10.3/poetry.lock000066400000000000000000003627371451367460400162240ustar00rootroot00000000000000# This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand. [[package]] name = "astroid" version = "2.15.4" description = "An abstract syntax tree for Python with inference support." optional = false python-versions = ">=3.7.2" files = [ {file = "astroid-2.15.4-py3-none-any.whl", hash = "sha256:a1b8543ef9d36ea777194bc9b17f5f8678d2c56ee6a45b2c2f17eec96f242347"}, {file = "astroid-2.15.4.tar.gz", hash = "sha256:c81e1c7fbac615037744d067a9bb5f9aeb655edf59b63ee8b59585475d6f80d8"}, ] [package.dependencies] lazy-object-proxy = ">=1.4.0" typed-ast = {version = ">=1.4.0,<2.0", markers = "implementation_name == \"cpython\" and python_version < \"3.8\""} typing-extensions = {version = ">=4.0.0", markers = "python_version < \"3.11\""} wrapt = [ {version = ">=1.11,<2", markers = "python_version < \"3.11\""}, {version = ">=1.14,<2", markers = "python_version >= \"3.11\""}, ] [[package]] name = "backports-zoneinfo" version = "0.2.1" description = "Backport of the standard library zoneinfo module" optional = false python-versions = ">=3.6" files = [ {file = "backports.zoneinfo-0.2.1-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:da6013fd84a690242c310d77ddb8441a559e9cb3d3d59ebac9aca1a57b2e18bc"}, {file = "backports.zoneinfo-0.2.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:89a48c0d158a3cc3f654da4c2de1ceba85263fafb861b98b59040a5086259722"}, {file = "backports.zoneinfo-0.2.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:1c5742112073a563c81f786e77514969acb58649bcdf6cdf0b4ed31a348d4546"}, {file = "backports.zoneinfo-0.2.1-cp36-cp36m-win32.whl", hash = "sha256:e8236383a20872c0cdf5a62b554b27538db7fa1bbec52429d8d106effbaeca08"}, {file = "backports.zoneinfo-0.2.1-cp36-cp36m-win_amd64.whl", hash = "sha256:8439c030a11780786a2002261569bdf362264f605dfa4d65090b64b05c9f79a7"}, {file = "backports.zoneinfo-0.2.1-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:f04e857b59d9d1ccc39ce2da1021d196e47234873820cbeaad210724b1ee28ac"}, {file = "backports.zoneinfo-0.2.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:17746bd546106fa389c51dbea67c8b7c8f0d14b5526a579ca6ccf5ed72c526cf"}, {file = "backports.zoneinfo-0.2.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:5c144945a7752ca544b4b78c8c41544cdfaf9786f25fe5ffb10e838e19a27570"}, {file = "backports.zoneinfo-0.2.1-cp37-cp37m-win32.whl", hash = "sha256:e55b384612d93be96506932a786bbcde5a2db7a9e6a4bb4bffe8b733f5b9036b"}, {file = "backports.zoneinfo-0.2.1-cp37-cp37m-win_amd64.whl", hash = "sha256:a76b38c52400b762e48131494ba26be363491ac4f9a04c1b7e92483d169f6582"}, {file = "backports.zoneinfo-0.2.1-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:8961c0f32cd0336fb8e8ead11a1f8cd99ec07145ec2931122faaac1c8f7fd987"}, {file = "backports.zoneinfo-0.2.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:e81b76cace8eda1fca50e345242ba977f9be6ae3945af8d46326d776b4cf78d1"}, {file = "backports.zoneinfo-0.2.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:7b0a64cda4145548fed9efc10322770f929b944ce5cee6c0dfe0c87bf4c0c8c9"}, {file = "backports.zoneinfo-0.2.1-cp38-cp38-win32.whl", hash = "sha256:1b13e654a55cd45672cb54ed12148cd33628f672548f373963b0bff67b217328"}, {file = "backports.zoneinfo-0.2.1-cp38-cp38-win_amd64.whl", hash = "sha256:4a0f800587060bf8880f954dbef70de6c11bbe59c673c3d818921f042f9954a6"}, {file = "backports.zoneinfo-0.2.1.tar.gz", hash = "sha256:fadbfe37f74051d024037f223b8e001611eac868b5c5b06144ef4d8b799862f2"}, ] [package.extras] tzdata = ["tzdata"] [[package]] name = "bandit" version = "1.7.5" description = "Security oriented static analyser for python code." optional = true python-versions = ">=3.7" files = [ {file = "bandit-1.7.5-py3-none-any.whl", hash = "sha256:75665181dc1e0096369112541a056c59d1c5f66f9bb74a8d686c3c362b83f549"}, {file = "bandit-1.7.5.tar.gz", hash = "sha256:bdfc739baa03b880c2d15d0431b31c658ffc348e907fe197e54e0389dd59e11e"}, ] [package.dependencies] colorama = {version = ">=0.3.9", markers = "platform_system == \"Windows\""} GitPython = ">=1.0.1" PyYAML = ">=5.3.1" rich = "*" stevedore = ">=1.20.0" [package.extras] test = ["beautifulsoup4 (>=4.8.0)", "coverage (>=4.5.4)", "fixtures (>=3.0.0)", "flake8 (>=4.0.0)", "pylint (==1.9.4)", "stestr (>=2.5.0)", "testscenarios (>=0.5.0)", "testtools (>=2.3.0)", "tomli (>=1.1.0)"] toml = ["tomli (>=1.1.0)"] yaml = ["PyYAML"] [[package]] name = "bleach" version = "6.0.0" description = "An easy safelist-based HTML-sanitizing tool." optional = false python-versions = ">=3.7" files = [ {file = "bleach-6.0.0-py3-none-any.whl", hash = "sha256:33c16e3353dbd13028ab4799a0f89a83f113405c766e9c122df8a06f5b85b3f4"}, {file = "bleach-6.0.0.tar.gz", hash = "sha256:1a1a85c1595e07d8db14c5f09f09e6433502c51c595970edc090551f0db99414"}, ] [package.dependencies] six = ">=1.9.0" webencodings = "*" [package.extras] css = ["tinycss2 (>=1.1.0,<1.2)"] [[package]] name = "build" version = "0.10.0" description = "A simple, correct Python build frontend" optional = true python-versions = ">= 3.7" files = [ {file = "build-0.10.0-py3-none-any.whl", hash = "sha256:af266720050a66c893a6096a2f410989eeac74ff9a68ba194b3f6473e8e26171"}, {file = "build-0.10.0.tar.gz", hash = "sha256:d5b71264afdb5951d6704482aac78de887c80691c52b88a9ad195983ca2c9269"}, ] [package.dependencies] colorama = {version = "*", markers = "os_name == \"nt\""} importlib-metadata = {version = ">=0.22", markers = "python_version < \"3.8\""} packaging = ">=19.0" pyproject_hooks = "*" tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} [package.extras] docs = ["furo (>=2021.08.31)", "sphinx (>=4.0,<5.0)", "sphinx-argparse-cli (>=1.5)", "sphinx-autodoc-typehints (>=1.10)"] test = ["filelock (>=3)", "pytest (>=6.2.4)", "pytest-cov (>=2.12)", "pytest-mock (>=2)", "pytest-rerunfailures (>=9.1)", "pytest-xdist (>=1.34)", "setuptools (>=42.0.0)", "setuptools (>=56.0.0)", "toml (>=0.10.0)", "wheel (>=0.36.0)"] typing = ["importlib-metadata (>=5.1)", "mypy (==0.991)", "tomli", "typing-extensions (>=3.7.4.3)"] virtualenv = ["virtualenv (>=20.0.35)"] [[package]] name = "certifi" version = "2023.7.22" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.6" files = [ {file = "certifi-2023.7.22-py3-none-any.whl", hash = "sha256:92d6037539857d8206b8f6ae472e8b77db8058fec5937a1ef3f54304089edbb9"}, {file = "certifi-2023.7.22.tar.gz", hash = "sha256:539cc1d13202e33ca466e88b2807e29f4c13049d6d87031a3c110744495cb082"}, ] [[package]] name = "cffi" version = "1.15.1" description = "Foreign Function Interface for Python calling C code." optional = false python-versions = "*" files = [ {file = "cffi-1.15.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:a66d3508133af6e8548451b25058d5812812ec3798c886bf38ed24a98216fab2"}, {file = "cffi-1.15.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:470c103ae716238bbe698d67ad020e1db9d9dba34fa5a899b5e21577e6d52ed2"}, {file = "cffi-1.15.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:9ad5db27f9cabae298d151c85cf2bad1d359a1b9c686a275df03385758e2f914"}, {file = "cffi-1.15.1-cp27-cp27m-win32.whl", hash = "sha256:b3bbeb01c2b273cca1e1e0c5df57f12dce9a4dd331b4fa1635b8bec26350bde3"}, {file = "cffi-1.15.1-cp27-cp27m-win_amd64.whl", hash = "sha256:e00b098126fd45523dd056d2efba6c5a63b71ffe9f2bbe1a4fe1716e1d0c331e"}, {file = "cffi-1.15.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:d61f4695e6c866a23a21acab0509af1cdfd2c013cf256bbf5b6b5e2695827162"}, {file = "cffi-1.15.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:ed9cb427ba5504c1dc15ede7d516b84757c3e3d7868ccc85121d9310d27eed0b"}, {file = "cffi-1.15.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:39d39875251ca8f612b6f33e6b1195af86d1b3e60086068be9cc053aa4376e21"}, {file = "cffi-1.15.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:285d29981935eb726a4399badae8f0ffdff4f5050eaa6d0cfc3f64b857b77185"}, {file = "cffi-1.15.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3eb6971dcff08619f8d91607cfc726518b6fa2a9eba42856be181c6d0d9515fd"}, {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:21157295583fe8943475029ed5abdcf71eb3911894724e360acff1d61c1d54bc"}, {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5635bd9cb9731e6d4a1132a498dd34f764034a8ce60cef4f5319c0541159392f"}, {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2012c72d854c2d03e45d06ae57f40d78e5770d252f195b93f581acf3ba44496e"}, {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd86c085fae2efd48ac91dd7ccffcfc0571387fe1193d33b6394db7ef31fe2a4"}, {file = "cffi-1.15.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:fa6693661a4c91757f4412306191b6dc88c1703f780c8234035eac011922bc01"}, {file = "cffi-1.15.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:59c0b02d0a6c384d453fece7566d1c7e6b7bae4fc5874ef2ef46d56776d61c9e"}, {file = "cffi-1.15.1-cp310-cp310-win32.whl", hash = "sha256:cba9d6b9a7d64d4bd46167096fc9d2f835e25d7e4c121fb2ddfc6528fb0413b2"}, {file = "cffi-1.15.1-cp310-cp310-win_amd64.whl", hash = "sha256:ce4bcc037df4fc5e3d184794f27bdaab018943698f4ca31630bc7f84a7b69c6d"}, {file = "cffi-1.15.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3d08afd128ddaa624a48cf2b859afef385b720bb4b43df214f85616922e6a5ac"}, {file = "cffi-1.15.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3799aecf2e17cf585d977b780ce79ff0dc9b78d799fc694221ce814c2c19db83"}, {file = "cffi-1.15.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a591fe9e525846e4d154205572a029f653ada1a78b93697f3b5a8f1f2bc055b9"}, {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3548db281cd7d2561c9ad9984681c95f7b0e38881201e157833a2342c30d5e8c"}, {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:91fc98adde3d7881af9b59ed0294046f3806221863722ba7d8d120c575314325"}, {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94411f22c3985acaec6f83c6df553f2dbe17b698cc7f8ae751ff2237d96b9e3c"}, {file = "cffi-1.15.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:03425bdae262c76aad70202debd780501fabeaca237cdfddc008987c0e0f59ef"}, {file = "cffi-1.15.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:cc4d65aeeaa04136a12677d3dd0b1c0c94dc43abac5860ab33cceb42b801c1e8"}, {file = "cffi-1.15.1-cp311-cp311-win32.whl", hash = "sha256:a0f100c8912c114ff53e1202d0078b425bee3649ae34d7b070e9697f93c5d52d"}, {file = "cffi-1.15.1-cp311-cp311-win_amd64.whl", hash = "sha256:04ed324bda3cda42b9b695d51bb7d54b680b9719cfab04227cdd1e04e5de3104"}, {file = "cffi-1.15.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50a74364d85fd319352182ef59c5c790484a336f6db772c1a9231f1c3ed0cbd7"}, {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e263d77ee3dd201c3a142934a086a4450861778baaeeb45db4591ef65550b0a6"}, {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cec7d9412a9102bdc577382c3929b337320c4c4c4849f2c5cdd14d7368c5562d"}, {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4289fc34b2f5316fbb762d75362931e351941fa95fa18789191b33fc4cf9504a"}, {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:173379135477dc8cac4bc58f45db08ab45d228b3363adb7af79436135d028405"}, {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:6975a3fac6bc83c4a65c9f9fcab9e47019a11d3d2cf7f3c0d03431bf145a941e"}, {file = "cffi-1.15.1-cp36-cp36m-win32.whl", hash = "sha256:2470043b93ff09bf8fb1d46d1cb756ce6132c54826661a32d4e4d132e1977adf"}, {file = "cffi-1.15.1-cp36-cp36m-win_amd64.whl", hash = "sha256:30d78fbc8ebf9c92c9b7823ee18eb92f2e6ef79b45ac84db507f52fbe3ec4497"}, {file = "cffi-1.15.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:198caafb44239b60e252492445da556afafc7d1e3ab7a1fb3f0584ef6d742375"}, {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5ef34d190326c3b1f822a5b7a45f6c4535e2f47ed06fec77d3d799c450b2651e"}, {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8102eaf27e1e448db915d08afa8b41d6c7ca7a04b7d73af6514df10a3e74bd82"}, {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5df2768244d19ab7f60546d0c7c63ce1581f7af8b5de3eb3004b9b6fc8a9f84b"}, {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a8c4917bd7ad33e8eb21e9a5bbba979b49d9a97acb3a803092cbc1133e20343c"}, {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e2642fe3142e4cc4af0799748233ad6da94c62a8bec3a6648bf8ee68b1c7426"}, {file = "cffi-1.15.1-cp37-cp37m-win32.whl", hash = "sha256:e229a521186c75c8ad9490854fd8bbdd9a0c9aa3a524326b55be83b54d4e0ad9"}, {file = "cffi-1.15.1-cp37-cp37m-win_amd64.whl", hash = "sha256:a0b71b1b8fbf2b96e41c4d990244165e2c9be83d54962a9a1d118fd8657d2045"}, {file = "cffi-1.15.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:320dab6e7cb2eacdf0e658569d2575c4dad258c0fcc794f46215e1e39f90f2c3"}, {file = "cffi-1.15.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e74c6b51a9ed6589199c787bf5f9875612ca4a8a0785fb2d4a84429badaf22a"}, {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5c84c68147988265e60416b57fc83425a78058853509c1b0629c180094904a5"}, {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3b926aa83d1edb5aa5b427b4053dc420ec295a08e40911296b9eb1b6170f6cca"}, {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:87c450779d0914f2861b8526e035c5e6da0a3199d8f1add1a665e1cbc6fc6d02"}, {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f2c9f67e9821cad2e5f480bc8d83b8742896f1242dba247911072d4fa94c192"}, {file = "cffi-1.15.1-cp38-cp38-win32.whl", hash = "sha256:8b7ee99e510d7b66cdb6c593f21c043c248537a32e0bedf02e01e9553a172314"}, {file = "cffi-1.15.1-cp38-cp38-win_amd64.whl", hash = "sha256:00a9ed42e88df81ffae7a8ab6d9356b371399b91dbdf0c3cb1e84c03a13aceb5"}, {file = "cffi-1.15.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:54a2db7b78338edd780e7ef7f9f6c442500fb0d41a5a4ea24fff1c929d5af585"}, {file = "cffi-1.15.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:fcd131dd944808b5bdb38e6f5b53013c5aa4f334c5cad0c72742f6eba4b73db0"}, {file = "cffi-1.15.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7473e861101c9e72452f9bf8acb984947aa1661a7704553a9f6e4baa5ba64415"}, {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c9a799e985904922a4d207a94eae35c78ebae90e128f0c4e521ce339396be9d"}, {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3bcde07039e586f91b45c88f8583ea7cf7a0770df3a1649627bf598332cb6984"}, {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:33ab79603146aace82c2427da5ca6e58f2b3f2fb5da893ceac0c42218a40be35"}, {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5d598b938678ebf3c67377cdd45e09d431369c3b1a5b331058c338e201f12b27"}, {file = "cffi-1.15.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:db0fbb9c62743ce59a9ff687eb5f4afbe77e5e8403d6697f7446e5f609976f76"}, {file = "cffi-1.15.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:98d85c6a2bef81588d9227dde12db8a7f47f639f4a17c9ae08e773aa9c697bf3"}, {file = "cffi-1.15.1-cp39-cp39-win32.whl", hash = "sha256:40f4774f5a9d4f5e344f31a32b5096977b5d48560c5592e2f3d2c4374bd543ee"}, {file = "cffi-1.15.1-cp39-cp39-win_amd64.whl", hash = "sha256:70df4e3b545a17496c9b3f41f5115e69a4f2e77e94e1d2a8e1070bc0c38c8a3c"}, {file = "cffi-1.15.1.tar.gz", hash = "sha256:d400bfb9a37b1351253cb402671cea7e89bdecc294e8016a707f6d1d8ac934f9"}, ] [package.dependencies] pycparser = "*" [[package]] name = "cfgv" version = "3.3.1" description = "Validate configuration and produce human readable error messages." optional = false python-versions = ">=3.6.1" files = [ {file = "cfgv-3.3.1-py2.py3-none-any.whl", hash = "sha256:c6a0883f3917a037485059700b9e75da2464e6c27051014ad85ba6aaa5884426"}, {file = "cfgv-3.3.1.tar.gz", hash = "sha256:f5a830efb9ce7a445376bb66ec94c638a9787422f96264c98edc6bdeed8ab736"}, ] [[package]] name = "charset-normalizer" version = "3.1.0" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." optional = false python-versions = ">=3.7.0" files = [ {file = "charset-normalizer-3.1.0.tar.gz", hash = "sha256:34e0a2f9c370eb95597aae63bf85eb5e96826d81e3dcf88b8886012906f509b5"}, {file = "charset_normalizer-3.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e0ac8959c929593fee38da1c2b64ee9778733cdf03c482c9ff1d508b6b593b2b"}, {file = "charset_normalizer-3.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d7fc3fca01da18fbabe4625d64bb612b533533ed10045a2ac3dd194bfa656b60"}, {file = "charset_normalizer-3.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:04eefcee095f58eaabe6dc3cc2262f3bcd776d2c67005880894f447b3f2cb9c1"}, {file = "charset_normalizer-3.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:20064ead0717cf9a73a6d1e779b23d149b53daf971169289ed2ed43a71e8d3b0"}, {file = "charset_normalizer-3.1.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1435ae15108b1cb6fffbcea2af3d468683b7afed0169ad718451f8db5d1aff6f"}, {file = "charset_normalizer-3.1.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c84132a54c750fda57729d1e2599bb598f5fa0344085dbde5003ba429a4798c0"}, {file = "charset_normalizer-3.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75f2568b4189dda1c567339b48cba4ac7384accb9c2a7ed655cd86b04055c795"}, {file = "charset_normalizer-3.1.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:11d3bcb7be35e7b1bba2c23beedac81ee893ac9871d0ba79effc7fc01167db6c"}, {file = "charset_normalizer-3.1.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:891cf9b48776b5c61c700b55a598621fdb7b1e301a550365571e9624f270c203"}, {file = "charset_normalizer-3.1.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:5f008525e02908b20e04707a4f704cd286d94718f48bb33edddc7d7b584dddc1"}, {file = "charset_normalizer-3.1.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:b06f0d3bf045158d2fb8837c5785fe9ff9b8c93358be64461a1089f5da983137"}, {file = "charset_normalizer-3.1.0-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:49919f8400b5e49e961f320c735388ee686a62327e773fa5b3ce6721f7e785ce"}, {file = "charset_normalizer-3.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:22908891a380d50738e1f978667536f6c6b526a2064156203d418f4856d6e86a"}, {file = "charset_normalizer-3.1.0-cp310-cp310-win32.whl", hash = "sha256:12d1a39aa6b8c6f6248bb54550efcc1c38ce0d8096a146638fd4738e42284448"}, {file = "charset_normalizer-3.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:65ed923f84a6844de5fd29726b888e58c62820e0769b76565480e1fdc3d062f8"}, {file = "charset_normalizer-3.1.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9a3267620866c9d17b959a84dd0bd2d45719b817245e49371ead79ed4f710d19"}, {file = "charset_normalizer-3.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6734e606355834f13445b6adc38b53c0fd45f1a56a9ba06c2058f86893ae8017"}, {file = "charset_normalizer-3.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f8303414c7b03f794347ad062c0516cee0e15f7a612abd0ce1e25caf6ceb47df"}, {file = "charset_normalizer-3.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aaf53a6cebad0eae578f062c7d462155eada9c172bd8c4d250b8c1d8eb7f916a"}, {file = "charset_normalizer-3.1.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3dc5b6a8ecfdc5748a7e429782598e4f17ef378e3e272eeb1340ea57c9109f41"}, {file = "charset_normalizer-3.1.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e1b25e3ad6c909f398df8921780d6a3d120d8c09466720226fc621605b6f92b1"}, {file = "charset_normalizer-3.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0ca564606d2caafb0abe6d1b5311c2649e8071eb241b2d64e75a0d0065107e62"}, {file = "charset_normalizer-3.1.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b82fab78e0b1329e183a65260581de4375f619167478dddab510c6c6fb04d9b6"}, {file = "charset_normalizer-3.1.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:bd7163182133c0c7701b25e604cf1611c0d87712e56e88e7ee5d72deab3e76b5"}, {file = "charset_normalizer-3.1.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:11d117e6c63e8f495412d37e7dc2e2fff09c34b2d09dbe2bee3c6229577818be"}, {file = "charset_normalizer-3.1.0-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:cf6511efa4801b9b38dc5546d7547d5b5c6ef4b081c60b23e4d941d0eba9cbeb"}, {file = "charset_normalizer-3.1.0-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:abc1185d79f47c0a7aaf7e2412a0eb2c03b724581139193d2d82b3ad8cbb00ac"}, {file = "charset_normalizer-3.1.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:cb7b2ab0188829593b9de646545175547a70d9a6e2b63bf2cd87a0a391599324"}, {file = "charset_normalizer-3.1.0-cp311-cp311-win32.whl", hash = "sha256:c36bcbc0d5174a80d6cccf43a0ecaca44e81d25be4b7f90f0ed7bcfbb5a00909"}, {file = "charset_normalizer-3.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:cca4def576f47a09a943666b8f829606bcb17e2bc2d5911a46c8f8da45f56755"}, {file = "charset_normalizer-3.1.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0c95f12b74681e9ae127728f7e5409cbbef9cd914d5896ef238cc779b8152373"}, {file = "charset_normalizer-3.1.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fca62a8301b605b954ad2e9c3666f9d97f63872aa4efcae5492baca2056b74ab"}, {file = "charset_normalizer-3.1.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ac0aa6cd53ab9a31d397f8303f92c42f534693528fafbdb997c82bae6e477ad9"}, {file = "charset_normalizer-3.1.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c3af8e0f07399d3176b179f2e2634c3ce9c1301379a6b8c9c9aeecd481da494f"}, {file = "charset_normalizer-3.1.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a5fc78f9e3f501a1614a98f7c54d3969f3ad9bba8ba3d9b438c3bc5d047dd28"}, {file = "charset_normalizer-3.1.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:628c985afb2c7d27a4800bfb609e03985aaecb42f955049957814e0491d4006d"}, {file = "charset_normalizer-3.1.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:74db0052d985cf37fa111828d0dd230776ac99c740e1a758ad99094be4f1803d"}, {file = "charset_normalizer-3.1.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:1e8fcdd8f672a1c4fc8d0bd3a2b576b152d2a349782d1eb0f6b8e52e9954731d"}, {file = "charset_normalizer-3.1.0-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:04afa6387e2b282cf78ff3dbce20f0cc071c12dc8f685bd40960cc68644cfea6"}, {file = "charset_normalizer-3.1.0-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:dd5653e67b149503c68c4018bf07e42eeed6b4e956b24c00ccdf93ac79cdff84"}, {file = "charset_normalizer-3.1.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:d2686f91611f9e17f4548dbf050e75b079bbc2a82be565832bc8ea9047b61c8c"}, {file = "charset_normalizer-3.1.0-cp37-cp37m-win32.whl", hash = "sha256:4155b51ae05ed47199dc5b2a4e62abccb274cee6b01da5b895099b61b1982974"}, {file = "charset_normalizer-3.1.0-cp37-cp37m-win_amd64.whl", hash = "sha256:322102cdf1ab682ecc7d9b1c5eed4ec59657a65e1c146a0da342b78f4112db23"}, {file = "charset_normalizer-3.1.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e633940f28c1e913615fd624fcdd72fdba807bf53ea6925d6a588e84e1151531"}, {file = "charset_normalizer-3.1.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:3a06f32c9634a8705f4ca9946d667609f52cf130d5548881401f1eb2c39b1e2c"}, {file = "charset_normalizer-3.1.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7381c66e0561c5757ffe616af869b916c8b4e42b367ab29fedc98481d1e74e14"}, {file = "charset_normalizer-3.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3573d376454d956553c356df45bb824262c397c6e26ce43e8203c4c540ee0acb"}, {file = "charset_normalizer-3.1.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e89df2958e5159b811af9ff0f92614dabf4ff617c03a4c1c6ff53bf1c399e0e1"}, {file = "charset_normalizer-3.1.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:78cacd03e79d009d95635e7d6ff12c21eb89b894c354bd2b2ed0b4763373693b"}, {file = "charset_normalizer-3.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:de5695a6f1d8340b12a5d6d4484290ee74d61e467c39ff03b39e30df62cf83a0"}, {file = "charset_normalizer-3.1.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1c60b9c202d00052183c9be85e5eaf18a4ada0a47d188a83c8f5c5b23252f649"}, {file = "charset_normalizer-3.1.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:f645caaf0008bacf349875a974220f1f1da349c5dbe7c4ec93048cdc785a3326"}, {file = "charset_normalizer-3.1.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:ea9f9c6034ea2d93d9147818f17c2a0860d41b71c38b9ce4d55f21b6f9165a11"}, {file = "charset_normalizer-3.1.0-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:80d1543d58bd3d6c271b66abf454d437a438dff01c3e62fdbcd68f2a11310d4b"}, {file = "charset_normalizer-3.1.0-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:73dc03a6a7e30b7edc5b01b601e53e7fc924b04e1835e8e407c12c037e81adbd"}, {file = "charset_normalizer-3.1.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6f5c2e7bc8a4bf7c426599765b1bd33217ec84023033672c1e9a8b35eaeaaaf8"}, {file = "charset_normalizer-3.1.0-cp38-cp38-win32.whl", hash = "sha256:12a2b561af122e3d94cdb97fe6fb2bb2b82cef0cdca131646fdb940a1eda04f0"}, {file = "charset_normalizer-3.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:3160a0fd9754aab7d47f95a6b63ab355388d890163eb03b2d2b87ab0a30cfa59"}, {file = "charset_normalizer-3.1.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:38e812a197bf8e71a59fe55b757a84c1f946d0ac114acafaafaf21667a7e169e"}, {file = "charset_normalizer-3.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6baf0baf0d5d265fa7944feb9f7451cc316bfe30e8df1a61b1bb08577c554f31"}, {file = "charset_normalizer-3.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:8f25e17ab3039b05f762b0a55ae0b3632b2e073d9c8fc88e89aca31a6198e88f"}, {file = "charset_normalizer-3.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3747443b6a904001473370d7810aa19c3a180ccd52a7157aacc264a5ac79265e"}, {file = "charset_normalizer-3.1.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b116502087ce8a6b7a5f1814568ccbd0e9f6cfd99948aa59b0e241dc57cf739f"}, {file = "charset_normalizer-3.1.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d16fd5252f883eb074ca55cb622bc0bee49b979ae4e8639fff6ca3ff44f9f854"}, {file = "charset_normalizer-3.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21fa558996782fc226b529fdd2ed7866c2c6ec91cee82735c98a197fae39f706"}, {file = "charset_normalizer-3.1.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6f6c7a8a57e9405cad7485f4c9d3172ae486cfef1344b5ddd8e5239582d7355e"}, {file = "charset_normalizer-3.1.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ac3775e3311661d4adace3697a52ac0bab17edd166087d493b52d4f4f553f9f0"}, {file = "charset_normalizer-3.1.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:10c93628d7497c81686e8e5e557aafa78f230cd9e77dd0c40032ef90c18f2230"}, {file = "charset_normalizer-3.1.0-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:6f4f4668e1831850ebcc2fd0b1cd11721947b6dc7c00bf1c6bd3c929ae14f2c7"}, {file = "charset_normalizer-3.1.0-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:0be65ccf618c1e7ac9b849c315cc2e8a8751d9cfdaa43027d4f6624bd587ab7e"}, {file = "charset_normalizer-3.1.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:53d0a3fa5f8af98a1e261de6a3943ca631c526635eb5817a87a59d9a57ebf48f"}, {file = "charset_normalizer-3.1.0-cp39-cp39-win32.whl", hash = "sha256:a04f86f41a8916fe45ac5024ec477f41f886b3c435da2d4e3d2709b22ab02af1"}, {file = "charset_normalizer-3.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:830d2948a5ec37c386d3170c483063798d7879037492540f10a475e3fd6f244b"}, {file = "charset_normalizer-3.1.0-py3-none-any.whl", hash = "sha256:3d9098b479e78c85080c98e1e35ff40b4a31d8953102bb0fd7d1b6f8a2111a3d"}, ] [[package]] name = "colorama" version = "0.4.6" description = "Cross-platform colored terminal text." optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" files = [ {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] [[package]] name = "coverage" version = "6.5.0" description = "Code coverage measurement for Python" optional = false python-versions = ">=3.7" files = [ {file = "coverage-6.5.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ef8674b0ee8cc11e2d574e3e2998aea5df5ab242e012286824ea3c6970580e53"}, {file = "coverage-6.5.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:784f53ebc9f3fd0e2a3f6a78b2be1bd1f5575d7863e10c6e12504f240fd06660"}, {file = "coverage-6.5.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b4a5be1748d538a710f87542f22c2cad22f80545a847ad91ce45e77417293eb4"}, {file = "coverage-6.5.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:83516205e254a0cb77d2d7bb3632ee019d93d9f4005de31dca0a8c3667d5bc04"}, {file = "coverage-6.5.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:af4fffaffc4067232253715065e30c5a7ec6faac36f8fc8d6f64263b15f74db0"}, {file = "coverage-6.5.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:97117225cdd992a9c2a5515db1f66b59db634f59d0679ca1fa3fe8da32749cae"}, {file = "coverage-6.5.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:a1170fa54185845505fbfa672f1c1ab175446c887cce8212c44149581cf2d466"}, {file = "coverage-6.5.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:11b990d520ea75e7ee8dcab5bc908072aaada194a794db9f6d7d5cfd19661e5a"}, {file = "coverage-6.5.0-cp310-cp310-win32.whl", hash = "sha256:5dbec3b9095749390c09ab7c89d314727f18800060d8d24e87f01fb9cfb40b32"}, {file = "coverage-6.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:59f53f1dc5b656cafb1badd0feb428c1e7bc19b867479ff72f7a9dd9b479f10e"}, {file = "coverage-6.5.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4a5375e28c5191ac38cca59b38edd33ef4cc914732c916f2929029b4bfb50795"}, {file = "coverage-6.5.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c4ed2820d919351f4167e52425e096af41bfabacb1857186c1ea32ff9983ed75"}, {file = "coverage-6.5.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:33a7da4376d5977fbf0a8ed91c4dffaaa8dbf0ddbf4c8eea500a2486d8bc4d7b"}, {file = "coverage-6.5.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a8fb6cf131ac4070c9c5a3e21de0f7dc5a0fbe8bc77c9456ced896c12fcdad91"}, {file = "coverage-6.5.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a6b7d95969b8845250586f269e81e5dfdd8ff828ddeb8567a4a2eaa7313460c4"}, {file = "coverage-6.5.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:1ef221513e6f68b69ee9e159506d583d31aa3567e0ae84eaad9d6ec1107dddaa"}, {file = "coverage-6.5.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:cca4435eebea7962a52bdb216dec27215d0df64cf27fc1dd538415f5d2b9da6b"}, {file = "coverage-6.5.0-cp311-cp311-win32.whl", hash = "sha256:98e8a10b7a314f454d9eff4216a9a94d143a7ee65018dd12442e898ee2310578"}, {file = "coverage-6.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:bc8ef5e043a2af066fa8cbfc6e708d58017024dc4345a1f9757b329a249f041b"}, {file = "coverage-6.5.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:4433b90fae13f86fafff0b326453dd42fc9a639a0d9e4eec4d366436d1a41b6d"}, {file = "coverage-6.5.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f4f05d88d9a80ad3cac6244d36dd89a3c00abc16371769f1340101d3cb899fc3"}, {file = "coverage-6.5.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:94e2565443291bd778421856bc975d351738963071e9b8839ca1fc08b42d4bef"}, {file = "coverage-6.5.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:027018943386e7b942fa832372ebc120155fd970837489896099f5cfa2890f79"}, {file = "coverage-6.5.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:255758a1e3b61db372ec2736c8e2a1fdfaf563977eedbdf131de003ca5779b7d"}, {file = "coverage-6.5.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:851cf4ff24062c6aec510a454b2584f6e998cada52d4cb58c5e233d07172e50c"}, {file = "coverage-6.5.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:12adf310e4aafddc58afdb04d686795f33f4d7a6fa67a7a9d4ce7d6ae24d949f"}, {file = "coverage-6.5.0-cp37-cp37m-win32.whl", hash = "sha256:b5604380f3415ba69de87a289a2b56687faa4fe04dbee0754bfcae433489316b"}, {file = "coverage-6.5.0-cp37-cp37m-win_amd64.whl", hash = "sha256:4a8dbc1f0fbb2ae3de73eb0bdbb914180c7abfbf258e90b311dcd4f585d44bd2"}, {file = "coverage-6.5.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d900bb429fdfd7f511f868cedd03a6bbb142f3f9118c09b99ef8dc9bf9643c3c"}, {file = "coverage-6.5.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2198ea6fc548de52adc826f62cb18554caedfb1d26548c1b7c88d8f7faa8f6ba"}, {file = "coverage-6.5.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c4459b3de97b75e3bd6b7d4b7f0db13f17f504f3d13e2a7c623786289dd670e"}, {file = "coverage-6.5.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:20c8ac5386253717e5ccc827caad43ed66fea0efe255727b1053a8154d952398"}, {file = "coverage-6.5.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b07130585d54fe8dff3d97b93b0e20290de974dc8177c320aeaf23459219c0b"}, {file = "coverage-6.5.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:dbdb91cd8c048c2b09eb17713b0c12a54fbd587d79adcebad543bc0cd9a3410b"}, {file = "coverage-6.5.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:de3001a203182842a4630e7b8d1a2c7c07ec1b45d3084a83d5d227a3806f530f"}, {file = "coverage-6.5.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:e07f4a4a9b41583d6eabec04f8b68076ab3cd44c20bd29332c6572dda36f372e"}, {file = "coverage-6.5.0-cp38-cp38-win32.whl", hash = "sha256:6d4817234349a80dbf03640cec6109cd90cba068330703fa65ddf56b60223a6d"}, {file = "coverage-6.5.0-cp38-cp38-win_amd64.whl", hash = "sha256:7ccf362abd726b0410bf8911c31fbf97f09f8f1061f8c1cf03dfc4b6372848f6"}, {file = "coverage-6.5.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:633713d70ad6bfc49b34ead4060531658dc6dfc9b3eb7d8a716d5873377ab745"}, {file = "coverage-6.5.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:95203854f974e07af96358c0b261f1048d8e1083f2de9b1c565e1be4a3a48cfc"}, {file = "coverage-6.5.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b9023e237f4c02ff739581ef35969c3739445fb059b060ca51771e69101efffe"}, {file = "coverage-6.5.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:265de0fa6778d07de30bcf4d9dc471c3dc4314a23a3c6603d356a3c9abc2dfcf"}, {file = "coverage-6.5.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f830ed581b45b82451a40faabb89c84e1a998124ee4212d440e9c6cf70083e5"}, {file = "coverage-6.5.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:7b6be138d61e458e18d8e6ddcddd36dd96215edfe5f1168de0b1b32635839b62"}, {file = "coverage-6.5.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:42eafe6778551cf006a7c43153af1211c3aaab658d4d66fa5fcc021613d02518"}, {file = "coverage-6.5.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:723e8130d4ecc8f56e9a611e73b31219595baa3bb252d539206f7bbbab6ffc1f"}, {file = "coverage-6.5.0-cp39-cp39-win32.whl", hash = "sha256:d9ecf0829c6a62b9b573c7bb6d4dcd6ba8b6f80be9ba4fc7ed50bf4ac9aecd72"}, {file = "coverage-6.5.0-cp39-cp39-win_amd64.whl", hash = "sha256:fc2af30ed0d5ae0b1abdb4ebdce598eafd5b35397d4d75deb341a614d333d987"}, {file = "coverage-6.5.0-pp36.pp37.pp38-none-any.whl", hash = "sha256:1431986dac3923c5945271f169f59c45b8802a114c8f548d611f2015133df77a"}, {file = "coverage-6.5.0.tar.gz", hash = "sha256:f642e90754ee3e06b0e7e51bce3379590e76b7f76b708e1a71ff043f87025c84"}, ] [package.dependencies] tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.11.0a6\" and extra == \"toml\""} [package.extras] toml = ["tomli"] [[package]] name = "coveralls" version = "3.3.1" description = "Show coverage stats online via coveralls.io" optional = false python-versions = ">= 3.5" files = [ {file = "coveralls-3.3.1-py2.py3-none-any.whl", hash = "sha256:f42015f31d386b351d4226389b387ae173207058832fbf5c8ec4b40e27b16026"}, {file = "coveralls-3.3.1.tar.gz", hash = "sha256:b32a8bb5d2df585207c119d6c01567b81fba690c9c10a753bfe27a335bfc43ea"}, ] [package.dependencies] coverage = ">=4.1,<6.0.dev0 || >6.1,<6.1.1 || >6.1.1,<7.0" docopt = ">=0.6.1" requests = ">=1.0.0" [package.extras] yaml = ["PyYAML (>=3.10)"] [[package]] name = "cryptography" version = "41.0.4" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." optional = false python-versions = ">=3.7" files = [ {file = "cryptography-41.0.4-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:80907d3faa55dc5434a16579952ac6da800935cd98d14dbd62f6f042c7f5e839"}, {file = "cryptography-41.0.4-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:35c00f637cd0b9d5b6c6bd11b6c3359194a8eba9c46d4e875a3660e3b400005f"}, {file = "cryptography-41.0.4-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cecfefa17042941f94ab54f769c8ce0fe14beff2694e9ac684176a2535bf9714"}, {file = "cryptography-41.0.4-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e40211b4923ba5a6dc9769eab704bdb3fbb58d56c5b336d30996c24fcf12aadb"}, {file = "cryptography-41.0.4-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:23a25c09dfd0d9f28da2352503b23e086f8e78096b9fd585d1d14eca01613e13"}, {file = "cryptography-41.0.4-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:2ed09183922d66c4ec5fdaa59b4d14e105c084dd0febd27452de8f6f74704143"}, {file = "cryptography-41.0.4-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:5a0f09cefded00e648a127048119f77bc2b2ec61e736660b5789e638f43cc397"}, {file = "cryptography-41.0.4-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:9eeb77214afae972a00dee47382d2591abe77bdae166bda672fb1e24702a3860"}, {file = "cryptography-41.0.4-cp37-abi3-win32.whl", hash = "sha256:3b224890962a2d7b57cf5eeb16ccaafba6083f7b811829f00476309bce2fe0fd"}, {file = "cryptography-41.0.4-cp37-abi3-win_amd64.whl", hash = "sha256:c880eba5175f4307129784eca96f4e70b88e57aa3f680aeba3bab0e980b0f37d"}, {file = "cryptography-41.0.4-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:004b6ccc95943f6a9ad3142cfabcc769d7ee38a3f60fb0dddbfb431f818c3a67"}, {file = "cryptography-41.0.4-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:86defa8d248c3fa029da68ce61fe735432b047e32179883bdb1e79ed9bb8195e"}, {file = "cryptography-41.0.4-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:37480760ae08065437e6573d14be973112c9e6dcaf5f11d00147ee74f37a3829"}, {file = "cryptography-41.0.4-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:b5f4dfe950ff0479f1f00eda09c18798d4f49b98f4e2006d644b3301682ebdca"}, {file = "cryptography-41.0.4-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:7e53db173370dea832190870e975a1e09c86a879b613948f09eb49324218c14d"}, {file = "cryptography-41.0.4-pp38-pypy38_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:5b72205a360f3b6176485a333256b9bcd48700fc755fef51c8e7e67c4b63e3ac"}, {file = "cryptography-41.0.4-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:93530900d14c37a46ce3d6c9e6fd35dbe5f5601bf6b3a5c325c7bffc030344d9"}, {file = "cryptography-41.0.4-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:efc8ad4e6fc4f1752ebfb58aefece8b4e3c4cae940b0994d43649bdfce8d0d4f"}, {file = "cryptography-41.0.4-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:c3391bd8e6de35f6f1140e50aaeb3e2b3d6a9012536ca23ab0d9c35ec18c8a91"}, {file = "cryptography-41.0.4-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:0d9409894f495d465fe6fda92cb70e8323e9648af912d5b9141d616df40a87b8"}, {file = "cryptography-41.0.4-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:8ac4f9ead4bbd0bc8ab2d318f97d85147167a488be0e08814a37eb2f439d5cf6"}, {file = "cryptography-41.0.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:047c4603aeb4bbd8db2756e38f5b8bd7e94318c047cfe4efeb5d715e08b49311"}, {file = "cryptography-41.0.4.tar.gz", hash = "sha256:7febc3094125fc126a7f6fb1f420d0da639f3f32cb15c8ff0dc3997c4549f51a"}, ] [package.dependencies] cffi = ">=1.12" [package.extras] docs = ["sphinx (>=5.3.0)", "sphinx-rtd-theme (>=1.1.1)"] docstest = ["pyenchant (>=1.6.11)", "sphinxcontrib-spelling (>=4.0.1)", "twine (>=1.12.0)"] nox = ["nox"] pep8test = ["black", "check-sdist", "mypy", "ruff"] sdist = ["build"] ssh = ["bcrypt (>=3.1.5)"] test = ["pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-xdist"] test-randomorder = ["pytest-randomly"] [[package]] name = "dill" version = "0.3.6" description = "serialize all of python" optional = false python-versions = ">=3.7" files = [ {file = "dill-0.3.6-py3-none-any.whl", hash = "sha256:a07ffd2351b8c678dfc4a856a3005f8067aea51d6ba6c700796a4d9e280f39f0"}, {file = "dill-0.3.6.tar.gz", hash = "sha256:e5db55f3687856d8fbdab002ed78544e1c4559a130302693d839dfe8f93f2373"}, ] [package.extras] graph = ["objgraph (>=1.7.2)"] [[package]] name = "distlib" version = "0.3.6" description = "Distribution utilities" optional = false python-versions = "*" files = [ {file = "distlib-0.3.6-py2.py3-none-any.whl", hash = "sha256:f35c4b692542ca110de7ef0bea44d73981caeb34ca0b9b6b2e6d7790dda8f80e"}, {file = "distlib-0.3.6.tar.gz", hash = "sha256:14bad2d9b04d3a36127ac97f30b12a19268f211063d8f8ee4f47108896e11b46"}, ] [[package]] name = "docopt" version = "0.6.2" description = "Pythonic argument parser, that will make you smile" optional = false python-versions = "*" files = [ {file = "docopt-0.6.2.tar.gz", hash = "sha256:49b3a825280bd66b3aa83585ef59c4a8c82f2c8a522dbe754a8bc8d08c85c491"}, ] [[package]] name = "docutils" version = "0.19" description = "Docutils -- Python Documentation Utilities" optional = false python-versions = ">=3.7" files = [ {file = "docutils-0.19-py3-none-any.whl", hash = "sha256:5e1de4d849fee02c63b040a4a3fd567f4ab104defd8a5511fbbc24a8a017efbc"}, {file = "docutils-0.19.tar.gz", hash = "sha256:33995a6753c30b7f577febfc2c50411fec6aac7f7ffeb7c4cfe5991072dcf9e6"}, ] [[package]] name = "dodgy" version = "0.2.1" description = "Dodgy: Searches for dodgy looking lines in Python code" optional = false python-versions = "*" files = [ {file = "dodgy-0.2.1-py3-none-any.whl", hash = "sha256:51f54c0fd886fa3854387f354b19f429d38c04f984f38bc572558b703c0542a6"}, {file = "dodgy-0.2.1.tar.gz", hash = "sha256:28323cbfc9352139fdd3d316fa17f325cc0e9ac74438cbba51d70f9b48f86c3a"}, ] [[package]] name = "exceptiongroup" version = "1.1.1" description = "Backport of PEP 654 (exception groups)" optional = false python-versions = ">=3.7" files = [ {file = "exceptiongroup-1.1.1-py3-none-any.whl", hash = "sha256:232c37c63e4f682982c8b6459f33a8981039e5fb8756b2074364e5055c498c9e"}, {file = "exceptiongroup-1.1.1.tar.gz", hash = "sha256:d484c3090ba2889ae2928419117447a14daf3c1231d5e30d0aae34f354f01785"}, ] [package.extras] test = ["pytest (>=6)"] [[package]] name = "filelock" version = "3.12.0" description = "A platform independent file lock." optional = false python-versions = ">=3.7" files = [ {file = "filelock-3.12.0-py3-none-any.whl", hash = "sha256:ad98852315c2ab702aeb628412cbf7e95b7ce8c3bf9565670b4eaecf1db370a9"}, {file = "filelock-3.12.0.tar.gz", hash = "sha256:fc03ae43288c013d2ea83c8597001b1129db351aad9c57fe2409327916b8e718"}, ] [package.extras] docs = ["furo (>=2023.3.27)", "sphinx (>=6.1.3)", "sphinx-autodoc-typehints (>=1.23,!=1.23.4)"] testing = ["covdefaults (>=2.3)", "coverage (>=7.2.3)", "diff-cover (>=7.5)", "pytest (>=7.3.1)", "pytest-cov (>=4)", "pytest-mock (>=3.10)", "pytest-timeout (>=2.1)"] [[package]] name = "flake8" version = "2.3.0" description = "the modular source code checker: pep8, pyflakes and co" optional = false python-versions = "*" files = [ {file = "flake8-2.3.0-py2.py3-none-any.whl", hash = "sha256:c99cc9716d6655d9c8bcb1e77632b8615bf0abd282d7abd9f5c2148cad7fc669"}, {file = "flake8-2.3.0.tar.gz", hash = "sha256:5ee1a43ccd0716d6061521eec6937c983efa027793013e572712c4da55c7c83e"}, ] [package.dependencies] mccabe = ">=0.2.1" pep8 = ">=1.5.7" pyflakes = ">=0.8.1" [[package]] name = "flake8-polyfill" version = "1.0.2" description = "Polyfill package for Flake8 plugins" optional = false python-versions = "*" files = [ {file = "flake8-polyfill-1.0.2.tar.gz", hash = "sha256:e44b087597f6da52ec6393a709e7108b2905317d0c0b744cdca6208e670d8eda"}, {file = "flake8_polyfill-1.0.2-py2.py3-none-any.whl", hash = "sha256:12be6a34ee3ab795b19ca73505e7b55826d5f6ad7230d31b18e106400169b9e9"}, ] [package.dependencies] flake8 = "*" [[package]] name = "gitdb" version = "4.0.10" description = "Git Object Database" optional = false python-versions = ">=3.7" files = [ {file = "gitdb-4.0.10-py3-none-any.whl", hash = "sha256:c286cf298426064079ed96a9e4a9d39e7f3e9bf15ba60701e95f5492f28415c7"}, {file = "gitdb-4.0.10.tar.gz", hash = "sha256:6eb990b69df4e15bad899ea868dc46572c3f75339735663b81de79b06f17eb9a"}, ] [package.dependencies] smmap = ">=3.0.1,<6" [[package]] name = "gitpython" version = "3.1.37" description = "GitPython is a Python library used to interact with Git repositories" optional = false python-versions = ">=3.7" files = [ {file = "GitPython-3.1.37-py3-none-any.whl", hash = "sha256:5f4c4187de49616d710a77e98ddf17b4782060a1788df441846bddefbb89ab33"}, {file = "GitPython-3.1.37.tar.gz", hash = "sha256:f9b9ddc0761c125d5780eab2d64be4873fc6817c2899cbcb34b02344bdc7bc54"}, ] [package.dependencies] gitdb = ">=4.0.1,<5" typing-extensions = {version = ">=3.7.4.3", markers = "python_version < \"3.8\""} [package.extras] test = ["black", "coverage[toml]", "ddt (>=1.1.1,!=1.4.3)", "mypy", "pre-commit", "pytest", "pytest-cov", "pytest-sugar"] [[package]] name = "identify" version = "2.5.24" description = "File identification library for Python" optional = false python-versions = ">=3.7" files = [ {file = "identify-2.5.24-py2.py3-none-any.whl", hash = "sha256:986dbfb38b1140e763e413e6feb44cd731faf72d1909543178aa79b0e258265d"}, {file = "identify-2.5.24.tar.gz", hash = "sha256:0aac67d5b4812498056d28a9a512a483f5085cc28640b02b258a59dac34301d4"}, ] [package.extras] license = ["ukkonen"] [[package]] name = "idna" version = "3.4" description = "Internationalized Domain Names in Applications (IDNA)" optional = false python-versions = ">=3.5" files = [ {file = "idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"}, {file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"}, ] [[package]] name = "importlib-metadata" version = "4.13.0" description = "Read metadata from Python packages" optional = false python-versions = ">=3.7" files = [ {file = "importlib_metadata-4.13.0-py3-none-any.whl", hash = "sha256:8a8a81bcf996e74fee46f0d16bd3eaa382a7eb20fd82445c3ad11f4090334116"}, {file = "importlib_metadata-4.13.0.tar.gz", hash = "sha256:dd0173e8f150d6815e098fd354f6414b0f079af4644ddfe90c71e2fc6174346d"}, ] [package.dependencies] typing-extensions = {version = ">=3.6.4", markers = "python_version < \"3.8\""} zipp = ">=0.5" [package.extras] docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)"] perf = ["ipython"] testing = ["flake8 (<5)", "flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)"] [[package]] name = "importlib-resources" version = "5.12.0" description = "Read resources from Python packages" optional = false python-versions = ">=3.7" files = [ {file = "importlib_resources-5.12.0-py3-none-any.whl", hash = "sha256:7b1deeebbf351c7578e09bf2f63fa2ce8b5ffec296e0d349139d43cca061a81a"}, {file = "importlib_resources-5.12.0.tar.gz", hash = "sha256:4be82589bf5c1d7999aedf2a45159d10cb3ca4f19b2271f8792bc8e6da7b22f6"}, ] [package.dependencies] zipp = {version = ">=3.1.0", markers = "python_version < \"3.10\""} [package.extras] docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] testing = ["flake8 (<5)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"] [[package]] name = "iniconfig" version = "2.0.0" description = "brain-dead simple config-ini parsing" optional = false python-versions = ">=3.7" files = [ {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, ] [[package]] name = "isort" version = "5.11.5" description = "A Python utility / library to sort Python imports." optional = false python-versions = ">=3.7.0" files = [ {file = "isort-5.11.5-py3-none-any.whl", hash = "sha256:ba1d72fb2595a01c7895a5128f9585a5cc4b6d395f1c8d514989b9a7eb2a8746"}, {file = "isort-5.11.5.tar.gz", hash = "sha256:6be1f76a507cb2ecf16c7cf14a37e41609ca082330be4e3436a18ef74add55db"}, ] [package.extras] colors = ["colorama (>=0.4.3,<0.5.0)"] pipfile-deprecated-finder = ["pip-shims (>=0.5.2)", "pipreqs", "requirementslib"] plugins = ["setuptools"] requirements-deprecated-finder = ["pip-api", "pipreqs"] [[package]] name = "jaraco-classes" version = "3.2.3" description = "Utility functions for Python class constructs" optional = false python-versions = ">=3.7" files = [ {file = "jaraco.classes-3.2.3-py3-none-any.whl", hash = "sha256:2353de3288bc6b82120752201c6b1c1a14b058267fa424ed5ce5984e3b922158"}, {file = "jaraco.classes-3.2.3.tar.gz", hash = "sha256:89559fa5c1d3c34eff6f631ad80bb21f378dbcbb35dd161fd2c6b93f5be2f98a"}, ] [package.dependencies] more-itertools = "*" [package.extras] docs = ["jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)"] testing = ["flake8 (<5)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"] [[package]] name = "jeepney" version = "0.8.0" description = "Low-level, pure Python DBus protocol wrapper." optional = false python-versions = ">=3.7" files = [ {file = "jeepney-0.8.0-py3-none-any.whl", hash = "sha256:c0a454ad016ca575060802ee4d590dd912e35c122fa04e70306de3d076cce755"}, {file = "jeepney-0.8.0.tar.gz", hash = "sha256:5efe48d255973902f6badc3ce55e2aa6c5c3b3bc642059ef3a91247bcfcc5806"}, ] [package.extras] test = ["async-timeout", "pytest", "pytest-asyncio (>=0.17)", "pytest-trio", "testpath", "trio"] trio = ["async_generator", "trio"] [[package]] name = "keyring" version = "23.13.1" description = "Store and access your passwords safely." optional = false python-versions = ">=3.7" files = [ {file = "keyring-23.13.1-py3-none-any.whl", hash = "sha256:771ed2a91909389ed6148631de678f82ddc73737d85a927f382a8a1b157898cd"}, {file = "keyring-23.13.1.tar.gz", hash = "sha256:ba2e15a9b35e21908d0aaf4e0a47acc52d6ae33444df0da2b49d41a46ef6d678"}, ] [package.dependencies] importlib-metadata = {version = ">=4.11.4", markers = "python_version < \"3.12\""} importlib-resources = {version = "*", markers = "python_version < \"3.9\""} "jaraco.classes" = "*" jeepney = {version = ">=0.4.2", markers = "sys_platform == \"linux\""} pywin32-ctypes = {version = ">=0.2.0", markers = "sys_platform == \"win32\""} SecretStorage = {version = ">=3.2", markers = "sys_platform == \"linux\""} [package.extras] completion = ["shtab"] docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)"] testing = ["flake8 (<5)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"] [[package]] name = "lazy-object-proxy" version = "1.9.0" description = "A fast and thorough lazy object proxy." optional = false python-versions = ">=3.7" files = [ {file = "lazy-object-proxy-1.9.0.tar.gz", hash = "sha256:659fb5809fa4629b8a1ac5106f669cfc7bef26fbb389dda53b3e010d1ac4ebae"}, {file = "lazy_object_proxy-1.9.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b40387277b0ed2d0602b8293b94d7257e17d1479e257b4de114ea11a8cb7f2d7"}, {file = "lazy_object_proxy-1.9.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8c6cfb338b133fbdbc5cfaa10fe3c6aeea827db80c978dbd13bc9dd8526b7d4"}, {file = "lazy_object_proxy-1.9.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:721532711daa7db0d8b779b0bb0318fa87af1c10d7fe5e52ef30f8eff254d0cd"}, {file = "lazy_object_proxy-1.9.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:66a3de4a3ec06cd8af3f61b8e1ec67614fbb7c995d02fa224813cb7afefee701"}, {file = "lazy_object_proxy-1.9.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:1aa3de4088c89a1b69f8ec0dcc169aa725b0ff017899ac568fe44ddc1396df46"}, {file = "lazy_object_proxy-1.9.0-cp310-cp310-win32.whl", hash = "sha256:f0705c376533ed2a9e5e97aacdbfe04cecd71e0aa84c7c0595d02ef93b6e4455"}, {file = "lazy_object_proxy-1.9.0-cp310-cp310-win_amd64.whl", hash = "sha256:ea806fd4c37bf7e7ad82537b0757999264d5f70c45468447bb2b91afdbe73a6e"}, {file = "lazy_object_proxy-1.9.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:946d27deaff6cf8452ed0dba83ba38839a87f4f7a9732e8f9fd4107b21e6ff07"}, {file = "lazy_object_proxy-1.9.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:79a31b086e7e68b24b99b23d57723ef7e2c6d81ed21007b6281ebcd1688acb0a"}, {file = "lazy_object_proxy-1.9.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f699ac1c768270c9e384e4cbd268d6e67aebcfae6cd623b4d7c3bfde5a35db59"}, {file = "lazy_object_proxy-1.9.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:bfb38f9ffb53b942f2b5954e0f610f1e721ccebe9cce9025a38c8ccf4a5183a4"}, {file = "lazy_object_proxy-1.9.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:189bbd5d41ae7a498397287c408617fe5c48633e7755287b21d741f7db2706a9"}, {file = "lazy_object_proxy-1.9.0-cp311-cp311-win32.whl", hash = "sha256:81fc4d08b062b535d95c9ea70dbe8a335c45c04029878e62d744bdced5141586"}, {file = "lazy_object_proxy-1.9.0-cp311-cp311-win_amd64.whl", hash = "sha256:f2457189d8257dd41ae9b434ba33298aec198e30adf2dcdaaa3a28b9994f6adb"}, {file = "lazy_object_proxy-1.9.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:d9e25ef10a39e8afe59a5c348a4dbf29b4868ab76269f81ce1674494e2565a6e"}, {file = "lazy_object_proxy-1.9.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cbf9b082426036e19c6924a9ce90c740a9861e2bdc27a4834fd0a910742ac1e8"}, {file = "lazy_object_proxy-1.9.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9f5fa4a61ce2438267163891961cfd5e32ec97a2c444e5b842d574251ade27d2"}, {file = "lazy_object_proxy-1.9.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:8fa02eaab317b1e9e03f69aab1f91e120e7899b392c4fc19807a8278a07a97e8"}, {file = "lazy_object_proxy-1.9.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:e7c21c95cae3c05c14aafffe2865bbd5e377cfc1348c4f7751d9dc9a48ca4bda"}, {file = "lazy_object_proxy-1.9.0-cp37-cp37m-win32.whl", hash = "sha256:f12ad7126ae0c98d601a7ee504c1122bcef553d1d5e0c3bfa77b16b3968d2734"}, {file = "lazy_object_proxy-1.9.0-cp37-cp37m-win_amd64.whl", hash = "sha256:edd20c5a55acb67c7ed471fa2b5fb66cb17f61430b7a6b9c3b4a1e40293b1671"}, {file = "lazy_object_proxy-1.9.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2d0daa332786cf3bb49e10dc6a17a52f6a8f9601b4cf5c295a4f85854d61de63"}, {file = "lazy_object_proxy-1.9.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cd077f3d04a58e83d04b20e334f678c2b0ff9879b9375ed107d5d07ff160171"}, {file = "lazy_object_proxy-1.9.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:660c94ea760b3ce47d1855a30984c78327500493d396eac4dfd8bd82041b22be"}, {file = "lazy_object_proxy-1.9.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:212774e4dfa851e74d393a2370871e174d7ff0ebc980907723bb67d25c8a7c30"}, {file = "lazy_object_proxy-1.9.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:f0117049dd1d5635bbff65444496c90e0baa48ea405125c088e93d9cf4525b11"}, {file = "lazy_object_proxy-1.9.0-cp38-cp38-win32.whl", hash = "sha256:0a891e4e41b54fd5b8313b96399f8b0e173bbbfc03c7631f01efbe29bb0bcf82"}, {file = "lazy_object_proxy-1.9.0-cp38-cp38-win_amd64.whl", hash = "sha256:9990d8e71b9f6488e91ad25f322898c136b008d87bf852ff65391b004da5e17b"}, {file = "lazy_object_proxy-1.9.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9e7551208b2aded9c1447453ee366f1c4070602b3d932ace044715d89666899b"}, {file = "lazy_object_proxy-1.9.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5f83ac4d83ef0ab017683d715ed356e30dd48a93746309c8f3517e1287523ef4"}, {file = "lazy_object_proxy-1.9.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7322c3d6f1766d4ef1e51a465f47955f1e8123caee67dd641e67d539a534d006"}, {file = "lazy_object_proxy-1.9.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:18b78ec83edbbeb69efdc0e9c1cb41a3b1b1ed11ddd8ded602464c3fc6020494"}, {file = "lazy_object_proxy-1.9.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:09763491ce220c0299688940f8dc2c5d05fd1f45af1e42e636b2e8b2303e4382"}, {file = "lazy_object_proxy-1.9.0-cp39-cp39-win32.whl", hash = "sha256:9090d8e53235aa280fc9239a86ae3ea8ac58eff66a705fa6aa2ec4968b95c821"}, {file = "lazy_object_proxy-1.9.0-cp39-cp39-win_amd64.whl", hash = "sha256:db1c1722726f47e10e0b5fdbf15ac3b8adb58c091d12b3ab713965795036985f"}, ] [[package]] name = "markdown-it-py" version = "2.2.0" description = "Python port of markdown-it. Markdown parsing, done right!" optional = false python-versions = ">=3.7" files = [ {file = "markdown-it-py-2.2.0.tar.gz", hash = "sha256:7c9a5e412688bc771c67432cbfebcdd686c93ce6484913dccf06cb5a0bea35a1"}, {file = "markdown_it_py-2.2.0-py3-none-any.whl", hash = "sha256:5a35f8d1870171d9acc47b99612dc146129b631baf04970128b568f190d0cc30"}, ] [package.dependencies] mdurl = ">=0.1,<1.0" typing_extensions = {version = ">=3.7.4", markers = "python_version < \"3.8\""} [package.extras] benchmarking = ["psutil", "pytest", "pytest-benchmark"] code-style = ["pre-commit (>=3.0,<4.0)"] compare = ["commonmark (>=0.9,<1.0)", "markdown (>=3.4,<4.0)", "mistletoe (>=1.0,<2.0)", "mistune (>=2.0,<3.0)", "panflute (>=2.3,<3.0)"] linkify = ["linkify-it-py (>=1,<3)"] plugins = ["mdit-py-plugins"] profiling = ["gprof2dot"] rtd = ["attrs", "myst-parser", "pyyaml", "sphinx", "sphinx-copybutton", "sphinx-design", "sphinx_book_theme"] testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] [[package]] name = "mccabe" version = "0.7.0" description = "McCabe checker, plugin for flake8" optional = false python-versions = ">=3.6" files = [ {file = "mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"}, {file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"}, ] [[package]] name = "mdurl" version = "0.1.2" description = "Markdown URL utilities" optional = false python-versions = ">=3.7" files = [ {file = "mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"}, {file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"}, ] [[package]] name = "more-itertools" version = "9.1.0" description = "More routines for operating on iterables, beyond itertools" optional = false python-versions = ">=3.7" files = [ {file = "more-itertools-9.1.0.tar.gz", hash = "sha256:cabaa341ad0389ea83c17a94566a53ae4c9d07349861ecb14dc6d0345cf9ac5d"}, {file = "more_itertools-9.1.0-py3-none-any.whl", hash = "sha256:d2bc7f02446e86a68911e58ded76d6561eea00cddfb2a91e7019bbb586c799f3"}, ] [[package]] name = "mypy" version = "1.2.0" description = "Optional static typing for Python" optional = true python-versions = ">=3.7" files = [ {file = "mypy-1.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:701189408b460a2ff42b984e6bd45c3f41f0ac9f5f58b8873bbedc511900086d"}, {file = "mypy-1.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fe91be1c51c90e2afe6827601ca14353bbf3953f343c2129fa1e247d55fd95ba"}, {file = "mypy-1.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d26b513225ffd3eacece727f4387bdce6469192ef029ca9dd469940158bc89e"}, {file = "mypy-1.2.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:3a2d219775a120581a0ae8ca392b31f238d452729adbcb6892fa89688cb8306a"}, {file = "mypy-1.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:2e93a8a553e0394b26c4ca683923b85a69f7ccdc0139e6acd1354cc884fe0128"}, {file = "mypy-1.2.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3efde4af6f2d3ccf58ae825495dbb8d74abd6d176ee686ce2ab19bd025273f41"}, {file = "mypy-1.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:695c45cea7e8abb6f088a34a6034b1d273122e5530aeebb9c09626cea6dca4cb"}, {file = "mypy-1.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d0e9464a0af6715852267bf29c9553e4555b61f5904a4fc538547a4d67617937"}, {file = "mypy-1.2.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:8293a216e902ac12779eb7a08f2bc39ec6c878d7c6025aa59464e0c4c16f7eb9"}, {file = "mypy-1.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:f46af8d162f3d470d8ffc997aaf7a269996d205f9d746124a179d3abe05ac602"}, {file = "mypy-1.2.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:031fc69c9a7e12bcc5660b74122ed84b3f1c505e762cc4296884096c6d8ee140"}, {file = "mypy-1.2.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:390bc685ec209ada4e9d35068ac6988c60160b2b703072d2850457b62499e336"}, {file = "mypy-1.2.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:4b41412df69ec06ab141808d12e0bf2823717b1c363bd77b4c0820feaa37249e"}, {file = "mypy-1.2.0-cp37-cp37m-win_amd64.whl", hash = "sha256:4e4a682b3f2489d218751981639cffc4e281d548f9d517addfd5a2917ac78119"}, {file = "mypy-1.2.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:a197ad3a774f8e74f21e428f0de7f60ad26a8d23437b69638aac2764d1e06a6a"}, {file = "mypy-1.2.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:c9a084bce1061e55cdc0493a2ad890375af359c766b8ac311ac8120d3a472950"}, {file = "mypy-1.2.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eaeaa0888b7f3ccb7bcd40b50497ca30923dba14f385bde4af78fac713d6d6f6"}, {file = "mypy-1.2.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:bea55fc25b96c53affab852ad94bf111a3083bc1d8b0c76a61dd101d8a388cf5"}, {file = "mypy-1.2.0-cp38-cp38-win_amd64.whl", hash = "sha256:4c8d8c6b80aa4a1689f2a179d31d86ae1367ea4a12855cc13aa3ba24bb36b2d8"}, {file = "mypy-1.2.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:70894c5345bea98321a2fe84df35f43ee7bb0feec117a71420c60459fc3e1eed"}, {file = "mypy-1.2.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4a99fe1768925e4a139aace8f3fb66db3576ee1c30b9c0f70f744ead7e329c9f"}, {file = "mypy-1.2.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:023fe9e618182ca6317ae89833ba422c411469156b690fde6a315ad10695a521"}, {file = "mypy-1.2.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:4d19f1a239d59f10fdc31263d48b7937c585810288376671eaf75380b074f238"}, {file = "mypy-1.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:2de7babe398cb7a85ac7f1fd5c42f396c215ab3eff731b4d761d68d0f6a80f48"}, {file = "mypy-1.2.0-py3-none-any.whl", hash = "sha256:d8e9187bfcd5ffedbe87403195e1fc340189a68463903c39e2b63307c9fa0394"}, {file = "mypy-1.2.0.tar.gz", hash = "sha256:f70a40410d774ae23fcb4afbbeca652905a04de7948eaf0b1789c8d1426b72d1"}, ] [package.dependencies] mypy-extensions = ">=1.0.0" tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} typed-ast = {version = ">=1.4.0,<2", markers = "python_version < \"3.8\""} typing-extensions = ">=3.10" [package.extras] dmypy = ["psutil (>=4.0)"] install-types = ["pip"] python2 = ["typed-ast (>=1.4.0,<2)"] reports = ["lxml"] [[package]] name = "mypy-extensions" version = "1.0.0" description = "Type system extensions for programs checked with the mypy type checker." optional = true python-versions = ">=3.5" files = [ {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, ] [[package]] name = "nodeenv" version = "1.7.0" description = "Node.js virtual environment builder" optional = false python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*" files = [ {file = "nodeenv-1.7.0-py2.py3-none-any.whl", hash = "sha256:27083a7b96a25f2f5e1d8cb4b6317ee8aeda3bdd121394e5ac54e498028a042e"}, {file = "nodeenv-1.7.0.tar.gz", hash = "sha256:e0e7f7dfb85fc5394c6fe1e8fa98131a2473e04311a45afb6508f7cf1836fa2b"}, ] [package.dependencies] setuptools = "*" [[package]] name = "packaging" version = "23.1" description = "Core utilities for Python packages" optional = false python-versions = ">=3.7" files = [ {file = "packaging-23.1-py3-none-any.whl", hash = "sha256:994793af429502c4ea2ebf6bf664629d07c1a9fe974af92966e4b8d2df7edc61"}, {file = "packaging-23.1.tar.gz", hash = "sha256:a392980d2b6cffa644431898be54b0045151319d1e7ec34f0cfed48767dd334f"}, ] [[package]] name = "pbr" version = "5.11.1" description = "Python Build Reasonableness" optional = true python-versions = ">=2.6" files = [ {file = "pbr-5.11.1-py2.py3-none-any.whl", hash = "sha256:567f09558bae2b3ab53cb3c1e2e33e726ff3338e7bae3db5dc954b3a44eef12b"}, {file = "pbr-5.11.1.tar.gz", hash = "sha256:aefc51675b0b533d56bb5fd1c8c6c0522fe31896679882e1c4c63d5e4a0fccb3"}, ] [[package]] name = "pep8" version = "1.7.1" description = "Python style guide checker" optional = false python-versions = "*" files = [ {file = "pep8-1.7.1-py2.py3-none-any.whl", hash = "sha256:b22cfae5db09833bb9bd7c8463b53e1a9c9b39f12e304a8d0bba729c501827ee"}, {file = "pep8-1.7.1.tar.gz", hash = "sha256:fe249b52e20498e59e0b5c5256aa52ee99fc295b26ec9eaa85776ffdb9fe6374"}, ] [[package]] name = "pep8-naming" version = "0.10.0" description = "Check PEP-8 naming conventions, plugin for flake8" optional = false python-versions = "*" files = [ {file = "pep8-naming-0.10.0.tar.gz", hash = "sha256:f3b4a5f9dd72b991bf7d8e2a341d2e1aa3a884a769b5aaac4f56825c1763bf3a"}, {file = "pep8_naming-0.10.0-py2.py3-none-any.whl", hash = "sha256:5d9f1056cb9427ce344e98d1a7f5665710e2f20f748438e308995852cfa24164"}, ] [package.dependencies] flake8-polyfill = ">=1.0.2,<2" [[package]] name = "pkginfo" version = "1.9.6" description = "Query metadata from sdists / bdists / installed packages." optional = false python-versions = ">=3.6" files = [ {file = "pkginfo-1.9.6-py3-none-any.whl", hash = "sha256:4b7a555a6d5a22169fcc9cf7bfd78d296b0361adad412a346c1226849af5e546"}, {file = "pkginfo-1.9.6.tar.gz", hash = "sha256:8fd5896e8718a4372f0ea9cc9d96f6417c9b986e23a4d116dda26b62cc29d046"}, ] [package.extras] testing = ["pytest", "pytest-cov"] [[package]] name = "platformdirs" version = "3.5.0" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." optional = false python-versions = ">=3.7" files = [ {file = "platformdirs-3.5.0-py3-none-any.whl", hash = "sha256:47692bc24c1958e8b0f13dd727307cff1db103fca36399f457da8e05f222fdc4"}, {file = "platformdirs-3.5.0.tar.gz", hash = "sha256:7954a68d0ba23558d753f73437c55f89027cf8f5108c19844d4b82e5af396335"}, ] [package.dependencies] typing-extensions = {version = ">=4.5", markers = "python_version < \"3.8\""} [package.extras] docs = ["furo (>=2023.3.27)", "proselint (>=0.13)", "sphinx (>=6.1.3)", "sphinx-autodoc-typehints (>=1.23,!=1.23.4)"] test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.3.1)", "pytest-cov (>=4)", "pytest-mock (>=3.10)"] [[package]] name = "pluggy" version = "1.0.0" description = "plugin and hook calling mechanisms for python" optional = false python-versions = ">=3.6" files = [ {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"}, {file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"}, ] [package.dependencies] importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} [package.extras] dev = ["pre-commit", "tox"] testing = ["pytest", "pytest-benchmark"] [[package]] name = "pre-commit" version = "2.21.0" description = "A framework for managing and maintaining multi-language pre-commit hooks." optional = false python-versions = ">=3.7" files = [ {file = "pre_commit-2.21.0-py2.py3-none-any.whl", hash = "sha256:e2f91727039fc39a92f58a588a25b87f936de6567eed4f0e673e0507edc75bad"}, {file = "pre_commit-2.21.0.tar.gz", hash = "sha256:31ef31af7e474a8d8995027fefdfcf509b5c913ff31f2015b4ec4beb26a6f658"}, ] [package.dependencies] cfgv = ">=2.0.0" identify = ">=1.0.0" importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} nodeenv = ">=0.11.1" pyyaml = ">=5.1" virtualenv = ">=20.10.0" [[package]] name = "py" version = "1.11.0" description = "library with cross-python path, ini-parsing, io, code, log facilities" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" files = [ {file = "py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"}, {file = "py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719"}, ] [[package]] name = "py-cpuinfo" version = "9.0.0" description = "Get CPU info with pure Python" optional = false python-versions = "*" files = [ {file = "py-cpuinfo-9.0.0.tar.gz", hash = "sha256:3cdbbf3fac90dc6f118bfd64384f309edeadd902d7c8fb17f02ffa1fc3f49690"}, {file = "py_cpuinfo-9.0.0-py3-none-any.whl", hash = "sha256:859625bc251f64e21f077d099d4162689c762b5d6a4c3c97553d56241c9674d5"}, ] [[package]] name = "pycodestyle" version = "2.10.0" description = "Python style guide checker" optional = false python-versions = ">=3.6" files = [ {file = "pycodestyle-2.10.0-py2.py3-none-any.whl", hash = "sha256:8a4eaf0d0495c7395bdab3589ac2db602797d76207242c17d470186815706610"}, {file = "pycodestyle-2.10.0.tar.gz", hash = "sha256:347187bdb476329d98f695c213d7295a846d1152ff4fe9bacb8a9590b8ee7053"}, ] [[package]] name = "pycparser" version = "2.21" description = "C parser in Python" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ {file = "pycparser-2.21-py2.py3-none-any.whl", hash = "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9"}, {file = "pycparser-2.21.tar.gz", hash = "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"}, ] [[package]] name = "pydocstyle" version = "6.3.0" description = "Python docstring style checker" optional = false python-versions = ">=3.6" files = [ {file = "pydocstyle-6.3.0-py3-none-any.whl", hash = "sha256:118762d452a49d6b05e194ef344a55822987a462831ade91ec5c06fd2169d019"}, {file = "pydocstyle-6.3.0.tar.gz", hash = "sha256:7ce43f0c0ac87b07494eb9c0b462c0b73e6ff276807f204d6b53edc72b7e44e1"}, ] [package.dependencies] importlib-metadata = {version = ">=2.0.0,<5.0.0", markers = "python_version < \"3.8\""} snowballstemmer = ">=2.2.0" [package.extras] toml = ["tomli (>=1.2.3)"] [[package]] name = "pyflakes" version = "2.5.0" description = "passive checker of Python programs" optional = false python-versions = ">=3.6" files = [ {file = "pyflakes-2.5.0-py2.py3-none-any.whl", hash = "sha256:4579f67d887f804e67edb544428f264b7b24f435b263c4614f384135cea553d2"}, {file = "pyflakes-2.5.0.tar.gz", hash = "sha256:491feb020dca48ccc562a8c0cbe8df07ee13078df59813b83959cbdada312ea3"}, ] [[package]] name = "pygments" version = "2.15.1" description = "Pygments is a syntax highlighting package written in Python." optional = false python-versions = ">=3.7" files = [ {file = "Pygments-2.15.1-py3-none-any.whl", hash = "sha256:db2db3deb4b4179f399a09054b023b6a586b76499d36965813c71aa8ed7b5fd1"}, {file = "Pygments-2.15.1.tar.gz", hash = "sha256:8ace4d3c1dd481894b2005f560ead0f9f19ee64fe983366be1a21e171d12775c"}, ] [package.extras] plugins = ["importlib-metadata"] [[package]] name = "pylint" version = "2.17.4" description = "python code static checker" optional = false python-versions = ">=3.7.2" files = [ {file = "pylint-2.17.4-py3-none-any.whl", hash = "sha256:7a1145fb08c251bdb5cca11739722ce64a63db479283d10ce718b2460e54123c"}, {file = "pylint-2.17.4.tar.gz", hash = "sha256:5dcf1d9e19f41f38e4e85d10f511e5b9c35e1aa74251bf95cdd8cb23584e2db1"}, ] [package.dependencies] astroid = ">=2.15.4,<=2.17.0-dev0" colorama = {version = ">=0.4.5", markers = "sys_platform == \"win32\""} dill = [ {version = ">=0.2", markers = "python_version < \"3.11\""}, {version = ">=0.3.6", markers = "python_version >= \"3.11\""}, ] isort = ">=4.2.5,<6" mccabe = ">=0.6,<0.8" platformdirs = ">=2.2.0" tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} tomlkit = ">=0.10.1" typing-extensions = {version = ">=3.10.0", markers = "python_version < \"3.10\""} [package.extras] spelling = ["pyenchant (>=3.2,<4.0)"] testutils = ["gitpython (>3)"] [[package]] name = "pylint-celery" version = "0.3" description = "pylint-celery is a Pylint plugin to aid Pylint in recognising and understandingerrors caused when using the Celery library" optional = false python-versions = "*" files = [ {file = "pylint-celery-0.3.tar.gz", hash = "sha256:41e32094e7408d15c044178ea828dd524beedbdbe6f83f712c5e35bde1de4beb"}, ] [package.dependencies] astroid = ">=1.0" pylint = ">=1.0" pylint-plugin-utils = ">=0.2.1" [[package]] name = "pylint-django" version = "2.5.3" description = "A Pylint plugin to help Pylint understand the Django web framework" optional = false python-versions = "*" files = [ {file = "pylint-django-2.5.3.tar.gz", hash = "sha256:0ac090d106c62fe33782a1d01bda1610b761bb1c9bf5035ced9d5f23a13d8591"}, {file = "pylint_django-2.5.3-py3-none-any.whl", hash = "sha256:56b12b6adf56d548412445bd35483034394a1a94901c3f8571980a13882299d5"}, ] [package.dependencies] pylint = ">=2.0,<3" pylint-plugin-utils = ">=0.7" [package.extras] for-tests = ["coverage", "django-tables2", "django-tastypie", "factory-boy", "pylint (>=2.13)", "pytest", "wheel"] with-django = ["Django"] [[package]] name = "pylint-flask" version = "0.6" description = "pylint-flask is a Pylint plugin to aid Pylint in recognizing and understanding errors caused when using Flask" optional = false python-versions = "*" files = [ {file = "pylint-flask-0.6.tar.gz", hash = "sha256:f4d97de2216bf7bfce07c9c08b166e978fe9f2725de2a50a9845a97de7e31517"}, ] [package.dependencies] pylint-plugin-utils = ">=0.2.1" [[package]] name = "pylint-plugin-utils" version = "0.7" description = "Utilities and helpers for writing Pylint plugins" optional = false python-versions = ">=3.6.2" files = [ {file = "pylint-plugin-utils-0.7.tar.gz", hash = "sha256:ce48bc0516ae9415dd5c752c940dfe601b18fe0f48aa249f2386adfa95a004dd"}, {file = "pylint_plugin_utils-0.7-py3-none-any.whl", hash = "sha256:b3d43e85ab74c4f48bb46ae4ce771e39c3a20f8b3d56982ab17aa73b4f98d535"}, ] [package.dependencies] pylint = ">=1.7" [[package]] name = "pyproject-hooks" version = "1.0.0" description = "Wrappers to call pyproject.toml-based build backend hooks." optional = true python-versions = ">=3.7" files = [ {file = "pyproject_hooks-1.0.0-py3-none-any.whl", hash = "sha256:283c11acd6b928d2f6a7c73fa0d01cb2bdc5f07c57a2eeb6e83d5e56b97976f8"}, {file = "pyproject_hooks-1.0.0.tar.gz", hash = "sha256:f271b298b97f5955d53fb12b72c1fb1948c22c1a6b70b315c54cedaca0264ef5"}, ] [package.dependencies] tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} [[package]] name = "pyright" version = "1.1.306" description = "Command line wrapper for pyright" optional = true python-versions = ">=3.7" files = [ {file = "pyright-1.1.306-py3-none-any.whl", hash = "sha256:008eb2a29584ae274a154d749cf81476a3073fb562a462eac8d43a753378b9db"}, {file = "pyright-1.1.306.tar.gz", hash = "sha256:16d5d198be64de497d5f9002000a271176c381e21b977ca5566cf779b643c9ed"}, ] [package.dependencies] nodeenv = ">=1.6.0" typing-extensions = {version = ">=3.7", markers = "python_version < \"3.8\""} [package.extras] all = ["twine (>=3.4.1)"] dev = ["twine (>=3.4.1)"] [[package]] name = "pyroma" version = "4.2" description = "Test your project's packaging friendliness" optional = true python-versions = ">=3.7" files = [ {file = "pyroma-4.2-py3-none-any.whl", hash = "sha256:a59854b6f8a72b55384cc1de42410e5c5ac59d0c40a92e84fd8364aa6cec3e37"}, {file = "pyroma-4.2.tar.gz", hash = "sha256:6c727dc4a7a10e12274faed5fb47ebd499ca0821995befec98e3cfcaf1e7383c"}, ] [package.dependencies] build = ">=0.7.0" docutils = "*" packaging = "*" pygments = "*" requests = "*" setuptools = ">=42" trove-classifiers = ">=2022.6.26" [package.extras] test = ["setuptools (>=60)", "zest.releaser[recommended]"] [[package]] name = "pytest" version = "7.3.1" description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.7" files = [ {file = "pytest-7.3.1-py3-none-any.whl", hash = "sha256:3799fa815351fea3a5e96ac7e503a96fa51cc9942c3753cda7651b93c1cfa362"}, {file = "pytest-7.3.1.tar.gz", hash = "sha256:434afafd78b1d78ed0addf160ad2b77a30d35d4bdf8af234fe621919d9ed15e3"}, ] [package.dependencies] colorama = {version = "*", markers = "sys_platform == \"win32\""} exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} iniconfig = "*" packaging = "*" pluggy = ">=0.12,<2.0" tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""} [package.extras] testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "xmlschema"] [[package]] name = "pytest-benchmark" version = "4.0.0" description = "A ``pytest`` fixture for benchmarking code. It will group the tests into rounds that are calibrated to the chosen timer." optional = false python-versions = ">=3.7" files = [ {file = "pytest-benchmark-4.0.0.tar.gz", hash = "sha256:fb0785b83efe599a6a956361c0691ae1dbb5318018561af10f3e915caa0048d1"}, {file = "pytest_benchmark-4.0.0-py3-none-any.whl", hash = "sha256:fdb7db64e31c8b277dff9850d2a2556d8b60bcb0ea6524e36e28ffd7c87f71d6"}, ] [package.dependencies] py-cpuinfo = "*" pytest = ">=3.8" [package.extras] aspect = ["aspectlib"] elasticsearch = ["elasticsearch"] histogram = ["pygal", "pygaljs"] [[package]] name = "pytest-cov" version = "4.0.0" description = "Pytest plugin for measuring coverage." optional = false python-versions = ">=3.6" files = [ {file = "pytest-cov-4.0.0.tar.gz", hash = "sha256:996b79efde6433cdbd0088872dbc5fb3ed7fe1578b68cdbba634f14bb8dd0470"}, {file = "pytest_cov-4.0.0-py3-none-any.whl", hash = "sha256:2feb1b751d66a8bd934e5edfa2e961d11309dc37b73b0eabe73b5945fee20f6b"}, ] [package.dependencies] coverage = {version = ">=5.2.1", extras = ["toml"]} pytest = ">=4.6" [package.extras] testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtualenv"] [[package]] name = "pytz-deprecation-shim" version = "0.1.0.post0" description = "Shims to make deprecation of pytz easier" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" files = [ {file = "pytz_deprecation_shim-0.1.0.post0-py2.py3-none-any.whl", hash = "sha256:8314c9692a636c8eb3bda879b9f119e350e93223ae83e70e80c31675a0fdc1a6"}, {file = "pytz_deprecation_shim-0.1.0.post0.tar.gz", hash = "sha256:af097bae1b616dde5c5744441e2ddc69e74dfdcb0c263129610d85b87445a59d"}, ] [package.dependencies] "backports.zoneinfo" = {version = "*", markers = "python_version >= \"3.6\" and python_version < \"3.9\""} tzdata = {version = "*", markers = "python_version >= \"3.6\""} [[package]] name = "pywin32-ctypes" version = "0.2.0" description = "" optional = false python-versions = "*" files = [ {file = "pywin32-ctypes-0.2.0.tar.gz", hash = "sha256:24ffc3b341d457d48e8922352130cf2644024a4ff09762a2261fd34c36ee5942"}, {file = "pywin32_ctypes-0.2.0-py2.py3-none-any.whl", hash = "sha256:9dc2d991b3479cc2df15930958b674a48a227d5361d413827a4cfd0b5876fc98"}, ] [[package]] name = "pyyaml" version = "6.0" description = "YAML parser and emitter for Python" optional = false python-versions = ">=3.6" files = [ {file = "PyYAML-6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53"}, {file = "PyYAML-6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c"}, {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc"}, {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b"}, {file = "PyYAML-6.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5"}, {file = "PyYAML-6.0-cp310-cp310-win32.whl", hash = "sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513"}, {file = "PyYAML-6.0-cp310-cp310-win_amd64.whl", hash = "sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a"}, {file = "PyYAML-6.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d4b0ba9512519522b118090257be113b9468d804b19d63c71dbcf4a48fa32358"}, {file = "PyYAML-6.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:81957921f441d50af23654aa6c5e5eaf9b06aba7f0a19c18a538dc7ef291c5a1"}, {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:afa17f5bc4d1b10afd4466fd3a44dc0e245382deca5b3c353d8b757f9e3ecb8d"}, {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dbad0e9d368bb989f4515da330b88a057617d16b6a8245084f1b05400f24609f"}, {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:432557aa2c09802be39460360ddffd48156e30721f5e8d917f01d31694216782"}, {file = "PyYAML-6.0-cp311-cp311-win32.whl", hash = "sha256:bfaef573a63ba8923503d27530362590ff4f576c626d86a9fed95822a8255fd7"}, {file = "PyYAML-6.0-cp311-cp311-win_amd64.whl", hash = "sha256:01b45c0191e6d66c470b6cf1b9531a771a83c1c4208272ead47a3ae4f2f603bf"}, {file = "PyYAML-6.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86"}, {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f"}, {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92"}, {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4"}, {file = "PyYAML-6.0-cp36-cp36m-win32.whl", hash = "sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293"}, {file = "PyYAML-6.0-cp36-cp36m-win_amd64.whl", hash = "sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57"}, {file = "PyYAML-6.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c"}, {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0"}, {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0ce82d761c532fe4ec3f87fc45688bdd3a4c1dc5e0b4a19814b9009a29baefd4"}, {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:231710d57adfd809ef5d34183b8ed1eeae3f76459c18fb4a0b373ad56bedcdd9"}, {file = "PyYAML-6.0-cp37-cp37m-win32.whl", hash = "sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737"}, {file = "PyYAML-6.0-cp37-cp37m-win_amd64.whl", hash = "sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d"}, {file = "PyYAML-6.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0b4624f379dab24d3725ffde76559cff63d9ec94e1736b556dacdfebe5ab6d4b"}, {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:213c60cd50106436cc818accf5baa1aba61c0189ff610f64f4a3e8c6726218ba"}, {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34"}, {file = "PyYAML-6.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287"}, {file = "PyYAML-6.0-cp38-cp38-win32.whl", hash = "sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78"}, {file = "PyYAML-6.0-cp38-cp38-win_amd64.whl", hash = "sha256:1e4747bc279b4f613a09eb64bba2ba602d8a6664c6ce6396a4d0cd413a50ce07"}, {file = "PyYAML-6.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b"}, {file = "PyYAML-6.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174"}, {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803"}, {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3"}, {file = "PyYAML-6.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0"}, {file = "PyYAML-6.0-cp39-cp39-win32.whl", hash = "sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb"}, {file = "PyYAML-6.0-cp39-cp39-win_amd64.whl", hash = "sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c"}, {file = "PyYAML-6.0.tar.gz", hash = "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2"}, ] [[package]] name = "readme-renderer" version = "37.3" description = "readme_renderer is a library for rendering \"readme\" descriptions for Warehouse" optional = false python-versions = ">=3.7" files = [ {file = "readme_renderer-37.3-py3-none-any.whl", hash = "sha256:f67a16caedfa71eef48a31b39708637a6f4664c4394801a7b0d6432d13907343"}, {file = "readme_renderer-37.3.tar.gz", hash = "sha256:cd653186dfc73055656f090f227f5cb22a046d7f71a841dfa305f55c9a513273"}, ] [package.dependencies] bleach = ">=2.1.0" docutils = ">=0.13.1" Pygments = ">=2.5.1" [package.extras] md = ["cmarkgfm (>=0.8.0)"] [[package]] name = "requests" version = "2.31.0" description = "Python HTTP for Humans." optional = false python-versions = ">=3.7" files = [ {file = "requests-2.31.0-py3-none-any.whl", hash = "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f"}, {file = "requests-2.31.0.tar.gz", hash = "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1"}, ] [package.dependencies] certifi = ">=2017.4.17" charset-normalizer = ">=2,<4" idna = ">=2.5,<4" urllib3 = ">=1.21.1,<3" [package.extras] socks = ["PySocks (>=1.5.6,!=1.5.7)"] use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] [[package]] name = "requests-toolbelt" version = "1.0.0" description = "A utility belt for advanced users of python-requests" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ {file = "requests-toolbelt-1.0.0.tar.gz", hash = "sha256:7681a0a3d047012b5bdc0ee37d7f8f07ebe76ab08caeccfc3921ce23c88d5bc6"}, {file = "requests_toolbelt-1.0.0-py2.py3-none-any.whl", hash = "sha256:cccfdd665f0a24fcf4726e690f65639d272bb0637b9b92dfd91a5568ccf6bd06"}, ] [package.dependencies] requests = ">=2.0.1,<3.0.0" [[package]] name = "requirements-detector" version = "1.2.1" description = "Python tool to find and list requirements of a Python project" optional = false python-versions = ">=3.7,<4.0" files = [ {file = "requirements_detector-1.2.1-py3-none-any.whl", hash = "sha256:ae4aa170106e8940482fd450e0fafbae1e3627b0ea15b852a191c509e6f8c4db"}, {file = "requirements_detector-1.2.1.tar.gz", hash = "sha256:3c3f2eaf40f86337b07826be65ee2a413a6337da2484a887523c816a921f04b6"}, ] [package.dependencies] astroid = ">=2.0,<3.0" packaging = ">=21.3" semver = ">=3.0.0,<4.0.0" toml = ">=0.10.2,<0.11.0" [[package]] name = "rfc3986" version = "2.0.0" description = "Validating URI References per RFC 3986" optional = false python-versions = ">=3.7" files = [ {file = "rfc3986-2.0.0-py2.py3-none-any.whl", hash = "sha256:50b1502b60e289cb37883f3dfd34532b8873c7de9f49bb546641ce9cbd256ebd"}, {file = "rfc3986-2.0.0.tar.gz", hash = "sha256:97aacf9dbd4bfd829baad6e6309fa6573aaf1be3f6fa735c8ab05e46cecb261c"}, ] [package.extras] idna2008 = ["idna"] [[package]] name = "rich" version = "13.3.5" description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" optional = false python-versions = ">=3.7.0" files = [ {file = "rich-13.3.5-py3-none-any.whl", hash = "sha256:69cdf53799e63f38b95b9bf9c875f8c90e78dd62b2f00c13a911c7a3b9fa4704"}, {file = "rich-13.3.5.tar.gz", hash = "sha256:2d11b9b8dd03868f09b4fffadc84a6a8cda574e40dc90821bd845720ebb8e89c"}, ] [package.dependencies] markdown-it-py = ">=2.2.0,<3.0.0" pygments = ">=2.13.0,<3.0.0" typing-extensions = {version = ">=4.0.0,<5.0", markers = "python_version < \"3.9\""} [package.extras] jupyter = ["ipywidgets (>=7.5.1,<9)"] [[package]] name = "secretstorage" version = "3.3.3" description = "Python bindings to FreeDesktop.org Secret Service API" optional = false python-versions = ">=3.6" files = [ {file = "SecretStorage-3.3.3-py3-none-any.whl", hash = "sha256:f356e6628222568e3af06f2eba8df495efa13b3b63081dafd4f7d9a7b7bc9f99"}, {file = "SecretStorage-3.3.3.tar.gz", hash = "sha256:2403533ef369eca6d2ba81718576c5e0f564d5cca1b58f73a8b23e7d4eeebd77"}, ] [package.dependencies] cryptography = ">=2.0" jeepney = ">=0.6" [[package]] name = "semver" version = "3.0.0" description = "Python helper for Semantic Versioning (https://semver.org)" optional = false python-versions = ">=3.7" files = [ {file = "semver-3.0.0-py3-none-any.whl", hash = "sha256:ab4f69fb1d1ecfb5d81f96411403d7a611fa788c45d252cf5b408025df3ab6ce"}, {file = "semver-3.0.0.tar.gz", hash = "sha256:94df43924c4521ec7d307fc86da1531db6c2c33d9d5cdc3e64cca0eb68569269"}, ] [[package]] name = "setoptconf-tmp" version = "0.3.1" description = "A module for retrieving program settings from various sources in a consistant method." optional = false python-versions = "*" files = [ {file = "setoptconf-tmp-0.3.1.tar.gz", hash = "sha256:e0480addd11347ba52f762f3c4d8afa3e10ad0affbc53e3ffddc0ca5f27d5778"}, {file = "setoptconf_tmp-0.3.1-py3-none-any.whl", hash = "sha256:76035d5cd1593d38b9056ae12d460eca3aaa34ad05c315b69145e138ba80a745"}, ] [package.extras] yaml = ["pyyaml"] [[package]] name = "setuptools" version = "67.7.2" description = "Easily download, build, install, upgrade, and uninstall Python packages" optional = false python-versions = ">=3.7" files = [ {file = "setuptools-67.7.2-py3-none-any.whl", hash = "sha256:23aaf86b85ca52ceb801d32703f12d77517b2556af839621c641fca11287952b"}, {file = "setuptools-67.7.2.tar.gz", hash = "sha256:f104fa03692a2602fa0fec6c6a9e63b6c8a968de13e17c026957dd1f53d80990"}, ] [package.extras] docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (==0.8.3)", "sphinx-reredirects", "sphinxcontrib-towncrier"] testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8 (<5)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pip-run (>=8.8)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] [[package]] name = "six" version = "1.16.0" description = "Python 2 and 3 compatibility utilities" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" files = [ {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, ] [[package]] name = "smmap" version = "5.0.0" description = "A pure Python implementation of a sliding window memory map manager" optional = false python-versions = ">=3.6" files = [ {file = "smmap-5.0.0-py3-none-any.whl", hash = "sha256:2aba19d6a040e78d8b09de5c57e96207b09ed71d8e55ce0959eeee6c8e190d94"}, {file = "smmap-5.0.0.tar.gz", hash = "sha256:c840e62059cd3be204b0c9c9f74be2c09d5648eddd4580d9314c3ecde0b30936"}, ] [[package]] name = "snowballstemmer" version = "2.2.0" description = "This package provides 29 stemmers for 28 languages generated from Snowball algorithms." optional = false python-versions = "*" files = [ {file = "snowballstemmer-2.2.0-py2.py3-none-any.whl", hash = "sha256:c8e1716e83cc398ae16824e5572ae04e0d9fc2c6b985fb0f900f5f0c96ecba1a"}, {file = "snowballstemmer-2.2.0.tar.gz", hash = "sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1"}, ] [[package]] name = "stevedore" version = "3.5.2" description = "Manage dynamic plugins for Python applications" optional = true python-versions = ">=3.6" files = [ {file = "stevedore-3.5.2-py3-none-any.whl", hash = "sha256:fa2630e3d0ad3e22d4914aff2501445815b9a4467a6edc49387c667a38faf5bf"}, {file = "stevedore-3.5.2.tar.gz", hash = "sha256:cf99f41fc0d5a4f185ca4d3d42b03be9011b0a1ec1a4ea1a282be1b4b306dcc2"}, ] [package.dependencies] importlib-metadata = {version = ">=1.7.0", markers = "python_version < \"3.8\""} pbr = ">=2.0.0,<2.1.0 || >2.1.0" [[package]] name = "toml" version = "0.10.2" description = "Python Library for Tom's Obvious, Minimal Language" optional = false python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" files = [ {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, ] [[package]] name = "tomli" version = "2.0.1" description = "A lil' TOML parser" optional = false python-versions = ">=3.7" files = [ {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, ] [[package]] name = "tomlkit" version = "0.11.8" description = "Style preserving TOML library" optional = false python-versions = ">=3.7" files = [ {file = "tomlkit-0.11.8-py3-none-any.whl", hash = "sha256:8c726c4c202bdb148667835f68d68780b9a003a9ec34167b6c673b38eff2a171"}, {file = "tomlkit-0.11.8.tar.gz", hash = "sha256:9330fc7faa1db67b541b28e62018c17d20be733177d290a13b24c62d1614e0c3"}, ] [[package]] name = "tox" version = "3.28.0" description = "tox is a generic virtualenv management and test command line tool" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" files = [ {file = "tox-3.28.0-py2.py3-none-any.whl", hash = "sha256:57b5ab7e8bb3074edc3c0c0b4b192a4f3799d3723b2c5b76f1fa9f2d40316eea"}, {file = "tox-3.28.0.tar.gz", hash = "sha256:d0d28f3fe6d6d7195c27f8b054c3e99d5451952b54abdae673b71609a581f640"}, ] [package.dependencies] colorama = {version = ">=0.4.1", markers = "platform_system == \"Windows\""} filelock = ">=3.0.0" importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} packaging = ">=14" pluggy = ">=0.12.0" py = ">=1.4.17" six = ">=1.14.0" tomli = {version = ">=2.0.1", markers = "python_version >= \"3.7\" and python_version < \"3.11\""} virtualenv = ">=16.0.0,<20.0.0 || >20.0.0,<20.0.1 || >20.0.1,<20.0.2 || >20.0.2,<20.0.3 || >20.0.3,<20.0.4 || >20.0.4,<20.0.5 || >20.0.5,<20.0.6 || >20.0.6,<20.0.7 || >20.0.7" [package.extras] docs = ["pygments-github-lexers (>=0.0.5)", "sphinx (>=2.0.0)", "sphinxcontrib-autoprogram (>=0.1.5)", "towncrier (>=18.5.0)"] testing = ["flaky (>=3.4.0)", "freezegun (>=0.3.11)", "pathlib2 (>=2.3.3)", "psutil (>=5.6.1)", "pytest (>=4.0.0)", "pytest-cov (>=2.5.1)", "pytest-mock (>=1.10.0)", "pytest-randomly (>=1.0.0)"] [[package]] name = "trove-classifiers" version = "2023.5.2" description = "Canonical source for classifiers on PyPI (pypi.org)." optional = true python-versions = "*" files = [ {file = "trove-classifiers-2023.5.2.tar.gz", hash = "sha256:c46d6e40a9581599b16c712e0164fec3764872a4085c673c07559787caedb867"}, {file = "trove_classifiers-2023.5.2-py3-none-any.whl", hash = "sha256:0f3eceb7d16186211bcd7edafc7b7934399f738ed985998e4e557e52fe136a71"}, ] [[package]] name = "twine" version = "4.0.2" description = "Collection of utilities for publishing packages on PyPI" optional = false python-versions = ">=3.7" files = [ {file = "twine-4.0.2-py3-none-any.whl", hash = "sha256:929bc3c280033347a00f847236564d1c52a3e61b1ac2516c97c48f3ceab756d8"}, {file = "twine-4.0.2.tar.gz", hash = "sha256:9e102ef5fdd5a20661eb88fad46338806c3bd32cf1db729603fe3697b1bc83c8"}, ] [package.dependencies] importlib-metadata = ">=3.6" keyring = ">=15.1" pkginfo = ">=1.8.1" readme-renderer = ">=35.0" requests = ">=2.20" requests-toolbelt = ">=0.8.0,<0.9.0 || >0.9.0" rfc3986 = ">=1.4.0" rich = ">=12.0.0" urllib3 = ">=1.26.0" [[package]] name = "typed-ast" version = "1.5.4" description = "a fork of Python 2 and 3 ast modules with type comment support" optional = false python-versions = ">=3.6" files = [ {file = "typed_ast-1.5.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:669dd0c4167f6f2cd9f57041e03c3c2ebf9063d0757dc89f79ba1daa2bfca9d4"}, {file = "typed_ast-1.5.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:211260621ab1cd7324e0798d6be953d00b74e0428382991adfddb352252f1d62"}, {file = "typed_ast-1.5.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:267e3f78697a6c00c689c03db4876dd1efdfea2f251a5ad6555e82a26847b4ac"}, {file = "typed_ast-1.5.4-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:c542eeda69212fa10a7ada75e668876fdec5f856cd3d06829e6aa64ad17c8dfe"}, {file = "typed_ast-1.5.4-cp310-cp310-win_amd64.whl", hash = "sha256:a9916d2bb8865f973824fb47436fa45e1ebf2efd920f2b9f99342cb7fab93f72"}, {file = "typed_ast-1.5.4-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:79b1e0869db7c830ba6a981d58711c88b6677506e648496b1f64ac7d15633aec"}, {file = "typed_ast-1.5.4-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a94d55d142c9265f4ea46fab70977a1944ecae359ae867397757d836ea5a3f47"}, {file = "typed_ast-1.5.4-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:183afdf0ec5b1b211724dfef3d2cad2d767cbefac291f24d69b00546c1837fb6"}, {file = "typed_ast-1.5.4-cp36-cp36m-win_amd64.whl", hash = "sha256:639c5f0b21776605dd6c9dbe592d5228f021404dafd377e2b7ac046b0349b1a1"}, {file = "typed_ast-1.5.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:cf4afcfac006ece570e32d6fa90ab74a17245b83dfd6655a6f68568098345ff6"}, {file = "typed_ast-1.5.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ed855bbe3eb3715fca349c80174cfcfd699c2f9de574d40527b8429acae23a66"}, {file = "typed_ast-1.5.4-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:6778e1b2f81dfc7bc58e4b259363b83d2e509a65198e85d5700dfae4c6c8ff1c"}, {file = "typed_ast-1.5.4-cp37-cp37m-win_amd64.whl", hash = "sha256:0261195c2062caf107831e92a76764c81227dae162c4f75192c0d489faf751a2"}, {file = "typed_ast-1.5.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2efae9db7a8c05ad5547d522e7dbe62c83d838d3906a3716d1478b6c1d61388d"}, {file = "typed_ast-1.5.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7d5d014b7daa8b0bf2eaef684295acae12b036d79f54178b92a2b6a56f92278f"}, {file = "typed_ast-1.5.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:370788a63915e82fd6f212865a596a0fefcbb7d408bbbb13dea723d971ed8bdc"}, {file = "typed_ast-1.5.4-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:4e964b4ff86550a7a7d56345c7864b18f403f5bd7380edf44a3c1fb4ee7ac6c6"}, {file = "typed_ast-1.5.4-cp38-cp38-win_amd64.whl", hash = "sha256:683407d92dc953c8a7347119596f0b0e6c55eb98ebebd9b23437501b28dcbb8e"}, {file = "typed_ast-1.5.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4879da6c9b73443f97e731b617184a596ac1235fe91f98d279a7af36c796da35"}, {file = "typed_ast-1.5.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3e123d878ba170397916557d31c8f589951e353cc95fb7f24f6bb69adc1a8a97"}, {file = "typed_ast-1.5.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ebd9d7f80ccf7a82ac5f88c521115cc55d84e35bf8b446fcd7836eb6b98929a3"}, {file = "typed_ast-1.5.4-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:98f80dee3c03455e92796b58b98ff6ca0b2a6f652120c263efdba4d6c5e58f72"}, {file = "typed_ast-1.5.4-cp39-cp39-win_amd64.whl", hash = "sha256:0fdbcf2fef0ca421a3f5912555804296f0b0960f0418c440f5d6d3abb549f3e1"}, {file = "typed_ast-1.5.4.tar.gz", hash = "sha256:39e21ceb7388e4bb37f4c679d72707ed46c2fbf2a5609b8b8ebc4b067d977df2"}, ] [[package]] name = "types-pyyaml" version = "6.0.12.9" description = "Typing stubs for PyYAML" optional = false python-versions = "*" files = [ {file = "types-PyYAML-6.0.12.9.tar.gz", hash = "sha256:c51b1bd6d99ddf0aa2884a7a328810ebf70a4262c292195d3f4f9a0005f9eeb6"}, {file = "types_PyYAML-6.0.12.9-py3-none-any.whl", hash = "sha256:5aed5aa66bd2d2e158f75dda22b059570ede988559f030cf294871d3b647e3e8"}, ] [[package]] name = "types-setuptools" version = "57.4.18" description = "Typing stubs for setuptools" optional = false python-versions = "*" files = [ {file = "types-setuptools-57.4.18.tar.gz", hash = "sha256:8ee03d823fe7fda0bd35faeae33d35cb5c25b497263e6a58b34c4cfd05f40bcf"}, {file = "types_setuptools-57.4.18-py3-none-any.whl", hash = "sha256:9660b8774b12cd61b448e2fd87a667c02e7ec13ce9f15171f1d49a4654c4df6a"}, ] [[package]] name = "typing-extensions" version = "4.5.0" description = "Backported and Experimental Type Hints for Python 3.7+" optional = false python-versions = ">=3.7" files = [ {file = "typing_extensions-4.5.0-py3-none-any.whl", hash = "sha256:fb33085c39dd998ac16d1431ebc293a8b3eedd00fd4a32de0ff79002c19511b4"}, {file = "typing_extensions-4.5.0.tar.gz", hash = "sha256:5cb5f4a79139d699607b3ef622a1dedafa84e115ab0024e0d9c044a9479ca7cb"}, ] [[package]] name = "tzdata" version = "2023.3" description = "Provider of IANA time zone data" optional = false python-versions = ">=2" files = [ {file = "tzdata-2023.3-py2.py3-none-any.whl", hash = "sha256:7e65763eef3120314099b6939b5546db7adce1e7d6f2e179e3df563c70511eda"}, {file = "tzdata-2023.3.tar.gz", hash = "sha256:11ef1e08e54acb0d4f95bdb1be05da659673de4acbd21bf9c69e94cc5e907a3a"}, ] [[package]] name = "tzlocal" version = "4.3" description = "tzinfo object for the local timezone" optional = false python-versions = ">=3.7" files = [ {file = "tzlocal-4.3-py3-none-any.whl", hash = "sha256:b44c4388f3d34f25862cfbb387578a4d70fec417649da694a132f628a23367e2"}, {file = "tzlocal-4.3.tar.gz", hash = "sha256:3f21d09e1b2aa9f2dacca12da240ca37de3ba5237a93addfd6d593afe9073355"}, ] [package.dependencies] "backports.zoneinfo" = {version = "*", markers = "python_version < \"3.9\""} pytz-deprecation-shim = "*" tzdata = {version = "*", markers = "platform_system == \"Windows\""} [package.extras] devenv = ["black", "check-manifest", "flake8", "pyroma", "pytest (>=4.3)", "pytest-cov", "pytest-mock (>=3.3)", "zest.releaser"] [[package]] name = "urllib3" version = "2.0.7" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false python-versions = ">=3.7" files = [ {file = "urllib3-2.0.7-py3-none-any.whl", hash = "sha256:fdb6d215c776278489906c2f8916e6e7d4f5a9b602ccbcfdf7f016fc8da0596e"}, {file = "urllib3-2.0.7.tar.gz", hash = "sha256:c97dfde1f7bd43a71c8d2a58e369e9b2bf692d1334ea9f9cae55add7d0dd0f84"}, ] [package.extras] brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] secure = ["certifi", "cryptography (>=1.9)", "idna (>=2.0.0)", "pyopenssl (>=17.1.0)", "urllib3-secure-extra"] socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] zstd = ["zstandard (>=0.18.0)"] [[package]] name = "virtualenv" version = "20.21.1" description = "Virtual Python Environment builder" optional = false python-versions = ">=3.7" files = [ {file = "virtualenv-20.21.1-py3-none-any.whl", hash = "sha256:09ddbe1af0c8ed2bb4d6ed226b9e6415718ad18aef9fa0ba023d96b7a8356049"}, {file = "virtualenv-20.21.1.tar.gz", hash = "sha256:4c104ccde994f8b108163cf9ba58f3d11511d9403de87fb9b4f52bf33dbc8668"}, ] [package.dependencies] distlib = ">=0.3.6,<1" filelock = ">=3.4.1,<4" importlib-metadata = {version = ">=4.8.3", markers = "python_version < \"3.8\""} platformdirs = ">=2.4,<4" [package.extras] docs = ["furo (>=2023.3.27)", "proselint (>=0.13)", "sphinx (>=6.1.3)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=22.12)"] test = ["covdefaults (>=2.3)", "coverage (>=7.2.3)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.3.1)", "pytest-env (>=0.8.1)", "pytest-freezegun (>=0.4.2)", "pytest-mock (>=3.10)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)"] [[package]] name = "vulture" version = "2.7" description = "Find dead code" optional = true python-versions = ">=3.6" files = [ {file = "vulture-2.7-py2.py3-none-any.whl", hash = "sha256:bccc51064ed76db15a6b58277cea8885936af047f53d2655fb5de575e93d0bca"}, {file = "vulture-2.7.tar.gz", hash = "sha256:67fb80a014ed9fdb599dd44bb96cb54311032a104106fc2e706ef7a6dad88032"}, ] [package.dependencies] toml = "*" [[package]] name = "webencodings" version = "0.5.1" description = "Character encoding aliases for legacy web content" optional = false python-versions = "*" files = [ {file = "webencodings-0.5.1-py2.py3-none-any.whl", hash = "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78"}, {file = "webencodings-0.5.1.tar.gz", hash = "sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923"}, ] [[package]] name = "wrapt" version = "1.15.0" description = "Module for decorators, wrappers and monkey patching." optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" files = [ {file = "wrapt-1.15.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:ca1cccf838cd28d5a0883b342474c630ac48cac5df0ee6eacc9c7290f76b11c1"}, {file = "wrapt-1.15.0-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:e826aadda3cae59295b95343db8f3d965fb31059da7de01ee8d1c40a60398b29"}, {file = "wrapt-1.15.0-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:5fc8e02f5984a55d2c653f5fea93531e9836abbd84342c1d1e17abc4a15084c2"}, {file = "wrapt-1.15.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:96e25c8603a155559231c19c0349245eeb4ac0096fe3c1d0be5c47e075bd4f46"}, {file = "wrapt-1.15.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:40737a081d7497efea35ab9304b829b857f21558acfc7b3272f908d33b0d9d4c"}, {file = "wrapt-1.15.0-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:f87ec75864c37c4c6cb908d282e1969e79763e0d9becdfe9fe5473b7bb1e5f09"}, {file = "wrapt-1.15.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:1286eb30261894e4c70d124d44b7fd07825340869945c79d05bda53a40caa079"}, {file = "wrapt-1.15.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:493d389a2b63c88ad56cdc35d0fa5752daac56ca755805b1b0c530f785767d5e"}, {file = "wrapt-1.15.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:58d7a75d731e8c63614222bcb21dd992b4ab01a399f1f09dd82af17bbfc2368a"}, {file = "wrapt-1.15.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:21f6d9a0d5b3a207cdf7acf8e58d7d13d463e639f0c7e01d82cdb671e6cb7923"}, {file = "wrapt-1.15.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ce42618f67741d4697684e501ef02f29e758a123aa2d669e2d964ff734ee00ee"}, {file = "wrapt-1.15.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:41d07d029dd4157ae27beab04d22b8e261eddfc6ecd64ff7000b10dc8b3a5727"}, {file = "wrapt-1.15.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:54accd4b8bc202966bafafd16e69da9d5640ff92389d33d28555c5fd4f25ccb7"}, {file = "wrapt-1.15.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2fbfbca668dd15b744418265a9607baa970c347eefd0db6a518aaf0cfbd153c0"}, {file = "wrapt-1.15.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:76e9c727a874b4856d11a32fb0b389afc61ce8aaf281ada613713ddeadd1cfec"}, {file = "wrapt-1.15.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e20076a211cd6f9b44a6be58f7eeafa7ab5720eb796975d0c03f05b47d89eb90"}, {file = "wrapt-1.15.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a74d56552ddbde46c246b5b89199cb3fd182f9c346c784e1a93e4dc3f5ec9975"}, {file = "wrapt-1.15.0-cp310-cp310-win32.whl", hash = "sha256:26458da5653aa5b3d8dc8b24192f574a58984c749401f98fff994d41d3f08da1"}, {file = "wrapt-1.15.0-cp310-cp310-win_amd64.whl", hash = "sha256:75760a47c06b5974aa5e01949bf7e66d2af4d08cb8c1d6516af5e39595397f5e"}, {file = "wrapt-1.15.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ba1711cda2d30634a7e452fc79eabcadaffedf241ff206db2ee93dd2c89a60e7"}, {file = "wrapt-1.15.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:56374914b132c702aa9aa9959c550004b8847148f95e1b824772d453ac204a72"}, {file = "wrapt-1.15.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a89ce3fd220ff144bd9d54da333ec0de0399b52c9ac3d2ce34b569cf1a5748fb"}, {file = "wrapt-1.15.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3bbe623731d03b186b3d6b0d6f51865bf598587c38d6f7b0be2e27414f7f214e"}, {file = "wrapt-1.15.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3abbe948c3cbde2689370a262a8d04e32ec2dd4f27103669a45c6929bcdbfe7c"}, {file = "wrapt-1.15.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:b67b819628e3b748fd3c2192c15fb951f549d0f47c0449af0764d7647302fda3"}, {file = "wrapt-1.15.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:7eebcdbe3677e58dd4c0e03b4f2cfa346ed4049687d839adad68cc38bb559c92"}, {file = "wrapt-1.15.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:74934ebd71950e3db69960a7da29204f89624dde411afbfb3b4858c1409b1e98"}, {file = "wrapt-1.15.0-cp311-cp311-win32.whl", hash = "sha256:bd84395aab8e4d36263cd1b9308cd504f6cf713b7d6d3ce25ea55670baec5416"}, {file = "wrapt-1.15.0-cp311-cp311-win_amd64.whl", hash = "sha256:a487f72a25904e2b4bbc0817ce7a8de94363bd7e79890510174da9d901c38705"}, {file = "wrapt-1.15.0-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:4ff0d20f2e670800d3ed2b220d40984162089a6e2c9646fdb09b85e6f9a8fc29"}, {file = "wrapt-1.15.0-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:9ed6aa0726b9b60911f4aed8ec5b8dd7bf3491476015819f56473ffaef8959bd"}, {file = "wrapt-1.15.0-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:896689fddba4f23ef7c718279e42f8834041a21342d95e56922e1c10c0cc7afb"}, {file = "wrapt-1.15.0-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:75669d77bb2c071333417617a235324a1618dba66f82a750362eccbe5b61d248"}, {file = "wrapt-1.15.0-cp35-cp35m-win32.whl", hash = "sha256:fbec11614dba0424ca72f4e8ba3c420dba07b4a7c206c8c8e4e73f2e98f4c559"}, {file = "wrapt-1.15.0-cp35-cp35m-win_amd64.whl", hash = "sha256:fd69666217b62fa5d7c6aa88e507493a34dec4fa20c5bd925e4bc12fce586639"}, {file = "wrapt-1.15.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:b0724f05c396b0a4c36a3226c31648385deb6a65d8992644c12a4963c70326ba"}, {file = "wrapt-1.15.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bbeccb1aa40ab88cd29e6c7d8585582c99548f55f9b2581dfc5ba68c59a85752"}, {file = "wrapt-1.15.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:38adf7198f8f154502883242f9fe7333ab05a5b02de7d83aa2d88ea621f13364"}, {file = "wrapt-1.15.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:578383d740457fa790fdf85e6d346fda1416a40549fe8db08e5e9bd281c6a475"}, {file = "wrapt-1.15.0-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:a4cbb9ff5795cd66f0066bdf5947f170f5d63a9274f99bdbca02fd973adcf2a8"}, {file = "wrapt-1.15.0-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:af5bd9ccb188f6a5fdda9f1f09d9f4c86cc8a539bd48a0bfdc97723970348418"}, {file = "wrapt-1.15.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:b56d5519e470d3f2fe4aa7585f0632b060d532d0696c5bdfb5e8319e1d0f69a2"}, {file = "wrapt-1.15.0-cp36-cp36m-win32.whl", hash = "sha256:77d4c1b881076c3ba173484dfa53d3582c1c8ff1f914c6461ab70c8428b796c1"}, {file = "wrapt-1.15.0-cp36-cp36m-win_amd64.whl", hash = "sha256:077ff0d1f9d9e4ce6476c1a924a3332452c1406e59d90a2cf24aeb29eeac9420"}, {file = "wrapt-1.15.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:5c5aa28df055697d7c37d2099a7bc09f559d5053c3349b1ad0c39000e611d317"}, {file = "wrapt-1.15.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3a8564f283394634a7a7054b7983e47dbf39c07712d7b177b37e03f2467a024e"}, {file = "wrapt-1.15.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:780c82a41dc493b62fc5884fb1d3a3b81106642c5c5c78d6a0d4cbe96d62ba7e"}, {file = "wrapt-1.15.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e169e957c33576f47e21864cf3fc9ff47c223a4ebca8960079b8bd36cb014fd0"}, {file = "wrapt-1.15.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:b02f21c1e2074943312d03d243ac4388319f2456576b2c6023041c4d57cd7019"}, {file = "wrapt-1.15.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:f2e69b3ed24544b0d3dbe2c5c0ba5153ce50dcebb576fdc4696d52aa22db6034"}, {file = "wrapt-1.15.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:d787272ed958a05b2c86311d3a4135d3c2aeea4fc655705f074130aa57d71653"}, {file = "wrapt-1.15.0-cp37-cp37m-win32.whl", hash = "sha256:02fce1852f755f44f95af51f69d22e45080102e9d00258053b79367d07af39c0"}, {file = "wrapt-1.15.0-cp37-cp37m-win_amd64.whl", hash = "sha256:abd52a09d03adf9c763d706df707c343293d5d106aea53483e0ec8d9e310ad5e"}, {file = "wrapt-1.15.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:cdb4f085756c96a3af04e6eca7f08b1345e94b53af8921b25c72f096e704e145"}, {file = "wrapt-1.15.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:230ae493696a371f1dbffaad3dafbb742a4d27a0afd2b1aecebe52b740167e7f"}, {file = "wrapt-1.15.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63424c681923b9f3bfbc5e3205aafe790904053d42ddcc08542181a30a7a51bd"}, {file = "wrapt-1.15.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d6bcbfc99f55655c3d93feb7ef3800bd5bbe963a755687cbf1f490a71fb7794b"}, {file = "wrapt-1.15.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c99f4309f5145b93eca6e35ac1a988f0dc0a7ccf9ccdcd78d3c0adf57224e62f"}, {file = "wrapt-1.15.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b130fe77361d6771ecf5a219d8e0817d61b236b7d8b37cc045172e574ed219e6"}, {file = "wrapt-1.15.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:96177eb5645b1c6985f5c11d03fc2dbda9ad24ec0f3a46dcce91445747e15094"}, {file = "wrapt-1.15.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:d5fe3e099cf07d0fb5a1e23d399e5d4d1ca3e6dfcbe5c8570ccff3e9208274f7"}, {file = "wrapt-1.15.0-cp38-cp38-win32.whl", hash = "sha256:abd8f36c99512755b8456047b7be10372fca271bf1467a1caa88db991e7c421b"}, {file = "wrapt-1.15.0-cp38-cp38-win_amd64.whl", hash = "sha256:b06fa97478a5f478fb05e1980980a7cdf2712015493b44d0c87606c1513ed5b1"}, {file = "wrapt-1.15.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2e51de54d4fb8fb50d6ee8327f9828306a959ae394d3e01a1ba8b2f937747d86"}, {file = "wrapt-1.15.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0970ddb69bba00670e58955f8019bec4a42d1785db3faa043c33d81de2bf843c"}, {file = "wrapt-1.15.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76407ab327158c510f44ded207e2f76b657303e17cb7a572ffe2f5a8a48aa04d"}, {file = "wrapt-1.15.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cd525e0e52a5ff16653a3fc9e3dd827981917d34996600bbc34c05d048ca35cc"}, {file = "wrapt-1.15.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9d37ac69edc5614b90516807de32d08cb8e7b12260a285ee330955604ed9dd29"}, {file = "wrapt-1.15.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:078e2a1a86544e644a68422f881c48b84fef6d18f8c7a957ffd3f2e0a74a0d4a"}, {file = "wrapt-1.15.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:2cf56d0e237280baed46f0b5316661da892565ff58309d4d2ed7dba763d984b8"}, {file = "wrapt-1.15.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:7dc0713bf81287a00516ef43137273b23ee414fe41a3c14be10dd95ed98a2df9"}, {file = "wrapt-1.15.0-cp39-cp39-win32.whl", hash = "sha256:46ed616d5fb42f98630ed70c3529541408166c22cdfd4540b88d5f21006b0eff"}, {file = "wrapt-1.15.0-cp39-cp39-win_amd64.whl", hash = "sha256:eef4d64c650f33347c1f9266fa5ae001440b232ad9b98f1f43dfe7a79435c0a6"}, {file = "wrapt-1.15.0-py3-none-any.whl", hash = "sha256:64b1df0f83706b4ef4cfb4fb0e4c2669100fd7ecacfb59e091fad300d4e04640"}, {file = "wrapt-1.15.0.tar.gz", hash = "sha256:d06730c6aed78cee4126234cf2d071e01b44b915e725a6cb439a879ec9754a3a"}, ] [[package]] name = "zipp" version = "3.15.0" description = "Backport of pathlib-compatible object wrapper for zip files" optional = false python-versions = ">=3.7" files = [ {file = "zipp-3.15.0-py3-none-any.whl", hash = "sha256:48904fc76a60e542af151aded95726c1a5c34ed43ab4134b597665c86d7ad556"}, {file = "zipp-3.15.0.tar.gz", hash = "sha256:112929ad649da941c23de50f356a2b5570c954b65150642bccdd66bf194d224b"}, ] [package.extras] docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] testing = ["big-O", "flake8 (<5)", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"] [extras] with-bandit = ["bandit"] with-everything = ["bandit", "mypy", "pyright", "pyroma", "vulture"] with-mypy = ["mypy"] with-pyright = ["pyright"] with-pyroma = ["pyroma"] with-vulture = ["vulture"] [metadata] lock-version = "2.0" python-versions = ">=3.7.2,<4.0" content-hash = "75a5de25257a637163b74a454c8540edbc32f06f619735d1e22a06c17c4075bd" prospector-1.10.3/prospector/000077500000000000000000000000001451367460400162065ustar00rootroot00000000000000prospector-1.10.3/prospector/__init__.py000066400000000000000000000000001451367460400203050ustar00rootroot00000000000000prospector-1.10.3/prospector/__main__.py000066400000000000000000000001071451367460400202760ustar00rootroot00000000000000from prospector.run import main if __name__ == "__main__": main() prospector-1.10.3/prospector/autodetect.py000066400000000000000000000056131451367460400207260ustar00rootroot00000000000000import os import re import warnings from pathlib import Path from requirements_detector import find_requirements from requirements_detector.detect import RequirementsNotFound from prospector import encoding from prospector.exceptions import PermissionMissing from prospector.pathutils import is_virtualenv POSSIBLE_LIBRARIES = ("django", "celery", "flask") # see http://docs.python.org/2/reference/lexical_analysis.html#identifiers _FROM_IMPORT_REGEX = re.compile(r"^\s*from ([\._a-zA-Z0-9]+) import .*$") _IMPORT_REGEX = re.compile(r"^\s*import ([\._a-zA-Z0-9]+)$") _IMPORT_MULTIPLE_REGEX = re.compile(r"^\s*import ([\._a-zA-Z0-9]+(, ){1})+") def find_from_imports(file_contents): names = set() for line in file_contents.split("\n"): match = _IMPORT_MULTIPLE_REGEX.match(line) if match: import_names = [] first = match.group(1) import_names.append(first[:-2]) for name in line.split(first)[1].split(","): import_names.append(name.strip()) else: match = _IMPORT_REGEX.match(line) or _FROM_IMPORT_REGEX.match(line) if match is None: continue import_names = match.group(1).split(".") for import_name in import_names: if import_name in POSSIBLE_LIBRARIES: names.add(import_name) return names def find_from_path(path: Path): names = set() try: for item in path.iterdir(): if item.is_dir(): if is_virtualenv(item): continue names |= find_from_path(item) elif not item.is_symlink() and item.suffix == ".py": try: contents = encoding.read_py_file(item) names |= find_from_imports(contents) except encoding.CouldNotHandleEncoding as err: # TODO: this output will break output formats such as JSON warnings.warn(f"{err.path}: {err.__cause__}", ImportWarning) if len(names) == len(POSSIBLE_LIBRARIES): # don't continue on recursing, there's no point! break except PermissionError as err: raise PermissionMissing(path) from err return names def find_from_requirements(path): reqs = find_requirements(path) names = [] for requirement in reqs: if requirement.name is not None and requirement.name.lower() in POSSIBLE_LIBRARIES: names.append(requirement.name.lower()) return names def autodetect_libraries(path): if os.path.isfile(path): path = os.path.dirname(path) if path == "": path = "." libraries = [] try: libraries = find_from_requirements(path) except RequirementsNotFound: pass if len(libraries) < len(POSSIBLE_LIBRARIES): libraries = find_from_path(path) return libraries prospector-1.10.3/prospector/blender.py000066400000000000000000000105341451367460400201760ustar00rootroot00000000000000# This module contains the logic for "blending" of errors. # Since prospector runs multiple tools with overlapping functionality, this # module exists to merge together equivalent warnings from different tools for # the same line. For example, both pyflakes and pylint will generate an # "Unused Import" warning on the same line. This is obviously redundant, so we # remove duplicates. from collections import defaultdict import pkg_resources import yaml __all__ = ( "blend", "BLEND_COMBOS", ) def blend_line(messages, blend_combos=None): """ Given a list of messages on the same line, blend them together so that we end up with one message per actual problem. Note that we can still return more than one message here if there are two or more different errors for the line. """ blend_combos = blend_combos or BLEND_COMBOS blend_lists = [[] for _ in range(len(blend_combos))] blended = [] # first we split messages into each of the possible blendable categories # so that we have a list of lists of messages which can be blended together for message in messages: key = (message.source, message.code) found = False for blend_combo_idx, blend_combo in enumerate(blend_combos): if key in blend_combo: found = True blend_lists[blend_combo_idx].append(message) # note: we use 'found=True' here rather than a simple break/for-else # because this allows the same message to be put into more than one # 'bucket'. This means that the same message from pycodestyle can 'subsume' # two from pylint, for example. if not found: # if we get here, then this is not a message which can be blended, # so by definition is already blended blended.append(message) # we should now have a list of messages which all represent the same # problem on the same line, so we will sort them according to the priority # in BLEND and pick the first one for blend_combo_idx, blend_list in enumerate(blend_lists): if len(blend_list) == 0: continue # pylint:disable=cell-var-from-loop blend_list.sort( key=lambda msg: blend_combos[blend_combo_idx].index( (msg.source, msg.code), ), ) if blend_list[0] not in blended: # We may have already added this message if it represents # several messages in other tools which are not being run - # for example, pylint missing-docstring is blended with pydocstyle # D100, D101 and D102, but should not appear 3 times! blended.append(blend_list[0]) # Some messages from a tool point out an error that in another tool is handled by two # different errors or more. For example, pylint emits the same warning (multiple-statements) # for "two statements on a line" separated by a colon and a semi-colon, while pycodestyle has E701 # and E702 for those cases respectively. In this case, the pylint error will not be 'blended' as # it will appear in two blend_lists. Therefore we mark anything not taken from the blend list # as "consumed" and then filter later, to avoid such cases. for now_used in blend_list[1:]: now_used.used = True return [m for m in blended if not getattr(m, "used", False)] def blend(messages, blend_combos=None): blend_combos = blend_combos or BLEND_COMBOS # group messages by file and then line number msgs_grouped = defaultdict(lambda: defaultdict(list)) for message in messages: msgs_grouped[message.location.path][message.location.line].append( message, ) # now blend together all messages on the same line out = [] for by_line in msgs_grouped.values(): for messages_on_line in by_line.values(): out += blend_line(messages_on_line, blend_combos) return out def get_default_blend_combinations(): combos = yaml.safe_load(pkg_resources.resource_string(__name__, "blender_combinations.yaml")) combos = combos.get("combinations", []) defaults = [] for combo in combos: toblend = [] for msg in combo: toblend += msg.items() defaults.append(tuple(toblend)) return tuple(defaults) BLEND_COMBOS = get_default_blend_combinations() prospector-1.10.3/prospector/blender_combinations.yaml000066400000000000000000000146271451367460400232640ustar00rootroot00000000000000# 'combinations' is a list of lists of codes to merge together. The earlier # codes will take priority, so if all are found, only the first one will be # left after blending. # # Note that since not all tools will necessarily be run, the first message for # a line as sorted by the code list will be the one remaining after blending. combinations: - # Unused Import - pylint: unused-import - pyflakes: F401 - frosted: E101 - # Syntax Error - dodgy: diff - pylint: syntax-error - pyflakes: F999 - pep8: E901 - pycodestyle: E901 - mccabe: MC0000 - frosted: E402 - # Undefined local variable - pylint: undefined-variable - pyflakes: F821 - frosted: E303 - # Unused variable - pylint: unused-variable - vulture: unused-variable - pyflakes: F841 - frosted: E307 - # Mixed tabs and spaces - pep257: D206 - pydocstyle: D206 - pep8: E101 - pycodestyle: E101 - pylint: indentation-mixture - # Import from __future__ not first import - pylint: misplaced-future - pyflakes: F404 - frosted: E207 - # Line too long - pep8: E501 - pycodestyle: E501 - pylint: line-too-long - # Trailing whitespace - pep8: W291 - pycodestyle: W291 - pylint: trailing-whitespace - # Blank line contains whitespace - pep8: W293 - pycodestyle: W293 - pylint: trailing-whitespace - # No newline at end of file - pep8: W292 - pycodestyle: W292 - pylint: missing-final-newline - # line ends with semi-colon - pep8: E703 - pycodestyle: E703 - pylint: unnecessary-semicolon - # multiple statements on one line (colon) - pep8: E701 - pycodestyle: E701 - pylint: multiple-statements - # multiple statements on one line (semicolon) - pep8: E702 - pycodestyle: E702 - pylint: multiple-statements - # incorrect indentation - pep257: D207 - pydocstyle: D207 - pep8: E111 - pycodestyle: E111 - pylint: bad-indentation - # incorrect indentation - pep257: D208 - pydocstyle: D208 - pep8: E111 - pycodestyle: E111 - pylint: bad-indentation - # comma not followed by a space - pep8: E231 - pycodestyle: E231 - pylint: C0324 - pylint: bad-whitespace - # missing whitespace around operator - pep8: E225 - pycodestyle: E225 - pylint: C0322 - pylint: bad-whitespace - # missing whitespace around operator - pep8: E225 - pycodestyle: E225 - pylint: C0323 - pylint: bad-whitespace - # undefined name in __all__ - pylint: undefined-all-variable - pyflakes: F822 - frosted: E304 - # duplicate argument in function definition - pylint: duplicate-argument-name - pyflakes: F831 - frosted: E206 - # redefinition of unused function - pyflakes: F811 - pylint: function-redefined - # f-string is missing placeholders - pylint: f-string-without-interpolation - pyflakes: F541 - # Duplicate key in dictionary - pylint: duplicate-key - pyflakes: F601 - # More than one starred expression in assignment - pylint: too-many-star-expressions - pyflakes: F622 - # Assert called on a tuple - pylint: assert-on-tuple - pyflakes: F631 - # 'break' outside loop - pylint: not-in-loop - pyflakes: F701 - # 'continue' not properly in loop - pylint: not-in-loop - pyflakes: F702 - # 'continue' not supported inside 'finally' clause - pylint: continue-in-finally - pyflakes: F703 - # Yield outside function - pylint: yield-outside-function - pyflakes: F704 - # Return outside function - pylint: return-outside-function - pyflakes: F706 - # default 'except:' must be last - pylint: bad-except-order - pyflakes: F707 - # NotImplemented raised - should raise NotImplementedError - pylint: notimplemented-raised - pyflakes: F901 - # first argument of a classmethod should be named 'cls' - pep8: N804 - pycodestyle: N804 - pylint: bad-classmethod-argument - # '<>' is deprecated, use '!=' - pep8: W603 - pycodestyle: W603 - pylint: W0331 - # backticks are deprecated, use 'repr()' - pep8: W604 - pycodestyle: W604 - pylint: W0333 - # Redefining name from outer scope - pylint: redefined-outer-name - pyflakes: F810 - frosted: E306 - # Wildcard import - pylint: wildcard-import - pyflakes: F403 - frosted: E103 - # Return with argument inside generator - pylint: return-arg-in-generator - frosted: E208 - # Too many positional arguments for function call - pylint: too-many-function-args - frosted: E203 - # Passing unexpected keyword argument - pylint: unexpected-keyword-arg - frosted: E204 - # Missing mandatory keyword argument - pylint: missing-kwoa - frosted: E205 - # No exception type(s) specified - pylint: bare-except - frosted: W101 - pep8: E722 - pycodestyle: E722 - # Spaces around keyword/parameter equals - pep8: E251 - pycodestyle: E251 - pylint: bad-whitespace - # Missing space after a comma - pep8: E231 - pycodestyle: E231 - pylint: bad-whitespace - # redefinition of unused %r from line %r - pyflakes: F811 - frosted: E301 - # list comprehension redefines %r from line %r - pyflakes: F812 - frosted: E302 - # import %r from line %r shadowed by loop variable - pyflakes: F402 - frosted: E102 - # syntax error in doctest - pyflakes: FL0007 - frosted: E401 - # local variable %r referenced before assignment - pyflakes: F823 - frosted: E305 - # pep8-naming incorrectly suggests that the first argument of a metaclass __new__ method should be 'cls' - pylint: bad-mcs-classmethod-argument - pep8: N804 - pycodestyle: N804 - # class names should be camelcase - pep8: N801 - pycodestyle: N801 - pylint: invalid-name - # too complex - mccabe: MC0001 - pylint: too-many-branches - - mccabe: MC0001 - pylint: too-many-statements - # pep257 takes preference over pylint documentation warnings - pep257: D100 - pydocstyle: D100 - pylint: missing-docstring - - pep257: D101 - pydocstyle: D101 - pylint: missing-docstring - - pep257: D102 - pydocstyle: D102 - pylint: missing-docstring - - pep257: D103 - pydocstyle: D103 - pylint: missing-docstring - - pylint: singleton-comparison - pep8: E711 - pycodestyle: E711 prospector-1.10.3/prospector/compat.py000066400000000000000000000005501451367460400200430ustar00rootroot00000000000000from pathlib import Path def is_relative_to(relpath: Path, otherpath: Path) -> bool: # is_relative_to was only added to Path in Python 3.9 if hasattr(relpath, "is_relative_to"): return relpath.is_relative_to(otherpath) try: relpath.relative_to(otherpath) except ValueError: return False else: return True prospector-1.10.3/prospector/config/000077500000000000000000000000001451367460400174535ustar00rootroot00000000000000prospector-1.10.3/prospector/config/__init__.py000066400000000000000000000323411451367460400215670ustar00rootroot00000000000000import os import re import sys from pathlib import Path from typing import Dict, List, Union try: # Python >= 3.11 import re._constants as sre_constants except ImportError: import sre_constants from prospector import tools from prospector.autodetect import autodetect_libraries from prospector.compat import is_relative_to from prospector.config import configuration as cfg from prospector.message import Message from prospector.profiles import AUTO_LOADED_PROFILES from prospector.profiles.profile import BUILTIN_PROFILE_PATH, CannotParseProfile, ProfileNotFound, ProspectorProfile from prospector.tools import DEFAULT_TOOLS, DEPRECATED_TOOL_NAMES class ProspectorConfig: # There are several methods on this class which could technically # be functions (they don't use the 'self' argument) but that would # make this module/class a bit ugly. # Also the 'too many instance attributes' warning is ignored, as this # is a config object and its sole purpose is to hold many properties! def __init__(self, workdir: Path = None): self.config, self.arguments = self._configure_prospector() self.paths = self._get_work_path(self.config, self.arguments) self.explicit_file_mode = all(p.is_file for p in self.paths) self.workdir = workdir or Path.cwd() self.profile, self.strictness = self._get_profile(self.workdir, self.config) self.libraries = self._find_used_libraries(self.config, self.profile) self.tools_to_run = self._determine_tool_runners(self.config, self.profile) self.ignores = self._determine_ignores(self.config, self.profile, self.libraries) self.configured_by: Dict[str, str] = {} self.messages: List[Message] = [] def make_exclusion_filter(self): # Only close over the attributes required by the filter, rather # than the entire self, because ProspectorConfig can't be pickled # because of the config attribute, which would break parallel # pylint. ignores, workdir = self.ignores, self.workdir def _filter(path: Path): for ignore in ignores: # first figure out where the path is, relative to the workdir # ignore-paths/patterns will usually be relative to a repository # root or the CWD, but the path passed to prospector may not be path = path.resolve().absolute() if is_relative_to(path, workdir): path = path.relative_to(workdir) if ignore.match(str(path)): return True return False return _filter def get_tools(self, found_files): self.configured_by = {} runners = [] for tool_name in self.tools_to_run: tool = tools.TOOLS[tool_name]() config_result = tool.configure(self, found_files) if config_result is None: configured_by = None messages = [] else: configured_by, messages = config_result if messages is None: messages = [] self.configured_by[tool_name] = configured_by self.messages += messages runners.append(tool) return runners def replace_deprecated_tool_names(self) -> List[str]: # pep8 was renamed pycodestyle ; pep257 was renamed pydocstyle # for backwards compatibility, these have been deprecated but will remain until prospector v2 deprecated_found = [] replaced = [] for tool_name in self.tools_to_run: if tool_name in DEPRECATED_TOOL_NAMES: replaced.append(DEPRECATED_TOOL_NAMES[tool_name]) deprecated_found.append(tool_name) else: replaced.append(tool_name) self.tools_to_run = replaced return deprecated_found def get_output_report(self): # Get the output formatter if self.config.output_format is not None: output_report = self.config.output_format else: output_report = [(self.profile.output_format, self.profile.output_target)] for index, report in enumerate(output_report): if not all(report): output_report[index] = (report[0] or "grouped", report[1] or []) return output_report def _configure_prospector(self): # first we will configure prospector as a whole mgr = cfg.build_manager() config = mgr.retrieve(*cfg.build_default_sources()) return config, mgr.arguments def _get_work_path(self, config, arguments) -> List[Path]: # Figure out what paths we're prospecting if config["path"]: paths = [Path(self.config["path"])] elif arguments["checkpath"]: paths = [Path(p) for p in arguments["checkpath"]] else: paths = [Path.cwd()] return [p.resolve() for p in paths] def _get_profile(self, workdir: Path, config): # Use the specified profiles profile_provided = False if len(config.profiles) > 0: profile_provided = True cmdline_implicit = [] # if there is a '.prospector.ya?ml' or a '.prospector/prospector.ya?ml' or equivalent landscape config # file then we'll include that profile_name: Union[None, str, Path] = None if not profile_provided: for possible_profile in AUTO_LOADED_PROFILES: prospector_yaml = os.path.join(workdir, possible_profile) if os.path.exists(prospector_yaml) and os.path.isfile(prospector_yaml): profile_provided = True profile_name = possible_profile break strictness = None if profile_provided: if profile_name is None: profile_name = config.profiles[0] extra_profiles = config.profiles[1:] else: extra_profiles = config.profiles strictness = "from profile" else: # Use the preconfigured prospector profiles profile_name = "default" extra_profiles = [] if config.doc_warnings is not None and config.doc_warnings: cmdline_implicit.append("doc_warnings") if config.test_warnings is not None and config.test_warnings: cmdline_implicit.append("test_warnings") if config.no_style_warnings is not None and config.no_style_warnings: cmdline_implicit.append("no_pep8") if config.full_pep8 is not None and config.full_pep8: cmdline_implicit.append("full_pep8") if config.member_warnings is not None and config.member_warnings: cmdline_implicit.append("member_warnings") # Use the strictness profile only if no profile has been given if config.strictness is not None and config.strictness: cmdline_implicit.append("strictness_%s" % config.strictness) strictness = config.strictness # the profile path is # * anything provided as an argument # * a directory called .prospector in the check path # * the check path # * prospector provided profiles profile_path = [Path(path).absolute() for path in config.profile_path] prospector_dir = workdir / ".prospector" if os.path.exists(prospector_dir) and os.path.isdir(prospector_dir): profile_path.append(prospector_dir) profile_path.append(workdir) profile_path.append(BUILTIN_PROFILE_PATH) try: forced_inherits = cmdline_implicit + extra_profiles profile = ProspectorProfile.load(profile_name, profile_path, forced_inherits=forced_inherits) except CannotParseProfile as cpe: sys.stderr.write( "Failed to run:\nCould not parse profile %s as it is not valid YAML\n%s\n" % (cpe.filepath, cpe.get_parse_message()) ) sys.exit(1) except ProfileNotFound as nfe: search_path = ":".join(map(str, nfe.profile_path)) profile = nfe.name.split(":")[0] sys.stderr.write( f"""Failed to run: Could not find profile {nfe.name}. Search path: {search_path}, or in module 'prospector_profile_{profile}' """ ) sys.exit(1) else: return profile, strictness def _find_used_libraries(self, config, profile): libraries = [] # Bring in adaptors that we automatically detect are needed if config.autodetect and profile.autodetect is True: for found_dep in autodetect_libraries(self.workdir): libraries.append(found_dep) # Bring in adaptors for the specified libraries for name in set(config.uses + profile.uses): if name not in libraries: libraries.append(name) return libraries def _determine_tool_runners(self, config, profile): if config.tools is None: # we had no command line settings for an explicit list of # tools, so we use the defaults to_run = set(DEFAULT_TOOLS) # we can also use any that the profiles dictate for tool in tools.TOOLS: if profile.is_tool_enabled(tool): to_run.add(tool) else: to_run = set(config.tools) # profiles have no say in the list of tools run when # a command line is specified for tool in config.with_tools: to_run.add(tool) for tool in config.without_tools: if tool in to_run: to_run.remove(tool) # if config.tools is None and len(config.with_tools) == 0 and len(config.without_tools) == 0: for tool in tools.TOOLS: enabled = profile.is_tool_enabled(tool) if enabled is None: enabled = tool in DEFAULT_TOOLS if tool in to_run and not enabled and tool not in config.with_tools and tool not in (config.tools or []): # if this is not enabled in a profile but is asked for in a command line arg, we keep it, otherwise # remove it from the list to run to_run.remove(tool) return sorted(list(to_run)) def _determine_ignores(self, config, profile, libraries): # Grab ignore patterns from the options ignores = [] for pattern in config.ignore_patterns + profile.ignore_patterns: if pattern is None: # this can happen if someone has a profile with an empty ignore-patterns value, eg: # # ignore-patterns: # uses: django continue try: ignores.append(re.compile(pattern)) except sre_constants.error: pass # Convert ignore paths into patterns boundary = r"(^|/|\\)%s(/|\\|$)" for ignore_path in config.ignore_paths + profile.ignore_paths: ignore_path = str(ignore_path) if ignore_path.endswith("/") or ignore_path.endswith("\\"): ignore_path = ignore_path[:-1] ignores.append(re.compile(boundary % re.escape(ignore_path))) # some libraries have further automatic ignores if "django" in libraries: ignores += [re.compile("(^|/)(south_)?migrations(/|$)")] return ignores def get_summary_information(self): return { "libraries": self.libraries, "strictness": self.strictness, "profiles": ", ".join(self.profile.list_profiles()), "tools": self.tools_to_run, } def exit_with_zero_on_success(self): return self.config.zero_exit def get_disabled_messages(self, tool_name): return self.profile.get_disabled_messages(tool_name) def use_external_config(self, _): # Currently there is only one single global setting for whether to use # global config, but this could be extended in the future return not self.config.no_external_config def tool_options(self, tool_name): tool = getattr(self.profile, tool_name, None) if tool is None: return {} return tool.get("options", {}) def external_config_location(self, tool_name): return getattr(self.config, "%s_config_file" % tool_name, None) @property def die_on_tool_error(self): return self.config.die_on_tool_error @property def summary_only(self): return self.config.summary_only @property def messages_only(self): return self.config.messages_only @property def quiet(self): return self.config.quiet @property def blending(self): return self.config.blending @property def absolute_paths(self): return self.config.absolute_paths @property def max_line_length(self): return self.config.max_line_length @property def include_tool_stdout(self): return self.config.include_tool_stdout @property def direct_tool_stdout(self): return self.config.direct_tool_stdout @property def show_profile(self): return self.config.show_profile @property def legacy_tool_names(self) -> bool: return self.config.legacy_tool_names prospector-1.10.3/prospector/config/configuration.py000066400000000000000000000331211451367460400226740ustar00rootroot00000000000000# flake8: noqa import pkg_resources import setoptconf as soc from prospector.config.datatype import OutputChoice from prospector.formatters import FORMATTERS from prospector.tools import DEFAULT_TOOLS, TOOLS __all__ = ("build_manager",) _VERSION = pkg_resources.get_distribution("prospector").version def build_manager(): manager = soc.ConfigurationManager("prospector") manager.add(soc.BooleanSetting("zero_exit", default=False)) manager.add(soc.BooleanSetting("autodetect", default=True)) manager.add(soc.ListSetting("uses", soc.String, default=[])) manager.add(soc.BooleanSetting("blending", default=True)) manager.add(soc.BooleanSetting("doc_warnings", default=None)) manager.add(soc.BooleanSetting("test_warnings", default=None)) manager.add(soc.BooleanSetting("no_style_warnings", default=None)) manager.add(soc.BooleanSetting("member_warnings", default=None)) manager.add(soc.BooleanSetting("full_pep8", default=None)) manager.add(soc.IntegerSetting("max_line_length", default=None)) manager.add(soc.BooleanSetting("messages_only", default=False)) manager.add(soc.BooleanSetting("summary_only", default=False)) manager.add(soc.BooleanSetting("quiet", default=False)) manager.add( soc.ListSetting( "output_format", OutputChoice(sorted(FORMATTERS.keys())), default=None, ) ) manager.add(soc.BooleanSetting("absolute_paths", default=False)) manager.add( soc.ListSetting( "tools", soc.Choice(sorted(TOOLS.keys())), default=None, ) ) manager.add(soc.ListSetting("with_tools", soc.String, default=[])) manager.add(soc.ListSetting("without_tools", soc.String, default=[])) manager.add(soc.ListSetting("profiles", soc.String, default=[])) manager.add(soc.ListSetting("profile_path", soc.String, default=[])) manager.add( soc.ChoiceSetting( "strictness", ["veryhigh", "high", "medium", "low", "verylow"], default=None, ) ) manager.add(soc.BooleanSetting("show_profile", default=False)) manager.add(soc.BooleanSetting("no_external_config", default=False)) manager.add(soc.BooleanSetting("legacy_tool_names", default=False)) manager.add(soc.StringSetting("pylint_config_file", default=None)) manager.add(soc.StringSetting("path", default=None)) manager.add(soc.ListSetting("ignore_patterns", soc.String, default=[])) manager.add(soc.ListSetting("ignore_paths", soc.String, default=[])) manager.add(soc.BooleanSetting("die_on_tool_error", default=False)) manager.add(soc.BooleanSetting("include_tool_stdout", default=False)) manager.add(soc.BooleanSetting("direct_tool_stdout", default=False)) return manager def build_default_sources(): sources = [ build_command_line_source(), soc.EnvironmentVariableSource(), soc.ConfigFileSource( ( ".prospectorrc", "setup.cfg", "tox.ini", ) ), soc.ConfigFileSource( ( soc.ConfigDirectory(".prospectorrc"), soc.HomeDirectory(".prospectorrc"), ) ), ] return sources def build_command_line_source(prog=None, description="Performs static analysis of Python code"): parser_options = {} if prog is not None: parser_options["prog"] = prog if description is not None: parser_options["description"] = description options = { "zero_exit": { "flags": ["-0", "--zero-exit"], "help": "Prospector will exit with a code of 1 (one) if any messages" " are found. This makes automation easier; if there are any" " problems at all, the exit code is non-zero. However this behaviour" " is not always desirable, so if this flag is set, prospector will" " exit with a code of 0 if it ran successfully, and non-zero if" " it failed to run.", }, "autodetect": { "flags": ["-A", "--no-autodetect"], "help": "Turn off auto-detection of frameworks and libraries used." " By default, autodetection will be used. To specify" " manually, see the --uses option.", }, "uses": { "flags": ["-u", "--uses"], "help": "A list of one or more libraries or frameworks that the" " project uses. Possible values are: django, celery, flask. This will be" " autodetected by default, but if autodetection doesn't" " work, manually specify them using this flag.", }, "blending": { "flags": ["-B", "--no-blending"], "help": "Turn off blending of messages. Prospector will merge" " together messages from different tools if they represent" " the same error. Use this option to see all unmerged" " messages.", }, "doc_warnings": { "flags": ["-D", "--doc-warnings"], "help": "Include warnings about documentation.", }, "test_warnings": { "flags": ["-T", "--test-warnings"], "help": "Also check test modules and packages.", }, "legacy_tool_names": { "flags": ["--legacy-tool-names"], "help": "Output deprecated names for tools (pep8, pep257) " "instead of updated names (pycodestyle, pydocstyle)", }, "no_style_warnings": { "flags": ["-8", "--no-style-warnings"], "help": "Don't create any warnings about style. This disables the" " PEP8 tool and similar checks for formatting.", }, "member_warnings": { "flags": ["-m", "--member-warnings"], "help": "Attempt to warn when code tries to access an attribute of a " "class or member of a module which does not exist. This is disabled " "by default as it tends to be quite inaccurate.", }, "quiet": { "flags": ["-q", "--quiet"], "help": "Run but do not output anything to stdout. Useful to suppress " "output in scripts without sending to a file (via -o)", }, "full_pep8": { "flags": ["-F", "--full-pep8"], "help": "Enables every PEP8 warning, so that all PEP8 style violation will be reported.", }, "max_line_length": { "flags": ["--max-line-length"], "help": "The maximum line length allowed. This will be set by the strictness if no" " value is explicitly specified", }, "messages_only": { "flags": ["-M", "--messages-only"], "help": "Only output message information (don't output summary" " information about the checks)", }, "summary_only": { "flags": ["-S", "--summary-only"], "help": "Only output summary information about the checks (don't" "output message information)", }, "output_format": { "flags": ["-o", "--output-format"], "help": "The output format. Valid values are: %s. This will output to stdout by default, " "however a target file can be used instead by adding :path-to-output-file, eg, -o json:output.json" % (", ".join(sorted(FORMATTERS.keys())),), }, "absolute_paths": { "help": "Whether to output absolute paths when referencing files " "in messages. By default, paths will be relative to the " "project path", }, "tools": { "flags": ["-t", "--tool"], "help": "A list of tools to run. This lets you set exactly which " "tools to run. To add extra tools to the defaults, see " "--with-tool. Possible values are: %s. By " "default, the following tools will be run: %s" % ( ", ".join(sorted(TOOLS.keys())), ", ".join(sorted(DEFAULT_TOOLS)), ), }, "with_tools": { "flags": ["-w", "--with-tool"], "help": "A list of tools to run in addition to the default tools. " "To specify all tools explicitly, use the --tool argument. " "Possible values are %s." % (", ".join(sorted(TOOLS.keys()))), }, "without_tools": { "flags": ["-W", "--without-tool"], "help": "A list of tools that should not be run. Useful to turn off " "only a single tool from the defaults. " "To specify all tools explicitly, use the --tool argument. " "Possible values are %s." % (", ".join(sorted(TOOLS.keys()))), }, "profiles": { "flags": ["-P", "--profile"], "help": "The list of profiles to load. A profile is a certain" " 'type' of behaviour for prospector, and is represented" " by a YAML configuration file. Either a full path to the YAML" " file describing the profile must be provided, or it must be" " on the profile path (see --profile-path)", }, "profile_path": { "flags": ["--profile-path"], "help": "Additional paths to search for profile files. By default this" " is the path that prospector will check, and a directory " ' called ".prospector" in the path that prospector will check.', }, "show_profile": { "flags": ["--show-profile"], "help": "Include the computed profile in the summary. This will show what" " prospector has decided the overall profile is once all profiles" " have been combined and inherited from. This will produce a large" " output in most cases so is only useful when trying to debug why" " prospector is not behaving like you expect.", }, "strictness": { "flags": ["-s", "--strictness"], "help": "How strict the checker should be. This affects how" " harshly the checker will enforce coding guidelines. The" ' default value is "medium", possible values are' ' "veryhigh", "high", "medium", "low" and "verylow".', }, "no_external_config": { "flags": ["-E", "--no-external-config"], "help": "Determines how prospector should behave when" " configuration already exists for a tool. By default," " prospector will use existing configuration. This flag" " will cause prospector to ignore existing configuration" " and use its own settings for every tool. Note that" " prospector will always use its own config for tools which" " do not have custom configuration.", }, "pylint_config_file": { "flags": ["--pylint-config-file"], "help": "The path to a pylintrc file to use to configure pylint. Prospector will find" " .pylintrc files in the root of the project, but you can use this option to " "specify manually where it is.", }, "ignore_patterns": { "flags": ["-I", "--ignore-patterns"], "help": "A list of paths to ignore, as a list of regular" " expressions. Files and folders will be ignored if their" " full path contains any of these patterns.", }, "ignore_paths": { "flags": ["-i", "--ignore-paths"], "help": "A list of file or directory names to ignore. If the" " complete name matches any of the items in this list, the" " file or directory (and all subdirectories) will be" " ignored.", }, "die_on_tool_error": { "flags": ["-X", "--die-on-tool-error"], "help": "If a tool fails to run, prospector will try to carry on." " Use this flag to cause prospector to die and raise the" " exception the tool generated. Mostly useful for" " development on prospector.", }, "include-tool-stdout": { "flags": ["--include-tool-stdout"], "help": "There are various places where tools will output warnings to " "stdout/stderr, which breaks parsing of JSON output. Therefore while tols " "is running, this is suppressed. For developing, it is sometimes useful to " "see this. This flag will cause stdout/stderr from a tool to be shown as " "a normal message amongst other warnings. See also --direct-tool-stdout", }, "direct-tool-stdout": { "flags": ["--direct-tool-stdout"], "help": "Same as --include-tool-stdout, except the output will be printed " "directly rather than shown as a message.", }, "path": { "flags": ["-p", "--path"], "help": "The path to a Python project to inspect. Defaults to PWD" " if not specified. Note: This command line argument is" " deprecated and will be removed in a future update. Please" " use the positional PATH argument instead.", }, } positional = ( ( "checkpath", { "help": "The path to a Python project to inspect. Defaults to PWD" " if not specified. If multiple paths are specified," " they must all be files (no directories).", "metavar": "PATH", "nargs": "*", }, ), ) return soc.CommandLineSource( options=options, version=_VERSION, parser_options=parser_options, positional=positional, ) prospector-1.10.3/prospector/config/datatype.py000066400000000000000000000011641451367460400216420ustar00rootroot00000000000000import os import re import sys from setoptconf.datatype import Choice class OutputChoice(Choice): def sanitize(self, value): parsed = re.split(r"[;:]", value) output_format, output_targets = parsed[0], parsed[1:] checked_targets = [] for target in output_targets: if sys.platform.startswith("win") and target.startswith((os.path.sep, os.path.altsep)): checked_targets[-1] += ":" + target else: checked_targets.append(target) validated_format = super().sanitize(output_format) return validated_format, checked_targets prospector-1.10.3/prospector/encoding.py000066400000000000000000000030061451367460400203450ustar00rootroot00000000000000import tokenize from pathlib import Path from prospector.exceptions import CouldNotHandleEncoding, PermissionMissing # note: annotating return type with AnyStr does not work here for reasons I can't be bothered to work out # mypy complains with 'Incompatible return value type (got "str", expected "bytes")' def read_py_file(filepath: Path): # see https://docs.python.org/3/library/tokenize.html#tokenize.detect_encoding # first just see if the file is properly encoded try: with open(filepath, "rb") as bfile_: tokenize.detect_encoding(bfile_.readline) except PermissionError as err: raise PermissionMissing(filepath) from err except SyntaxError as err: # this warning is issued: # (1) in badly authored files (contains non-utf8 in a comment line) # (2) a coding is specified, but wrong and # (3) no coding is specified, and the default # 'utf-8' fails to decode. # (4) the encoding specified by a pep263 declaration did not match # with the encoding detected by inspecting the BOM raise CouldNotHandleEncoding(filepath) from err try: with tokenize.open(filepath) as file_: return file_.read() # this warning is issued: # (1) if utf-8 is specified, but latin1 is used with something like \x0e9 appearing # (see http://stackoverflow.com/a/5552623) except UnicodeDecodeError as err: raise CouldNotHandleEncoding(filepath) from err prospector-1.10.3/prospector/exceptions.py000066400000000000000000000024131451367460400207410ustar00rootroot00000000000000import os from pathlib import Path class FatalProspectorException(Exception): """ Exception used to indicate an internal prospector problem. Problems in prospector itself should raise this to notify the user directly. Errors in dependent tools should be caught and the user notified elegantly. """ # (see also the --die-on-tool-error flag) def __init__(self, message: str): super().__init__(message) self.message = message class CouldNotHandleEncoding(Exception): def __init__(self, path: Path): super().__init__() self.path = path class PermissionMissing(Exception): def __init__(self, path: Path): docs_url = "https://prospector.landscape.io/en/master/profiles.html#ignoring-paths-and-patterns" if os.path.isdir(path): what = f"directory {path}" else: what = f"the file {path}" error_msg = ( f"The current user {os.getlogin()} does not have permission to open " f"{what}. Either fix permissions or tell prospector to skip it " f"by adding this path to `--ignore-paths` on the commandline " f"or in `ignore-paths` in the prospector profile (see {docs_url})" ) super().__init__(error_msg) prospector-1.10.3/prospector/finder.py000066400000000000000000000111631451367460400200310ustar00rootroot00000000000000from pathlib import Path from typing import Callable, Iterable, Iterator, List from prospector.exceptions import PermissionMissing from prospector.pathutils import is_python_module, is_python_package, is_virtualenv _SKIP_DIRECTORIES = (".git", ".tox", ".mypy_cache", ".pytest_cache", ".venv", "__pycache__", "node_modules") class FileFinder: """ This class is responsible for taking a combination of command-line arguments and configuration loaded from a profile to discover all files and modules which should be inspected. Individual tools can be told to ignore certain files, so the job of this class is basically to know which files to pass to which tools to be inspected. """ def __init__(self, *provided_paths: Path, exclusion_filters: Iterable[Callable] = None): """ :param provided_paths: A list of Path objects to search for files and modules - can be either directories or files :param exclusion_filters: An optional list of filters. All paths will be checked against this list - if any return True, the path is excluded. """ self._provided_files = [] self._provided_dirs = [] self._exclusion_filters = [ # we always want to ignore some things lambda _path: _path.is_dir() and _path.name in _SKIP_DIRECTORIES, is_virtualenv, ] + list(exclusion_filters or []) for path in provided_paths: if not path.exists(): raise FileNotFoundError(path) # ensure all paths from now one are absolute paths; they can be converted # to relative paths for output purposes later path = path.absolute() if path.is_file(): self._provided_files.append(path) if path.is_dir(): self._provided_dirs.append(path) def make_syspath(self) -> List[Path]: paths = set() for path in self._provided_dirs: paths.add(path) for module in self.python_modules: paths.add(module.parent) return sorted(paths) def is_excluded(self, path: Path) -> bool: return any(filt(path) for filt in self._exclusion_filters) def _filter(self, paths: Iterable[Path]) -> List[Path]: return [path for path in paths if not self.is_excluded(path)] def _walk(self, directory: Path) -> Iterator[Path]: if not self.is_excluded(directory): yield directory for path in directory.iterdir(): if self.is_excluded(path): continue if path.is_dir(): yield from self._walk(path) else: yield path @property def files(self) -> List[Path]: """ List every individual file found from the given configuration. This method is useful for tools which require an explicit list of files to check. """ files = set() for path in self._provided_files: files.add(path) for directory in self.directories: for path in self._walk(directory): if path.is_file(): files.add(path) return self._filter(files) @property def python_packages(self) -> List[Path]: """ Lists every directory found in the given configuration which is a python module (that is, contains an `__init__.py` file). This method is useful for passing to tools which will do their own discovery of python files. """ return self._filter(d for d in self.directories if is_python_package(d)) @property def python_modules(self) -> List[Path]: """ Lists every directory found in the given configuration which is a python module (that is, contains an `__init__.py` file). This method is useful for passing to tools which will do their own discovery of python files. """ return self._filter(f for f in self.files if is_python_module(f)) @property def directories(self) -> List[Path]: """ Lists every directory found from the given configuration, regardless of its contents. This method is useful for passing to tools which will do their own discovery of python files. """ dirs = set() for directory in self._provided_dirs: dirs.add(directory) try: for obj in self._walk(directory): if obj.is_dir(): dirs.add(obj) except PermissionError as err: raise PermissionMissing(obj) from err return self._filter(dirs) prospector-1.10.3/prospector/formatters/000077500000000000000000000000001451367460400203745ustar00rootroot00000000000000prospector-1.10.3/prospector/formatters/__init__.py000066400000000000000000000006671451367460400225160ustar00rootroot00000000000000from . import emacs, grouped, json, pylint, text, vscode, xunit, yaml from .base import Formatter __all__ = ("FORMATTERS", "Formatter") FORMATTERS = { "json": json.JsonFormatter, "text": text.TextFormatter, "grouped": grouped.GroupedFormatter, "emacs": emacs.EmacsFormatter, "yaml": yaml.YamlFormatter, "pylint": pylint.PylintFormatter, "xunit": xunit.XunitFormatter, "vscode": vscode.VSCodeFormatter, } prospector-1.10.3/prospector/formatters/base.py000066400000000000000000000023441451367460400216630ustar00rootroot00000000000000from abc import ABC, abstractmethod __all__ = ("Formatter",) from pathlib import Path from prospector.message import Message class Formatter(ABC): def __init__(self, summary, messages, profile, paths_relative_to: Path = None): self.summary = summary self.messages = messages self.profile = profile self.paths_relative_to = paths_relative_to @abstractmethod def render(self, summary=True, messages=True, profile=False): raise NotImplementedError def _make_path(self, path: Path) -> str: if self.paths_relative_to is None: path = path.absolute() elif path.is_absolute(): path = path.relative_to(self.paths_relative_to) return str(path) def _message_to_dict(self, message: Message) -> dict: loc = { "path": self._make_path(message.location.path), "module": message.location.module, "function": message.location.function, "line": message.location.line, "character": message.location.character, } return { "source": message.source, "code": message.code, "location": loc, "message": message.message, } prospector-1.10.3/prospector/formatters/emacs.py000066400000000000000000000013771451367460400220460ustar00rootroot00000000000000from prospector.formatters.text import TextFormatter __all__ = ("EmacsFormatter",) class EmacsFormatter(TextFormatter): def render_message(self, message): output = [ "%s:%s:%d:" % ( self._make_path(message.location.path), message.location.line, (message.location.character or 0) + 1, ), " L%s:%s %s: %s - %s" % ( message.location.line or "-", message.location.character if message.location.line else "-", message.location.function, message.source, message.code, ), " %s" % message.message, ] return "\n".join(output) prospector-1.10.3/prospector/formatters/grouped.py000066400000000000000000000023271451367460400224170ustar00rootroot00000000000000from collections import defaultdict from prospector.formatters.text import TextFormatter __all__ = ("GroupedFormatter",) class GroupedFormatter(TextFormatter): def render_messages(self): output = [ "Messages", "========", "", ] groups = defaultdict(lambda: defaultdict(list)) for message in self.messages: groups[self._make_path(message.location.path)][message.location.line].append(message) for filename in sorted(groups.keys()): output.append(str(filename)) for line in sorted(groups[filename].keys(), key=lambda x: 0 if x is None else int(x)): output.append(" Line: %s" % line) for message in groups[filename][line]: output.append( " %s: %s / %s%s" % ( message.source, message.code, message.message, (" (col %s)" % message.location.character) if message.location.character else "", ) ) output.append("") return "\n".join(output) prospector-1.10.3/prospector/formatters/json.py000066400000000000000000000016421451367460400217220ustar00rootroot00000000000000import json from datetime import datetime from prospector.formatters.base import Formatter __all__ = ("JsonFormatter",) class JsonFormatter(Formatter): def render(self, summary=True, messages=True, profile=False): output = {} if summary: # we need to slightly change the types and format # of a few of the items in the summary to make # them play nice with JSON formatting munged = {} for key, value in self.summary.items(): if isinstance(value, datetime): munged[key] = str(value) else: munged[key] = value output["summary"] = munged if profile: output["profile"] = self.profile.as_dict() if messages: output["messages"] = [self._message_to_dict(m) for m in self.messages] return json.dumps(output, indent=2) prospector-1.10.3/prospector/formatters/pylint.py000066400000000000000000000033151451367460400222670ustar00rootroot00000000000000import os import re from prospector.formatters.base import Formatter class PylintFormatter(Formatter): """ This formatter outputs messages in the same way as pylint -f parseable , which is used by several tools to parse pylint output. This formatter is therefore a compatibility shim between tools built on top of pylint and prospector itself. """ def render(self, summary=True, messages=True, profile=False): # this formatter will always ignore the summary and profile cur_loc = None output = [] for message in sorted(self.messages): if cur_loc != message.location.path: cur_loc = message.location.path module_name = self._make_path(message.location.path).replace(os.path.sep, ".") module_name = re.sub(r"(\.__init__)?\.py$", "", module_name) header = "************* Module %s" % module_name output.append(header) # ={path}:{line}: [{msg_id}({symbol}), {obj}] {msg} # prospector/configuration.py:65: [missing-docstring(missing-docstring), build_default_sources] \ # Missing function docstring template = "%(path)s:%(line)s: [%(code)s(%(source)s), %(function)s] %(message)s" output.append( template % { "path": self._make_path(message.location.path), "line": message.location.line, "source": message.source, "code": message.code, "function": message.location.function, "message": message.message.strip(), } ) return "\n".join(output) prospector-1.10.3/prospector/formatters/text.py000066400000000000000000000057141451367460400217410ustar00rootroot00000000000000from prospector.formatters.base import Formatter __all__ = ("TextFormatter",) # pylint: disable=unnecessary-lambda class TextFormatter(Formatter): summary_labels = ( ("started", "Started"), ("completed", "Finished"), ("time_taken", "Time Taken", lambda x: "%s seconds" % x), ("formatter", "Formatter"), ("profiles", "Profiles"), ("strictness", "Strictness"), ("libraries", "Libraries Used", lambda x: ", ".join(x)), ("tools", "Tools Run", lambda x: ", ".join(x)), ("adaptors", "Adaptors", lambda x: ", ".join(x)), ("message_count", "Messages Found"), ("external_config", "External Config"), ) def render_summary(self): output = [ "Check Information", "=================", ] label_width = max(len(label[1]) for label in self.summary_labels) for summary_label in self.summary_labels: key = summary_label[0] if key in self.summary: label = summary_label[1] if len(summary_label) > 2: value = summary_label[2](self.summary[key]) else: value = self.summary[key] output.append( " %s: %s" % ( label.rjust(label_width), value, ) ) return "\n".join(output) def render_message(self, message): output = [] if message.location.module: output.append(f"{message.location.module} ({self._make_path(message.location.path)}):") else: output.append("%s:" % self._make_path(message.location.path)) output.append( " L%s:%s %s: %s - %s" % ( message.location.line or "-", message.location.character if message.location.character else "-", message.location.function, message.source, message.code, ) ) output.append(" %s" % message.message) return "\n".join(output) def render_messages(self): output = [ "Messages", "========", "", ] for message in self.messages: output.append(self.render_message(message)) output.append("") return "\n".join(output) def render_profile(self): output = ["Profile", "=======", "", self.profile.as_yaml().strip()] return "\n".join(output) def render(self, summary=True, messages=True, profile=False): output = [] if messages and self.messages: # if there are no messages, don't render an empty header output.append(self.render_messages()) if profile: output.append(self.render_profile()) if summary: output.append(self.render_summary()) return "\n\n\n".join(output) + "\n" prospector-1.10.3/prospector/formatters/vscode.py000066400000000000000000000024161451367460400222340ustar00rootroot00000000000000import os import re from prospector.formatters.base import Formatter class VSCodeFormatter(Formatter): """ This formatter outputs messages in the same way as vscode prospector linter expects. """ def render(self, summary=True, messages=True, profile=False): # this formatter will always ignore the summary and profile cur_loc = None output = [] for message in sorted(self.messages): if cur_loc != message.location.path: cur_loc = message.location.path module_name = self._make_path(message.location.path).replace(os.path.sep, ".") module_name = re.sub(r"(\.__init__)?\.py$", "", module_name) header = "************* Module %s" % module_name output.append(header) template = "%(line)s,%(character)s,%(code)s,%(code)s:%(source)s %(message)s" output.append( template % { "line": message.location.line, "character": message.location.character, "source": message.source, "code": message.code, "message": message.message.strip(), } ) return "\n".join(output) prospector-1.10.3/prospector/formatters/xunit.py000066400000000000000000000045771451367460400221320ustar00rootroot00000000000000from xml.dom.minidom import Document from prospector.formatters.base import Formatter class XunitFormatter(Formatter): """ This formatter outputs messages in the Xunit xml format, which is used by several CI tools to parse output. This formatter is therefore a compatibility shim between tools built to use Xunit and prospector itself. """ def render(self, summary=True, messages=True, profile=False): xml_doc = Document() testsuite_el = xml_doc.createElement("testsuite") testsuite_el.setAttribute("errors", str(self.summary["message_count"])) testsuite_el.setAttribute("failures", "0") testsuite_el.setAttribute("name", "prospector-%s" % "-".join(self.summary["tools"])) testsuite_el.setAttribute("tests", str(self.summary["message_count"])) testsuite_el.setAttribute("time", str(self.summary["time_taken"])) xml_doc.appendChild(testsuite_el) prop_el = xml_doc.createElement("properties") testsuite_el.appendChild(prop_el) sysout_el = xml_doc.createElement("system-out") sysout_el.appendChild(xml_doc.createCDATASection("")) testsuite_el.appendChild(sysout_el) syserr_el = xml_doc.createElement("system-err") syserr_el.appendChild(xml_doc.createCDATASection("")) testsuite_el.appendChild(syserr_el) for message in sorted(self.messages): testcase_el = xml_doc.createElement("testcase") testcase_el.setAttribute("name", f"{self._make_path(message.location.path)}-{message.location.line}") failure_el = xml_doc.createElement("error") failure_el.setAttribute("message", message.message.strip()) failure_el.setAttribute("type", "%s Error" % message.source) template = "%(path)s:%(line)s: [%(code)s(%(source)s), %(function)s] %(message)s" cdata = template % { "path": self._make_path(message.location.path), "line": message.location.line, "source": message.source, "code": message.code, "function": message.location.function, "message": message.message.strip(), } failure_el.appendChild(xml_doc.createCDATASection(cdata)) testcase_el.appendChild(failure_el) testsuite_el.appendChild(testcase_el) return xml_doc.toprettyxml() prospector-1.10.3/prospector/formatters/yaml.py000066400000000000000000000011451451367460400217110ustar00rootroot00000000000000import yaml from prospector.formatters.base import Formatter __all__ = ("YamlFormatter",) class YamlFormatter(Formatter): def render(self, summary=True, messages=True, profile=False): output = {} if summary: output["summary"] = self.summary if profile: output["profile"] = self.profile.as_dict() if messages: output["messages"] = [self._message_to_dict(m) for m in self.messages] return yaml.safe_dump( output, indent=2, default_flow_style=False, allow_unicode=True, ) prospector-1.10.3/prospector/message.py000066400000000000000000000052541451367460400202120ustar00rootroot00000000000000from pathlib import Path from typing import Optional, Union class Location: def __init__( self, path: Union[Path, str], module: Optional[str], function: Optional[str], line: int, character: int ): if isinstance(path, Path): self._path = path elif isinstance(path, str): self._path = Path(path) else: raise ValueError self.module = module or None self.function = function or None self.line = None if line == -1 else line self.character = None if character == -1 else character @property def path(self): return self._path def absolute_path(self) -> Path: return self._path.absolute() def relative_path(self, root: Path) -> Path: return self._path.relative_to(root) def __repr__(self) -> str: return f"{self._path}:L{self.line}:{self.character}" def __hash__(self) -> int: return hash((self._path, self.line, self.character)) def __eq__(self, other: object) -> bool: if not isinstance(other, Location): return False return self._path == other._path and self.line == other.line and self.character == other.character def __lt__(self, other: object) -> bool: if not isinstance(other, Location): raise ValueError if self._path == other._path: if self.line == other.line: return (self.character or -1) < (other.character or -1) return (self.line or -1) < (other.line or -1) # line can be None if it's a file-global warning return self._path < other._path class Message: def __init__(self, source: str, code: str, location: Location, message: str): self.source = source self.code = code self.location = location self.message = message def __repr__(self) -> str: return f"{self.source}-{self.code}" def __eq__(self, other: object) -> bool: if not isinstance(other, Message): return False if self.location == other.location: return self.code == other.code return False def __lt__(self, other) -> bool: if self.location == other.location: return self.code < other.code return self.location < other.location def make_tool_error_message( filepath: Union[Path, str], source: str, code: str, message: str, line: int = 0, character: int = 0, module: str = None, function: str = None, ) -> Message: location = Location(path=filepath, module=module, function=function, line=line, character=character) return Message(source=source, code=code, location=location, message=message) prospector-1.10.3/prospector/pathutils.py000066400000000000000000000024321451367460400205760ustar00rootroot00000000000000import os from pathlib import Path def is_python_package(path: Path) -> bool: return path.is_dir() and (path / "__init__.py").exists() def is_python_module(path: Path) -> bool: # TODO: is this too simple? return path.suffix == ".py" def is_virtualenv(path: Path) -> bool: if os.name == "nt": # Windows! clues = ("Scripts", "lib", "include") else: clues = ("bin", "lib", "include") try: # just get the name, iterdir returns absolute paths by default dircontents = [obj.name for obj in path.iterdir()] except (OSError, TypeError): # listdir failed, probably due to path length issues in windows return False if not all(clue in dircontents for clue in clues): # we don't have the 3 directories which would imply # this is a virtualenvironment return False if not all((path / clue).is_dir() for clue in clues): # some of them are not actually directories return False # if we do have all three directories, make sure that it's not # just a coincidence by doing some heuristics on the rest of # the directory if len(dircontents) > 7: # if there are more than 7 things it's probably not a virtualenvironment return False return True prospector-1.10.3/prospector/postfilter.py000066400000000000000000000042731451367460400207610ustar00rootroot00000000000000from pathlib import Path from typing import List from prospector.message import Message from prospector.suppression import get_suppressions def filter_messages(filepaths: List[Path], messages: List[Message]) -> List[Message]: """ This method post-processes all messages output by all tools, in order to filter out any based on the overall output. The main aim currently is to use information about messages suppressed by pylint due to inline comments, and use that to suppress messages from other tools representing the same problem. For example: import banana # pylint:disable=unused-import In this situation, pylint will not warn about an unused import as there is inline configuration to disable the warning. Pyflakes will still raise that error, however, because it does not understand pylint disabling messages. This method uses the information about suppressed messages from pylint to squash the unwanted redundant error from pyflakes and frosted. """ paths_to_ignore, lines_to_ignore, messages_to_ignore = get_suppressions(filepaths, messages) filtered = [] for message in messages: # first get rid of the pylint informational messages relative_message_path = Path(message.location.path) if message.source == "pylint" and message.code in ( "suppressed-message", "file-ignored", ): continue # some files are skipped entirely by messages if relative_message_path in paths_to_ignore: continue # some lines are skipped entirely by messages if relative_message_path in lines_to_ignore: if message.location.line in lines_to_ignore[relative_message_path]: continue # and some lines have only certain messages explicitly ignored if relative_message_path in messages_to_ignore: if message.location.line in messages_to_ignore[relative_message_path]: if message.code in messages_to_ignore[relative_message_path][message.location.line]: continue # otherwise this message was not filtered filtered.append(message) return filtered prospector-1.10.3/prospector/profiles/000077500000000000000000000000001451367460400200315ustar00rootroot00000000000000prospector-1.10.3/prospector/profiles/__init__.py000066400000000000000000000012531451367460400221430ustar00rootroot00000000000000from pathlib import Path AUTO_LOADED_PROFILES = list( Path(*parts) for parts in ( (".landscape.yml",), (".landscape.yaml",), ("landscape.yml",), ("landscape.yaml",), (".prospector.yaml",), (".prospector.yml",), ("prospector.yaml",), ("prospector.yml",), ("prospector", ".prospector.yaml"), ("prospector", ".prospector.yml"), ("prospector", "prospector.yaml"), ("prospector", "prospector.yml"), (".prospector", ".prospector.yaml"), (".prospector", ".prospector.yml"), (".prospector", "prospector.yaml"), (".prospector", "prospector.yml"), ) ) prospector-1.10.3/prospector/profiles/exceptions.py000066400000000000000000000015471451367460400225730ustar00rootroot00000000000000class ProfileNotFound(Exception): def __init__(self, name, profile_path): super().__init__() self.name = name self.profile_path = profile_path def __repr__(self): return "Could not find profile {}; searched in {}".format( self.name, ":".join(self.profile_path), ) class CannotParseProfile(Exception): def __init__(self, filepath, parse_error): super().__init__() self.filepath = filepath self.parse_error = parse_error def get_parse_message(self): return "{}\n on line {} : char {}".format( self.parse_error.problem, self.parse_error.problem_mark.line, self.parse_error.problem_mark.column, ) def __repr__(self): return "Could not parse profile found at %s - it is not valid YAML" % self.filepath prospector-1.10.3/prospector/profiles/profile.py000066400000000000000000000400721451367460400220460ustar00rootroot00000000000000import codecs import json import os import pkgutil from pathlib import Path from typing import Any, Dict, List, Optional, Tuple, Union import yaml from prospector.profiles.exceptions import CannotParseProfile, ProfileNotFound from prospector.tools import DEFAULT_TOOLS, TOOLS BUILTIN_PROFILE_PATH = (Path(__file__).parent / "profiles").absolute() class ProspectorProfile: def __init__(self, name: str, profile_dict: Dict[str, Any], inherit_order: List[str]): self.name = name self.inherit_order = inherit_order self.ignore_paths = _ensure_list(profile_dict.get("ignore-paths", [])) # The 'ignore' directive is an old one which should be deprecated at some point self.ignore_patterns = _ensure_list(profile_dict.get("ignore-patterns", []) + profile_dict.get("ignore", [])) self.output_format = profile_dict.get("output-format") self.output_target = profile_dict.get("output-target") self.autodetect = profile_dict.get("autodetect", True) self.uses = [ uses for uses in _ensure_list(profile_dict.get("uses", [])) if uses in ("django", "celery", "flask") ] self.max_line_length = profile_dict.get("max-line-length") # informational shorthands self.strictness = profile_dict.get("strictness") self.test_warnings = profile_dict.get("test-warnings") self.doc_warnings = profile_dict.get("doc-warnings") self.member_warnings = profile_dict.get("member-warnings") # TODO: this is needed by Landscape but not by prospector; there is probably a better place for it self.requirements = _ensure_list(profile_dict.get("requirements", [])) for tool in TOOLS: tool_conf = profile_dict.get(tool, {}) # set the defaults for everything conf: Dict[str, Any] = {"disable": [], "enable": [], "run": None, "options": {}} # use the "old" tool name conf.update(tool_conf) if self.max_line_length is not None and tool in ("pylint", "pycodestyle"): conf["options"]["max-line-length"] = self.max_line_length setattr(self, tool, conf) def get_disabled_messages(self, tool_name): disable = getattr(self, tool_name)["disable"] enable = getattr(self, tool_name)["enable"] return list(set(disable) - set(enable)) def is_tool_enabled(self, name): enabled = getattr(self, name).get("run") if enabled is not None: return enabled # this is not explicitly enabled or disabled, so use the default return name in DEFAULT_TOOLS def list_profiles(self): # this profile is itself included return [str(profile) for profile in self.inherit_order] def as_dict(self): out = { "ignore-paths": self.ignore_paths, "ignore-patterns": self.ignore_patterns, "output-format": self.output_format, "output-target": self.output_target, "autodetect": self.autodetect, "uses": self.uses, "max-line-length": self.max_line_length, "member-warnings": self.member_warnings, "doc-warnings": self.doc_warnings, "test-warnings": self.test_warnings, "strictness": self.strictness, "requirements": self.requirements, } for tool in TOOLS: out[tool] = getattr(self, tool) return out def as_json(self): return json.dumps(self.as_dict()) def as_yaml(self): return yaml.safe_dump(self.as_dict()) @staticmethod def load( name_or_path: Union[str, Path], profile_path: List[Path], allow_shorthand: bool = True, forced_inherits: Optional[List[str]] = None, ): # First simply load all of the profiles and those that it explicitly inherits from data, inherits = _load_and_merge( name_or_path, profile_path, allow_shorthand, forced_inherits=forced_inherits or [], ) return ProspectorProfile(str(name_or_path), data, inherits) def _is_valid_extension(filename): ext = os.path.splitext(filename)[1] return ext in (".yml", ".yaml") def _load_content_package(name): name_split = name.split(":", 1) module_name = f"prospector_profile_{name_split[0]}" file_names = ( ["prospector.yaml", "prospector.yml"] if len(name_split) == 1 else [f"{name_split[1]}.yaml", f"{name_split[1]}.yaml"] ) data = None used_name = None for file_name in file_names: used_name = f"{module_name}:{file_name}" data = pkgutil.get_data(module_name, file_name) if data is not None: break if data is None: return None try: return yaml.safe_load(data) or {} except yaml.parser.ParserError as parse_error: raise CannotParseProfile(used_name, parse_error) from parse_error def _load_content(name_or_path, profile_path): filename = None optional = False if isinstance(name_or_path, str) and name_or_path.endswith("?"): optional = True name_or_path = name_or_path[:-1] if _is_valid_extension(name_or_path): for path in profile_path: filepath = os.path.join(path, name_or_path) if os.path.exists(filepath): # this is a full path that we can load filename = filepath break else: for path in profile_path: for ext in ("yml", "yaml"): filepath = os.path.join(path, f"{name_or_path}.{ext}") if os.path.exists(filepath): filename = filepath break if filename is None: result = _load_content_package(name_or_path) if result is not None: return result if optional: return {} raise ProfileNotFound(name_or_path, profile_path) with codecs.open(filename) as fct: try: return yaml.safe_load(fct) or {} except yaml.parser.ParserError as parse_error: raise CannotParseProfile(filename, parse_error) from parse_error def _ensure_list(value): if isinstance(value, list): return value return [value] def _simple_merge_dict(priority, base): out = dict(base.items()) out.update(dict(priority.items())) return out def _merge_tool_config(priority, base): out = dict(base.items()) # add options that are missing, but keep existing options from the priority dictionary # TODO: write a unit test for this :-| out["options"] = _simple_merge_dict(priority.get("options", {}), base.get("options", {})) # copy in some basic pieces for key in ("run", "load-plugins"): value = priority.get(key, base.get(key)) if value is not None: out[key] = value # anything enabled in the 'priority' dict is removed # from 'disabled' in the base dict and vice versa base_disabled = base.get("disable") or [] base_enabled = base.get("enable") or [] pri_disabled = priority.get("disable") or [] pri_enabled = priority.get("enable") or [] out["disable"] = list(set(pri_disabled) | (set(base_disabled) - set(pri_enabled))) out["enable"] = list(set(pri_enabled) | (set(base_enabled) - set(pri_disabled))) return out def _merge_profile_dict(priority: dict, base: dict) -> dict: # copy the base dict into our output out = dict(base.items()) for key, value in priority.items(): if key in ( "strictness", "doc-warnings", "test-warnings", "member-warnings", "output-format", "autodetect", "max-line-length", "pep8", ): # some keys are simple values which are overwritten out[key] = value elif key in ( "ignore", "ignore-patterns", "ignore-paths", "uses", "requirements", "python-targets", "output-target", ): # some keys should be appended out[key] = _ensure_list(value) + _ensure_list(base.get(key, [])) elif key in TOOLS: # this is tool config! out[key] = _merge_tool_config(value, base.get(key, {})) return out def _determine_strictness(profile_dict, inherits): for profile in inherits: if profile.startswith("strictness_"): return None, False strictness = profile_dict.get("strictness") if strictness is None: return None, False return ("strictness_%s" % strictness), True def _determine_pep8(profile_dict): pep8 = profile_dict.get("pep8") if pep8 == "full": return "full_pep8", True if pep8 == "none": return "no_pep8", True if isinstance(pep8, dict) and pep8.get("full", False): return "full_pep8", False return None, False def _determine_doc_warnings(profile_dict): doc_warnings = profile_dict.get("doc-warnings") if doc_warnings is None: return None, False return ("doc_warnings" if doc_warnings else "no_doc_warnings"), True def _determine_test_warnings(profile_dict): test_warnings = profile_dict.get("test-warnings") if test_warnings is None: return None, False return (None if test_warnings else "no_test_warnings"), True def _determine_member_warnings(profile_dict): member_warnings = profile_dict.get("member-warnings") if member_warnings is None: return None, False return ("member_warnings" if member_warnings else "no_member_warnings"), True def _determine_implicit_inherits(profile_dict, already_inherits, shorthands_found): # Note: the ordering is very important here - the earlier items # in the list have precedence over the later items. The point of # the doc/test/pep8 profiles is usually to restore items which were # turned off in the strictness profile, so they must appear first. implicit = [ ("pep8", _determine_pep8(profile_dict)), ("docs", _determine_doc_warnings(profile_dict)), ("tests", _determine_test_warnings(profile_dict)), ("strictness", _determine_strictness(profile_dict, already_inherits)), ("members", _determine_member_warnings(profile_dict)), ] inherits = [] for shorthand_name, determined in implicit: if shorthand_name in shorthands_found: continue extra_inherits, shorthand_found = determined if not shorthand_found: continue shorthands_found.add(shorthand_name) if extra_inherits is not None: inherits.append(extra_inherits) return inherits, shorthands_found def _append_profiles(name, profile_path, data, inherit_list, allow_shorthand=False): new_data, new_il, _ = _load_profile(name, profile_path, allow_shorthand=allow_shorthand) data.update(new_data) inherit_list += new_il return data, inherit_list def _load_and_merge( name_or_path: Union[str, Path], profile_path: List[Path], allow_shorthand: bool = True, forced_inherits: List[str] = None, ) -> Tuple[Dict[str, Any], List[str]]: # First simply load all of the profiles and those that it explicitly inherits from data, inherit_list, shorthands_found = _load_profile( str(name_or_path), profile_path, allow_shorthand=allow_shorthand, forced_inherits=forced_inherits or [], ) if allow_shorthand: if "docs" not in shorthands_found: data, inherit_list = _append_profiles("no_doc_warnings", profile_path, data, inherit_list) if "members" not in shorthands_found: data, inherit_list = _append_profiles("no_member_warnings", profile_path, data, inherit_list) if "tests" not in shorthands_found: data, inherit_list = _append_profiles("no_test_warnings", profile_path, data, inherit_list) if "strictness" not in shorthands_found: # if no strictness was specified, then we should manually insert the medium strictness for inherit in inherit_list: if inherit.startswith("strictness_"): break else: data, inherit_list = _append_profiles("strictness_medium", profile_path, data, inherit_list) # Now we merge all of the values together, from 'right to left' (ie, from the # top of the inheritance tree to the bottom). This means that the lower down # values overwrite those from above, meaning that the initially provided profile # has precedence. merged: dict = {} for name in inherit_list[::-1]: priority = data[name] merged = _merge_profile_dict(priority, merged) return merged, inherit_list def _transform_legacy(profile_dict): """ After pep8 was renamed to pycodestyle, this pre-filter just moves profile config blocks using the old name to use the new name, merging if both are specified. Same for pep257->pydocstyle """ out = {} # copy in existing pep8/pep257 using new names to start if "pycodestyle" in profile_dict: out["pycodestyle"] = profile_dict["pycodestyle"] if "pydocstyle" in profile_dict: out["pydocstyle"] = profile_dict["pydocstyle"] # pep8 is tricky as it's overloaded as a tool configuration and a shorthand # first, is this the short "pep8: full" version or a configuration of the # pycodestyle tool using the old name? if "pep8" in profile_dict: pep8conf = profile_dict["pep8"] if isinstance(pep8conf, dict): # merge in with existing config if there is any out["pycodestyle"] = _simple_merge_dict(out.get("pycodestyle", {}), pep8conf) else: # otherwise it's shortform, just copy it in directly out["pep8"] = pep8conf del profile_dict["pep8"] if "pep257" in profile_dict: out["pydocstyle"] = _simple_merge_dict(out.get("pydocstyle", {}), profile_dict["pep257"]) del profile_dict["pep257"] # now just copy the rest in for key, value in profile_dict.items(): if key in ("pycodestyle", "pydocstyle"): # already handled these continue out[key] = value return out def _load_profile( name_or_path, profile_path, shorthands_found=None, already_loaded=None, allow_shorthand=True, forced_inherits=None, ): # recursively get the contents of the basic profile and those it inherits from base_contents = _load_content(name_or_path, profile_path) base_contents = _transform_legacy(base_contents) inherit_order = [name_or_path] shorthands_found = shorthands_found or set() already_loaded = already_loaded or [] already_loaded.append(name_or_path) inherits = _ensure_list(base_contents.get("inherits", [])) if forced_inherits is not None: inherits += forced_inherits # There are some 'shorthand' options in profiles which implicitly mean that we # should inherit from some of prospector's built-in profiles if base_contents.get("allow-shorthand", True) and allow_shorthand: extra_inherits, extra_shorthands = _determine_implicit_inherits(base_contents, inherits, shorthands_found) inherits += extra_inherits shorthands_found |= extra_shorthands contents_dict = {name_or_path: base_contents} for inherit_profile in inherits: if inherit_profile in already_loaded: # we already have this loaded and in the list continue already_loaded.append(inherit_profile) new_cd, new_il, new_sh = _load_profile( inherit_profile, profile_path, shorthands_found, already_loaded, allow_shorthand, ) contents_dict.update(new_cd) inherit_order += new_il shorthands_found |= new_sh # note: a new list is returned here rather than simply using inherit_order to give astroid a # clue about the type of the returned object, as otherwise it can recurse infinitely and crash, # this meaning that prospector does not run on prospector cleanly! return contents_dict, list(inherit_order), shorthands_found prospector-1.10.3/prospector/profiles/profiles/000077500000000000000000000000001451367460400216545ustar00rootroot00000000000000prospector-1.10.3/prospector/profiles/profiles/default.yaml000066400000000000000000000001441451367460400241630ustar00rootroot00000000000000strictness: medium doc-warnings: false test-warnings: false autodetect: true member-warnings: false prospector-1.10.3/prospector/profiles/profiles/doc_warnings.yaml000066400000000000000000000000601451367460400252110ustar00rootroot00000000000000allow-shorthand: false pydocstyle: run: true prospector-1.10.3/prospector/profiles/profiles/flake8.yaml000066400000000000000000000002341451367460400237110ustar00rootroot00000000000000allow-shorthand: false pylint: run: false pyroma: run: false frosted: run: false vulture: run: false dodgy: run: false pep257: run: false prospector-1.10.3/prospector/profiles/profiles/full_pep8.yaml000066400000000000000000000015511451367460400244400ustar00rootroot00000000000000allow-shorthand: false pycodestyle: run: true enable: - E101 - E111 - E112 - E113 - E114 - E115 - E116 - E121 - E122 - E123 - E124 - E125 - E126 - E127 - E128 - E201 - E202 - E203 - E211 - E221 - E222 - E223 - E224 - E225 - E227 - E228 - E231 - E251 - E261 - E262 - E265 - E266 - E271 - E272 - E273 - E274 - E301 - E302 - E303 - E304 - E401 - E402 - E501 - E502 - E701 - E702 - E703 - E704 - E711 - E712 - E721 - E731 - E901 - E902 - W191 - W291 - W292 - W293 - W391 - W503 - W601 - W602 - W603 - W604 - N801 - N802 - N803 - N804 - N805 - N806 - N811 - N812 - N813 - N814 prospector-1.10.3/prospector/profiles/profiles/member_warnings.yaml000066400000000000000000000001221451367460400257120ustar00rootroot00000000000000allow-shorthand: false pylint: enable: - no-member - no-name-in-module prospector-1.10.3/prospector/profiles/profiles/no_doc_warnings.yaml000066400000000000000000000004171451367460400257130ustar00rootroot00000000000000allow-shorthand: false ignore-patterns: - ^docs?/ pylint: disable: - empty-docstring - missing-docstring - missing-module-docstring - missing-function-docstring - missing-class-docstring frosted: disable: - E401 pydocstyle: run: false prospector-1.10.3/prospector/profiles/profiles/no_member_warnings.yaml000066400000000000000000000001231451367460400264070ustar00rootroot00000000000000allow-shorthand: false pylint: disable: - no-member - no-name-in-module prospector-1.10.3/prospector/profiles/profiles/no_pep8.yaml000066400000000000000000000005341451367460400241120ustar00rootroot00000000000000allow-shorthand: false pylint: disable: - line-too-long - too-many-lines - trailing-whitespace - missing-final-newline - unnecessary-semicolon - multiple-statements - C0322 - C0323 - C0324 pycodestyle: run: false # TODO: remove this in prospector 2.0 - here for legacy backwards compat pep8: run: false prospector-1.10.3/prospector/profiles/profiles/no_test_warnings.yaml000066400000000000000000000002571451367460400261270ustar00rootroot00000000000000allow-shorthand: false ignore-patterns: - ^tests?/? - /tests?(/|$) - .*/tests(/|$) - (^|/)test_[_a-zA-Z0-9]+.py$ - (^|/)[_a-zA-Z0-9]+_tests?.py$ - (^|/)tests?.py prospector-1.10.3/prospector/profiles/profiles/strictness_high.yaml000066400000000000000000000014621451367460400257430ustar00rootroot00000000000000allow-shorthand: false inherits: - strictness_veryhigh ignore-patterns: - ^setup.py$ pylint: disable: - trailing-whitespace - missing-final-newline - too-few-public-methods - too-many-public-methods - deprecated-lambda - bad-builtin - star-args - global-statement - assignment-from-none - unused-format-string-key - W5103 options: max-locals: 15 max-returns: 6 max-branches: 15 max-statements: 60 min-public-methods: 1 max-public-methods: 20 max-line-length: 99 pycodestyle: options: max-line-length: 99 disable: - E304 - E265 - E266 - W291 - W292 - W391 - N811 - N812 - N813 - N814 pyroma: disable: - PYR15 - PYR18 - PYR17 pydocstyle: disable: - D400 - D401 prospector-1.10.3/prospector/profiles/profiles/strictness_low.yaml000066400000000000000000000020521451367460400256210ustar00rootroot00000000000000allow-shorthand: false inherits: - strictness_medium pylint: disable: - blacklisted-name - missing-docstring - line-too-long - E1103 - duplicate-code - too-many-branches - too-many-arguments - too-many-locals - too-many-statements - R0924 - unnecessary-pass - unnecessary-lambda - duplicate-key - eval-used - lost-exception - bad-staticmethod-argument - protected-access - signature-differs - lowercase-l-suffix - deprecated-module - global-variable-not-assigned - unused-import - unused-variable - unused-argument - unused-wildcard-import - redefined-builtin - redefine-in-handler - bare-except - logging-not-lazy - bad-format-string-key - anomalous-unicode-escape-in-string - W5101 pyflakes: disable: - F401 - F841 frosted: disable: - E101 - E307 - W101 pycodestyle: disable: - E501 - E711 - E712 - E721 - W503 pyroma: disable: - PYR06 - PYR09 pydocstyle: run: false prospector-1.10.3/prospector/profiles/profiles/strictness_medium.yaml000066400000000000000000000034541451367460400263070ustar00rootroot00000000000000allow-shorthand: false inherits: - strictness_high pylint: disable: - invalid-name - bad-classmethod-argument - bad-mcs-method-argument - bad-mcs-classmethod-argument - too-many-lines - multiple-statements - C0322 - C0323 - C0324 - superfluous-parens - bad-whitespace - bad-continuation - no-self-argument - E1103 - I0014 - no-self-use - too-many-ancestors - too-many-instance-attributes - too-many-return-statements - abstract-class-little-used - exec-used - attribute-defined-outside-init - abstract-method - super-init-not-called - no-init - unnecessary-semicolon - wildcard-import - relative-import - global-variable-undefined - redefined-outer-name - W0701 - broad-except - pointless-except - W0713 - property-on-old-class - anomalous-backslash-in-string - wrong-import-order - ungrouped-imports options: max-line-length: 159 mccabe: options: max-complexity: 15 pyflakes: disable: - F403 - F810 frosted: disable: - E103 - E306 pycodestyle: disable: - E111 - E121 - E122 - E123 - E124 - E125 - E126 - E127 - E128 - E133 - E201 - E202 - E203 - E211 - E221 - E222 - E223 - E224 - E225 - E226 - E227 - E228 - E231 - E241 - E242 - E251 - E261 - E262 - E271 - E272 - E273 - E274 - E301 - E302 - E303 - E401 - E402 - E502 - E701 - E702 - E703 - E731 - N801 - N802 - N803 - N804 - N805 - N806 - W191 - W293 options: max-line-length: 159 pyroma: disable: - PYR04 pydocstyle: disable: - D100 - D101 - D102 - D103 prospector-1.10.3/prospector/profiles/profiles/strictness_none.yaml000066400000000000000000000002271451367460400257610ustar00rootroot00000000000000# Dummy profile used for 'strictness: none', which is necessary to # prevent a default of medium if no strictness is specified. allow-shorthand: false prospector-1.10.3/prospector/profiles/profiles/strictness_veryhigh.yaml000066400000000000000000000011221451367460400266420ustar00rootroot00000000000000# This will enable almost every single warning allow-shorthand: false ignore-patterns: - (^|/)\..+ pylint: disable: - fixme - bad-continuation options: max-locals: 15 max-returns: 6 max-branches: 12 max-statements: 50 max-parents: 7 max-attributes: 7 min-public-methods: 2 max-public-methods: 20 max-module-lines: 1000 max-line-length: 79 mccabe: options: max-complexity: 10 pycodestyle: options: max-line-length: 79 single-line-if-stmt: n pyroma: disable: - PYR19 - PYR16 pydocstyle: disable: - D000 prospector-1.10.3/prospector/profiles/profiles/strictness_verylow.yaml000066400000000000000000000015011451367460400265250ustar00rootroot00000000000000allow-shorthand: false inherits: - strictness_low pylint: disable: - empty-docstring - old-style-class - notimplemented-raised - missing-module-attribute - super-on-old-class - no-member - not-callable - assignment-from-no-return - mixed-format-string - abstract-class-not-used - interface-not-implemented - pointless-statement - pointless-string-statement - expression-not-assigned - W0121 - assert-on-tuple - attribute-defined-outside-init - arguments-differ - non-iterator-returned - W0331 - W0333 - reimported - global-at-module-level - unbalanced-tuple-unpacking - nonstandard-exception - W0712 mccabe: options: max-complexity: 25 pycodestyle: disable: - W601 - W602 - W603 - W604 - W293 prospector-1.10.3/prospector/profiles/profiles/test_warnings.yaml000066400000000000000000000000271451367460400254260ustar00rootroot00000000000000allow-shorthand: false prospector-1.10.3/prospector/run.py000066400000000000000000000176461451367460400174020ustar00rootroot00000000000000import codecs import os.path import sys import warnings from datetime import datetime from pathlib import Path from typing import TextIO from prospector import blender, postfilter, tools from prospector.compat import is_relative_to from prospector.config import ProspectorConfig from prospector.config import configuration as cfg from prospector.exceptions import FatalProspectorException from prospector.finder import FileFinder from prospector.formatters import FORMATTERS, Formatter from prospector.message import Location, Message from prospector.tools import DEPRECATED_TOOL_NAMES from prospector.tools.utils import CaptureOutput class Prospector: def __init__(self, config: ProspectorConfig): self.config = config self.summary = None self.messages = config.messages def process_messages(self, found_files, messages): if self.config.blending: messages = blender.blend(messages) if self.config.legacy_tool_names: updated = [] new_names = {v: k for k, v in DEPRECATED_TOOL_NAMES.items()} for msg in messages: msg.source = new_names.get(msg.source, msg.source) updated.append(msg) messages = updated return postfilter.filter_messages(found_files.python_modules, messages) def execute(self): deprecated_names = self.config.replace_deprecated_tool_names() summary = { "started": datetime.now(), } summary.update(self.config.get_summary_information()) paths = [Path(p) for p in self.config.paths] found_files = FileFinder(*paths, exclusion_filters=[self.config.make_exclusion_filter()]) messages = [] # see if any old tool names are run for deprecated_name in deprecated_names: loc = Location(self.config.workdir, None, None, None, None) new_name = DEPRECATED_TOOL_NAMES[deprecated_name] msg = ( f"Tool {deprecated_name} has been renamed to {new_name}. " f"The old name {deprecated_name} is now deprecated and will be removed in Prospector 2.0. " f"Please update your prospector configuration." ) message = Message( "prospector", "Deprecation", loc, message=msg, ) messages.append(message) warnings.warn(msg, category=DeprecationWarning) # Run the tools for tool in self.config.get_tools(found_files): for name, cls in tools.TOOLS.items(): if cls == tool.__class__: toolname = name break else: toolname = "Unknown" try: # Tools can output to stdout/stderr in unexpected places, for example, # pydocstyle emits warnings about __all__ and as pyroma exec's the setup.py # file, it will execute any print statements in that, etc etc... with CaptureOutput(hide=not self.config.direct_tool_stdout) as capture: messages += tool.run(found_files) if self.config.include_tool_stdout: loc = Location(self.config.workdir, None, None, None, None) if capture.get_hidden_stderr(): msg = f"stderr from {toolname}:\n{capture.get_hidden_stderr()}" messages.append(Message(toolname, "hidden-output", loc, message=msg)) if capture.get_hidden_stdout(): msg = f"stdout from {toolname}:\n{capture.get_hidden_stdout()}" messages.append(Message(toolname, "hidden-output", loc, message=msg)) except FatalProspectorException as fatal: sys.stderr.write(str(fatal)) sys.exit(2) except Exception as ex: # pylint:disable=broad-except if self.config.die_on_tool_error: raise FatalProspectorException(f"Tool {toolname} failed to run.") from ex loc = Location(self.config.workdir, None, None, None, None) msg = ( f"Tool {toolname} failed to run " f"(exception was raised, re-run prospector with -X to see the stacktrace)" ) message = Message( toolname, "failure", loc, message=msg, ) messages.append(message) messages = self.process_messages(found_files, messages) summary["message_count"] = len(messages) summary["completed"] = datetime.now() delta = summary["completed"] - summary["started"] summary["time_taken"] = "%0.2f" % delta.total_seconds() external_config = [] for tool, configured_by in self.config.configured_by.items(): if configured_by is not None: external_config.append((tool, configured_by)) if len(external_config) > 0: summary["external_config"] = ", ".join(["%s: %s" % info for info in external_config]) self.summary = summary self.messages = self.messages + messages def get_summary(self): return self.summary def get_messages(self): return self.messages def print_messages(self): output_reports = self.config.get_output_report() for report in output_reports: output_format, output_files = report self.summary["formatter"] = output_format relative_to = None # use relative paths by default unless explicitly told otherwise (with a --absolute-paths flag) # or if some paths passed to prospector are not relative to the CWD if not self.config.absolute_paths and all( is_relative_to(p, self.config.workdir) for p in self.config.paths ): relative_to = self.config.workdir formatter = FORMATTERS[output_format](self.summary, self.messages, self.config.profile, relative_to) if not output_files and not self.config.quiet: self.write_to(formatter, sys.stdout) for output_file in output_files: with codecs.open(output_file, "w+") as target: self.write_to(formatter, target) def write_to(self, formatter: Formatter, target: TextIO): # Produce the output target.write( formatter.render( summary=not self.config.messages_only, messages=not self.config.summary_only, profile=self.config.show_profile, ) ) target.write("\n") def get_parser(): """ This is a helper method to return an argparse parser, to be used with the Sphinx argparse plugin for documentation. """ manager = cfg.build_manager() source = cfg.build_command_line_source(prog="prospector", description=None) return source.build_parser(manager.settings, None) def main(): # Get our configuration config = ProspectorConfig() paths = config.paths if len(paths) > 1 and not all(os.path.isfile(path) for path in paths): sys.stderr.write("\nIn multi-path mode, all inputs must be files, " "not directories.\n\n") get_parser().print_usage() sys.exit(2) # Make it so prospector = Prospector(config) prospector.execute() prospector.print_messages() if config.exit_with_zero_on_success(): # if we ran successfully, and the user wants us to, then we'll # exit cleanly sys.exit(0) # otherwise, finding messages is grounds for exiting with an error # code, to make it easier for bash scripts and similar situations # to know if any errors have been found. if len(prospector.get_messages()) > 0: sys.exit(1) sys.exit(0) if __name__ == "__main__": main() prospector-1.10.3/prospector/suppression.py000066400000000000000000000105631451367460400211570ustar00rootroot00000000000000r""" Each tool has its own method of ignoring errors and warnings. For example, pylint requires a comment of the form # pylint disable= PEP8 will not warn on lines with # noqa Additionally, flake8 follows that convention for pyflakes errors, but pyflakes itself does not. Finally, an entire file is ignored by flake8 if this line is found in the file: # flake8\: noqa (the \ is needed to stop prospector ignoring this file :)) This module's job is to attempt to collect all of these methods into a single coherent list of error suppression locations. """ import re import warnings from collections import defaultdict from pathlib import Path from typing import List from prospector import encoding from prospector.exceptions import FatalProspectorException from prospector.message import Message _FLAKE8_IGNORE_FILE = re.compile(r"flake8[:=]\s*noqa", re.IGNORECASE) _PEP8_IGNORE_LINE = re.compile(r"#\s+noqa", re.IGNORECASE) _PYLINT_SUPPRESSED_MESSAGE = re.compile(r"^Suppressed \'([a-z0-9-]+)\' \(from line \d+\)$") def get_noqa_suppressions(file_contents): """ Finds all pep8/flake8 suppression messages :param file_contents: A list of file lines :return: A pair - the first is whether to ignore the whole file, the second is a set of (0-indexed) line numbers to ignore. """ ignore_whole_file = False ignore_lines = set() for line_number, line in enumerate(file_contents): if _FLAKE8_IGNORE_FILE.search(line): ignore_whole_file = True if _PEP8_IGNORE_LINE.search(line): ignore_lines.add(line_number + 1) return ignore_whole_file, ignore_lines _PYLINT_EQUIVALENTS = { # TODO: blending has this info already? "unused-import": ( ("pyflakes", "FL0001"), ("frosted", "E101"), ) } def _parse_pylint_informational(messages: List[Message]): ignore_files = set() ignore_messages: dict = defaultdict(lambda: defaultdict(list)) for message in messages: if message.source == "pylint": if message.code == "suppressed-message": # this is a message indicating that a message was raised # by pylint but suppressed by configuration in the file match = _PYLINT_SUPPRESSED_MESSAGE.match(message.message) if not match: raise FatalProspectorException(f"Could not parsed suppressed message from {message.message}") suppressed_code = match.group(1) line_dict = ignore_messages[message.location.path] line_dict[message.location.line].append(suppressed_code) elif message.code == "file-ignored": ignore_files.add(message.location.path) return ignore_files, ignore_messages def get_suppressions(filepaths: List[Path], messages): """ Given every message which was emitted by the tools, and the list of files to inspect, create a list of files to ignore, and a map of filepath -> line-number -> codes to ignore """ paths_to_ignore = set() lines_to_ignore: dict = defaultdict(set) messages_to_ignore: dict = defaultdict(lambda: defaultdict(set)) # first deal with 'noqa' style messages for filepath in filepaths: try: file_contents = encoding.read_py_file(filepath).split("\n") except encoding.CouldNotHandleEncoding as err: # TODO: this output will break output formats such as JSON warnings.warn(f"{err.path}: {err.__cause__}", ImportWarning) continue ignore_file, ignore_lines = get_noqa_suppressions(file_contents) if ignore_file: paths_to_ignore.add(filepath) lines_to_ignore[filepath] |= ignore_lines # now figure out which messages were suppressed by pylint pylint_ignore_files, pylint_ignore_messages = _parse_pylint_informational(messages) paths_to_ignore |= pylint_ignore_files for filepath, line in pylint_ignore_messages.items(): for line_number, codes in line.items(): for code in codes: messages_to_ignore[filepath][line_number].add(("pylint", code)) if code in _PYLINT_EQUIVALENTS: for equivalent in _PYLINT_EQUIVALENTS[code]: messages_to_ignore[filepath][line_number].add(equivalent) return paths_to_ignore, lines_to_ignore, messages_to_ignore prospector-1.10.3/prospector/tools/000077500000000000000000000000001451367460400173465ustar00rootroot00000000000000prospector-1.10.3/prospector/tools/__init__.py000066400000000000000000000051331451367460400214610ustar00rootroot00000000000000import importlib from prospector.exceptions import FatalProspectorException from prospector.tools.base import ToolBase from prospector.tools.dodgy import DodgyTool from prospector.tools.mccabe import McCabeTool from prospector.tools.pycodestyle import PycodestyleTool from prospector.tools.pydocstyle import PydocstyleTool from prospector.tools.pyflakes import PyFlakesTool from prospector.tools.pylint import PylintTool def _tool_not_available(name, install_option_name): class NotAvailableTool(ToolBase): """ Dummy tool class to return when a particular dependency is not found (such as mypy, or bandit) for an optional tool. This does not error immediately since the tool is optional, but rather if the user tries to run prospector and specifies using the tool at which point an error is raised. """ def configure(self, prospector_config, found_files): pass def run(self, _): raise FatalProspectorException( f"\nCannot run tool {name} as support was not installed.\n" f"Please install by running 'pip install prospector[{install_option_name}]'\n\n" ) return NotAvailableTool def _optional_tool(name, package_name=None, tool_class_name=None, install_option_name=None): package_name = "prospector.tools.%s" % (package_name or name) tool_class_name = tool_class_name or f"{name.title()}Tool" install_option_name = install_option_name or f"with_{name}" try: tool_package = __import__(package_name, fromlist=[tool_class_name]) except ImportError: tool_class = _tool_not_available(name, install_option_name) else: tool_class = getattr(tool_package, tool_class_name) return tool_class def _profile_validator_tool(*args, **kwargs): # bit of a hack to avoid a cyclic import... mdl = importlib.import_module("prospector.tools.profile_validator") return mdl.ProfileValidationTool(*args, **kwargs) TOOLS = { "dodgy": DodgyTool, "mccabe": McCabeTool, "pyflakes": PyFlakesTool, "pycodestyle": PycodestyleTool, "pylint": PylintTool, "pydocstyle": PydocstyleTool, "profile-validator": _profile_validator_tool, "vulture": _optional_tool("vulture"), "pyroma": _optional_tool("pyroma"), "pyright": _optional_tool("pyright"), "mypy": _optional_tool("mypy"), "bandit": _optional_tool("bandit"), } DEFAULT_TOOLS = ( "dodgy", "mccabe", "pyflakes", "pycodestyle", "pylint", "pydocstyle", "profile-validator", ) DEPRECATED_TOOL_NAMES = {"pep8": "pycodestyle", "pep257": "pydocstyle"} prospector-1.10.3/prospector/tools/bandit/000077500000000000000000000000001451367460400206075ustar00rootroot00000000000000prospector-1.10.3/prospector/tools/bandit/__init__.py000066400000000000000000000042441451367460400227240ustar00rootroot00000000000000from bandit.cli.main import _get_profile, _init_extensions from bandit.core.config import BanditConfig from bandit.core.constants import RANKING from bandit.core.manager import BanditManager from prospector.message import Location, Message from prospector.tools.base import ToolBase class BanditTool(ToolBase): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.manager = None self.profile = None self.config_file = None self.agg_type = "file" self.severity = 0 self.confidence = 0 def configure(self, prospector_config, _): options = prospector_config.tool_options("bandit") if "profile" in options: self.profile = options["profile"] if "config" in options: self.config_file = options["config"] if "severity" in options: self.severity = options["severity"] if not 0 <= self.severity <= 2: raise ValueError(f"severity {self.severity!r} must be between 0 and 2") if "confidence" in options: self.confidence = options["confidence"] if not 0 <= self.confidence <= 2: raise ValueError(f"confidence {self.confidence!r} must be between 0 and 2") b_conf = BanditConfig(config_file=self.config_file) profile = _get_profile(b_conf, self.profile, self.config_file) extension_mgr = _init_extensions() extension_mgr.validate_profile(profile) self.manager = BanditManager(b_conf, None, profile=profile) def run(self, found_files): self.manager.files_list = sorted(found_files.files) self.manager.exclude_files = [] if not self.manager.b_ts.tests: raise ValueError("No test will run for bandit") self.manager.run_tests() results = self.manager.get_issue_list(sev_level=RANKING[self.severity], conf_level=RANKING[self.confidence]) messages = [] for result in results: loc = Location(result.fname, None, "", int(result.lineno), 0) msg = Message("bandit", result.test_id, loc, result.text) messages.append(msg) return messages prospector-1.10.3/prospector/tools/base.py000066400000000000000000000030471451367460400206360ustar00rootroot00000000000000from abc import ABC, abstractmethod from typing import Iterable, List, Optional, Tuple from prospector.message import Message class ToolBase(ABC): @abstractmethod def configure(self, prospector_config, found_files) -> Tuple[str, Optional[Iterable[Message]]]: """ Tools have their own way of being configured from configuration files on the current path - for example, a .pep8rc file. Prospector will use its own configuration settings unless this method discovers some tool-specific configuration that should be used instead. :return: A tuple: the first element is a string indicating how or where this tool was configured from. For example, this can be a path to the .pylintrc file used, if used. None means that prospector defaults were used. The second element should be an iterable of Message objects representing any issues which were found when trying to load configuration - for example, bad values in a .pylintrc file. It is also possible to simply return None if neither value is useful. """ raise NotImplementedError @abstractmethod def run(self, found_files) -> List[Message]: """ Actually run the tool and collect the various messages emitted by the tool. It is expected that this will convert whatever output of the tool into the standard prospector Message and Location objects. """ raise NotImplementedError prospector-1.10.3/prospector/tools/dodgy/000077500000000000000000000000001451367460400204545ustar00rootroot00000000000000prospector-1.10.3/prospector/tools/dodgy/__init__.py000066400000000000000000000027111451367460400225660ustar00rootroot00000000000000import mimetypes from pathlib import Path from dodgy.checks import check_file_contents from prospector.encoding import CouldNotHandleEncoding, read_py_file from prospector.finder import FileFinder from prospector.message import Location, Message from prospector.tools.base import ToolBase def module_from_path(path: Path): # TODO hacky... return ".".join(path.parts[1:-1] + (path.stem,)) class DodgyTool(ToolBase): def configure(self, prospector_config, found_files): # empty: just implementing to satisfy the ABC contract pass def run(self, found_files: FileFinder): warnings = [] for filepath in found_files.files: mimetype = mimetypes.guess_type(str(filepath.absolute())) if mimetype[0] is None or not mimetype[0].startswith("text/") or mimetype[1] is not None: continue try: contents = read_py_file(filepath) except CouldNotHandleEncoding: continue for line, code, message in check_file_contents(contents): warnings.append({"line": line, "code": code, "message": message, "path": filepath}) messages = [] for warning in warnings: path = warning["path"] loc = Location(path, module_from_path(path), "", warning["line"], 0) msg = Message("dodgy", warning["code"], loc, warning["message"]) messages.append(msg) return messages prospector-1.10.3/prospector/tools/exceptions.py000066400000000000000000000002521451367460400221000ustar00rootroot00000000000000class BadToolConfig(Exception): def __init__(self, tool_name: str, message: str): super().__init__(f"Bad option value found for tool {tool_name}: {message}") prospector-1.10.3/prospector/tools/mccabe/000077500000000000000000000000001451367460400205605ustar00rootroot00000000000000prospector-1.10.3/prospector/tools/mccabe/__init__.py000066400000000000000000000055471451367460400227040ustar00rootroot00000000000000import ast from mccabe import PathGraphingAstVisitor from prospector.encoding import CouldNotHandleEncoding, read_py_file from prospector.message import Location, Message, make_tool_error_message from prospector.tools.base import ToolBase __all__ = ("McCabeTool",) class McCabeTool(ToolBase): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.ignore_codes = () self.max_complexity = 10 def configure(self, prospector_config, _): self.ignore_codes = prospector_config.get_disabled_messages("mccabe") options = prospector_config.tool_options("mccabe") if "max-complexity" in options: self.max_complexity = options["max-complexity"] def run(self, found_files): messages = [] for code_file in found_files.python_modules: try: contents = read_py_file(code_file) tree = ast.parse( contents, filename=code_file, ) except CouldNotHandleEncoding as err: messages.append( make_tool_error_message( code_file, "mccabe", "MC0000", message=f"Could not handle the encoding of this file: {err.encoding}", ) ) continue except SyntaxError as err: messages.append( make_tool_error_message( code_file, "mccabe", "MC0000", line=err.lineno, character=err.offset, message="Syntax Error", ) ) continue except TypeError: messages.append(make_tool_error_message(code_file, "mccabe", "MC0000", message="Unable to parse file")) continue visitor = PathGraphingAstVisitor() visitor.preorder(tree, visitor) for graph in visitor.graphs.values(): complexity = graph.complexity() if complexity > self.max_complexity: location = Location( path=code_file, module=None, function=graph.entity, line=graph.lineno, character=0 ) message = Message( source="mccabe", code="MC0001", location=location, message=f"{graph.entity} is too complex ({complexity})", ) messages.append(message) return self.filter_messages(messages) def filter_messages(self, messages): return [message for message in messages if message.code not in self.ignore_codes] prospector-1.10.3/prospector/tools/mypy/000077500000000000000000000000001451367460400203445ustar00rootroot00000000000000prospector-1.10.3/prospector/tools/mypy/__init__.py000066400000000000000000000104171451367460400224600ustar00rootroot00000000000000from multiprocessing import Process, Queue from mypy import api from prospector.message import Location, Message from prospector.tools import ToolBase __all__ = ("MypyTool",) from prospector.tools.exceptions import BadToolConfig LIST_OPTIONS = ["allow", "check", "disallow", "no-check", "no-warn", "warn"] VALID_OPTIONS = LIST_OPTIONS + [ "use-dmypy", "strict", "follow-imports", "ignore-missing-imports", "implicit-optional", "strict-optional", "platform", "python-2-mode", "python-version", "namespace-packages", ] def format_message(message): try: (path, line, char, err_type, err_msg) = message.split(":", 4) line = int(line) character = int(char) except ValueError: try: (path, line, err_type, err_msg) = message.split(":", 3) line = int(line) character = None except ValueError: (path, err_type, err_msg) = message.split(":", 2) line = 0 character = None location = Location( path=path, module=None, function=None, line=line, character=character, ) return Message( source="mypy", code=err_type.lstrip(" "), location=location, message=err_msg.lstrip(" "), ) def _run_in_subprocess(q, cmd, paths): """ This function exists only to be called by multiprocessing.Process as using lambda is forbidden """ q.put(cmd(paths)) class MypyTool(ToolBase): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.checker = api self.options = ["--show-column-numbers", "--no-error-summary"] self.use_dmypy = False def configure(self, prospector_config, _): options = prospector_config.tool_options("mypy") for option_key in options.keys(): if option_key not in VALID_OPTIONS: url = "https://github.com/PyCQA/prospector/blob/master/prospector/tools/mypy/__init__.py" raise BadToolConfig( "mypy", f"Option {option_key} is not valid. " f"See the list of possible options: {url}" ) self.use_dmypy = options.get("use-dmypy", False) strict = options.get("strict", False) follow_imports = options.get("follow-imports", "normal") ignore_missing_imports = options.get("ignore-missing-imports", False) implict_optional = options.get("implict-optional", False) platform = options.get("platform", None) python_2_mode = options.get("python-2-mode", False) python_version = options.get("python-version", None) strict_optional = options.get("strict-optional", False) namespace_packages = options.get("namespace-packages", False) self.options.append(f"--follow-imports={follow_imports}") if strict: self.options.append("--strict") if ignore_missing_imports: self.options.append("--ignore-missing-imports") if implict_optional: self.options.append("--implict-optional") if platform: self.options.append(f"--platform {platform}") if python_2_mode: self.options.append("--py2") if python_version: self.options.append(f"--python-version {python_version}") if strict_optional: self.options.append("--strict-optional") if namespace_packages: self.options.append("--namespace-packages") for list_option in LIST_OPTIONS: for entry in options.get(list_option, []): self.options.append(f"--{list_option}-{entry}") def run(self, found_files): paths = [str(path) for path in found_files.python_modules] paths.extend(self.options) if self.use_dmypy: # Due to dmypy messing with stdout/stderr we call it in a separate # process q = Queue(1) p = Process(target=_run_in_subprocess, args=(q, self.checker.run_dmypy, ["run", "--"] + paths)) p.start() result = q.get() p.join() else: result = self.checker.run(paths) report, _ = result[0], result[1:] # noqa return [format_message(message) for message in report.splitlines()] prospector-1.10.3/prospector/tools/profile_validator/000077500000000000000000000000001451367460400230535ustar00rootroot00000000000000prospector-1.10.3/prospector/tools/profile_validator/__init__.py000066400000000000000000000173721451367460400251760ustar00rootroot00000000000000import re from pathlib import Path try: # Python >= 3.11 import re._constants as sre_constants except ImportError: import sre_constants import yaml from prospector.finder import FileFinder from prospector.message import Location, Message from prospector.profiles import AUTO_LOADED_PROFILES from prospector.tools import DEPRECATED_TOOL_NAMES, TOOLS, ToolBase, pyflakes PROFILE_IS_EMPTY = "profile-is-empty" CONFIG_SETTING_SHOULD_BE_LIST = "should-be-list" CONFIG_UNKNOWN_SETTING = "unknown-setting" CONFIG_SETTING_MUST_BE_INTEGER = "should-be-int" CONFIG_SETTING_MUST_BE_BOOL = "should-be-bool" CONFIG_INVALID_VALUE = "invalid-value" CONFIG_INVALID_REGEXP = "invalid-regexp" CONFIG_DEPRECATED_SETTING = "deprecated" CONFIG_DEPRECATED_CODE = "deprecated-tool-code" __all__ = ("ProfileValidationTool",) def _tool_names(with_deprecated: bool = True): tools = list(TOOLS) if with_deprecated: tools += DEPRECATED_TOOL_NAMES.keys() return tools class ProfileValidationTool(ToolBase): LIST_SETTINGS = ("inherits", "uses", "ignore", "ignore-paths", "ignore-patterns") BOOL_SETTINGS = ("doc-warnings", "test-warnings", "autodetect") OTHER_SETTINGS = ( "strictness", "max-line-length", "output-format", "output-target", "member-warnings", "pep8", # bit of a grim hack; prospector does not use the following but Landscape does: # TODO: think of a better way to avoid Landscape-specific config leaking into prospector "python-targets", ) ALL_SETTINGS = LIST_SETTINGS + BOOL_SETTINGS + OTHER_SETTINGS def __init__(self): self.to_check = set(AUTO_LOADED_PROFILES) self.ignore_codes = () def configure(self, prospector_config, found_files): for profile in prospector_config.config.profiles: self.to_check.add(profile) self.ignore_codes = prospector_config.get_disabled_messages("profile-validator") def validate(self, filepath: Path): # noqa # pylint: disable=too-many-locals # TODO: this should be broken down into smaller pieces messages = [] with filepath.open() as profile_file: _file_contents = profile_file.read() parsed = yaml.safe_load(_file_contents) raw_contents = _file_contents.split("\n") def add_message(code, message, setting): if code in self.ignore_codes: return line = -1 for number, fileline in enumerate(raw_contents): if setting in fileline: line = number + 1 break location = Location(filepath, None, None, line, 0, False) message = Message("profile-validator", code, location, message) messages.append(message) if parsed is None: # this happens if a completely empty profile is found add_message( PROFILE_IS_EMPTY, f"{filepath} is a completely empty profile", "entire-file", ) return messages for setting in ProfileValidationTool.BOOL_SETTINGS: if not isinstance(parsed.get(setting, False), bool): add_message( CONFIG_SETTING_MUST_BE_BOOL, f'"{setting}" should be true or false', setting, ) if not isinstance(parsed.get("max-line-length", 0), int): add_message( CONFIG_SETTING_MUST_BE_INTEGER, '"max-line-length" should be an integer', "max-line-length", ) if "strictness" in parsed: possible_strictness = ("veryhigh", "high", "medium", "low", "verylow", "none") if parsed["strictness"] not in possible_strictness: _joined = ", ".join(possible_strictness) add_message( CONFIG_INVALID_VALUE, f'"strictness" must be one of {_joined}', "strictness", ) if "uses" in parsed: possible_libs = ("django", "celery", "flask") parsed_list = parsed["uses"] if isinstance(parsed["uses"], list) else [parsed["uses"]] for uses in parsed_list: if uses not in possible_libs: _joined = ", ".join(possible_libs) add_message( CONFIG_INVALID_VALUE, f'"{uses}" is not valid for "uses", must be one of {_joined}', uses, ) if "ignore" in parsed: add_message( CONFIG_DEPRECATED_SETTING, '"ignore" is deprecated, please update to use "ignore-patterns" instead', "ignore", ) if "python-targets" in parsed: python_targets = ( parsed["python-targets"] if isinstance(parsed["python-targets"], list) else [parsed["python-targets"]] ) for target in python_targets: if str(target) not in ("2", "3"): add_message( CONFIG_INVALID_VALUE, f'"{target}" is not valid for "python-targets", must be either 2 or 3', str(target), ) for pattern in parsed.get("ignore-patterns", []): try: re.compile(pattern) except sre_constants.error: add_message(CONFIG_INVALID_REGEXP, "Invalid regular expression", pattern) for key in ProfileValidationTool.LIST_SETTINGS: if key not in parsed: continue if not isinstance(parsed[key], (tuple, list)): add_message(CONFIG_SETTING_SHOULD_BE_LIST, f'"{key}" should be a list', key) for key in parsed.keys(): if key not in ProfileValidationTool.ALL_SETTINGS and key not in _tool_names(): add_message( CONFIG_UNKNOWN_SETTING, f'"{key}" is not a valid prospector setting', key, ) if "pep257" in parsed: add_message( CONFIG_DEPRECATED_CODE, "pep257 tool has been renamed to 'pydocstyle'. " "The name pep257 will be removed in prospector 2.0+.", "pep257", ) if "pep8" in parsed: pep8val = parsed["pep8"] if isinstance(pep8val, dict): add_message( CONFIG_DEPRECATED_CODE, "pep8 tool has been renamed to 'pycodestyle'. " "Using pep8 to configure the tool will be removed in prospector 2.0+.", "pep8", ) elif pep8val not in ("full", "none"): add_message( CONFIG_UNKNOWN_SETTING, f"{pep8val} is not a valid setting for pep8 - must be either 'full' or 'none'", "pep8", ) if "pyflakes" in parsed: for code in parsed["pyflakes"].get("enable", []) + parsed["pyflakes"].get("disable", []): if code in pyflakes.LEGACY_CODE_MAP: _legacy = pyflakes.LEGACY_CODE_MAP[code] add_message( CONFIG_DEPRECATED_CODE, f"Pyflakes {code} was renamed to {_legacy}", "pyflakes", ) return messages def run(self, found_files: FileFinder): messages = [] for filepath in found_files.files: for possible in self.to_check: if filepath == possible: messages += self.validate(filepath) break return messages prospector-1.10.3/prospector/tools/pycodestyle/000077500000000000000000000000001451367460400217125ustar00rootroot00000000000000prospector-1.10.3/prospector/tools/pycodestyle/__init__.py000066400000000000000000000125141451367460400240260ustar00rootroot00000000000000import codecs import os import re from pep8ext_naming import NamingChecker from pycodestyle import PROJECT_CONFIG, USER_CONFIG, BaseReport, StyleGuide, register_check from prospector.finder import FileFinder from prospector.message import Location, Message from prospector.tools.base import ToolBase __all__ = ("PycodestyleTool",) class ProspectorReport(BaseReport): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self._prospector_messages = [] def error(self, line_number, offset, text, check): code = super().error( line_number, offset, text, check, ) if code is None: # The error pycodestyle found is being ignored, let's move on. return # Get a clean copy of the message text without the code embedded. text = text[5:] # mixed indentation (E101) is a file global message if code == "E101": line_number = None # Record the message using prospector's data structures. location = Location( path=self.filename, module=None, function=None, line=line_number, character=(offset + 1), ) message = Message( # TODO: legacy output naming source="pycodestyle", code=code, location=location, message=text, ) self._prospector_messages.append(message) def get_messages(self): return self._prospector_messages class ProspectorStyleGuide(StyleGuide): def __init__(self, config, found_files, *args, **kwargs): self._config = config self._files = found_files self._module_paths = found_files.python_modules # Override the default reporter with our custom one. kwargs["reporter"] = ProspectorReport super().__init__(*args, **kwargs) def excluded(self, filename, parent=None): if super().excluded(filename, parent): return True # If the file survived pycodestyle's exclusion rules, check it against # prospector's patterns. fullpath = self._config.workdir / (parent or "") / filename if fullpath.is_dir(): return False return fullpath not in self._module_paths class PycodestyleTool(ToolBase): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.checker = None def configure(self, prospector_config, found_files: FileFinder): # figure out if we should use a pre-existing config file # such as setup.cfg or tox.ini external_config = None # 'none' means we ignore any external config, so just carry on use_config = False if prospector_config.use_external_config("pycodestyle"): use_config = True paths = [os.path.join(prospector_config.workdir, name) for name in PROJECT_CONFIG] paths.append(USER_CONFIG) ext_loc = prospector_config.external_config_location("pycodestyle") if ext_loc is not None: paths = [ext_loc] + paths for conf_path in paths: if os.path.exists(conf_path) and os.path.isfile(conf_path): # this file exists - but does it have pep8 or pycodestyle config in it? # TODO: Remove this header = re.compile(r"\[(pep8|pycodestyle)\]") with codecs.open(conf_path) as conf_file: if any(header.search(line) for line in conf_file.readlines()): external_config = conf_path break # need to convert to strings rather than Path objects for compatibility with pycodestyle check_paths = [str(f.absolute()) for f in found_files.python_modules] # check_paths = [found_files.to_absolute_path(p) for p in check_paths] # Instantiate our custom pycodestyle checker. self.checker = ProspectorStyleGuide( config=prospector_config, paths=check_paths, found_files=found_files, config_file=external_config ) if not use_config or external_config is None: configured_by = None # This means that we don't have existing config to use. # Make sure pycodestyle's code ignores are fully reset to zero before # adding prospector-flavoured configuration. self.checker.options.select = () self.checker.options.ignore = tuple(prospector_config.get_disabled_messages("pycodestyle")) if "max-line-length" in prospector_config.tool_options("pycodestyle"): self.checker.options.max_line_length = prospector_config.tool_options("pycodestyle")["max-line-length"] else: configured_by = "Configuration found at %s" % external_config # if we have a command line --max-line-length argument, that # overrules everything max_line_length = prospector_config.max_line_length if max_line_length is not None: self.checker.options.max_line_length = max_line_length return configured_by, [] def run(self, _): report = self.checker.check_files() return report.get_messages() # Load pep8ext_naming into pycodestyle's configuration. register_check(NamingChecker) prospector-1.10.3/prospector/tools/pydocstyle/000077500000000000000000000000001451367460400215455ustar00rootroot00000000000000prospector-1.10.3/prospector/tools/pydocstyle/__init__.py000066400000000000000000000047511451367460400236650ustar00rootroot00000000000000from pydocstyle.checker import AllError, ConventionChecker from prospector.encoding import CouldNotHandleEncoding, read_py_file from prospector.finder import FileFinder from prospector.message import Location, Message, make_tool_error_message from prospector.tools.base import ToolBase __all__ = ("PydocstyleTool",) class PydocstyleTool(ToolBase): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self._code_files = [] self.ignore_codes = () def configure(self, prospector_config, found_files): self.ignore_codes = prospector_config.get_disabled_messages("pydocstyle") def run(self, found_files: FileFinder): messages = [] checker = ConventionChecker() for code_file in found_files.python_modules: try: for error in checker.check_source(read_py_file(code_file), str(code_file.absolute()), None): location = Location(path=code_file, module=None, function="", line=error.line, character=0) message = Message( source="pydocstyle", code=error.code, location=location, message=error.message.partition(":")[2].strip(), ) messages.append(message) except CouldNotHandleEncoding as err: messages.append( make_tool_error_message( code_file, "pydocstyle", "D000", message=f"Could not handle the encoding of this file: {err.__cause__}", ) ) continue except AllError as exc: # pydocstyle's Parser.parse_all method raises AllError when an # attempt to analyze the __all__ definition has failed. This # occurs when __all__ is too complex to be parsed. messages.append( make_tool_error_message( code_file, "pydocstyle", "D000", line=1, character=0, message=exc.args[0], ) ) continue return self.filter_messages(messages) def filter_messages(self, messages): return [message for message in messages if message.code not in self.ignore_codes] prospector-1.10.3/prospector/tools/pyflakes/000077500000000000000000000000001451367460400211645ustar00rootroot00000000000000prospector-1.10.3/prospector/tools/pyflakes/__init__.py000066400000000000000000000116051451367460400233000ustar00rootroot00000000000000from pyflakes.api import checkPath from pyflakes.reporter import Reporter from prospector.message import Location, Message from prospector.tools.base import ToolBase __all__ = ("PyFlakesTool",) # Prospector uses the same pyflakes codes as flake8 defines, # see https://flake8.pycqa.org/en/latest/user/error-codes.html # and https://gitlab.com/pycqa/flake8/-/blob/e817c63a/src/flake8/plugins/pyflakes.py _MESSAGE_CODES = { "UnusedImport": "F401", "ImportShadowedByLoopVar": "F402", "ImportStarUsed": "F403", "LateFutureImport": "F404", "ImportStarUsage": "F405", "ImportStarNotPermitted": "F406", "FutureFeatureNotDefined": "F407", "PercentFormatInvalidFormat": "F501", "PercentFormatExpectedMapping": "F502", "PercentFormatExpectedSequence": "F503", "PercentFormatExtraNamedArguments": "F504", "PercentFormatMissingArgument": "F505", "PercentFormatMixedPositionalAndNamed": "F506", "PercentFormatPositionalCountMismatch": "F507", "PercentFormatStarRequiresSequence": "F508", "PercentFormatUnsupportedFormatCharacter": "F509", "StringDotFormatInvalidFormat": "F521", "StringDotFormatExtraNamedArguments": "F522", "StringDotFormatExtraPositionalArguments": "F523", "StringDotFormatMissingArgument": "F524", "StringDotFormatMixingAutomatic": "F525", "FStringMissingPlaceholders": "F541", "MultiValueRepeatedKeyLiteral": "F601", "MultiValueRepeatedKeyVariable": "F602", "TooManyExpressionsInStarredAssignment": "F621", "TwoStarredExpressions": "F622", "AssertTuple": "F631", "IsLiteral": "F632", "InvalidPrintSyntax": "F633", "IfTuple": "F634", "BreakOutsideLoop": "F701", "ContinueOutsideLoop": "F702", "ContinueInFinally": "F703", "YieldOutsideFunction": "F704", "ReturnWithArgsInsideGenerator": "F705", "ReturnOutsideFunction": "F706", "DefaultExceptNotLast": "F707", "DoctestSyntaxError": "F721", "ForwardAnnotationSyntaxError": "F722", "CommentAnnotationSyntaxError": "F723", "Redefined": "F810", "RedefinedWhileUnused": "F811", "RedefinedInListComp": "F812", "UndefinedName": "F821", "UndefinedExport": "F822", "UndefinedLocal": "F823", "DuplicateArgument": "F831", "UnusedVariable": "F841", "RaiseNotImplemented": "F901", } # The old prospector codes were deprecated in favour of using the codes # defined by flake8. This maps the old codes to the new codes. LEGACY_CODE_MAP = { "FL0001": "F401", "FL0002": "F811", "FL0003": "F812", "FL0004": "F402", "FL0005": "F403", "FL0006": "F821", "FL0008": "F822", "FL0009": "F823", "FL0010": "F831", "FL0011": "F810", "FL0012": "F404", "FL0013": "F841", } class ProspectorReporter(Reporter): def __init__(self, ignore=None): super().__init__(None, None) self._messages = [] self.ignore = ignore or () # pylint: disable=too-many-arguments def record_message(self, filename=None, line=None, character=None, code=None, message=None): code = code or "F999" if code in self.ignore: return location = Location( path=filename, module=None, function=None, line=line, character=character, ) message = Message( source="pyflakes", code=code, location=location, message=message, ) self._messages.append(message) def unexpectedError(self, filename, msg): # noqa self.record_message( filename=filename, code="F999", message=msg, ) # pylint: disable=too-many-arguments def syntaxError(self, filename, msg, lineno, offset, text): # noqa self.record_message( filename=filename, line=lineno, character=offset, code="F999", message=msg, ) def flake(self, message): code = _MESSAGE_CODES.get(message.__class__.__name__, "F999") self.record_message( filename=message.filename, line=message.lineno, character=(message.col + 1), code=code, message=message.message % message.message_args, ) def get_messages(self): return self._messages class PyFlakesTool(ToolBase): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.ignore_codes = () def configure(self, prospector_config, _): ignores = prospector_config.get_disabled_messages("pyflakes") # convert old style to new self.ignore_codes = [LEGACY_CODE_MAP.get(code, code) for code in ignores] def run(self, found_files): reporter = ProspectorReporter(ignore=self.ignore_codes) for filepath in found_files.python_modules: checkPath(str(filepath.absolute()), reporter) return reporter.get_messages() prospector-1.10.3/prospector/tools/pylint/000077500000000000000000000000001451367460400206655ustar00rootroot00000000000000prospector-1.10.3/prospector/tools/pylint/__init__.py000066400000000000000000000232351451367460400230030ustar00rootroot00000000000000import os import re import sys from collections import defaultdict from pathlib import Path from typing import List from pylint.config import find_pylintrc from pylint.exceptions import UnknownMessageError from pylint.lint.run import _cpu_count from prospector.finder import FileFinder from prospector.message import Location, Message from prospector.tools.base import ToolBase from prospector.tools.pylint.collector import Collector from prospector.tools.pylint.linter import ProspectorLinter _UNUSED_WILDCARD_IMPORT_RE = re.compile(r"^Unused import(\(s\))? (.*) from wildcard import") def _is_in_dir(subpath: Path, path: Path) -> bool: return subpath.parent == path class PylintTool(ToolBase): # There are several methods on this class which could technically # be functions (they don't use the 'self' argument) but that would # make this module/class a bit ugly. def __init__(self): self._args = None self._collector = self._linter = None self._orig_sys_path = [] def _prospector_configure(self, prospector_config, linter: ProspectorLinter): errors = [] if "django" in prospector_config.libraries: linter.load_plugin_modules(["pylint_django"]) if "celery" in prospector_config.libraries: linter.load_plugin_modules(["pylint_celery"]) if "flask" in prospector_config.libraries: linter.load_plugin_modules(["pylint_flask"]) profile_path = os.path.join(prospector_config.workdir, prospector_config.profile.name) for plugin in prospector_config.profile.pylint.get("load-plugins", []): try: linter.load_plugin_modules([plugin]) except ImportError: errors.append(self._error_message(profile_path, f"Could not load plugin {plugin}")) for msg_id in prospector_config.get_disabled_messages("pylint"): try: linter.disable(msg_id) except UnknownMessageError: # If the msg_id doesn't exist in PyLint any more, # don't worry about it. pass options = prospector_config.tool_options("pylint") for checker in linter.get_checkers(): if not hasattr(checker, "options"): continue for option in checker.options: if option[0] in options: checker.set_option(option[0], options[option[0]]) # The warnings about disabling warnings are useful for figuring out # with other tools to suppress messages from. For example, an unused # import which is disabled with 'pylint disable=unused-import' will # still generate an 'FL0001' unused import warning from pyflakes. # Using the information from these messages, we can figure out what # was disabled. linter.disable("locally-disabled") # notification about disabling a message linter.enable("file-ignored") # notification about disabling an entire file linter.enable("suppressed-message") # notification about a message being suppressed linter.disable("deprecated-pragma") # notification about use of deprecated 'pragma' option max_line_length = prospector_config.max_line_length for checker in linter.get_checkers(): if not hasattr(checker, "options"): continue for option in checker.options: if max_line_length is not None: if option[0] == "max-line-length": checker.set_option("max-line-length", max_line_length) return errors def _error_message(self, filepath, message): location = Location(filepath, None, None, 0, 0) return Message("prospector", "config-problem", location, message) def _pylintrc_configure(self, pylintrc, linter): errors = [] are_plugins_loaded = linter.config_from_file(pylintrc) if not are_plugins_loaded and hasattr(linter.config, "load_plugins"): for plugin in linter.config.load_plugins: try: linter.load_plugin_modules([plugin]) except ImportError: errors.append(self._error_message(pylintrc, f"Could not load plugin {plugin}")) return errors def configure(self, prospector_config, found_files: FileFinder): extra_sys_path = found_files.make_syspath() check_paths = self._get_pylint_check_paths(found_files) pylint_options = prospector_config.tool_options("pylint") self._set_path_finder(extra_sys_path, pylint_options) linter = ProspectorLinter(found_files) config_messages, configured_by = self._get_pylint_configuration( check_paths, linter, prospector_config, pylint_options ) # we don't want similarity reports right now linter.disable("similarities") # use the collector 'reporter' to simply gather the messages # given by PyLint self._collector = Collector(linter.msgs_store) linter.set_reporter(self._collector) if linter.config.jobs == 0: linter.config.jobs = _cpu_count() self._linter = linter return configured_by, config_messages def _set_path_finder(self, extra_sys_path: List[Path], pylint_options): # insert the target path into the system path to get correct behaviour self._orig_sys_path = sys.path if not pylint_options.get("use_pylint_default_path_finder"): sys.path = sys.path + [str(path.absolute()) for path in extra_sys_path] def _get_pylint_check_paths(self, found_files: FileFinder) -> List[Path]: # create a list of packages, but don't include packages which are # subpackages of others as checks will be duplicated check_paths = set() modules = found_files.python_modules packages = found_files.python_packages packages.sort(key=lambda p: len(str(p))) # don't add modules that are in known packages for module in modules: for package in packages: if _is_in_dir(module, package): break else: check_paths.add(module) # sort from earlier packages first... for idx, package in enumerate(packages): # yuck o(n2) but... temporary for prev_pkg in packages[:idx]: if _is_in_dir(package, prev_pkg): # this is a sub-package of a package we know about break else: # we should care about this one check_paths.add(package) # need to sort to make sure multiple runs are deterministic return sorted(check_paths) def _get_pylint_configuration( self, check_paths: List[Path], linter: ProspectorLinter, prospector_config, pylint_options ): self._args = linter.load_command_line_configuration(str(path) for path in check_paths) linter.load_default_plugins() config_messages = self._prospector_configure(prospector_config, linter) configured_by = None if prospector_config.use_external_config("pylint"): # try to find a .pylintrc pylintrc = pylint_options.get("config_file") external_config = prospector_config.external_config_location("pylint") pylintrc = pylintrc or external_config or find_pylintrc() if pylintrc is None: # nothing explicitly configured for possible in (".pylintrc", "pylintrc", "pyproject.toml", "setup.cfg"): pylintrc_path = os.path.join(prospector_config.workdir, possible) # TODO: pyproject and setup.cfg might not actually have any pylint config # in, they should be skipped in that case if os.path.exists(pylintrc_path): pylintrc = pylintrc_path break if pylintrc is not None: # load it! configured_by = pylintrc config_messages += self._pylintrc_configure(pylintrc, linter) return config_messages, configured_by def _combine_w0614(self, messages): """ For the "unused import from wildcard import" messages, we want to combine all warnings about the same line into a single message. """ by_loc = defaultdict(list) out = [] for message in messages: if message.code == "unused-wildcard-import": by_loc[message.location].append(message) else: out.append(message) for location, message_list in by_loc.items(): names = [] for msg in message_list: names.append(_UNUSED_WILDCARD_IMPORT_RE.match(msg.message).group(1)) msgtxt = "Unused imports from wildcard import: %s" % ", ".join(names) combined_message = Message("pylint", "unused-wildcard-import", location, msgtxt) out.append(combined_message) return out def combine(self, messages): """ Combine repeated messages. Some error messages are repeated, causing many errors where only one is strictly necessary. For example, having a wildcard import will result in one 'Unused Import' warning for every unused import. This method will combine these into a single warning. """ combined = self._combine_w0614(messages) return sorted(combined) def run(self, found_files) -> List[Message]: self._linter.check(self._args) sys.path = self._orig_sys_path messages = self._collector.get_messages() return self.combine(messages) prospector-1.10.3/prospector/tools/pylint/collector.py000066400000000000000000000024661451367460400232350ustar00rootroot00000000000000from io import StringIO from typing import List from pylint.exceptions import UnknownMessageError from pylint.message import Message as PylintMessage from pylint.reporters import BaseReporter from prospector.message import Location, Message class Collector(BaseReporter): name = "collector" def __init__(self, message_store): BaseReporter.__init__(self, output=StringIO()) self._message_store = message_store self._messages = [] def handle_message(self, msg: PylintMessage) -> None: loc = Location(msg.abspath, msg.module, msg.obj, msg.line, msg.column) # At this point pylint will give us the code but we want the # more user-friendly symbol try: msg_data = self._message_store.get_message_definitions(msg.msg_id) except UnknownMessageError: # this shouldn't happen, as all pylint errors should be # in the message store, but just in case we'll fall back # to using the code. msg_symbol = msg.msg_id else: msg_symbol = msg_data[0].symbol message = Message("pylint", msg_symbol, loc, msg.msg) self._messages.append(message) def _display(self, layout) -> None: pass def get_messages(self) -> List[Message]: return self._messages prospector-1.10.3/prospector/tools/pylint/linter.py000066400000000000000000000034341451367460400225400ustar00rootroot00000000000000from pathlib import Path from packaging import version as packaging_version from pylint import version as pylint_version from pylint.lint import PyLinter from pylint.utils import _splitstrip class ProspectorLinter(PyLinter): def __init__(self, found_files, *args, **kwargs): self._files = found_files # set up the standard PyLint linter PyLinter.__init__(self, *args, **kwargs) def config_from_file(self, config_file=None): """Will return `True` if plugins have been loaded. For pylint>=1.5. Else `False`.""" self.read_config_file(config_file) if self.cfgfile_parser.has_option("MASTER", "load-plugins"): plugins = _splitstrip(self.cfgfile_parser.get("MASTER", "load-plugins")) self.load_plugin_modules(plugins) self.load_config_file() return True def _expand_files(self, modules): expanded = super()._expand_files(modules) filtered = {} # PyLinter._expand_files returns dict since 2.15.7. if packaging_version.parse(pylint_version) > packaging_version.parse("2.15.6"): for module in expanded: if not self._files.is_excluded(Path(module)): filtered[module] = expanded[module] return filtered else: for module in expanded: # need to de-duplicate, as pylint also walks directories given to it, so it will find # files that prospector has already provided and end up checking it more than once if not self._files.is_excluded(Path(module["path"])): # if the key exists, just overwrite it with the same value, so we don't need an extra if statement filtered[module["path"]] = module return filtered.values() prospector-1.10.3/prospector/tools/pyright/000077500000000000000000000000001451367460400210345ustar00rootroot00000000000000prospector-1.10.3/prospector/tools/pyright/__init__.py000066400000000000000000000055631451367460400231560ustar00rootroot00000000000000import json import subprocess import pyright from prospector.message import Location, Message from prospector.tools import ToolBase __all__ = ("PyrightTool",) from prospector.tools.exceptions import BadToolConfig VALID_OPTIONS = [ "level", "project", "pythonplatform", "pythonversion", "skipunannotated", "typeshed-path", "venv-path", ] def format_messages(json_encoded): json_decoded = json.loads(json_encoded) diagnostics = json_decoded.get("generalDiagnostics", []) messages = [] for diag in diagnostics: start_range = diag.get("range", {}).get("start", {}) location = Location( path=diag["file"], module=None, function=None, line=start_range.get("line", -1), character=start_range.get("character", -1), ) messages.append( Message(source="pyright", code=diag.get("rule", ""), location=location, message=diag.get("message", "")) ) return messages class PyrightTool(ToolBase): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.checker = pyright self.options = ["--outputjson"] def configure(self, prospector_config, _): options = prospector_config.tool_options("pyright") for option_key in options.keys(): if option_key not in VALID_OPTIONS: url = "https://github.com/PyCQA/prospector/blob/master/prospector/tools/pyright/__init__.py" raise BadToolConfig( "pyright", f"Option {option_key} is not valid. " f"See the list of possible options: {url}" ) level = options.get("level", None) project = options.get("project", None) pythonplatform = options.get("pythonplatform", None) pythonversion = options.get("pythonversion", None) skipunannotated = options.get("skipunannotated", False) typeshed_path = options.get("typeshed-path", None) venv_path = options.get("venv-path", None) if level: self.options.extend(["--level", level]) if project: self.options.extend(["--project", project]) if pythonplatform: self.options.extend(["--pythonplatform", pythonplatform]) if pythonversion: self.options.extend(["--pythonversion", pythonversion]) if skipunannotated: self.options.append("--skipunannotated") if typeshed_path: self.options.extend(["--typeshed-path", typeshed_path]) if venv_path: self.options.extend(["--venv-path", venv_path]) def run(self, found_files): paths = [str(path) for path in found_files.python_modules] paths.extend(self.options) result = self.checker.run(*paths, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) return format_messages(result.stdout) prospector-1.10.3/prospector/tools/pyroma/000077500000000000000000000000001451367460400206555ustar00rootroot00000000000000prospector-1.10.3/prospector/tools/pyroma/__init__.py000066400000000000000000000056031451367460400227720ustar00rootroot00000000000000import logging from typing import TYPE_CHECKING, List from prospector.finder import FileFinder from prospector.message import Location, Message from prospector.tools.base import ToolBase if TYPE_CHECKING: from prospector.config import ProspectorConfig # HACK: pyroma configures logging in its __init__.py so by importing, # it will change existing logging configuration to DEBUG which causes # problems with other 3rd party modules as everything now logs to # stdout... _old = logging.basicConfig try: logging.basicConfig = lambda **k: None from pyroma import projectdata, ratings # if pyroma doesn't exist, will raise an ImportError and be caught "upstream" finally: # always restore logging.basicConfig logging.basicConfig = _old PYROMA_ALL_CODES = { "Name": "PYR01", "Version": "PYR02", "VersionIsString": "PYR03", "PEPVersion": "PYR04", "Description": "PYR05", "LongDescription": "PYR06", "Classifiers": "PYR07", "PythonVersion": "PYR08", "Keywords": "PYR09", "Author": "PYR10", "AuthorEmail": "PYR11", "Url": "PYR12", "License": "PYR13", "LicenceClassifier": "PYR14", "ZipSafe": "PYR15", "SDist": "PYR16", "PackageDocs": "PYR17", "ValidREST": "PYR18", "BusFactor": "PYR19", } PYROMA_CODES = {} def _copy_codes(): for name, code in PYROMA_ALL_CODES.items(): if hasattr(ratings, name): PYROMA_CODES[getattr(ratings, name)] = code _copy_codes() PYROMA_TEST_CLASSES = [t.__class__ for t in ratings.ALL_TESTS] class PyromaTool(ToolBase): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.ignore_codes = () def configure(self, prospector_config: "ProspectorConfig", found_files: FileFinder): self.ignore_codes = prospector_config.get_disabled_messages("pyroma") def run(self, found_files: FileFinder) -> List[Message]: messages = [] for directory in found_files.directories: # just list directories which are not ignored, but find any `setup.py` ourselves # as that is excluded by default for filepath in directory.iterdir(): if filepath.is_dir() or filepath.name != "setup.py": continue data = projectdata.get_data(directory.resolve()) all_tests = [m() for m in PYROMA_TEST_CLASSES] for test in all_tests: code = PYROMA_CODES.get(test.__class__, "PYRUNKNOWN") if code in self.ignore_codes: continue passed = test.test(data) if passed is False: # passed can be True, False or None... loc = Location(filepath, "setup", None, -1, -1) msg = Message("pyroma", code, loc, test.message()) messages.append(msg) return messages prospector-1.10.3/prospector/tools/utils.py000066400000000000000000000022111451367460400210540ustar00rootroot00000000000000import sys class CaptureStream: def __init__(self): self.contents = "" def write(self, text): self.contents += text def close(self): pass def flush(self): pass class CaptureOutput: def __init__(self, hide): self.hide = hide self._prev_streams = None self.stdout, self.stderr = None, None def __enter__(self): if self.hide: self._prev_streams = [ sys.stdout, sys.stderr, sys.__stdout__, sys.__stderr__, ] self.stdout = CaptureStream() self.stderr = CaptureStream() sys.stdout, sys.__stdout__ = self.stdout, self.stdout sys.stderr, sys.__stderr__ = self.stderr, self.stderr return self def get_hidden_stdout(self): return self.stdout.contents def get_hidden_stderr(self): return self.stderr.contents def __exit__(self, exc_type, exc_val, exc_tb): if self.hide: sys.stdout, sys.stderr, sys.__stdout__, sys.__stderr__ = self._prev_streams del self._prev_streams prospector-1.10.3/prospector/tools/vulture/000077500000000000000000000000001451367460400210545ustar00rootroot00000000000000prospector-1.10.3/prospector/tools/vulture/__init__.py000066400000000000000000000057621451367460400231770ustar00rootroot00000000000000from vulture import Vulture from prospector.encoding import CouldNotHandleEncoding, read_py_file from prospector.message import Location, Message, make_tool_error_message from prospector.tools.base import ToolBase class ProspectorVulture(Vulture): def __init__(self, found_files): Vulture.__init__(self, verbose=False) self._files = found_files self._internal_messages = [] self.file = None self.filename = None def scavenge(self, _=None, __=None): # The argument is a list of paths, but we don't care # about that as we use the found_files object. The # argument is here to explicitly acknowledge that we # are overriding the Vulture.scavenge method. for module in self._files.python_modules: try: module_string = read_py_file(module) except CouldNotHandleEncoding as err: self._internal_messages.append( make_tool_error_message( module, "vulture", "V000", message=f"Could not handle the encoding of this file: {err.encoding}", ) ) continue self.file = module self.filename = module try: self.scan(module_string, filename=module) except TypeError: self.scan(module_string) def get_messages(self): all_items = ( ("unused-function", "Unused function %s", self.unused_funcs), ("unused-property", "Unused property %s", self.unused_props), ("unused-variable", "Unused variable %s", self.unused_vars), ("unused-attribute", "Unused attribute %s", self.unused_attrs), ) vulture_messages = [] for code, template, items in all_items: for item in items: try: filename = item.file except AttributeError: filename = item.filename if hasattr(item, "lineno"): lineno = item.lineno # for older versions of vulture else: lineno = item.first_lineno loc = Location(filename, None, None, lineno, -1) message_text = template % item message = Message("vulture", code, loc, message_text) vulture_messages.append(message) return self._internal_messages + vulture_messages class VultureTool(ToolBase): def __init__(self): ToolBase.__init__(self) self._vulture = None self.ignore_codes = () def configure(self, prospector_config, found_files): self.ignore_codes = prospector_config.get_disabled_messages("vulture") def run(self, found_files): vulture = ProspectorVulture(found_files) vulture.scavenge() return [message for message in vulture.get_messages() if message.code not in self.ignore_codes] prospector-1.10.3/pyproject.toml000066400000000000000000000053201451367460400167220ustar00rootroot00000000000000[tool.poetry] name = "prospector" version = "1.10.3" description = "Prospector is a tool to analyse Python code by aggregating the result of other tools." authors = ["Carl Crowder "] maintainers = ["Carl Crowder ", "Carlos Coelho ", "Pierre Sassoulas "] readme = "README.rst" homepage = "http://prospector.readthedocs.io" repository = "https://github.com/PyCQA/prospector" keywords = ["pylint", "prospector", "static code analysis"] license = "GPLv2+" classifiers = [ "Development Status :: 5 - Production/Stable", "Environment :: Console", "Intended Audience :: Developers", "Operating System :: Unix", "Topic :: Software Development :: Quality Assurance", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "License :: OSI Approved :: GNU General Public License v2 or later (GPLv2+)", ] packages = [ { include = "prospector/"} ] include = [ "prospector/blender_combinations.yaml", "prospector/profiles/profiles/*.yaml", ] [tool.poetry.scripts] prospector = 'prospector.run:main' [tool.poetry.dependencies] python = ">=3.7.2,<4.0" pylint = ">=2.8.3" pylint-celery = "0.3" pylint-django = "~2.5" pylint-plugin-utils = "~0.7" pylint-flask = "0.6" requirements-detector = ">=1.2.0" PyYAML = "*" mccabe = "^0.7.0" flake8 = "<6.0.0" pyflakes = ">=2.2.0,<3.*" pycodestyle = ">=2.9.0" pep8-naming = ">=0.3.3,<=0.10.0" pydocstyle = ">=2.0.0" dodgy = "^0.2.1" toml = "^0.10.2" setoptconf-tmp = "^0.3.1" GitPython = "^3.1.27" packaging = "*" bandit = {version = ">=1.5.1", optional = true} vulture = {version = ">=1.5", optional = true} mypy = {version = ">=0.600", optional = true} pyright = {version = ">=1.1.3", optional = true} pyroma = {version = ">=2.4", optional = true} [tool.poetry.extras] with_bandit = ["bandit"] with_mypy = ["mypy"] with_pyright = ["pyright"] with_pyroma = ["pyroma"] with_vulture = ["vulture"] with_everything = ["bandit", "mypy", "pyright", "pyroma", "vulture"] [tool.poetry.dev-dependencies] coveralls = "^3.3.1" pytest = "^7.2.0" pytest-benchmark = "^4.0.0" pytest-cov = "^4.0.0" tzlocal = "^4.2" coverage = "^6.5" pre-commit = "^2.20.0" tox = "^3.27.1" twine = "^4.0.2" types-PyYAML = "^6.0.4" types-setuptools = "^57.4.9" [build-system] requires = ["poetry-core>=1.0.0"] build-backend = "poetry.core.masonry.api" [tool.black] line-length = 120 exclude = ''' /( tests/ )/ ''' [tool.isort] multi_line_output = 3 include_trailing_comma = true force_grid_wrap = 0 use_parentheses = true line_length = 120 prospector-1.10.3/tests/000077500000000000000000000000001451367460400151505ustar00rootroot00000000000000prospector-1.10.3/tests/__init__.py000066400000000000000000000000001451367460400172470ustar00rootroot00000000000000prospector-1.10.3/tests/config/000077500000000000000000000000001451367460400164155ustar00rootroot00000000000000prospector-1.10.3/tests/config/.prospector/000077500000000000000000000000001451367460400206735ustar00rootroot00000000000000prospector-1.10.3/tests/config/.prospector/prospector-int-ignores.yaml000066400000000000000000000000401451367460400262050ustar00rootroot00000000000000ignore-paths: - 2017 - 2018 prospector-1.10.3/tests/config/.prospector/prospector-str-ignores.yaml000066400000000000000000000000441451367460400262270ustar00rootroot00000000000000ignore-paths: - "2017" - "2018" prospector-1.10.3/tests/config/__init__.py000066400000000000000000000000001451367460400205140ustar00rootroot00000000000000prospector-1.10.3/tests/config/test_config.py000066400000000000000000000030131451367460400212700ustar00rootroot00000000000000import re from pathlib import Path from prospector.config import ProspectorConfig from prospector.finder import FileFinder from ..utils import patch_execution def test_relative_ignores(): """ Tests that if 'ignore-paths: something' is set, then it is ignored; that is, paths relative to the working directory should be ignored too """ workdir = Path(__file__).parent / "testdata/test_relative_ignores" with patch_execution("-P", "profile_relative_ignores.yml", set_cwd=workdir): config = ProspectorConfig() files = FileFinder(*config.paths, exclusion_filters=[config.make_exclusion_filter()]) assert 2 == len(files.python_modules) def test_determine_ignores_all_str(): with patch_execution("-P", "prospector-str-ignores", set_cwd=Path(__file__).parent): config = ProspectorConfig() assert len(config.ignores) > 0 boundary = r"(^|/|\\)%s(/|\\|$)" paths = ["2017", "2018"] for path in paths: compiled_ignored_path = re.compile(boundary % re.escape(path)) assert compiled_ignored_path in config.ignores def test_determine_ignores_containing_int_values_wont_throw_attr_exc(): with patch_execution("-P", "prospector-int-ignores", set_cwd=Path(__file__).parent): config = ProspectorConfig() assert len(config.ignores) > 0 boundary = r"(^|/|\\)%s(/|\\|$)" paths = ["2017", "2018"] for path in paths: compiled_ignored_path = re.compile(boundary % re.escape(path)) assert compiled_ignored_path in config.ignores prospector-1.10.3/tests/config/test_datatype.py000066400000000000000000000125121451367460400216420ustar00rootroot00000000000000from unittest import TestCase from unittest.mock import patch from prospector.config.datatype import OutputChoice class TestOutputChoice(TestCase): @patch("sys.platform", "linux") @patch("os.path.sep", "/") @patch("os.path.altsep", None) def test_sanitize_rel_path_colon_posix(self): output_choice = OutputChoice(["xunit"]) self.assertEqual( output_choice.sanitize("xunit:./test-results.xml"), ("xunit", ["./test-results.xml"]), ) @patch("sys.platform", "linux") @patch("os.path.sep", "/") @patch("os.path.altsep", None) def test_sanitize_rel_path_semicolon_posix(self): output_choice = OutputChoice(["xunit"]) self.assertEqual( output_choice.sanitize("xunit;./test-results.xml"), ("xunit", ["./test-results.xml"]), ) @patch("sys.platform", "win32") @patch("os.path.sep", "\\") @patch("os.path.altsep", "/") def test_sanitize_rel_path_colon_windows(self): output_choice = OutputChoice(["xunit"]) self.assertEqual( output_choice.sanitize("xunit:.\\test-results.xml"), ("xunit", [".\\test-results.xml"]), ) @patch("sys.platform", "win32") @patch("os.path.sep", "\\") @patch("os.path.altsep", "/") def test_sanitize_rel_path_semicolon_windows(self): output_choice = OutputChoice(["xunit"]) self.assertEqual( output_choice.sanitize("xunit;.\\test-results.xml"), ("xunit", [".\\test-results.xml"]), ) @patch("sys.platform", "linux") @patch("os.path.sep", "/") @patch("os.path.altsep", None) def test_sanitize_abs_path_colon_posix(self): output_choice = OutputChoice(["xunit"]) self.assertEqual( output_choice.sanitize("xunit:/home/test-results.xml"), ("xunit", ["/home/test-results.xml"]), ) @patch("sys.platform", "linux") @patch("os.path.sep", "/") @patch("os.path.altsep", None) def test_sanitize_abs_path_semicolon_posix(self): output_choice = OutputChoice(["xunit"]) self.assertEqual( output_choice.sanitize("xunit;/home/test-results.xml"), ("xunit", ["/home/test-results.xml"]), ) @patch("sys.platform", "win32") @patch("os.path.sep", "\\") @patch("os.path.altsep", "/") def test_sanitize_abs_path_colon_windows(self): output_choice = OutputChoice(["xunit"]) self.assertEqual( output_choice.sanitize("xunit:C:\\testResults\\test-results.xml"), ("xunit", ["C:\\testResults\\test-results.xml"]), ) @patch("sys.platform", "win32") @patch("os.path.sep", "\\") @patch("os.path.altsep", "/") def test_sanitize_abs_path_semicolon_windows(self): output_choice = OutputChoice(["xunit"]) self.assertEqual( output_choice.sanitize("xunit;C:\\testResults\\test-results.xml"), ("xunit", ["C:\\testResults\\test-results.xml"]), ) @patch("sys.platform", "win32") @patch("os.path.sep", "\\") @patch("os.path.altsep", "/") def test_sanitize_abs_path_colon_windows_alternate(self): output_choice = OutputChoice(["xunit"]) self.assertEqual( output_choice.sanitize("xunit:C:/testResults/test-results.xml"), ("xunit", ["C:/testResults/test-results.xml"]), ) @patch("sys.platform", "win32") @patch("os.path.sep", "\\") @patch("os.path.altsep", "/") def test_sanitize_abs_path_semicolon_windows_alternate(self): output_choice = OutputChoice(["xunit"]) self.assertEqual( output_choice.sanitize("xunit;C:/testResults/test-results.xml"), ("xunit", ["C:/testResults/test-results.xml"]), ) @patch("sys.platform", "linux") @patch("os.path.sep", "/") @patch("os.path.altsep", None) def test_sanitize_abs_rel_path_colon_posix(self): output_choice = OutputChoice(["xunit"]) self.assertEqual( output_choice.sanitize("xunit:/home/test-results.xml:./test-results.xml"), ("xunit", ["/home/test-results.xml", "./test-results.xml"]), ) @patch("sys.platform", "linux") @patch("os.path.sep", "/") @patch("os.path.altsep", None) def test_sanitize_abs_rel_path_semicolon_posix(self): output_choice = OutputChoice(["xunit"]) self.assertEqual( output_choice.sanitize("xunit;/home/test-results.xml;./test-results.xml"), ("xunit", ["/home/test-results.xml", "./test-results.xml"]), ) @patch("sys.platform", "win32") @patch("os.path.sep", "\\") @patch("os.path.altsep", "/") def test_sanitize_abs_rel_path_colon_windows(self): output_choice = OutputChoice(["xunit"]) self.assertEqual( output_choice.sanitize("xunit:C:\\home\\test-results.xml:.\\test-results.xml"), ("xunit", ["C:\\home\\test-results.xml", ".\\test-results.xml"]), ) @patch("sys.platform", "win32") @patch("os.path.sep", "\\") @patch("os.path.altsep", "/") def test_sanitize_abs_rel_path_semicolon_windows(self): output_choice = OutputChoice(["xunit"]) self.assertEqual( output_choice.sanitize("xunit;C:\\home\\test-results.xml;.\\test-results.xml"), ("xunit", ["C:\\home\\test-results.xml", ".\\test-results.xml"]), ) prospector-1.10.3/tests/config/testdata/000077500000000000000000000000001451367460400202265ustar00rootroot00000000000000prospector-1.10.3/tests/config/testdata/test_relative_ignores/000077500000000000000000000000001451367460400246265ustar00rootroot00000000000000prospector-1.10.3/tests/config/testdata/test_relative_ignores/__init__.py000066400000000000000000000000001451367460400267250ustar00rootroot00000000000000prospector-1.10.3/tests/config/testdata/test_relative_ignores/pkg/000077500000000000000000000000001451367460400254075ustar00rootroot00000000000000prospector-1.10.3/tests/config/testdata/test_relative_ignores/pkg/__init__.py000066400000000000000000000000001451367460400275060ustar00rootroot00000000000000prospector-1.10.3/tests/config/testdata/test_relative_ignores/pkg/tests/000077500000000000000000000000001451367460400265515ustar00rootroot00000000000000prospector-1.10.3/tests/config/testdata/test_relative_ignores/pkg/tests/__init__.py000066400000000000000000000000001451367460400306500ustar00rootroot00000000000000prospector-1.10.3/tests/config/testdata/test_relative_ignores/pkg/tests/lala.py000066400000000000000000000000001451367460400300220ustar00rootroot00000000000000prospector-1.10.3/tests/config/testdata/test_relative_ignores/profile_relative_ignores.yml000066400000000000000000000001261451367460400324310ustar00rootroot00000000000000inherits: - strictness_medium - no_test_warnings ignore-paths: - something.py prospector-1.10.3/tests/config/testdata/test_relative_ignores/something.py000066400000000000000000000000201451367460400271650ustar00rootroot00000000000000x = 1 + unittes prospector-1.10.3/tests/config/testdata/test_relative_ignores/tests/000077500000000000000000000000001451367460400257705ustar00rootroot00000000000000prospector-1.10.3/tests/config/testdata/test_relative_ignores/tests/__init__.py000066400000000000000000000000001451367460400300670ustar00rootroot00000000000000prospector-1.10.3/tests/config/testdata/test_relative_ignores/tests/lala.py000066400000000000000000000000001451367460400272410ustar00rootroot00000000000000prospector-1.10.3/tests/execution/000077500000000000000000000000001451367460400171535ustar00rootroot00000000000000prospector-1.10.3/tests/execution/__init__.py000066400000000000000000000000761451367460400212670ustar00rootroot00000000000000""" These are full "end-to-end" tests of a prospector run """ prospector-1.10.3/tests/execution/test_execution.py000066400000000000000000000070521451367460400225730ustar00rootroot00000000000000""" Tests that prospector raises the expected errors on the expected files depending on the configuration of the file finder """ import shutil import tempfile from pathlib import Path from unittest.mock import patch from prospector.config import ProspectorConfig from prospector.finder import FileFinder from prospector.run import Prospector from prospector.tools import PylintTool from ..utils import patch_cli, patch_cwd, patch_execution TEST_DATA = Path(__file__).parent / "testdata" def test_non_subpath(): """ Create a temporary directory faaar away from the CWD when running, ensure prospector can run """ tempdir = Path(tempfile.mkdtemp()) try: with patch_execution(str(tempdir.absolute()), set_cwd=TEST_DATA / "something"): config = ProspectorConfig() pros = Prospector(config) pros.execute() finally: shutil.rmtree(tempdir) def test_ignored(): workdir = TEST_DATA / "ignore_test" with patch_execution("--profile", "profile.yml", str(workdir), set_cwd=workdir): config = ProspectorConfig() pros = Prospector(config) pros.execute() msgs = pros.get_messages() # only the pkg3.broken should be picked up as everything else is ignored assert len(msgs) == 1 assert msgs[0].location.module == "pkg1.pkg2.pkg3.broken" def test_total_errors(): # There are 5 python files, all with the same error # Some are in sub-packages, one in a directory without an __init__ # All should be found and exactly 1 error per file found workdir = TEST_DATA / "test_errors_found" with patch("sys.argv", ["prospector", str(workdir.absolute())]): config = ProspectorConfig() found_files = FileFinder(*config.paths) tool = PylintTool() tool.configure(config, found_files) messages = tool.run(found_files) assert len(messages) == 5 message_locs = {m.location.path: m for m in messages} for file in workdir.rglob("*.py"): del message_locs[file] assert len(message_locs) == 0 def test_relative_path_specified(): """ This test was to fix `prospector .` returning different results to `prospector ../prospector` (when running inside the prospector checkout """ root = TEST_DATA / "relative_specified" # oddness here : Path.cwd() uses os.getcwd() under the hood in python<=3.9 but # for python 3.10+, they return different things if only one is patched; therefore, # for this test to work in all python versions prospector supports, both need to # be patched (or, an "if python version" statement but it's easier to just patch both) with patch_cwd(root): with patch_cli("prospector"): config1 = ProspectorConfig() found_files1 = FileFinder(*config1.paths) with patch_cli("prospector", "../relative_specified"): config2 = ProspectorConfig() found_files2 = FileFinder(*config1.paths) with patch_cli("prospector", "."): config3 = ProspectorConfig() found_files3 = FileFinder(*config1.paths) assert root == config2.workdir == config1.workdir == config3.workdir assert config1.paths == config2.paths == config3.paths assert found_files3.files == found_files2.files == found_files1.files assert found_files3.python_modules == found_files2.python_modules == found_files1.python_modules assert found_files3.python_packages == found_files2.python_packages == found_files1.python_packages assert found_files3.directories == found_files2.directories == found_files1.directories prospector-1.10.3/tests/execution/testdata/000077500000000000000000000000001451367460400207645ustar00rootroot00000000000000prospector-1.10.3/tests/execution/testdata/ignore_test/000077500000000000000000000000001451367460400233065ustar00rootroot00000000000000prospector-1.10.3/tests/execution/testdata/ignore_test/pkg1/000077500000000000000000000000001451367460400241505ustar00rootroot00000000000000prospector-1.10.3/tests/execution/testdata/ignore_test/pkg1/__init__.py000066400000000000000000000000001451367460400262470ustar00rootroot00000000000000prospector-1.10.3/tests/execution/testdata/ignore_test/pkg1/pkg2/000077500000000000000000000000001451367460400250135ustar00rootroot00000000000000prospector-1.10.3/tests/execution/testdata/ignore_test/pkg1/pkg2/__init__.py000066400000000000000000000000001451367460400271120ustar00rootroot00000000000000prospector-1.10.3/tests/execution/testdata/ignore_test/pkg1/pkg2/asdr_partial_aserfg.py000066400000000000000000000000071451367460400313560ustar00rootroot00000000000000x += 1 prospector-1.10.3/tests/execution/testdata/ignore_test/pkg1/pkg2/pkg3/000077500000000000000000000000001451367460400256575ustar00rootroot00000000000000prospector-1.10.3/tests/execution/testdata/ignore_test/pkg1/pkg2/pkg3/__init__.py000066400000000000000000000000001451367460400277560ustar00rootroot00000000000000prospector-1.10.3/tests/execution/testdata/ignore_test/pkg1/pkg2/pkg3/broken.py000066400000000000000000000000121451367460400275020ustar00rootroot00000000000000x = y + 1 prospector-1.10.3/tests/execution/testdata/ignore_test/pkg1/pkg2/pkg3/safdv_partial_ag45.py000066400000000000000000000000071451367460400316650ustar00rootroot00000000000000x += 1 prospector-1.10.3/tests/execution/testdata/ignore_test/pkg1/pkg_ignore/000077500000000000000000000000001451367460400262745ustar00rootroot00000000000000prospector-1.10.3/tests/execution/testdata/ignore_test/pkg1/pkg_ignore/__init__.py000066400000000000000000000000001451367460400303730ustar00rootroot00000000000000prospector-1.10.3/tests/execution/testdata/ignore_test/pkg1/pkg_ignore/broken2.py000066400000000000000000000000161451367460400302050ustar00rootroot00000000000000a = y + 3 / 0 prospector-1.10.3/tests/execution/testdata/ignore_test/profile.yml000066400000000000000000000001271451367460400254710ustar00rootroot00000000000000strictness: medium ignore-paths: - pkg1/pkg_ignore ignore-patterns: - .*partial.* prospector-1.10.3/tests/execution/testdata/relative_specified/000077500000000000000000000000001451367460400246125ustar00rootroot00000000000000prospector-1.10.3/tests/execution/testdata/relative_specified/__init__.py000066400000000000000000000000001451367460400267110ustar00rootroot00000000000000prospector-1.10.3/tests/execution/testdata/something/000077500000000000000000000000001451367460400227615ustar00rootroot00000000000000prospector-1.10.3/tests/execution/testdata/something/.prospector.yaml000066400000000000000000000000601451367460400261170ustar00rootroot00000000000000ignore-paths: - boing mypy: run: false prospector-1.10.3/tests/execution/testdata/something/__init__.py000066400000000000000000000000001451367460400250600ustar00rootroot00000000000000prospector-1.10.3/tests/execution/testdata/test_errors_found/000077500000000000000000000000001451367460400245325ustar00rootroot00000000000000prospector-1.10.3/tests/execution/testdata/test_errors_found/__init__.py000066400000000000000000000000311451367460400266350ustar00rootroot00000000000000# flake8: noqa x = y + 1 prospector-1.10.3/tests/execution/testdata/test_errors_found/file1.py000066400000000000000000000000311451367460400260760ustar00rootroot00000000000000# flake8: noqa x = y + 1 prospector-1.10.3/tests/execution/testdata/test_errors_found/pkg/000077500000000000000000000000001451367460400253135ustar00rootroot00000000000000prospector-1.10.3/tests/execution/testdata/test_errors_found/pkg/__init__.py000066400000000000000000000000311451367460400274160ustar00rootroot00000000000000# flake8: noqa x = y + 1 prospector-1.10.3/tests/execution/testdata/test_errors_found/pkg/file2.py000066400000000000000000000000311451367460400266600ustar00rootroot00000000000000# flake8: noqa x = y + 1 prospector-1.10.3/tests/execution/testdata/test_errors_found/pkg2/000077500000000000000000000000001451367460400253755ustar00rootroot00000000000000prospector-1.10.3/tests/execution/testdata/test_errors_found/pkg2/file3.py000066400000000000000000000000311451367460400267430ustar00rootroot00000000000000# flake8: noqa x = y + 1 prospector-1.10.3/tests/finder/000077500000000000000000000000001451367460400164175ustar00rootroot00000000000000prospector-1.10.3/tests/finder/__init__.py000066400000000000000000000000001451367460400205160ustar00rootroot00000000000000prospector-1.10.3/tests/finder/test_filefinder.py000066400000000000000000000077011451367460400221440ustar00rootroot00000000000000from pathlib import Path from unittest import TestCase from prospector.finder import FileFinder from .utils import TEST_DATA class TestFileFinder(TestCase): def test_python_in_normal_dir(self): """ This test is to find packages and files when given a directory which is not itself a python module but contains python modules """ finder = FileFinder(TEST_DATA / "test1" / "somedir") found = list(finder.python_modules) self.assertEqual(1, len(found)) self.assertEqual("__init__.py", found[0].name) self.assertEqual("package2", found[0].parent.name) def test_directory_with_modules(self): """ Tests a non-module directory containing python modules finds all modules and files correctly """ finder = FileFinder(TEST_DATA / "test3") self.assertEqual(4, len(finder.files)) self.assertTrue(all(p.is_absolute() for p in finder.files)) self.assertEqual(4, len(finder.python_modules)) self.assertTrue(all(p.is_absolute() for p in finder.python_modules)) self.assertEqual(4, len(finder.python_packages)) self.assertTrue(all(p.is_absolute() for p in finder.python_packages)) def test_package_finder(self): """ Checks that packages are found correctly and all - including subpackages if asked - are listed """ finder = FileFinder(TEST_DATA / "test2") self.assertEqual(2, len(finder.python_packages)) finder = FileFinder(TEST_DATA / "test3") self.assertEqual(4, len(finder.python_packages)) finder = FileFinder(TEST_DATA / "test4") self.assertEqual(3, len(finder.python_packages)) def test_subdirectories_omitted_if_parent_excluded(self): """ Verifies that if the directory "b" in "a/b/c/d" is ignored, then so are all children """ finder = FileFinder(TEST_DATA / "filter_test", exclusion_filters=[lambda p: p.name == "ignore_me"]) self.assertEqual(1, len(finder.python_modules)) lvl2 = TEST_DATA / "filter_test/ignore_me/level2" self.assertNotIn(lvl2, finder.directories) self.assertNotIn(lvl2, finder.python_modules) def test_multiple_search_directories(self): """ The other tests only gave the finder one directory - this test gives it more, and checks there are no duplicates """ search = ( TEST_DATA / "test1", TEST_DATA / "test3", TEST_DATA / "test3", ) finder = FileFinder(*search) files = finder.files self.assertEqual(6, len(files)) modules = finder.python_modules self.assertEqual(6, len(modules)) packages = finder.python_packages self.assertEqual(6, len(packages)) dirs = finder.directories # note: 10 including the 'test1' and 'test3' directories themselves, which contain 8 in total self.assertEqual(10, len(dirs)) def test_non_existent_path(self): """ Checks that the finder can cleanly handle being given paths that do not exist """ self.assertRaises(FileNotFoundError, FileFinder, TEST_DATA / "does_not_exist") def test_exclusion_filters(self): """ Checks that paths excluded by filters are not returned in lists """ # exclude everything finder = FileFinder(TEST_DATA, exclusion_filters=[lambda _: True]) self.assertEqual(0, len(finder.files)) self.assertEqual(0, len(finder.python_modules)) self.assertEqual(0, len(finder.python_packages)) self.assertEqual(0, len(finder.directories)) # exclude anything under 'package1' pkg1 = Path(TEST_DATA / "test1" / "package1") def exclude(p: Path): return "package1" in p.parts finder = FileFinder(TEST_DATA / "test1", exclusion_filters=[exclude]) modules = finder.python_modules self.assertNotIn(pkg1, modules) prospector-1.10.3/tests/finder/test_pathutils.py000066400000000000000000000026471451367460400220560ustar00rootroot00000000000000from pathlib import Path from unittest import TestCase from prospector.pathutils import is_python_module, is_python_package, is_virtualenv from .utils import TEST_DATA class TestFinderUtils(TestCase): def test_is_python_package(self): this_dir = Path(__file__).parent self.assertTrue(is_python_package(this_dir)) self.assertFalse(is_python_package(TEST_DATA / "not_a_package")) self.assertFalse(is_python_package(TEST_DATA / "not_a_package" / "placeholder.txt")) def test_is_python(self): self.assertFalse(is_python_module(TEST_DATA / "not_a_package" / "placeholder.txt")) self.assertTrue(is_python_module(Path(__file__))) class TestVirtualenvDetection(TestCase): def test_is_a_venv(self): path = TEST_DATA / "venvs" / "is_a_venv" self.assertTrue(is_virtualenv(path)) def test_not_a_venv(self): path = TEST_DATA / "venvs" / "not_a_venv" self.assertFalse(is_virtualenv(path)) def test_long_path_not_a_venv(self): """ Windows doesn't allow extremely long paths. This unit test has to be run in Windows to be meaningful, though it shouldn't fail in other operating systems. """ path = TEST_DATA / "venvs" / "is_a_venv" for _ in range(14): path /= "long_path_not_a_venv" path /= "long_path_not_a_venv_long_path_not_a_v" self.assertFalse(is_virtualenv(path)) prospector-1.10.3/tests/finder/testdata/000077500000000000000000000000001451367460400202305ustar00rootroot00000000000000prospector-1.10.3/tests/finder/testdata/filter_test/000077500000000000000000000000001451367460400225545ustar00rootroot00000000000000prospector-1.10.3/tests/finder/testdata/filter_test/ignore_me/000077500000000000000000000000001451367460400245205ustar00rootroot00000000000000prospector-1.10.3/tests/finder/testdata/filter_test/ignore_me/level2/000077500000000000000000000000001451367460400257115ustar00rootroot00000000000000prospector-1.10.3/tests/finder/testdata/filter_test/ignore_me/level2/__init__.py000066400000000000000000000000001451367460400300100ustar00rootroot00000000000000prospector-1.10.3/tests/finder/testdata/filter_test/include_me/000077500000000000000000000000001451367460400246605ustar00rootroot00000000000000prospector-1.10.3/tests/finder/testdata/filter_test/include_me/something/000077500000000000000000000000001451367460400266555ustar00rootroot00000000000000prospector-1.10.3/tests/finder/testdata/filter_test/include_me/something/__init__.py000066400000000000000000000000001451367460400307540ustar00rootroot00000000000000prospector-1.10.3/tests/finder/testdata/not_a_package/000077500000000000000000000000001451367460400230035ustar00rootroot00000000000000prospector-1.10.3/tests/finder/testdata/not_a_package/placeholder.txt000066400000000000000000000000001451367460400260140ustar00rootroot00000000000000prospector-1.10.3/tests/finder/testdata/test1/000077500000000000000000000000001451367460400212705ustar00rootroot00000000000000prospector-1.10.3/tests/finder/testdata/test1/package1/000077500000000000000000000000001451367460400227445ustar00rootroot00000000000000prospector-1.10.3/tests/finder/testdata/test1/package1/__init__.py000066400000000000000000000000001451367460400250430ustar00rootroot00000000000000prospector-1.10.3/tests/finder/testdata/test1/somedir/000077500000000000000000000000001451367460400227325ustar00rootroot00000000000000prospector-1.10.3/tests/finder/testdata/test1/somedir/package2/000077500000000000000000000000001451367460400244075ustar00rootroot00000000000000prospector-1.10.3/tests/finder/testdata/test1/somedir/package2/__init__.py000066400000000000000000000000001451367460400265060ustar00rootroot00000000000000prospector-1.10.3/tests/finder/testdata/test2/000077500000000000000000000000001451367460400212715ustar00rootroot00000000000000prospector-1.10.3/tests/finder/testdata/test2/module.py000066400000000000000000000000001451367460400231160ustar00rootroot00000000000000prospector-1.10.3/tests/finder/testdata/test2/package/000077500000000000000000000000001451367460400226645ustar00rootroot00000000000000prospector-1.10.3/tests/finder/testdata/test2/package/__init__.py000066400000000000000000000000001451367460400247630ustar00rootroot00000000000000prospector-1.10.3/tests/finder/testdata/test2/package/subpackage/000077500000000000000000000000001451367460400247715ustar00rootroot00000000000000prospector-1.10.3/tests/finder/testdata/test2/package/subpackage/__init__.py000066400000000000000000000000001451367460400270700ustar00rootroot00000000000000prospector-1.10.3/tests/finder/testdata/test3/000077500000000000000000000000001451367460400212725ustar00rootroot00000000000000prospector-1.10.3/tests/finder/testdata/test3/package/000077500000000000000000000000001451367460400226655ustar00rootroot00000000000000prospector-1.10.3/tests/finder/testdata/test3/package/a/000077500000000000000000000000001451367460400231055ustar00rootroot00000000000000prospector-1.10.3/tests/finder/testdata/test3/package/a/__init__.py000066400000000000000000000000001451367460400252040ustar00rootroot00000000000000prospector-1.10.3/tests/finder/testdata/test3/package/b/000077500000000000000000000000001451367460400231065ustar00rootroot00000000000000prospector-1.10.3/tests/finder/testdata/test3/package/b/__init__.py000066400000000000000000000000001451367460400252050ustar00rootroot00000000000000prospector-1.10.3/tests/finder/testdata/test3/package/c/000077500000000000000000000000001451367460400231075ustar00rootroot00000000000000prospector-1.10.3/tests/finder/testdata/test3/package/c/__init__.py000066400000000000000000000000001451367460400252060ustar00rootroot00000000000000prospector-1.10.3/tests/finder/testdata/test3/package/d/000077500000000000000000000000001451367460400231105ustar00rootroot00000000000000prospector-1.10.3/tests/finder/testdata/test3/package/d/__init__.py000066400000000000000000000000001451367460400252070ustar00rootroot00000000000000prospector-1.10.3/tests/finder/testdata/test4/000077500000000000000000000000001451367460400212735ustar00rootroot00000000000000prospector-1.10.3/tests/finder/testdata/test4/__init__.py000066400000000000000000000000001451367460400233720ustar00rootroot00000000000000prospector-1.10.3/tests/finder/testdata/test4/sub1/000077500000000000000000000000001451367460400221455ustar00rootroot00000000000000prospector-1.10.3/tests/finder/testdata/test4/sub1/__init__.py000066400000000000000000000000001451367460400242440ustar00rootroot00000000000000prospector-1.10.3/tests/finder/testdata/test4/sub1/sub2/000077500000000000000000000000001451367460400230205ustar00rootroot00000000000000prospector-1.10.3/tests/finder/testdata/test4/sub1/sub2/__init__.py000066400000000000000000000000001451367460400251170ustar00rootroot00000000000000prospector-1.10.3/tests/finder/testdata/venvs/000077500000000000000000000000001451367460400213715ustar00rootroot00000000000000prospector-1.10.3/tests/finder/testdata/venvs/is_a_venv/000077500000000000000000000000001451367460400233425ustar00rootroot00000000000000prospector-1.10.3/tests/finder/testdata/venvs/is_a_venv/Scripts/000077500000000000000000000000001451367460400247715ustar00rootroot00000000000000prospector-1.10.3/tests/finder/testdata/venvs/is_a_venv/Scripts/README.txt000066400000000000000000000001271451367460400264670ustar00rootroot00000000000000This file is only here to ensure that the parent directory is present in Git checkouts prospector-1.10.3/tests/finder/testdata/venvs/is_a_venv/bin/000077500000000000000000000000001451367460400241125ustar00rootroot00000000000000prospector-1.10.3/tests/finder/testdata/venvs/is_a_venv/bin/README.txt000066400000000000000000000001271451367460400256100ustar00rootroot00000000000000This file is only here to ensure that the parent directory is present in Git checkouts prospector-1.10.3/tests/finder/testdata/venvs/is_a_venv/include/000077500000000000000000000000001451367460400247655ustar00rootroot00000000000000prospector-1.10.3/tests/finder/testdata/venvs/is_a_venv/include/README.txt000066400000000000000000000001271451367460400264630ustar00rootroot00000000000000This file is only here to ensure that the parent directory is present in Git checkouts prospector-1.10.3/tests/finder/testdata/venvs/is_a_venv/lib/000077500000000000000000000000001451367460400241105ustar00rootroot00000000000000prospector-1.10.3/tests/finder/testdata/venvs/is_a_venv/lib/README.txt000066400000000000000000000001271451367460400256060ustar00rootroot00000000000000This file is only here to ensure that the parent directory is present in Git checkouts prospector-1.10.3/tests/finder/testdata/venvs/long_path_not_a_venv/000077500000000000000000000000001451367460400255625ustar00rootroot00000000000000prospector-1.10.3/tests/finder/testdata/venvs/long_path_not_a_venv/long_path_not_a_venv/000077500000000000000000000000001451367460400317535ustar00rootroot00000000000000long_path_not_a_venv/000077500000000000000000000000001451367460400360655ustar00rootroot00000000000000prospector-1.10.3/tests/finder/testdata/venvs/long_path_not_a_venv/long_path_not_a_venvlong_path_not_a_venv/000077500000000000000000000000001451367460400422565ustar00rootroot00000000000000prospector-1.10.3/tests/finder/testdata/venvs/long_path_not_a_venv/long_path_not_a_venv/long_path_not_a_venvlong_path_not_a_venv/000077500000000000000000000000001451367460400464475ustar00rootroot00000000000000prospector-1.10.3/tests/finder/testdata/venvs/long_path_not_a_venv/long_path_not_a_venv/long_path_not_a_venv/long_path_not_a_venvlong_path_not_a_venv/000077500000000000000000000000001451367460400526405ustar00rootroot00000000000000prospector-1.10.3/tests/finder/testdata/venvs/long_path_not_a_venv/long_path_not_a_venv/long_path_not_a_venv/long_path_not_a_venv/long_path_not_a_venvlong_path_not_a_venv/long_path_not_a_venv/000077500000000000000000000000001451367460400570315ustar00rootroot00000000000000prospector-1.10.3/tests/finder/testdata/venvs/long_path_not_a_venv/long_path_not_a_venv/long_path_not_a_venv/long_path_not_a_venv/long_path_not_a_venvlong_path_not_a_venv/long_path_not_a_venv/long_path_not_a_venv/000077500000000000000000000000001451367460400632225ustar00rootroot00000000000000prospector-1.10.3/tests/finder/testdata/venvs/long_path_not_a_venv/long_path_not_a_venv/long_path_not_a_venv/long_path_not_a_venv/long_path_not_a_venvlong_path_not_a_venv/long_path_not_a_venv/long_path_not_a_venv/long_path_not_a_venv/000077500000000000000000000000001451367460400674135ustar00rootroot00000000000000prospector-1.10.3/tests/finder/testdata/venvs/long_path_not_a_venv/long_path_not_a_venv/long_path_not_a_venv/long_path_not_a_venv/long_path_not_a_venva6edd06703a3901d6ca8097a121118dee6e6378a.paxheader00006660000000000000000000000412145136746040020432xustar00rootroot00000000000000266 path=prospector-1.10.3/tests/finder/testdata/venvs/long_path_not_a_venv/long_path_not_a_venv/long_path_not_a_venv/long_path_not_a_venv/long_path_not_a_venv/long_path_not_a_venv/long_path_not_a_venv/long_path_not_a_venv/long_path_not_a_venv/long_path_not_a_venv/ a6edd06703a3901d6ca8097a121118dee6e6378a.data000077500000000000000000000000001451367460400172715ustar00rootroot0000000000000094690e9fb94265179d4b69fb7a528cdfe02af2ff.paxheader00006660000000000000000000000437145136746040020646xustar00rootroot00000000000000287 path=prospector-1.10.3/tests/finder/testdata/venvs/long_path_not_a_venv/long_path_not_a_venv/long_path_not_a_venv/long_path_not_a_venv/long_path_not_a_venv/long_path_not_a_venv/long_path_not_a_venv/long_path_not_a_venv/long_path_not_a_venv/long_path_not_a_venv/long_path_not_a_venv/ 94690e9fb94265179d4b69fb7a528cdfe02af2ff.data000077500000000000000000000000001451367460400174765ustar00rootroot00000000000000f50d2f2f3c7be22234e8d99a23aca5f6fb42e074.paxheader00006660000000000000000000000464145136746040020665xustar00rootroot00000000000000308 path=prospector-1.10.3/tests/finder/testdata/venvs/long_path_not_a_venv/long_path_not_a_venv/long_path_not_a_venv/long_path_not_a_venv/long_path_not_a_venv/long_path_not_a_venv/long_path_not_a_venv/long_path_not_a_venv/long_path_not_a_venv/long_path_not_a_venv/long_path_not_a_venv/long_path_not_a_venv/ f50d2f2f3c7be22234e8d99a23aca5f6fb42e074.data000077500000000000000000000000001451367460400175155ustar00rootroot00000000000000d81d71e1f2249ef511c58abe4b81f43108af2296.paxheader00006660000000000000000000000511145136746040020440xustar00rootroot00000000000000329 path=prospector-1.10.3/tests/finder/testdata/venvs/long_path_not_a_venv/long_path_not_a_venv/long_path_not_a_venv/long_path_not_a_venv/long_path_not_a_venv/long_path_not_a_venv/long_path_not_a_venv/long_path_not_a_venv/long_path_not_a_venv/long_path_not_a_venv/long_path_not_a_venv/long_path_not_a_venv/long_path_not_a_venv/ d81d71e1f2249ef511c58abe4b81f43108af2296.data000077500000000000000000000000001451367460400172775ustar00rootroot000000000000004cbcfe8d45a480c5245fb594a873fa91e89cc894.paxheader00006660000000000000000000000536145136746040020643xustar00rootroot00000000000000350 path=prospector-1.10.3/tests/finder/testdata/venvs/long_path_not_a_venv/long_path_not_a_venv/long_path_not_a_venv/long_path_not_a_venv/long_path_not_a_venv/long_path_not_a_venv/long_path_not_a_venv/long_path_not_a_venv/long_path_not_a_venv/long_path_not_a_venv/long_path_not_a_venv/long_path_not_a_venv/long_path_not_a_venv/long_path_not_a_venv/ 4cbcfe8d45a480c5245fb594a873fa91e89cc894.data000077500000000000000000000000001451367460400174735ustar00rootroot000000000000005c56fe7505b244d73268fec69aa4279ddaa0e88c.paxheader00006660000000000000000000000605145136746040020625xustar00rootroot00000000000000389 path=prospector-1.10.3/tests/finder/testdata/venvs/long_path_not_a_venv/long_path_not_a_venv/long_path_not_a_venv/long_path_not_a_venv/long_path_not_a_venv/long_path_not_a_venv/long_path_not_a_venv/long_path_not_a_venv/long_path_not_a_venv/long_path_not_a_venv/long_path_not_a_venv/long_path_not_a_venv/long_path_not_a_venv/long_path_not_a_venv/long_path_not_a_venv_long_path_not_a_v/ 5c56fe7505b244d73268fec69aa4279ddaa0e88c.data000077500000000000000000000000001451367460400174605ustar00rootroot0000000000000015eea908548e46b1c81c7f570972fff5c10c100a.paxheader00006660000000000000000000000617145136746040020447xustar00rootroot00000000000000399 path=prospector-1.10.3/tests/finder/testdata/venvs/long_path_not_a_venv/long_path_not_a_venv/long_path_not_a_venv/long_path_not_a_venv/long_path_not_a_venv/long_path_not_a_venv/long_path_not_a_venv/long_path_not_a_venv/long_path_not_a_venv/long_path_not_a_venv/long_path_not_a_venv/long_path_not_a_venv/long_path_not_a_venv/long_path_not_a_venv/long_path_not_a_venv_long_path_not_a_v/README.txt 15eea908548e46b1c81c7f570972fff5c10c100a.data000066400000000000000000000001271451367460400173010ustar00rootroot00000000000000This file is only here to ensure that the parent directory is present in Git checkouts prospector-1.10.3/tests/finder/testdata/venvs/not_a_venv/000077500000000000000000000000001451367460400235275ustar00rootroot00000000000000prospector-1.10.3/tests/finder/testdata/venvs/not_a_venv/bin/000077500000000000000000000000001451367460400242775ustar00rootroot00000000000000prospector-1.10.3/tests/finder/testdata/venvs/not_a_venv/bin/README.txt000066400000000000000000000001271451367460400257750ustar00rootroot00000000000000This file is only here to ensure that the parent directory is present in Git checkouts prospector-1.10.3/tests/finder/testdata/venvs/not_a_venv/thing.py000066400000000000000000000000001451367460400252000ustar00rootroot00000000000000prospector-1.10.3/tests/finder/utils.py000066400000000000000000000001111451367460400201220ustar00rootroot00000000000000from pathlib import Path TEST_DATA = Path(__file__).parent / "testdata" prospector-1.10.3/tests/formatters/000077500000000000000000000000001451367460400173365ustar00rootroot00000000000000prospector-1.10.3/tests/formatters/__init__.py000066400000000000000000000000001451367460400214350ustar00rootroot00000000000000prospector-1.10.3/tests/formatters/test_formatter_types.py000066400000000000000000000030671451367460400242040ustar00rootroot00000000000000import datetime from pathlib import Path import pytest from prospector.formatters import FORMATTERS from prospector.message import Location, Message from prospector.profiles.profile import ProspectorProfile @pytest.fixture def _simple_profile() -> ProspectorProfile: return ProspectorProfile(name="horse", profile_dict={}, inherit_order=["horse"]) @pytest.fixture def _simple_summary() -> dict: return { "started": datetime.datetime(2014, 1, 1), "completed": datetime.datetime(2014, 1, 1), "message_count": 0, "time_taken": "0", "libraries": [], "strictness": "veryhigh", "profiles": "", "tools": [], } def test_formatter_types(_simple_summary, _simple_profile): for formatter_name, formatter in FORMATTERS.items(): formatter_instance = formatter(_simple_summary, [], _simple_profile) assert isinstance(formatter_instance.render(True, True, False), str) def test_formatters_render(_simple_summary, _simple_profile): """ Basic test to ensure that formatters can at least render messages without erroring """ for formatter_name, formatter in FORMATTERS.items(): messages = [ Message( "testtool", "oh-no", Location(Path(__file__), "formatters/test_formatter_types", "test_formatters_render", 39, 12), "testing formatters work", ) ] formatter_instance = formatter(_simple_summary, messages, _simple_profile) formatter_instance.render(True, True, False) prospector-1.10.3/tests/profiles/000077500000000000000000000000001451367460400167735ustar00rootroot00000000000000prospector-1.10.3/tests/profiles/__init__.py000066400000000000000000000000001451367460400210720ustar00rootroot00000000000000prospector-1.10.3/tests/profiles/profiles/000077500000000000000000000000001451367460400206165ustar00rootroot00000000000000prospector-1.10.3/tests/profiles/profiles/empty_disable_list.yaml000066400000000000000000000000231451367460400253510ustar00rootroot00000000000000pylint: disable: prospector-1.10.3/tests/profiles/profiles/empty_profile.yaml000066400000000000000000000000001451367460400243460ustar00rootroot00000000000000prospector-1.10.3/tests/profiles/profiles/enabled_disabled.yaml000066400000000000000000000000531451367460400247210ustar00rootroot00000000000000pydocstyle: enable: D301 disable: D301 prospector-1.10.3/tests/profiles/profiles/ignores.yaml000066400000000000000000000000451451367460400231470ustar00rootroot00000000000000ignore: - /migrations/ - ^tests/ prospector-1.10.3/tests/profiles/profiles/inheritance/000077500000000000000000000000001451367460400231075ustar00rootroot00000000000000prospector-1.10.3/tests/profiles/profiles/inheritance/pep8/000077500000000000000000000000001451367460400237635ustar00rootroot00000000000000prospector-1.10.3/tests/profiles/profiles/inheritance/pep8/base.yaml000066400000000000000000000000131451367460400255530ustar00rootroot00000000000000pep8: none prospector-1.10.3/tests/profiles/profiles/inheritance/pep8/start.yaml000066400000000000000000000000371451367460400260040ustar00rootroot00000000000000inherits: - base pep8: full prospector-1.10.3/tests/profiles/profiles/inheritance/precedence/000077500000000000000000000000001451367460400252045ustar00rootroot00000000000000prospector-1.10.3/tests/profiles/profiles/inheritance/precedence/base1.yaml000066400000000000000000000000251451367460400270600ustar00rootroot00000000000000pylint: run: false prospector-1.10.3/tests/profiles/profiles/inheritance/precedence/base2.yaml000066400000000000000000000002141451367460400270610ustar00rootroot00000000000000inherits: - base1 pylint: run: true disable: - missing-docstring - expression-not-assigned pydocstyle: enable: - D401 prospector-1.10.3/tests/profiles/profiles/inheritance/precedence/start.yaml000066400000000000000000000002001451367460400272150ustar00rootroot00000000000000strictness: none inherits: - base2 pylint: run: true enable: - missing-docstring pydocstyle: disable: - D401 prospector-1.10.3/tests/profiles/profiles/inheritance/shorthand_inheritance/000077500000000000000000000000001451367460400274525ustar00rootroot00000000000000prospector-1.10.3/tests/profiles/profiles/inheritance/shorthand_inheritance/base1.yaml000066400000000000000000000000731451367460400313310ustar00rootroot00000000000000strictness: medium doc-warnings: true test-warnings: false prospector-1.10.3/tests/profiles/profiles/inheritance/shorthand_inheritance/base2.yaml000066400000000000000000000000751451367460400313340ustar00rootroot00000000000000inherits: - base1 test-warnings: true doc-warnings: false prospector-1.10.3/tests/profiles/profiles/inheritance/shorthand_inheritance/start.yaml000066400000000000000000000000711451367460400314710ustar00rootroot00000000000000inherits: - base2 strictness: high doc-warnings: true prospector-1.10.3/tests/profiles/profiles/inheritance/strictness_equivalence/000077500000000000000000000000001451367460400276715ustar00rootroot00000000000000prospector-1.10.3/tests/profiles/profiles/inheritance/strictness_equivalence/start.yaml000066400000000000000000000002031451367460400317050ustar00rootroot00000000000000# this should be basically equivalent to the strictness_medium profile strictness: medium doc-warnings: false test-warmings: false prospector-1.10.3/tests/profiles/profiles/inheritance/tool_enabled/000077500000000000000000000000001451367460400255365ustar00rootroot00000000000000prospector-1.10.3/tests/profiles/profiles/inheritance/tool_enabled/base.yaml000066400000000000000000000000521451367460400273310ustar00rootroot00000000000000pydocstyle: run: no pylint: run: yes prospector-1.10.3/tests/profiles/profiles/inheritance/tool_enabled/start.yaml000066400000000000000000000000761451367460400275620ustar00rootroot00000000000000inherits: - base pydocstyle: run: yes pylint: run: no prospector-1.10.3/tests/profiles/profiles/inherittest-module-file.yaml000066400000000000000000000000351451367460400262420ustar00rootroot00000000000000inherits: - test:alternate prospector-1.10.3/tests/profiles/profiles/inherittest-module.yaml000066400000000000000000000000231451367460400253220ustar00rootroot00000000000000inherits: - test prospector-1.10.3/tests/profiles/profiles/inherittest1.yaml000066400000000000000000000001101451367460400241150ustar00rootroot00000000000000inherits: - inherittest2 pylint: disable: - raw-checker-failed prospector-1.10.3/tests/profiles/profiles/inherittest2.yaml000066400000000000000000000000731451367460400241260ustar00rootroot00000000000000inherits: - inherittest3 pylint: disable: - I0002 prospector-1.10.3/tests/profiles/profiles/inherittest3.yaml000066400000000000000000000001141451367460400241230ustar00rootroot00000000000000inherits: - inherittest1 - inherittest2 pylint: disable: - I0003 prospector-1.10.3/tests/profiles/profiles/pydocstyle_and_pylint_disabled.yaml000066400000000000000000000000701451367460400277460ustar00rootroot00000000000000inherits: - pylint_disabled pydocstyle: run: false prospector-1.10.3/tests/profiles/profiles/pylint_disabled.yaml000066400000000000000000000000471451367460400246510ustar00rootroot00000000000000strictness: none pylint: run: false prospector-1.10.3/tests/profiles/profiles/pylint_load_plugins.yaml000066400000000000000000000000771451367460400255650ustar00rootroot00000000000000pylint: load-plugins: - first_plugin - second_plugin prospector-1.10.3/tests/profiles/profiles/renaming_pepX/000077500000000000000000000000001451367460400234125ustar00rootroot00000000000000prospector-1.10.3/tests/profiles/profiles/renaming_pepX/README.txt000066400000000000000000000002541451367460400251110ustar00rootroot00000000000000Test profiles for use to ensure that when renaming, old behaviour is retained for backwards compatibility during deprecation period pep8->pycodestyle pep257->pydocstyle prospector-1.10.3/tests/profiles/profiles/renaming_pepX/pep8_shorthand_pycodestyle.yaml000066400000000000000000000000751451367460400316520ustar00rootroot00000000000000pycodestyle: options: max-line-length: 120 pep8: full prospector-1.10.3/tests/profiles/profiles/renaming_pepX/renaming_newname.yaml000066400000000000000000000001261451367460400276070ustar00rootroot00000000000000pycodestyle: run: true options: max-line-length: 120 pydocstyle: run: true prospector-1.10.3/tests/profiles/profiles/renaming_pepX/renaming_oldname.yaml000066400000000000000000000001131451367460400275700ustar00rootroot00000000000000pep8: run: true options: max-line-length: 120 pep257: run: true prospector-1.10.3/tests/profiles/profiles/renaming_pepX/test_inheritance/000077500000000000000000000000001451367460400267425ustar00rootroot00000000000000prospector-1.10.3/tests/profiles/profiles/renaming_pepX/test_inheritance/base_newname.yaml000066400000000000000000000002151451367460400322500ustar00rootroot00000000000000pydocstyle: run: true disable: - D400 - D401 pycodestyle: run: true disable: - E266 options: max-line-length: 120 prospector-1.10.3/tests/profiles/profiles/renaming_pepX/test_inheritance/base_oldname.yaml000066400000000000000000000002031451367460400322320ustar00rootroot00000000000000pep257: run: true disable: - D400 - D401 pep8: run: false disable: - E266 options: max-line-length: 140 prospector-1.10.3/tests/profiles/profiles/renaming_pepX/test_inheritance/child_newname.yaml000066400000000000000000000001611451367460400324210ustar00rootroot00000000000000inherits: - base_oldname.yaml pydocstyle: enable: - D401 pycodestyle: run: true enable: - E266 prospector-1.10.3/tests/profiles/profiles/renaming_pepX/test_inheritance/child_oldname.yaml000066400000000000000000000001461451367460400324110ustar00rootroot00000000000000inherits: - base_newname.yaml pep257: enable: - D401 pep8: run: true enable: - E266 prospector-1.10.3/tests/profiles/profiles/simple.yaml000066400000000000000000000000531451367460400227710ustar00rootroot00000000000000pylint: disable: - C1000 - E1020 prospector-1.10.3/tests/profiles/profiles/test_optional/000077500000000000000000000000001451367460400235025ustar00rootroot00000000000000prospector-1.10.3/tests/profiles/profiles/test_optional/nonoptional_missing.yaml000066400000000000000000000000561451367460400304600ustar00rootroot00000000000000inherits: - doesnotexist - strictness_low prospector-1.10.3/tests/profiles/profiles/test_optional/optional_base.yaml000066400000000000000000000000241451367460400272010ustar00rootroot00000000000000dodgy: run: false prospector-1.10.3/tests/profiles/profiles/test_optional/optional_missing.yaml000066400000000000000000000000641451367460400277440ustar00rootroot00000000000000inherits: - does-not-exist? - strictness_medium prospector-1.10.3/tests/profiles/profiles/test_optional/optional_present.yaml000066400000000000000000000000351451367460400277510ustar00rootroot00000000000000inherits: - optional_base? prospector-1.10.3/tests/profiles/test_profile.py000066400000000000000000000217001451367460400220440ustar00rootroot00000000000000import os from pathlib import Path from unittest import TestCase from prospector.profiles.exceptions import ProfileNotFound from prospector.profiles.profile import ProspectorProfile THIS_DIR = Path(__file__).parent BUILTIN_PROFILES = THIS_DIR / "../../prospector/profiles/profiles" class ProfileTestBase(TestCase): def setUp(self): self._profile_path = [str(THIS_DIR / "profiles"), str(BUILTIN_PROFILES)] class TestOptionalProfiles(TestCase): def setUp(self) -> None: self._profile_path = [str(THIS_DIR / "profiles/test_optional"), str(BUILTIN_PROFILES)] def test_nonoptional_missing(self): self.assertRaises(ProfileNotFound, ProspectorProfile.load, "nonoptional_missing", self._profile_path) def test_optional_missing(self): # ensure loads without an exception to verify that a missing inherits works fine profile = ProspectorProfile.load("optional_missing", self._profile_path) self.assertTrue(profile.is_tool_enabled("dodgy")) def test_optional_present(self): # optional does not mean ignore so verify that values are inherited if present profile = ProspectorProfile.load("optional_present", self._profile_path) self.assertFalse(profile.is_tool_enabled("dodgy")) class TestToolRenaming(TestCase): def setUp(self) -> None: self._profile_path = [ str(THIS_DIR / "profiles/renaming_pepX/test_inheritance"), str(THIS_DIR / "profiles/renaming_pepX"), str(BUILTIN_PROFILES), ] def test_old_inherits_from_new(self): profile = ProspectorProfile.load("child_oldname.yaml", self._profile_path, allow_shorthand=False) assert profile.is_tool_enabled("pydocstyle") assert profile.is_tool_enabled("pycodestyle") assert "D401" in profile.pydocstyle["enable"] assert "D401" not in profile.pydocstyle["disable"] assert "E266" not in profile.pycodestyle["disable"] assert 120 == profile.pycodestyle["options"]["max-line-length"] def test_new_inherits_from_old(self): """ Ensure that `pep8` can inherit from a `pycodecstyle` block and vice versa """ profile = ProspectorProfile.load("child_newname.yaml", self._profile_path, allow_shorthand=False) assert profile.is_tool_enabled("pydocstyle") assert profile.is_tool_enabled("pycodestyle") assert "D401" not in profile.pydocstyle["disable"] assert "D401" in profile.pydocstyle["enable"] assert "E266" not in profile.pycodestyle["disable"] assert 140 == profile.pycodestyle["options"]["max-line-length"] def test_legacy_names_equivalent(self): """ 'pep8' tool was renamed to pycodestyle, 'pep257' tool was renamed to pydocstyle This test is to ensure that, for backwards compatibility until it is removed in prospector 2.0, that the old names and the new names are equivalent in profiles """ profile_old = ProspectorProfile.load("renaming_oldname", self._profile_path, allow_shorthand=False) profile_new = ProspectorProfile.load("renaming_newname", self._profile_path, allow_shorthand=False) # do they serialise to the same thing? for tool in ("pycodestyle", "pydocstyle"): old_dict = profile_old.as_dict()[tool] new_dict = profile_new.as_dict()[tool] self.assertListEqual(sorted(old_dict["disable"]), sorted(new_dict["disable"])) self.assertListEqual(sorted(old_dict["enable"]), sorted(new_dict["enable"])) self.assertDictEqual(profile_old.as_dict(), profile_new.as_dict()) # do they have the same settings for everything? for prof in (profile_old, profile_new): self.assertTrue(prof.is_tool_enabled("pycodestyle")) self.assertTrue(prof.is_tool_enabled("pydocstyle")) self.assertEqual(prof.pycodestyle["options"]["max-line-length"], 120) def test_pep8_shorthand_with_newname(self): """ 'pep8' is still a valid entry in the profile but in the future, only as a shorthand ("pep8: full") however for now, it also has to be able to configure pycodestyle """ profile = ProspectorProfile.load("pep8_shorthand_pycodestyle", self._profile_path, allow_shorthand=True) self.assertTrue("full_pep8" in profile.inherit_order) self.assertTrue(profile.is_tool_enabled("pycodestyle")) self.assertEqual(profile.pycodestyle["options"]["max-line-length"], 120) class TestProfileParsing(ProfileTestBase): def test_empty_disable_list(self): """ This test verifies that a profile can still be loaded if it contains an empty 'pylint.disable' list """ profile = ProspectorProfile.load("empty_disable_list", self._profile_path, allow_shorthand=False) self.assertEqual([], profile.pylint["disable"]) def test_empty_profile(self): """ Verifies that a completely empty profile can still be parsed and have default values """ profile = ProspectorProfile.load("empty_profile", self._profile_path, allow_shorthand=False) self.assertEqual([], profile.pylint["disable"]) def test_ignores(self): profile = ProspectorProfile.load("ignores", self._profile_path) self.assertEqual(["^tests/", "/migrations/"].sort(), profile.ignore_patterns.sort()) def test_enabled_in_disabled(self): """ If a :return: """ def test_disable_tool(self): profile = ProspectorProfile.load("pylint_disabled", self._profile_path) self.assertFalse(profile.is_tool_enabled("pylint")) self.assertTrue(profile.is_tool_enabled("pycodestyle")) def test_load_plugins(self): profile = ProspectorProfile.load("pylint_load_plugins", self._profile_path) self.assertEqual(["first_plugin", "second_plugin"], profile.pylint["load-plugins"]) class TestProfileInheritance(ProfileTestBase): def _example_path(self, testname): return os.path.join(os.path.dirname(__file__), "profiles", "inheritance", testname) def _load(self, testname): profile_path = self._profile_path + [self._example_path(testname)] return ProspectorProfile.load("start", profile_path) def test_simple_inheritance(self): profile = ProspectorProfile.load("inherittest3", self._profile_path, allow_shorthand=False) disable = profile.pylint["disable"] disable.sort() self.assertEqual(["I0002", "I0003", "raw-checker-failed"], disable) def test_disable_tool_inheritance(self): profile = ProspectorProfile.load("pydocstyle_and_pylint_disabled", self._profile_path) self.assertFalse(profile.is_tool_enabled("pylint")) self.assertFalse(profile.is_tool_enabled("pydocstyle")) def test_precedence(self): profile = self._load("precedence") self.assertTrue(profile.is_tool_enabled("pylint")) self.assertTrue("expression-not-assigned" in profile.get_disabled_messages("pylint")) # TODO: this doesn't work entirely as expected, but changing would be a backwards incompatible # change - so this is parked until version 2.0 # self.assertFalse("D401" in profile.get_disabled_messages("pydocstyle")) def test_strictness_equivalence(self): profile = self._load("strictness_equivalence") medium_strictness = ProspectorProfile.load("strictness_medium", self._profile_path) self.assertListEqual( sorted(profile.pylint["disable"]), sorted(medium_strictness.pylint["disable"]), ) def test_shorthand_inheritance(self): profile = self._load("shorthand_inheritance") high_strictness = ProspectorProfile.load( "strictness_high", self._profile_path, # don't implicitly add things allow_shorthand=False, # but do include the profiles that the start.yaml will forced_inherits=["doc_warnings", "no_member_warnings"], ) self.assertDictEqual(profile.pylint, high_strictness.pylint) self.assertDictEqual(profile.pycodestyle, high_strictness.pycodestyle) self.assertDictEqual(profile.pyflakes, high_strictness.pyflakes) def test_tool_enabled(self): profile = self._load("tool_enabled") self.assertTrue(profile.is_tool_enabled("pydocstyle")) self.assertFalse(profile.is_tool_enabled("pylint")) def test_pycodestyle_inheritance(self): profile = self._load("pep8") self.assertTrue("full_pep8" in profile.inherit_order) def test_module_inheritance(self): profile = ProspectorProfile.load("inherittest-module", self._profile_path, allow_shorthand=False) self.assertEqual(["test-from-module"], profile.pylint["disable"]) def test_module_file_inheritance(self): profile = ProspectorProfile.load("inherittest-module-file", self._profile_path, allow_shorthand=False) self.assertEqual(["alternate-test-from-module"], profile.pylint["disable"]) prospector-1.10.3/tests/prospector-profile-test/000077500000000000000000000000001451367460400217635ustar00rootroot00000000000000prospector-1.10.3/tests/prospector-profile-test/prospector_profile_test/000077500000000000000000000000001451367460400267425ustar00rootroot00000000000000prospector-1.10.3/tests/prospector-profile-test/prospector_profile_test/__init__.py000066400000000000000000000000001451367460400310410ustar00rootroot00000000000000prospector-1.10.3/tests/prospector-profile-test/prospector_profile_test/alternate.yaml000066400000000000000000000000641451367460400316050ustar00rootroot00000000000000pylint: disable: - alternate-test-from-module prospector-1.10.3/tests/prospector-profile-test/prospector_profile_test/prospector.yaml000066400000000000000000000000521451367460400320230ustar00rootroot00000000000000pylint: disable: - test-from-module prospector-1.10.3/tests/prospector-profile-test/pyproject.toml000066400000000000000000000003111451367460400246720ustar00rootroot00000000000000[project] name = "prospector-profile-test" description = "A package used to test the prospector external profile" version = "0.0.0" [tool.setuptools.package-data] prospector_profile_test = ["*.yaml"] prospector-1.10.3/tests/suppression/000077500000000000000000000000001451367460400175425ustar00rootroot00000000000000prospector-1.10.3/tests/suppression/__init__.py000066400000000000000000000000001451367460400216410ustar00rootroot00000000000000prospector-1.10.3/tests/suppression/test_suppression.py000066400000000000000000000031161451367460400235460ustar00rootroot00000000000000import os import unittest from pathlib import Path from prospector.suppression import get_noqa_suppressions from tests.utils import patch_workdir_argv class SuppressionTest(unittest.TestCase): def _get_file_contents(self, name): path = os.path.join(os.path.dirname(__file__), "testdata", name) with open(path) as testfile: return testfile.readlines() def test_ignore_file(self): file_contents = self._get_file_contents("test_ignore_file/test.py") whole_file, _ = get_noqa_suppressions(file_contents) self.assertTrue(whole_file) def test_ignore_lines(self): file_contents = self._get_file_contents("test_ignore_lines/test.py") _, lines = get_noqa_suppressions(file_contents) self.assertSetEqual({2, 3, 4}, lines) def test_ignore_enum_error(self): file_contents = self._get_file_contents("test_ignore_enum/test.py") _, lines = get_noqa_suppressions(file_contents) self.assertSetEqual({5}, lines) def test_filter_messages(self): with patch_workdir_argv( target="setoptconf.source.commandline.sys.argv", workdir=Path(__file__).parent / "testdata/test_filter_messages", ) as pros: self.assertEqual(0, pros.summary["message_count"]) def test_filter_messages_negative(self): with patch_workdir_argv( target="setoptconf.source.commandline.sys.argv", workdir=Path(__file__).parent / "testdata/test_filter_messages_negative", ) as pros: self.assertEqual(5, pros.summary["message_count"]) prospector-1.10.3/tests/suppression/testdata/000077500000000000000000000000001451367460400213535ustar00rootroot00000000000000prospector-1.10.3/tests/suppression/testdata/test_filter_messages/000077500000000000000000000000001451367460400255665ustar00rootroot00000000000000prospector-1.10.3/tests/suppression/testdata/test_filter_messages/.prospector.yaml000066400000000000000000000001451451367460400307300ustar00rootroot00000000000000output-format: pylint autodetect: false strictness: veryhigh test-warnings: true doc-warnings: false prospector-1.10.3/tests/suppression/testdata/test_filter_messages/ignore_enum.py000066400000000000000000000001211451367460400304410ustar00rootroot00000000000000from enum import Enum, unique @unique class FOO_BAR(Enum): # noqa BAZ = 1 prospector-1.10.3/tests/suppression/testdata/test_filter_messages/ignore_file.py000066400000000000000000000000331451367460400304160ustar00rootroot00000000000000import collections # noqa prospector-1.10.3/tests/suppression/testdata/test_filter_messages/ignore_lines.py000066400000000000000000000001051451367460400306110ustar00rootroot00000000000000import collections # noqa import os # noqa import tempfile # noqa prospector-1.10.3/tests/suppression/testdata/test_filter_messages_negative/000077500000000000000000000000001451367460400274505ustar00rootroot00000000000000prospector-1.10.3/tests/suppression/testdata/test_filter_messages_negative/.prospector.yaml000066400000000000000000000001451451367460400326120ustar00rootroot00000000000000output-format: pylint autodetect: false strictness: veryhigh test-warnings: true doc-warnings: false prospector-1.10.3/tests/suppression/testdata/test_filter_messages_negative/ignore_enum.py000066400000000000000000000001111451367460400323220ustar00rootroot00000000000000from enum import Enum, unique @unique class FOO_BAR(Enum): BAZ = 1 prospector-1.10.3/tests/suppression/testdata/test_filter_messages_negative/ignore_file.py000066400000000000000000000000231451367460400322770ustar00rootroot00000000000000import collections prospector-1.10.3/tests/suppression/testdata/test_filter_messages_negative/ignore_lines.py000066400000000000000000000000551451367460400324770ustar00rootroot00000000000000import collections import os import tempfile prospector-1.10.3/tests/suppression/testdata/test_ignore_enum/000077500000000000000000000000001451367460400247215ustar00rootroot00000000000000prospector-1.10.3/tests/suppression/testdata/test_ignore_enum/test.py000066400000000000000000000001211451367460400262440ustar00rootroot00000000000000from enum import Enum, unique @unique class FOO_BAR(Enum): # noqa BAZ = 1 prospector-1.10.3/tests/suppression/testdata/test_ignore_file/000077500000000000000000000000001451367460400246745ustar00rootroot00000000000000prospector-1.10.3/tests/suppression/testdata/test_ignore_file/test.py000066400000000000000000000000421451367460400262210ustar00rootroot00000000000000# flake8: noqa import collections prospector-1.10.3/tests/suppression/testdata/test_ignore_lines/000077500000000000000000000000001451367460400250675ustar00rootroot00000000000000prospector-1.10.3/tests/suppression/testdata/test_ignore_lines/test.py000066400000000000000000000001241451367460400264150ustar00rootroot00000000000000# flake8: noqa import collections # NOQA import os # noqa import tempfile # noqa prospector-1.10.3/tests/test_autodetect.py000066400000000000000000000000001451367460400207100ustar00rootroot00000000000000prospector-1.10.3/tests/test_blender.py000066400000000000000000000045631451367460400202040ustar00rootroot00000000000000from unittest import TestCase from prospector import blender from prospector.message import Location, Message class TestBlendLine(TestCase): BLEND = ( (("s1", "s1c01"), ("s2", "s2c12")), (("s3", "s3c81"), ("s1", "s1c04"), ("s2", "s2c44")), ) def _do_test(self, messages, expected): def _msg(source, code): loc = Location("path.py", "path", None, 1, 0) return Message(source, code, loc, "Test Message") messages = [_msg(*m) for m in messages] expected = set(expected) blended = blender.blend_line(messages, TestBlendLine.BLEND) result = {(msg.source, msg.code) for msg in blended} self.assertEqual(expected, result) def test_blend_line(self): messages = (("s2", "s2c12"), ("s2", "s2c11"), ("s1", "s1c01")) expected = ( ("s1", "s1c01"), ("s2", "s2c11"), # s2c12 should be blended with s1c01 ) self._do_test(messages, expected) def test_single_blend(self): # these three should be blended together messages = ( ("s1", "s1c04"), ("s2", "s2c44"), ("s3", "s3c81"), ) # the s3 message is the highest priority expected = (("s3", "s3c81"),) self._do_test(messages, expected) def test_nothing_to_blend(self): """ Verifies that messages pass through if there is nothing to blend """ messages = (("s4", "s4c99"), ("s4", "s4c01"), ("s5", "s5c51"), ("s6", "s6c66")) self._do_test(messages, messages) # expected = messages def test_no_messages(self): """ Ensures that the blending works fine when there are no messages to blend """ self._do_test((), ()) def test_multiple_lines(): def _msg(source, code, line_number): loc = Location("path.py", "path", None, line_number, 0) return Message(source, code, loc, "Test Message") messages = [ _msg("s1", "s1c001", 4), _msg("s2", "s2c001", 6), _msg("s2", "s2c101", 4), _msg("s1", "s1c001", 6), ] result = blender.blend(messages, ((("s1", "s1c001"), ("s2", "s2c101")),)) result = [(msg.source, msg.code, msg.location.line) for msg in result] result = set(result) expected = {("s1", "s1c001", 4), ("s1", "s1c001", 6), ("s2", "s2c001", 6)} assert expected == result prospector-1.10.3/tests/test_message.py000066400000000000000000000065271451367460400202170ustar00rootroot00000000000000# flake8: noqa from pathlib import Path from unittest import TestCase from prospector.message import Location class LocationPathTest(TestCase): def test_paths(self): """ Tests the absolute and relative path conversion """ root = Path(__file__).parent.parent loc = Location(__file__, "module", "func", 1, 2) self.assertEqual(loc.relative_path(root), Path("tests/test_message.py")) absolute = root / "tests/test_message.py" self.assertEqual(loc.absolute_path(), absolute) def test_strings_or_paths(self): """ For ease of use the Location object can accept a path as a Path or a string """ path = "/tmp/path/module1.py" args = ["module1", "somefunc", 12, 2] self.assertEqual(Location("/tmp/path/module1.py", *args), Location(Path(path), *args)) def test_bad_path_input(self): self.assertRaises(ValueError, Location, 3.2, "module", "func", 1, 2) self.assertRaises(ValueError, Location, None, "module", "func", 1, 2) class LocationOrderTest(TestCase): def test_path_order(self): locs = [ Location(Path("/tmp/path/module3.py"), "module3", "somefunc", 15, 0), Location(Path("/tmp/path/module1.py"), "module1", "somefunc", 10, 0), Location("/tmp/path/module2.py", "module2", "somefunc", 9, 0), ] paths = [loc.path for loc in locs] expected = sorted(paths) self.assertEqual(expected, [loc.path for loc in sorted(locs)]) def test_line_order(self): locs = [ Location("/tmp/path/module1.py", "module1", "somefunc", 15, 0), Location("/tmp/path/module1.py", "module1", "somefunc", 10, 0), Location("/tmp/path/module1.py", "module1", "somefunc", 12, 0), ] lines = [loc.line for loc in locs] expected = sorted(lines) self.assertEqual(expected, [loc.line for loc in sorted(locs)]) def test_sort_between_none_lines(self): locs = [ Location("/tmp/path/module1.py", "module1", "somefunc", 15, 0), Location("/tmp/path/module1.py", "module1", "somefunc", 10, 0), Location("/tmp/path/module1.py", "module1", "somefunc", -1, 0), ] lines = [(loc.line or -1) for loc in locs] expected = [None if l == -1 else l for l in sorted(lines)] self.assertEqual(expected, [loc.line for loc in sorted(locs)]) def test_char_order(self): locs = [ Location("/tmp/path/module1.py", "module1", "somefunc", 10, 7), Location("/tmp/path/module1.py", "module1", "somefunc", 10, 0), Location("/tmp/path/module1.py", "module1", "somefunc", 10, 2), ] chars = [loc.character for loc in locs] expected = sorted(chars) self.assertEqual(expected, [loc.character for loc in sorted(locs)]) def test_sort_between_none_chars(self): locs = [ Location("/tmp/path/module1.py", "module1", "somefunc", 10, -1), Location("/tmp/path/module1.py", "module1", "somefunc", 10, 1), Location("/tmp/path/module1.py", "module1", "somefunc", 10, 2), ] chars = [(loc.character or -1) for loc in locs] expected = [None if c == -1 else c for c in sorted(chars)] self.assertEqual(expected, [loc.character for loc in sorted(locs)]) prospector-1.10.3/tests/tools/000077500000000000000000000000001451367460400163105ustar00rootroot00000000000000prospector-1.10.3/tests/tools/__init__.py000066400000000000000000000000001451367460400204070ustar00rootroot00000000000000prospector-1.10.3/tests/tools/bandit/000077500000000000000000000000001451367460400175515ustar00rootroot00000000000000prospector-1.10.3/tests/tools/bandit/__init__.py000066400000000000000000000000001451367460400216500ustar00rootroot00000000000000prospector-1.10.3/tests/tools/bandit/test_bandit_tool.py000066400000000000000000000013421451367460400234600ustar00rootroot00000000000000from pathlib import Path from unittest import TestCase from unittest.mock import patch from prospector.config import ProspectorConfig from prospector.finder import FileFinder from prospector.tools.bandit import BanditTool class TestBanditTool(TestCase): def setUp(self): with patch("sys.argv", [""]): self.config = ProspectorConfig() self.bandit_tool = BanditTool() def test_hardcoded_password_string(self): found_files = FileFinder(Path(__file__).parent / "testpath/testfile.py") self.bandit_tool.configure(self.config, found_files) messages = self.bandit_tool.run(found_files) self.assertTrue(any(message.code in ["B107", "B105", "B106"] for message in messages)) prospector-1.10.3/tests/tools/bandit/testpath/000077500000000000000000000000001451367460400214055ustar00rootroot00000000000000prospector-1.10.3/tests/tools/bandit/testpath/__init__.py000066400000000000000000000000001451367460400235040ustar00rootroot00000000000000prospector-1.10.3/tests/tools/bandit/testpath/testfile.py000066400000000000000000000011661451367460400236020ustar00rootroot00000000000000# flake8: noqa def someFunction(user, password="Admin"): print("Hi " + user) def someFunction2(password): if password == "root": print("OK, logged in") def noMatch(password): if password == "": print("No password!") def NoMatch2(password): if password == "ajklawejrkl42348swfgkg": print("Nice password!") def doLogin(password="blerg"): pass def NoMatch3(a, b): pass doLogin(password="blerg") password = "blerg" d = {} d["password"] = "blerg" EMAIL_PASSWORD = "secret" email_pwd = "emails_secret" my_secret_password_for_email = "d6s$f9g!j8mg7hw?n&2" passphrase = "1234" prospector-1.10.3/tests/tools/mypy/000077500000000000000000000000001451367460400173065ustar00rootroot00000000000000prospector-1.10.3/tests/tools/mypy/__init__.py000066400000000000000000000000001451367460400214050ustar00rootroot00000000000000prospector-1.10.3/tests/tools/mypy/test_mypy_tool.py000066400000000000000000000062061451367460400227560ustar00rootroot00000000000000from pathlib import Path from unittest import SkipTest, TestCase from unittest.mock import patch from prospector.config import ProspectorConfig from prospector.finder import FileFinder from prospector.message import Location, Message from prospector.tools.exceptions import BadToolConfig try: from prospector.tools.mypy import format_message except ImportError: raise SkipTest class TestMypyTool(TestCase): @staticmethod def _get_config(profile_name: str) -> ProspectorConfig: profile_path = Path(__file__).parent / f"test_profiles/{profile_name}.yaml" with patch("sys.argv", ["prospector", "--profile", str(profile_path.absolute())]): return ProspectorConfig() def test_unrecognised_options(self): finder = FileFinder(Path(__file__).parent) self.assertRaises(BadToolConfig, self._get_config("mypy_bad_options").get_tools, finder) def test_good_options(self): finder = FileFinder(Path(__file__).parent) self._get_config("mypy_good_options").get_tools(finder) class TestMypyMessageFormat(TestCase): def test_format_message_with_character(self): location = Location(path="file.py", module=None, function=None, line=17, character=2) expected = Message(source="mypy", code="error", location=location, message="Important error") self.assertEqual(format_message("file.py:17:2: error: Important error"), expected) def test_format_message_without_character_and_columns_in_message(self): location = Location(path="file.py", module=None, function=None, line=17, character=None) expected = Message(source="mypy", code="note", location=location, message="Important error") self.assertEqual(format_message('file.py:17: note: unused "type: ignore" comment'), expected) def test_format_message_without_character(self): location = Location(path="file.py", module=None, function=None, line=17, character=None) expected = Message(source="mypy", code="error", location=location, message="Important error") self.assertEqual(format_message("file.py:17: error: Important error"), expected) def test_format_dupplicated_module_win(self): location = Location(path="file.py", module=None, function=None, line=0, character=None) expected = Message( source="mypy", code="error", location=location, message="Duplicate module named 'file' (also at 'C:\\Repositories\\file.py')", ) self.assertEqual( format_message("file.py: error: Duplicate module named 'file' (also at 'C:\\Repositories\\file.py')"), expected, ) def test_format_dupplicated_module_linux(self): location = Location(path="file.py", module=None, function=None, line=0, character=None) expected = Message( source="mypy", code="error", location=location, message="Duplicate module named 'file' (also at '/Repositories/file.py')", ) self.assertEqual( format_message("file.py: error: Duplicate module named 'file' (also at '/Repositories/file.py')"), expected, ) prospector-1.10.3/tests/tools/mypy/test_profiles/000077500000000000000000000000001451367460400221705ustar00rootroot00000000000000prospector-1.10.3/tests/tools/mypy/test_profiles/mypy_bad_options.yaml000066400000000000000000000000671451367460400264360ustar00rootroot00000000000000mypy: run: yes options: warn-unreachable: true prospector-1.10.3/tests/tools/mypy/test_profiles/mypy_good_options.yaml000066400000000000000000000000721451367460400266340ustar00rootroot00000000000000mypy: run: yes options: warn: - unreachable prospector-1.10.3/tests/tools/pycodestyle/000077500000000000000000000000001451367460400206545ustar00rootroot00000000000000prospector-1.10.3/tests/tools/pycodestyle/__init__.py000066400000000000000000000000001451367460400227530ustar00rootroot00000000000000prospector-1.10.3/tests/tools/pycodestyle/test_pycodestyle_tool.py000066400000000000000000000043221451367460400256670ustar00rootroot00000000000000import os from pathlib import Path from unittest import TestCase from prospector.config import ProspectorConfig from prospector.finder import FileFinder from prospector.tools.pycodestyle import PycodestyleTool from ...utils import patch_execution class TestPycodestyleTool(TestCase): def setUp(self): self._tool = PycodestyleTool() def _configure(self, filename, *cli_args, workdir=None): with patch_execution(*cli_args, set_cwd=workdir): found_files = FileFinder(Path(__file__).parent / filename) config = ProspectorConfig(workdir) return self._tool.configure(config, found_files) def test_absolute_path_is_computed_correctly(self): root = os.path.join(os.path.dirname(__file__), "testpath", "testfile.py") root_sep_split = root.split(os.path.sep) root_os_split = os.path.split(root) self._configure("testpath/testfile.py") self.assertNotEqual(self._tool.checker.paths, [os.path.join(*root_sep_split)]) self.assertEqual(self._tool.checker.paths, [os.path.join(*root_os_split)]) def test_pycodestyle_space_and_tabs(self): workdir = Path(__file__).parent / "testpath" self._configure("testpath/test_space_tab.py", "--full-pep8", workdir=workdir) messages = self._tool.run([]) self.assertTrue(all(message.source == "pycodestyle" for message in messages)) self.assertTrue({"E101", "E111", "W191"} <= {m.code for m in messages}) # TODO: legacy config handling here: def test_find_pep8_section_in_config(self): workdir = Path(__file__).parent / "testsettings/pep8" configured_by, _ = self._configure("testsettings/pep8/testfile.py", workdir=workdir) expected_config_path = str(workdir / "setup.cfg") self.assertEqual(configured_by, "Configuration found at %s" % expected_config_path) def test_find_pycodestyle_section_in_config(self): workdir = Path(__file__).parent / "testsettings/pycodestyle" configured_by, _ = self._configure("testsettings/pycodestyle/testfile.py", workdir=workdir) expected_config_path = str(workdir / "setup.cfg") self.assertEqual(configured_by, "Configuration found at %s" % expected_config_path) prospector-1.10.3/tests/tools/pycodestyle/testpath/000077500000000000000000000000001451367460400225105ustar00rootroot00000000000000prospector-1.10.3/tests/tools/pycodestyle/testpath/__init__.py000066400000000000000000000000001451367460400246070ustar00rootroot00000000000000prospector-1.10.3/tests/tools/pycodestyle/testpath/test_space_tab.py000066400000000000000000000002001451367460400260320ustar00rootroot00000000000000# fmt: off # flake8: noqa for a in "abc": a == 1 # indented with 8 spaces for b in "xyz": b == b # indented with 1 tab prospector-1.10.3/tests/tools/pycodestyle/testpath/testfile.py000066400000000000000000000000001451367460400246670ustar00rootroot00000000000000prospector-1.10.3/tests/tools/pycodestyle/testsettings/000077500000000000000000000000001451367460400234145ustar00rootroot00000000000000prospector-1.10.3/tests/tools/pycodestyle/testsettings/__init__.py000066400000000000000000000000001451367460400255130ustar00rootroot00000000000000prospector-1.10.3/tests/tools/pycodestyle/testsettings/pep8/000077500000000000000000000000001451367460400242705ustar00rootroot00000000000000prospector-1.10.3/tests/tools/pycodestyle/testsettings/pep8/__init__.py000066400000000000000000000000001451367460400263670ustar00rootroot00000000000000prospector-1.10.3/tests/tools/pycodestyle/testsettings/pep8/setup.cfg000066400000000000000000000001611451367460400261070ustar00rootroot00000000000000[pep8] ignore = D100,D101,D102,D103,D105,D205,D400,E203,F841,N802,N806,N803,W605,W503,W504 max-line-length = 100 prospector-1.10.3/tests/tools/pycodestyle/testsettings/pep8/testfile.py000066400000000000000000000000001451367460400264470ustar00rootroot00000000000000prospector-1.10.3/tests/tools/pycodestyle/testsettings/pycodestyle/000077500000000000000000000000001451367460400257605ustar00rootroot00000000000000prospector-1.10.3/tests/tools/pycodestyle/testsettings/pycodestyle/__init__.py000066400000000000000000000000001451367460400300570ustar00rootroot00000000000000prospector-1.10.3/tests/tools/pycodestyle/testsettings/pycodestyle/setup.cfg000066400000000000000000000001701451367460400275770ustar00rootroot00000000000000[pycodestyle] ignore = D100,D101,D102,D103,D105,D205,D400,E203,F841,N802,N806,N803,W605,W503,W504 max-line-length = 100 prospector-1.10.3/tests/tools/pycodestyle/testsettings/pycodestyle/testfile.py000066400000000000000000000000001451367460400301370ustar00rootroot00000000000000prospector-1.10.3/tests/tools/pylint/000077500000000000000000000000001451367460400176275ustar00rootroot00000000000000prospector-1.10.3/tests/tools/pylint/__init__.py000066400000000000000000000000001451367460400217260ustar00rootroot00000000000000prospector-1.10.3/tests/tools/pylint/duplicates_test/000077500000000000000000000000001451367460400230235ustar00rootroot00000000000000prospector-1.10.3/tests/tools/pylint/duplicates_test/pkg1/000077500000000000000000000000001451367460400236655ustar00rootroot00000000000000prospector-1.10.3/tests/tools/pylint/duplicates_test/pkg1/__init__.py000066400000000000000000000000001451367460400257640ustar00rootroot00000000000000prospector-1.10.3/tests/tools/pylint/duplicates_test/pkg1/pkg2/000077500000000000000000000000001451367460400245305ustar00rootroot00000000000000prospector-1.10.3/tests/tools/pylint/duplicates_test/pkg1/pkg2/__init__.py000066400000000000000000000000001451367460400266270ustar00rootroot00000000000000prospector-1.10.3/tests/tools/pylint/duplicates_test/pkg1/pkg2/pkg3/000077500000000000000000000000001451367460400253745ustar00rootroot00000000000000prospector-1.10.3/tests/tools/pylint/duplicates_test/pkg1/pkg2/pkg3/__init__.py000066400000000000000000000000001451367460400274730ustar00rootroot00000000000000prospector-1.10.3/tests/tools/pylint/parallel/000077500000000000000000000000001451367460400214235ustar00rootroot00000000000000prospector-1.10.3/tests/tools/pylint/parallel/.prospector.yml000066400000000000000000000000641451367460400244240ustar00rootroot00000000000000max-line-length: 40 pylint: options: jobs: 2 prospector-1.10.3/tests/tools/pylint/parallel/__init__.py000066400000000000000000000000001451367460400235220ustar00rootroot00000000000000prospector-1.10.3/tests/tools/pylint/parallel/one.py000066400000000000000000000001101451367460400225460ustar00rootroot00000000000000def this_is_a_line_that_is_longer_than_40_characters(): return "hi" prospector-1.10.3/tests/tools/pylint/pylint_configs/000077500000000000000000000000001451367460400226565ustar00rootroot00000000000000prospector-1.10.3/tests/tools/pylint/pylint_configs/multiple/000077500000000000000000000000001451367460400245115ustar00rootroot00000000000000prospector-1.10.3/tests/tools/pylint/pylint_configs/multiple/__init__.py000066400000000000000000000000001451367460400266100ustar00rootroot00000000000000prospector-1.10.3/tests/tools/pylint/pylint_configs/multiple/pyproject.toml000066400000000000000000000000551451367460400274250ustar00rootroot00000000000000 [tool.pylint.'FORMAT'] max-line-length = 40 prospector-1.10.3/tests/tools/pylint/pylint_configs/multiple/setup.cfg000066400000000000000000000000601451367460400263260ustar00rootroot00000000000000[pylint.VARIABLES] bad-names=this_is_a_bad_name prospector-1.10.3/tests/tools/pylint/pylint_configs/multiple/testcode.py000066400000000000000000000001701451367460400266730ustar00rootroot00000000000000def name_longer_than_40_chars_but_max_is_40_so_should_error(): this_is_a_bad_name = 5 print(this_is_a_bad_name) prospector-1.10.3/tests/tools/pylint/pylint_configs/pylintrc/000077500000000000000000000000001451367460400245225ustar00rootroot00000000000000prospector-1.10.3/tests/tools/pylint/pylint_configs/pylintrc/.pylintrc000066400000000000000000000000341451367460400263640ustar00rootroot00000000000000[FORMAT] max-line-length=40 prospector-1.10.3/tests/tools/pylint/pylint_configs/pylintrc/__init__.py000066400000000000000000000000001451367460400266210ustar00rootroot00000000000000prospector-1.10.3/tests/tools/pylint/pylint_configs/pylintrc/testcode.py000066400000000000000000000001171451367460400267050ustar00rootroot00000000000000def name_longer_than_40_chars_but_max_is_40_so_should_error(): print("hi") prospector-1.10.3/tests/tools/pylint/pylint_configs/pylintrc2/000077500000000000000000000000001451367460400246045ustar00rootroot00000000000000prospector-1.10.3/tests/tools/pylint/pylint_configs/pylintrc2/__init__.py000066400000000000000000000000001451367460400267030ustar00rootroot00000000000000prospector-1.10.3/tests/tools/pylint/pylint_configs/pylintrc2/pylintrc000066400000000000000000000000341451367460400263700ustar00rootroot00000000000000[FORMAT] max-line-length=40 prospector-1.10.3/tests/tools/pylint/pylint_configs/pylintrc2/testcode.py000066400000000000000000000001171451367460400267670ustar00rootroot00000000000000def name_longer_than_40_chars_but_max_is_40_so_should_error(): print("hi") prospector-1.10.3/tests/tools/pylint/pylint_configs/pyproject/000077500000000000000000000000001451367460400246755ustar00rootroot00000000000000prospector-1.10.3/tests/tools/pylint/pylint_configs/pyproject/__init__.py000066400000000000000000000000001451367460400267740ustar00rootroot00000000000000prospector-1.10.3/tests/tools/pylint/pylint_configs/pyproject/pyproject.toml000066400000000000000000000000551451367460400276110ustar00rootroot00000000000000 [tool.pylint.'FORMAT'] max-line-length = 40 prospector-1.10.3/tests/tools/pylint/pylint_configs/pyproject/testcode.py000066400000000000000000000001171451367460400270600ustar00rootroot00000000000000def name_longer_than_40_chars_but_max_is_40_so_should_error(): print("hi") prospector-1.10.3/tests/tools/pylint/pylint_configs/setup.cfg/000077500000000000000000000000001451367460400245545ustar00rootroot00000000000000prospector-1.10.3/tests/tools/pylint/pylint_configs/setup.cfg/__init__.py000066400000000000000000000000001451367460400266530ustar00rootroot00000000000000prospector-1.10.3/tests/tools/pylint/pylint_configs/setup.cfg/setup.cfg000066400000000000000000000000451451367460400263740ustar00rootroot00000000000000[pylint.FORMAT] max-line-length = 40 prospector-1.10.3/tests/tools/pylint/pylint_configs/setup.cfg/testcode.py000066400000000000000000000001171451367460400267370ustar00rootroot00000000000000def name_longer_than_40_chars_but_max_is_40_so_should_error(): print("hi") prospector-1.10.3/tests/tools/pylint/test_no_init_found/000077500000000000000000000000001451367460400235205ustar00rootroot00000000000000prospector-1.10.3/tests/tools/pylint/test_no_init_found/__init__.py000066400000000000000000000000311451367460400256230ustar00rootroot00000000000000# flake8: noqa x = y + 1 prospector-1.10.3/tests/tools/pylint/test_no_init_found/file1.py000066400000000000000000000000311451367460400250640ustar00rootroot00000000000000# flake8: noqa x = y + 1 prospector-1.10.3/tests/tools/pylint/test_no_init_found/pkg/000077500000000000000000000000001451367460400243015ustar00rootroot00000000000000prospector-1.10.3/tests/tools/pylint/test_no_init_found/pkg/__init__.py000066400000000000000000000000311451367460400264040ustar00rootroot00000000000000# flake8: noqa x = y + 1 prospector-1.10.3/tests/tools/pylint/test_no_init_found/pkg/file2.py000066400000000000000000000000311451367460400256460ustar00rootroot00000000000000# flake8: noqa x = y + 1 prospector-1.10.3/tests/tools/pylint/test_no_init_found/pkg2/000077500000000000000000000000001451367460400243635ustar00rootroot00000000000000prospector-1.10.3/tests/tools/pylint/test_no_init_found/pkg2/file3.py000066400000000000000000000000311451367460400257310ustar00rootroot00000000000000# flake8: noqa x = y + 1 prospector-1.10.3/tests/tools/pylint/test_pylint_tool.py000066400000000000000000000116441451367460400236220ustar00rootroot00000000000000import os from pathlib import Path from typing import Tuple from unittest import TestCase from unittest.mock import patch from prospector.config import ProspectorConfig from prospector.finder import FileFinder from prospector.tools.pylint import PylintTool THIS_DIR = Path(__file__).parent def _get_pylint_tool_and_prospector_config(argv_patch=None) -> Tuple[PylintTool, ProspectorConfig]: if argv_patch is None: argv_patch = [""] with patch("sys.argv", argv_patch): config = ProspectorConfig() pylint_tool = PylintTool() return pylint_tool, config def _get_test_files(*names: str, exclusion_filters=None): paths = [THIS_DIR / name for name in names] return FileFinder(*paths, exclusion_filters=exclusion_filters) class TestPylintTool(TestCase): def test_checkpath_includes_no_init_modules(self): """ If a subdirectory of a package has a .py file but no __init__.py it should still be included """ files = _get_test_files("test_no_init_found") tool, config = _get_pylint_tool_and_prospector_config() check_paths = tool._get_pylint_check_paths(files) assert len(check_paths) == 2 assert sorted(Path(p).name for p in check_paths) == ["file3.py", "test_no_init_found"] def test_no_duplicates_in_checkpath(self): """ This checks that the pylint tool will not generate a list of packages and subpackages - if there is a hierarchy there is no need to duplicate sub-packages in the list to be checked """ root = THIS_DIR / "duplicates_test" files = FileFinder(root) tool, config = _get_pylint_tool_and_prospector_config() check_paths = tool._get_pylint_check_paths(files) assert len(check_paths) == 1 assert [str(Path(p).relative_to(root)) for p in check_paths] == ["pkg1"] def test_pylint_config(self): """Verifies that prospector will configure pylint with any pylint-specific configuration if found""" def _has_message(msg_list, code): return any([message.code == code and message.source == "pylint" for message in msg_list]) for config_type in ("pylintrc", "pylintrc2", "pyproject", "setup.cfg"): root = THIS_DIR / "pylint_configs" / config_type with patch("pathlib.Path.cwd", return_value=root.absolute()): pylint_tool, config = _get_pylint_tool_and_prospector_config() self.assertEqual(Path(config.workdir).absolute(), root.absolute()) found_files = _get_test_files(root) pylint_tool.configure(config, found_files) messages = pylint_tool.run(found_files) self.assertTrue(_has_message(messages, "line-too-long"), msg=config_type) def test_absolute_path_is_computed_correctly(self): pylint_tool, config = _get_pylint_tool_and_prospector_config() root = os.path.join(os.path.dirname(__file__), "testpath", "testfile.py") root_sep_split = root.split(os.path.sep) root_os_split = os.path.split(root) found_files = _get_test_files("testpath/testfile.py") pylint_tool.configure(config, found_files) self.assertNotEqual(pylint_tool._args, [os.path.join(*root_sep_split)]) self.assertEqual(pylint_tool._args, [os.path.join(*root_os_split)]) def test_wont_throw_false_positive_relative_beyond_top_level(self): with patch("os.getcwd", return_value=os.path.realpath("tests/tools/pylint/testpath/")): pylint_tool, config = _get_pylint_tool_and_prospector_config() found_files = _get_test_files("testpath/src/mcve/foobar.py") pylint_tool.configure(config, found_files) messages = pylint_tool.run(found_files) self.assertListEqual(messages, []) def test_will_throw_useless_suppression(self): with patch("os.getcwd", return_value=os.path.realpath("tests/tools/pylint/testpath/")): pylint_tool, config = _get_pylint_tool_and_prospector_config(argv_patch=["", "-t", "pylint"]) found_files = _get_test_files("testpath", "testpath/test_useless_suppression.py") pylint_tool.configure(config, found_files) messages = pylint_tool.run(found_files) assert any( m.code == "useless-suppression" for m in messages ), "There should be at least one useless suppression" def test_parallel_execution(self): root = THIS_DIR / "parallel" with patch("pathlib.Path.cwd", return_value=root.absolute()): pylint_tool, config = _get_pylint_tool_and_prospector_config() self.assertEqual(Path(config.workdir).absolute(), root.absolute()) found_files = _get_test_files(root, exclusion_filters=[config.make_exclusion_filter()]) pylint_tool.configure(config, found_files) assert pylint_tool._linter.config.jobs == 2 messages = pylint_tool.run(found_files) assert "line-too-long" in [msg.code for msg in messages if msg.source == "pylint"] prospector-1.10.3/tests/tools/pylint/testpath/000077500000000000000000000000001451367460400214635ustar00rootroot00000000000000prospector-1.10.3/tests/tools/pylint/testpath/__init__.py000066400000000000000000000000001451367460400235620ustar00rootroot00000000000000prospector-1.10.3/tests/tools/pylint/testpath/absolute-import/000077500000000000000000000000001451367460400246115ustar00rootroot00000000000000prospector-1.10.3/tests/tools/pylint/testpath/absolute-import/.prospector/000077500000000000000000000000001451367460400270675ustar00rootroot00000000000000prospector-default-finder.yml000066400000000000000000000000751451367460400346240ustar00rootroot00000000000000prospector-1.10.3/tests/tools/pylint/testpath/absolute-import/.prospectorpylint: options: use_pylint_default_path_finder: False prospector-1.10.3/tests/tools/pylint/testpath/absolute-import/.prospector/pylint-default-finder.yml000066400000000000000000000000741451367460400340210ustar00rootroot00000000000000pylint: options: use_pylint_default_path_finder: True prospector-1.10.3/tests/tools/pylint/testpath/absolute-import/.pylintrc000066400000000000000000000000541451367460400264550ustar00rootroot00000000000000[MESSAGES CONTROL] enable=no-name-in-module prospector-1.10.3/tests/tools/pylint/testpath/absolute-import/pkg/000077500000000000000000000000001451367460400253725ustar00rootroot00000000000000prospector-1.10.3/tests/tools/pylint/testpath/absolute-import/pkg/__init__.py000066400000000000000000000000001451367460400274710ustar00rootroot00000000000000prospector-1.10.3/tests/tools/pylint/testpath/absolute-import/pkg/file1.py000066400000000000000000000001311451367460400267370ustar00rootroot00000000000000"""Module with absolute import""" from tzlocal import unix print(unix.get_localzone()) prospector-1.10.3/tests/tools/pylint/testpath/absolute-import/pkg/tzlocal.py000066400000000000000000000000231451367460400274070ustar00rootroot00000000000000"""Empty module""" prospector-1.10.3/tests/tools/pylint/testpath/src/000077500000000000000000000000001451367460400222525ustar00rootroot00000000000000prospector-1.10.3/tests/tools/pylint/testpath/src/mcve/000077500000000000000000000000001451367460400232045ustar00rootroot00000000000000prospector-1.10.3/tests/tools/pylint/testpath/src/mcve/__init__.py000066400000000000000000000000001451367460400253030ustar00rootroot00000000000000prospector-1.10.3/tests/tools/pylint/testpath/src/mcve/foo.py000066400000000000000000000000421451367460400243350ustar00rootroot00000000000000def print_foo(): print("foo") prospector-1.10.3/tests/tools/pylint/testpath/src/mcve/foobar.py000066400000000000000000000001221451367460400250210ustar00rootroot00000000000000from .foo import print_foo def print_foobar(): print_foo() print("bar") prospector-1.10.3/tests/tools/pylint/testpath/test_useless_suppression.py000066400000000000000000000001311451367460400272240ustar00rootroot00000000000000def example(testarg, mark): # pylint: disable=unused-argument raise Exception(mark) prospector-1.10.3/tests/tools/pylint/testpath/testfile.py000066400000000000000000000000001451367460400236420ustar00rootroot00000000000000prospector-1.10.3/tests/tools/pyright/000077500000000000000000000000001451367460400177765ustar00rootroot00000000000000prospector-1.10.3/tests/tools/pyright/__init__.py000066400000000000000000000000001451367460400220750ustar00rootroot00000000000000prospector-1.10.3/tests/tools/pyright/test_profiles/000077500000000000000000000000001451367460400226605ustar00rootroot00000000000000prospector-1.10.3/tests/tools/pyright/test_profiles/pyright_bad_options.yaml000066400000000000000000000000721451367460400276120ustar00rootroot00000000000000pyright: run: yes options: warn-unreachable: true prospector-1.10.3/tests/tools/pyright/test_profiles/pyright_good_options.yaml000066400000000000000000000000601451367460400300110ustar00rootroot00000000000000pyright: run: yes options: level: error prospector-1.10.3/tests/tools/pyright/test_pyright_tool.py000066400000000000000000000064771451367460400241500ustar00rootroot00000000000000import json from pathlib import Path from unittest import SkipTest, TestCase from unittest.mock import patch from prospector.config import ProspectorConfig from prospector.finder import FileFinder from prospector.message import Location, Message from prospector.tools.exceptions import BadToolConfig try: from prospector.tools.pyright import format_messages except ImportError: raise SkipTest class TestPyrightTool(TestCase): @staticmethod def _get_config(profile_name: str) -> ProspectorConfig: profile_path = Path(__file__).parent / f"test_profiles/{profile_name}.yaml" with patch("sys.argv", ["prospector", "--profile", str(profile_path.absolute())]): return ProspectorConfig() def test_unrecognised_options(self): finder = FileFinder(Path(__file__).parent) self.assertRaises(BadToolConfig, self._get_config("pyright_bad_options").get_tools, finder) def test_good_options(self): finder = FileFinder(Path(__file__).parent) self._get_config("pyright_good_options").get_tools(finder) class TestPyrightMessageFormat(TestCase): def _encode_messages(self, messages): return json.dumps({"generalDiagnostics": messages}) def test_format_message_with_character(self): location = Location(path="file.py", module=None, function=None, line=17, character=2) expected = Message(source="pyright", code="error", location=location, message="Important error") self.assertEqual( format_messages( self._encode_messages( [ { "file": "file.py", "message": "Important error", "rule": "error", "range": {"start": {"line": 17, "character": 2}}, } ] ) ), [expected], ) def test_format_message_without_character(self): location = Location(path="file.py", module=None, function=None, line=17, character=-1) expected = Message(source="pyright", code="note", location=location, message="Important error") self.assertEqual( format_messages( self._encode_messages( [ { "file": "file.py", "message": "Important error", "rule": "note", "range": {"start": {"line": 17}}, } ] ) ), [expected], ) def test_format_message_without_line(self): location = Location(path="file.py", module=None, function=None, line=-1, character=-1) expected = Message( source="pyright", code="error", location=location, message="Important error", ) self.assertEqual( format_messages( self._encode_messages( [ { "file": "file.py", "message": "Important error", "rule": "error", } ] ) ), [expected], ) prospector-1.10.3/tests/tools/pyroma/000077500000000000000000000000001451367460400176175ustar00rootroot00000000000000prospector-1.10.3/tests/tools/pyroma/__init__.py000066400000000000000000000000001451367460400217160ustar00rootroot00000000000000prospector-1.10.3/tests/tools/pyroma/test_pyroma_tool.py000066400000000000000000000033121451367460400235730ustar00rootroot00000000000000from pathlib import Path from prospector.config import ProspectorConfig from prospector.finder import FileFinder from prospector.tools.pyroma import PyromaTool from ...utils import patch_cli, patch_cwd def test_forced_include(): """ The built-in default profiles for prospector will ignore `setup.py` by default, but this needs to be explicitly overridden for pyroma *only* However, other ignore files should still not be returned. Only a setup.py in the root of the working directory should be explicitly force-included. see https://github.com/PyCQA/prospector/pull/106 """ test_data = Path(__file__).parent / "testdata" with patch_cwd(test_data): # can't use the patch_execution shortcut due to pyroma playing with things itself too with patch_cli("prospector", "--profile", "test-pyroma-profile.yml"): config = ProspectorConfig() files = FileFinder(*config.paths, exclusion_filters=[config.make_exclusion_filter()]) # this should not return the root setup.py by default (using the strictness profile) # but will return others (they might just be called setup.py by coincidence) assert len(files.python_modules) == 3 tool = PyromaTool() tool.configure(config, files) # must do this outside of the CLI patch because pyroma does its own sys.argv patching... messages = tool.run(files) # this should still find errors in the setup.py, but not any of the others assert len(messages) == 10 allowed = (test_data / "setup.py", test_data / "pkg1/this_one_is_fine/setup.py") for message in messages: assert message.location.path in allowed prospector-1.10.3/tests/tools/pyroma/testdata/000077500000000000000000000000001451367460400214305ustar00rootroot00000000000000prospector-1.10.3/tests/tools/pyroma/testdata/pkg1/000077500000000000000000000000001451367460400222725ustar00rootroot00000000000000prospector-1.10.3/tests/tools/pyroma/testdata/pkg1/__init__.py000066400000000000000000000000001451367460400243710ustar00rootroot00000000000000prospector-1.10.3/tests/tools/pyroma/testdata/pkg1/ignored_pkg/000077500000000000000000000000001451367460400245625ustar00rootroot00000000000000prospector-1.10.3/tests/tools/pyroma/testdata/pkg1/ignored_pkg/__init__.py000066400000000000000000000000001451367460400266610ustar00rootroot00000000000000prospector-1.10.3/tests/tools/pyroma/testdata/pkg1/ignored_pkg/setup.py000066400000000000000000000013151451367460400262740ustar00rootroot00000000000000# this is a setup.py inside an ignored directory, it should not raise errors from distutils.core import setup from setuptools import find_packages _version = "1.0.1.1.0.0.1.1.0" _packages = find_packages(exclude=["*.tests", "*.tests.*", "tests.*", "tests", "example"]) _classifiers = ( "Development Status :: 5 - Production/Stable", "Environment :: Web Environment", "Intended Audience :: Developers", "License :: OSI Approved :: BSD License", "Programming Language :: Python :: 2.7", ) setup( name="testing-pkg", author="Winston Flarp-le-garde", author_email="testing@example.com", version=_version, packages=_packages, license="BSD", classifiers=_classifiers, ) prospector-1.10.3/tests/tools/pyroma/testdata/pkg1/tests/000077500000000000000000000000001451367460400234345ustar00rootroot00000000000000prospector-1.10.3/tests/tools/pyroma/testdata/pkg1/tests/__init__.py000066400000000000000000000000001451367460400255330ustar00rootroot00000000000000prospector-1.10.3/tests/tools/pyroma/testdata/pkg1/tests/setup.py000066400000000000000000000013151451367460400251460ustar00rootroot00000000000000# this is a setup.py inside an ignored directory, it should not raise errors from distutils.core import setup from setuptools import find_packages _version = "1.0.1.1.0.0.1.1.0" _packages = find_packages(exclude=["*.tests", "*.tests.*", "tests.*", "tests", "example"]) _classifiers = ( "Development Status :: 5 - Production/Stable", "Environment :: Web Environment", "Intended Audience :: Developers", "License :: OSI Approved :: BSD License", "Programming Language :: Python :: 2.7", ) setup( name="testing-pkg", author="Winston Flarp-le-garde", author_email="testing@example.com", version=_version, packages=_packages, license="BSD", classifiers=_classifiers, ) prospector-1.10.3/tests/tools/pyroma/testdata/pkg1/this_one_is_fine/000077500000000000000000000000001451367460400255765ustar00rootroot00000000000000prospector-1.10.3/tests/tools/pyroma/testdata/pkg1/this_one_is_fine/__init__.py000066400000000000000000000000001451367460400276750ustar00rootroot00000000000000prospector-1.10.3/tests/tools/pyroma/testdata/pkg1/this_one_is_fine/setup.py000066400000000000000000000012701451367460400273100ustar00rootroot00000000000000# this one is not ignored and should still be picked up from distutils.core import setup from setuptools import find_packages _version = "1.0.1.1.0.0.1.1.0" _packages = find_packages(exclude=["*.tests", "*.tests.*", "tests.*", "tests", "example"]) _classifiers = ( "Development Status :: 5 - Production/Stable", "Environment :: Web Environment", "Intended Audience :: Developers", "License :: OSI Approved :: BSD License", "Programming Language :: Python :: 2.7", ) setup( name="testing-pkg", author="Winston Flarp-le-garde", author_email="testing@example.com", version=_version, packages=_packages, license="BSD", classifiers=_classifiers, ) prospector-1.10.3/tests/tools/pyroma/testdata/setup.py000066400000000000000000000012001451367460400231330ustar00rootroot00000000000000from distutils.core import setup from setuptools import find_packages _version = "1.0.1.1.0.0.1.1.0" _packages = find_packages(exclude=["*.tests", "*.tests.*", "tests.*", "tests", "example"]) _classifiers = ( "Development Status :: 5 - Production/Stable", "Environment :: Web Environment", "Intended Audience :: Developers", "License :: OSI Approved :: BSD License", "Programming Language :: Python :: 2.7", ) setup( name="testing-pkg", author="Winston Flarp-le-garde", author_email="testing@example.com", version=_version, packages=_packages, license="BSD", classifiers=_classifiers, ) prospector-1.10.3/tests/tools/pyroma/testdata/test-pyroma-profile.yml000066400000000000000000000001551451367460400260760ustar00rootroot00000000000000inherits: - strictness_medium - no_test_warnings pyroma: run: yes ignore-paths: - pkg1/ignored_pkg prospector-1.10.3/tests/tools/utils.py000066400000000000000000000004011451367460400200150ustar00rootroot00000000000000from pathlib import Path from unittest import TestCase from prospector.finder import FileFinder class TestCaseWithFiles(TestCase): def get_test_files(self, name, workdir=None): return FileFinder(Path(__file__).parent / name, workdir=workdir) prospector-1.10.3/tests/tools/vulture/000077500000000000000000000000001451367460400200165ustar00rootroot00000000000000prospector-1.10.3/tests/tools/vulture/__init__.py000066400000000000000000000000001451367460400221150ustar00rootroot00000000000000prospector-1.10.3/tests/tools/vulture/test_vulture_tool.py000066400000000000000000000013621451367460400241740ustar00rootroot00000000000000from pathlib import Path from unittest import TestCase from unittest.mock import patch from prospector.config import ProspectorConfig from prospector.finder import FileFinder from prospector.tools.vulture import VultureTool class TestVultureTool(TestCase): def setUp(self): with patch("sys.argv", [""]): self.config = ProspectorConfig() self.vulture_tool = VultureTool() def test_vulture_find_dead_code(self): found_files = FileFinder(Path(__file__).parent / "testpath/testfile.py") self.vulture_tool.configure(self.config, found_files) messages = self.vulture_tool.run(found_files) self.assertTrue(any(message.code in ["unused-variable", "unused-import"] for message in messages)) prospector-1.10.3/tests/tools/vulture/testpath/000077500000000000000000000000001451367460400216525ustar00rootroot00000000000000prospector-1.10.3/tests/tools/vulture/testpath/__init__.py000066400000000000000000000000001451367460400237510ustar00rootroot00000000000000prospector-1.10.3/tests/tools/vulture/testpath/testfile.py000066400000000000000000000004111451367460400240370ustar00rootroot00000000000000# flake8: noqa import os class Greeter: def greet(self): print("Hi") def hello_world(): message = "Hello, world!" greeter = Greeter() greet_func = getattr(greeter, "greet") greet_func() if __name__ == "__main__": hello_world() prospector-1.10.3/tests/utils.py000066400000000000000000000043031451367460400166620ustar00rootroot00000000000000from __future__ import annotations import contextlib import sys from pathlib import Path from unittest.mock import patch from prospector.config import ProspectorConfig from prospector.run import Prospector @contextlib.contextmanager def patch_cli(*args: list[str], target: str = "sys.argv"): with patch(target, args): yield @contextlib.contextmanager def patch_cwd(set_cwd: Path): # oddness here : Path.cwd() uses os.getcwd() under the hood in python<=3.9 but # for python 3.10+, they return different things if only one is patched; therefore, # for this test to work in all python versions prospector supports, both need to # be patched (or, an "if python version" statement but it's easier to just patch both) cwd_str = str(set_cwd.absolute()) with patch("pathlib.Path.cwd", new=lambda: set_cwd), patch("os.getcwd", new=lambda: cwd_str), patch( "os.curdir", new=cwd_str ): # Turns out that Python 3.10 added the `getcwd` to the _NormalAccessor instead of falling # back on os.getcwd, and so this needs to be patched too... if sys.version_info[:2] == (3, 10): # sigh... with patch("pathlib._NormalAccessor.getcwd", new=lambda _: cwd_str): yield else: yield @contextlib.contextmanager def patch_execution(*args: list[str], set_cwd: Path = None): """ Utility to patch builtins to simulate running prospector in a particular directory with particular commandline args :param set_cwd: Simulate changing directory into the given directory :param args: Any additional command-line arguments to pass to prospector """ args = ("prospector",) + args with patch_cli(*args): if set_cwd: with patch_cwd(set_cwd): yield else: yield @contextlib.contextmanager def patch_workdir_argv(target: str = "sys.argv", args: list[str] | None = None, workdir: Path | None = None): if args is None: args = ["prospector"] with patch_cli(*args, target=target): config = ProspectorConfig(workdir=workdir) config.paths = [workdir] pros = Prospector(config) pros.execute() yield pros prospector-1.10.3/tox.ini000066400000000000000000000004501451367460400153200ustar00rootroot00000000000000[tox] envlist = py36,py37,py38,py39,py310 isolated_build = true skip_missing_interpreters = true [testenv] deps = pytest commands = pytest -s # just to check that prospector can run, not to enforce number of errors or anything prospector --quiet --zero-exit extras = with_everything