├── tests ├── __init__.py ├── conftest.py └── test_linter_pickle.py ├── Makefile ├── .coveragerc ├── .gitignore ├── .pre-commit-config.yaml ├── tox.ini ├── setup.py ├── pyproject.toml ├── README.md ├── pylint_plugin_utils └── __init__.py ├── .github └── workflows │ └── ci.yaml ├── LICENSE └── poetry.lock /tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | install: 3 | pip install poetry 4 | poetry install 5 | -------------------------------------------------------------------------------- /tests/conftest.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from pylint.lint import PyLinter 3 | 4 | 5 | @pytest.fixture 6 | def linter(): 7 | _linter = PyLinter() 8 | return _linter 9 | -------------------------------------------------------------------------------- /.coveragerc: -------------------------------------------------------------------------------- 1 | [paths] 2 | source = 3 | pylint_plugin_utils 4 | 5 | [report] 6 | include = 7 | pylint_plugin_utils/* 8 | omit = 9 | */test/* 10 | exclude_lines = 11 | # Re-enable default pragma 12 | pragma: no cover 13 | 14 | # Debug-only code 15 | def __repr__ 16 | 17 | # Type checking code not executed during pytest runs 18 | if TYPE_CHECKING: 19 | @overload 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.py[cod] 2 | 3 | # C extensions 4 | *.so 5 | 6 | # Packages 7 | *.egg 8 | *.egg-info 9 | dist 10 | build 11 | eggs 12 | parts 13 | bin 14 | var 15 | sdist 16 | develop-eggs 17 | .installed.cfg 18 | lib 19 | lib64 20 | 21 | # Installer logs 22 | pip-log.txt 23 | 24 | # Unit test / coverage reports 25 | .coverage 26 | .tox 27 | nosetests.xml 28 | .pytest_cache 29 | .benchmarks 30 | htmlcov 31 | 32 | # Translations 33 | *.mo 34 | 35 | # Mr Developer 36 | .mr.developer.cfg 37 | .project 38 | .pydevproject 39 | .pylint-plugin-utils 40 | .idea 41 | *.swp 42 | 43 | .venv 44 | env.txt 45 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | # https://pre-commit.com/ 2 | repos: 3 | - repo: https://github.com/pre-commit/pre-commit-hooks 4 | rev: v6.0.0 5 | hooks: 6 | - id: trailing-whitespace 7 | - id: end-of-file-fixer 8 | - id: mixed-line-ending 9 | args: [--fix=lf] 10 | - id: debug-statements 11 | # code formatting 12 | - repo: https://github.com/PyCQA/flake8 13 | rev: 7.3.0 14 | hooks: 15 | - id: flake8 16 | args: [ --max-line-length=120 ] 17 | - repo: https://github.com/psf/black-pre-commit-mirror 18 | rev: 25.11.0 19 | hooks: 20 | - id: black 21 | args: [--safe, --line-length=120] 22 | - repo: https://github.com/PyCQA/isort 23 | rev: 7.0.0 24 | hooks: 25 | - id: isort 26 | args: ['--profile', 'black'] 27 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | minversion = 2.4 3 | envlist = formatting, py39, py310, py311, py312, py313 pypy 4 | skip_missing_interpreters = true 5 | requires = pip >=21.3.1 6 | 7 | [testenv:formatting] 8 | basepython = python3.9 9 | deps = 10 | pre-commit~=4.2 11 | commands = 12 | pre-commit run --all-files 13 | 14 | [testenv] 15 | setenv = 16 | COVERAGE_FILE = {toxinidir}/.coverage.{envname} 17 | deps = poetry 18 | commands = 19 | poetry install 20 | poetry run pytest {toxinidir}/tests/ {posargs:} 21 | 22 | [testenv:coverage-html] 23 | setenv = 24 | COVERAGE_FILE = {toxinidir}/.coverage 25 | deps = poetry 26 | skip_install = true 27 | commands = 28 | poetry install 29 | poetry run coverage combine 30 | poetry run coverage html --ignore-errors --rcfile={toxinidir}/.coveragerc 31 | -------------------------------------------------------------------------------- /tests/test_linter_pickle.py: -------------------------------------------------------------------------------- 1 | import pickle 2 | 3 | import pylint 4 | from pylint.checkers.typecheck import TypeChecker 5 | 6 | from pylint_plugin_utils import augment_visit, suppress_message 7 | 8 | 9 | def fake_augmentation_func(*args, **kwargs): ... 10 | 11 | 12 | def fake_suppress_func(*args, **kwargs): ... 13 | 14 | 15 | def test_linter_should_be_pickleable(linter): 16 | # after pylint>=2.13, dill is used in pylint - 17 | # see discussion https://github.com/PyCQA/pylint-plugin-utils/issues/26 18 | # therefore we can ignore that test, as the previous reasons for this 19 | # test no longer exist 20 | # (reason was https://github.com/PyCQA/pylint-plugin-utils/issues/20) 21 | if pylint.__version__ >= "2.13": 22 | return 23 | 24 | # Setup 25 | linter.register_checker(TypeChecker()) 26 | augment_visit(linter, TypeChecker.visit_attribute, fake_augmentation_func) 27 | suppress_message(linter, TypeChecker.visit_attribute, "no-member", fake_suppress_func) 28 | 29 | # Act and Assert 30 | pickle.dumps(linter) 31 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # -*- coding: UTF-8 -*- 2 | from setuptools import find_packages, setup 3 | 4 | _version = "0.7" 5 | _packages = find_packages(exclude=["*.tests", "*.tests.*", "tests.*", "tests"]) 6 | 7 | _short_description = "Utilities and helpers for writing Pylint plugins" 8 | 9 | _classifiers = [ 10 | "Development Status :: 5 - Production/Stable", 11 | "Environment :: Console", 12 | "Intended Audience :: Developers", 13 | "Operating System :: Unix", 14 | "Topic :: Software Development :: Quality Assurance", 15 | "Programming Language :: Python", 16 | "Programming Language :: Python :: 3", 17 | "Programming Language :: Python :: 3 :: Only", 18 | "Programming Language :: Python :: 3.6", 19 | "Programming Language :: Python :: 3.7", 20 | "Programming Language :: Python :: 3.8", 21 | "Programming Language :: Python :: 3.9", 22 | "Programming Language :: Python :: 3.10", 23 | ] 24 | 25 | setup( 26 | name="pylint-plugin-utils", 27 | url="https://github.com/PyCQA/pylint-plugin-utils", 28 | author="Python Code Quality Authority", 29 | author_email="code-quality@python.org", 30 | description=_short_description, 31 | version=_version, 32 | install_requires=["pylint>=1.7"], 33 | packages=_packages, 34 | license="GPLv2", 35 | classifiers=_classifiers, 36 | keywords="pylint plugin helpers", 37 | python_requires=">=3.6.2", 38 | ) 39 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "pylint-plugin-utils" 3 | version = "0.9.0" 4 | readme = "README.md" 5 | description = "Utilities and helpers for writing Pylint plugins" 6 | repository = "https://github.com/PyCQA/pylint-plugin-utils" 7 | authors = ["Carl Crowder "] 8 | license="GPL-2.0-or-later" 9 | classifiers=[ 10 | "Development Status :: 5 - Production/Stable", 11 | "Environment :: Console", 12 | "Intended Audience :: Developers", 13 | "Operating System :: Unix", 14 | "Topic :: Software Development :: Quality Assurance", 15 | "Programming Language :: Python :: 3.9", 16 | "Programming Language :: Python :: 3.10", 17 | "Programming Language :: Python :: 3.11", 18 | "Programming Language :: Python :: 3.12", 19 | "Programming Language :: Python :: 3.13", 20 | ] 21 | keywords=["pylint","plugin","helpers"] 22 | packages = [ 23 | {include = "pylint_plugin_utils/"} 24 | ] 25 | 26 | [tool.poetry.dependencies] 27 | python = ">=3.9,<4.0" 28 | pylint = ">=1.7" 29 | 30 | [tool.poetry.group.dev.dependencies] 31 | pytest = "~8.4" 32 | pre-commit = "~4.2" 33 | pytest-cov = "~6.2" 34 | tox = "^4.5.1" 35 | 36 | [build-system] 37 | requires = ["poetry-core>=1.0.0"] 38 | build-backend = "poetry.core.masonry.api" 39 | 40 | [tool.pytest.ini_options] 41 | python_files = ["tests.py","test_*.py","*_tests.py","tests/*.py"] 42 | 43 | [tool.black] 44 | line-length = 120 45 | exclude = """ 46 | /( 47 | tests/ 48 | )/ 49 | """ 50 | 51 | [tool.isort] 52 | multi_line_output = 3 53 | include_trailing_comma = true 54 | force_grid_wrap = 0 55 | use_parentheses = true 56 | line_length = 120 57 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # pylint-plugin-utils 2 | 3 | ## Status 4 | 5 | [![Build Status](https://github.com/PyCQA/pylint-plugin-utils/actions/workflows/ci.yaml/badge.svg?branch=master)](https://github.com/PyCQA/pylint-plugin-utils/actions) 6 | [![Code Style](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/ambv/black) 7 | [![Pypi Package version](https://img.shields.io/pypi/v/pylint-plugin-utils.svg)](https://pypi.python.org/pypi/pylint-plugin-utils) 8 | 9 | # About 10 | 11 | Utilities and helpers for writing Pylint plugins. This is not a direct Pylint plugin, but rather a set of tools and functions used by other plugins such as [pylint-django](https://github.com/PyCQA/pylint-django) and [pylint-celery](https://github.com/PyCQA/pylint-celery). 12 | 13 | # Testing 14 | Create virtualenv: 15 | ```bash 16 | python3.8 -m venv .pylint-plugin-utils 17 | source .pylint-plugin-utils/bin/activate 18 | pip install --upgrade pip setuptools 19 | ``` 20 | 21 | We use [tox](https://tox.readthedocs.io/en/latest/) for running the test suite. You should be able to install it with: 22 | ```bash 23 | pip install tox pytest 24 | ``` 25 | 26 | To run the test suite for a particular Python version, you can do: 27 | ```bash 28 | tox -e py39 29 | ``` 30 | 31 | To run individual tests with ``tox``, you can do: 32 | ```bash 33 | tox -e py39 -- -k test_linter_should_be_pickleable 34 | ``` 35 | 36 | We use ``pytest`` for testing ``pylint``, which you can use without using ``tox`` for a faster development cycle. 37 | 38 | If you want to run tests on a specific portion of the code with [pytest](https://docs.pytest.org/en/latest/), [pytest-cov](https://pypi.org/project/pytest-cov/) and your local python version:: 39 | ```bash 40 | pip install pytest-cov 41 | # Everything: 42 | python3 -m pytest tests/ --cov=pylint_plugin_utils 43 | coverage html 44 | ``` 45 | 46 | # License 47 | 48 | `pylint-plugin-utils` is available under the GPLv2 License. 49 | -------------------------------------------------------------------------------- /pylint_plugin_utils/__init__.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | 3 | from pylint.exceptions import UnknownMessageError 4 | from pylint.lint import PyLinter 5 | 6 | 7 | def get_class(module_name, kls): 8 | parts = kls.split(".") 9 | m = __import__(module_name) 10 | for mp in module_name.split(".")[1:]: 11 | m = getattr(m, mp) 12 | klass = getattr(m, parts[0]) 13 | return klass 14 | 15 | 16 | class NoSuchChecker(Exception): 17 | def __init__(self, checker_class): 18 | self.message = "Checker class %s was not found" % checker_class 19 | 20 | def __repr__(self): 21 | return self.message 22 | 23 | 24 | def get_checker(linter: PyLinter, checker_class): 25 | for checker in linter.get_checkers(): 26 | if isinstance(checker, checker_class): 27 | return checker 28 | raise NoSuchChecker(checker_class) 29 | 30 | 31 | def augment_visit(linter: PyLinter, checker_method, augmentation): 32 | """ 33 | Augmenting a visit enables additional errors to be raised (although that case is 34 | better served using a new checker) or to suppress all warnings in certain 35 | circumstances. 36 | 37 | Augmenting functions should accept a 'chain' function, which runs the checker 38 | method and possibly any other augmentations, and secondly an Astroid node. 39 | "chain()" can be called at any point to trigger the continuation of other 40 | checks, or not at all to prevent any further checking. 41 | """ 42 | 43 | try: 44 | checker = get_checker(linter, checker_method.__self__.__class__) 45 | except AttributeError: 46 | checker = get_checker(linter, get_class(checker_method.__module__, checker_method.__qualname__)) 47 | 48 | old_method = getattr(checker, checker_method.__name__) 49 | setattr(checker, checker_method.__name__, AugmentFunc(old_method, augmentation)) 50 | 51 | 52 | class AugmentFunc: 53 | def __init__(self, old_method, augmentation_func): 54 | self.old_method = old_method 55 | self.augmentation_func = augmentation_func 56 | 57 | def __call__(self, node): 58 | self.augmentation_func(Chain(self.old_method, node), node) 59 | 60 | 61 | class Chain: 62 | def __init__(self, old_method, node): 63 | self.old_method = old_method 64 | self.node = node 65 | 66 | def __call__(self): 67 | self.old_method(self.node) 68 | 69 | 70 | class Suppress: 71 | def __init__(self, linter): 72 | self._linter = linter 73 | self._suppress = [] 74 | self._messages_to_append = [] 75 | 76 | def __enter__(self): 77 | self._orig_add_message = self._linter.add_message 78 | self._linter.add_message = self.add_message 79 | return self 80 | 81 | def add_message(self, *args, **kwargs): 82 | self._messages_to_append.append((args, kwargs)) 83 | 84 | def suppress(self, *symbols): 85 | for symbol in symbols: 86 | self._suppress.append(symbol) 87 | 88 | def __exit__(self, exc_type, exc_val, exc_tb): 89 | self._linter.add_message = self._orig_add_message 90 | for to_append_args, to_append_kwargs in self._messages_to_append: 91 | if to_append_args[0] in self._suppress: 92 | continue 93 | self._linter.add_message(*to_append_args, **to_append_kwargs) 94 | 95 | 96 | def suppress_message(linter: PyLinter, checker_method, message_id_or_symbol, test_func): 97 | """ 98 | This wrapper allows the suppression of a message if the supplied test function 99 | returns True. It is useful to prevent one particular message from being raised 100 | in one particular case, while leaving the rest of the messages intact. 101 | """ 102 | augment_visit(linter, checker_method, DoSuppress(linter, message_id_or_symbol, test_func)) 103 | 104 | 105 | class DoSuppress: 106 | def __init__(self, linter: PyLinter, message_id_or_symbol, test_func): 107 | self.linter = linter 108 | self.message_id_or_symbol = message_id_or_symbol 109 | self.test_func = test_func 110 | 111 | def __call__(self, chain, node): 112 | with Suppress(self.linter) as s: 113 | if self.test_func(node): 114 | s.suppress(*self.symbols) 115 | chain() 116 | 117 | @property 118 | def symbols(self) -> List: 119 | # At some point, pylint started preferring message symbols to message IDs. 120 | # However, this is not done consistently or uniformly 121 | # - occasionally there are some message IDs with no matching symbols. 122 | # We try to work around this here by suppressing both the ID and the symbol. 123 | # This also gives us compatability with a broader range of pylint versions. 124 | 125 | # Similarly, between version 1.2 and 1.3 changed where the messages are stored 126 | # - see: 127 | # https://bitbucket.org/logilab/pylint/commits/0b67f42799bed08aebb47babdc9fb0e761efc4ff#chg-reporters/__init__.py 128 | # Therefore here, we try the new attribute name, and fall back to the old 129 | # version for compatability with <=1.2 and >=1.3 130 | 131 | try: 132 | pylint_messages = self.get_message_definitions(self.message_id_or_symbol) 133 | the_symbols = [ 134 | symbol 135 | for pylint_message in pylint_messages 136 | for symbol in (pylint_message.msgid, pylint_message.symbol) 137 | if symbol is not None 138 | ] 139 | except UnknownMessageError: 140 | # This can happen due to mismatches of pylint versions and plugin 141 | # expectations of available messages 142 | the_symbols = [self.message_id_or_symbol] 143 | 144 | return the_symbols 145 | 146 | def get_message_definitions(self, message_id_or_symbol): 147 | msgs_store = getattr(self.linter, "msgs_store", self.linter) 148 | 149 | if hasattr(msgs_store, "check_message_id"): 150 | return [msgs_store.check_message_id(message_id_or_symbol)] 151 | # pylint 2.0 renamed check_message_id to get_message_definition in: 152 | # https://github.com/PyCQA/pylint/commit/5ccbf9eaa54c0c302c9180bdfb745566c16e416d 153 | elif hasattr(msgs_store, "get_message_definition"): 154 | return [msgs_store.get_message_definition(message_id_or_symbol)] 155 | # pylint 2.3.0 renamed get_message_definition to get_message_definitions in: 156 | # https://github.com/PyCQA/pylint/commit/da67a9da682e51844fbc674229ff6619eb9c816a 157 | elif hasattr(msgs_store, "get_message_definitions"): 158 | return msgs_store.get_message_definitions(message_id_or_symbol) 159 | else: 160 | msg = "pylint.utils.MessagesStore does not have a " "get_message_definition(s) method" 161 | raise ValueError(msg) 162 | -------------------------------------------------------------------------------- /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: [push] 4 | 5 | env: 6 | CACHE_VERSION: 3 7 | DEFAULT_PYTHON: 3.9 8 | PRE_COMMIT_CACHE: ~/.cache/pre-commit 9 | 10 | jobs: 11 | prepare-base: 12 | name: Prepare base dependencies 13 | runs-on: ubuntu-latest 14 | outputs: 15 | python-key: ${{ steps.generate-python-key.outputs.key }} 16 | pre-commit-key: ${{ steps.generate-pre-commit-key.outputs.key }} 17 | steps: 18 | - name: Check out code from GitHub 19 | uses: actions/checkout@v3 20 | with: 21 | fetch-depth: 0 22 | - name: Set up Python ${{ env.DEFAULT_PYTHON }} 23 | id: python 24 | uses: actions/setup-python@v4 25 | with: 26 | python-version: ${{ env.DEFAULT_PYTHON }} 27 | - name: Generate partial Python venv restore key 28 | id: generate-python-key 29 | run: >- 30 | echo "::set-output name=key::base-venv-${{ env.CACHE_VERSION }}-${{ 31 | hashFiles('poetry.lock', 'pyproject.toml') 32 | }}" 33 | - name: Restore Python virtual environment 34 | id: cache-venv 35 | uses: actions/cache@v3 36 | with: 37 | path: venv 38 | key: >- 39 | ${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{ 40 | steps.generate-python-key.outputs.key }} 41 | restore-keys: | 42 | ${{ runner.os }}-${{ steps.python.outputs.python-version }}-base-venv-${{ env.CACHE_VERSION }}- 43 | - name: Create Python virtual environment 44 | if: steps.cache-venv.outputs.cache-hit != 'true' 45 | run: | 46 | python -m venv venv 47 | . venv/bin/activate 48 | python -m pip install -U pip poetry 49 | poetry install 50 | - name: Generate pre-commit restore key 51 | id: generate-pre-commit-key 52 | run: >- 53 | echo "::set-output name=key::pre-commit-${{ env.CACHE_VERSION }}-${{ 54 | hashFiles('.pre-commit-config.yaml') }}" 55 | - name: Restore pre-commit environment 56 | id: cache-precommit 57 | uses: actions/cache@v3 58 | with: 59 | path: ${{ env.PRE_COMMIT_CACHE }} 60 | key: >- 61 | ${{ runner.os }}-${{ steps.generate-pre-commit-key.outputs.key }} 62 | restore-keys: | 63 | ${{ runner.os }}-pre-commit-${{ env.CACHE_VERSION }}- 64 | - name: Install pre-commit dependencies 65 | if: steps.cache-precommit.outputs.cache-hit != 'true' 66 | run: | 67 | . venv/bin/activate 68 | pre-commit install --install-hooks 69 | 70 | prepare-tests-linux: 71 | name: Prepare tests for Python ${{ matrix.python-version }} (Linux) 72 | runs-on: ubuntu-latest 73 | strategy: 74 | matrix: 75 | python-version: [3.9, "3.10", "3.11", "3.12", "3.13"] 76 | outputs: 77 | python-key: ${{ steps.generate-python-key.outputs.key }} 78 | steps: 79 | - name: Check out code from GitHub 80 | uses: actions/checkout@v3 81 | with: 82 | fetch-depth: 0 83 | - name: Set up Python ${{ matrix.python-version }} 84 | id: python 85 | uses: actions/setup-python@v4 86 | with: 87 | python-version: ${{ matrix.python-version }} 88 | - name: Generate partial Python venv restore key 89 | id: generate-python-key 90 | run: >- 91 | echo "::set-output name=key::venv-${{ env.CACHE_VERSION }}-${{ 92 | hashFiles('poetry.lock', 'pyproject.toml') 93 | }}" 94 | - name: Restore Python virtual environment 95 | id: cache-venv 96 | uses: actions/cache@v3 97 | with: 98 | path: venv 99 | key: >- 100 | ${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{ 101 | steps.generate-python-key.outputs.key }} 102 | restore-keys: | 103 | ${{ runner.os }}-${{ steps.python.outputs.python-version }}-venv-${{ env.CACHE_VERSION }}- 104 | - name: Create Python virtual environment 105 | if: steps.cache-venv.outputs.cache-hit != 'true' 106 | run: | 107 | python -m venv venv 108 | . venv/bin/activate 109 | python -m pip install -U pip poetry 110 | poetry install 111 | 112 | pytest-linux: 113 | name: Run tests Python ${{ matrix.python-version }} (Linux) 114 | runs-on: ubuntu-latest 115 | needs: prepare-tests-linux 116 | strategy: 117 | fail-fast: false 118 | matrix: 119 | python-version: [3.9, "3.10", "3.11", "3.12", "3.13"] 120 | steps: 121 | - name: Check out code from GitHub 122 | uses: actions/checkout@v2.4.0 123 | - name: Set up Python ${{ matrix.python-version }} 124 | id: python 125 | uses: actions/setup-python@v4 126 | with: 127 | python-version: ${{ matrix.python-version }} 128 | - name: Restore Python virtual environment 129 | id: cache-venv 130 | uses: actions/cache@v3 131 | with: 132 | path: venv 133 | key: 134 | ${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{ 135 | needs.prepare-tests-linux.outputs.python-key }} 136 | - name: Fail job if Python cache restore failed 137 | if: steps.cache-venv.outputs.cache-hit != 'true' 138 | run: | 139 | echo "Failed to restore Python venv from cache" 140 | exit 1 141 | - name: Run pytest 142 | run: | 143 | . venv/bin/activate 144 | pytest --cov --cov-report= tests/ 145 | - name: Upload coverage artifact 146 | uses: actions/upload-artifact@v4 147 | with: 148 | name: coverage-${{ matrix.python-version }} 149 | path: .coverage 150 | 151 | prepare-tests-windows: 152 | name: Prepare tests for Python ${{ matrix.python-version }} (Windows) 153 | runs-on: windows-latest 154 | strategy: 155 | matrix: 156 | python-version: [3.9, "3.10", "3.11", "3.12", "3.13"] 157 | outputs: 158 | python-key: ${{ steps.generate-python-key.outputs.key }} 159 | steps: 160 | - name: Check out code from GitHub 161 | uses: actions/checkout@v3 162 | with: 163 | fetch-depth: 0 164 | - name: Set up Python ${{ matrix.python-version }} 165 | id: python 166 | uses: actions/setup-python@v4 167 | with: 168 | python-version: ${{ matrix.python-version }} 169 | - name: Generate partial Python venv restore key 170 | id: generate-python-key 171 | run: >- 172 | echo "::set-output name=key::venv-${{ env.CACHE_VERSION }}-${{ 173 | hashFiles('poetry.lock', 'pyproject.toml') 174 | }}" 175 | - name: Restore Python virtual environment 176 | id: cache-venv 177 | uses: actions/cache@v3 178 | with: 179 | path: venv 180 | key: >- 181 | ${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{ 182 | steps.generate-python-key.outputs.key }} 183 | restore-keys: | 184 | ${{ runner.os }}-${{ steps.python.outputs.python-version }}-venv-${{ env.CACHE_VERSION }}- 185 | - name: Create Python virtual environment 186 | if: steps.cache-venv.outputs.cache-hit != 'true' 187 | run: | 188 | python -m venv venv 189 | . venv\\Scripts\\activate 190 | python -m pip install -U pip poetry 191 | poetry install 192 | 193 | pytest-windows: 194 | name: Run tests Python ${{ matrix.python-version }} (Windows) 195 | runs-on: windows-latest 196 | needs: prepare-tests-windows 197 | strategy: 198 | fail-fast: false 199 | matrix: 200 | python-version: [3.9, "3.10", "3.11", "3.12", "3.13"] 201 | steps: 202 | - name: Set temp directory 203 | run: echo "TEMP=$env:USERPROFILE\AppData\Local\Temp" >> $env:GITHUB_ENV 204 | # Workaround to set correct temp directory on Windows 205 | # https://github.com/actions/virtual-environments/issues/712 206 | - name: Check out code from GitHub 207 | uses: actions/checkout@v3 208 | - name: Set up Python ${{ matrix.python-version }} 209 | id: python 210 | uses: actions/setup-python@v4 211 | with: 212 | python-version: ${{ matrix.python-version }} 213 | - name: Restore Python virtual environment 214 | id: cache-venv 215 | uses: actions/cache@v3 216 | with: 217 | path: venv 218 | key: 219 | ${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{ 220 | needs.prepare-tests-windows.outputs.python-key }} 221 | - name: Fail job if Python cache restore failed 222 | if: steps.cache-venv.outputs.cache-hit != 'true' 223 | run: | 224 | echo "Failed to restore Python venv from cache" 225 | exit 1 226 | - name: Run pytest 227 | run: | 228 | . venv\\Scripts\\activate 229 | pytest tests/ 230 | 231 | prepare-tests-pypy: 232 | name: Prepare tests for Python ${{ matrix.python-version }} 233 | runs-on: ubuntu-latest 234 | strategy: 235 | matrix: 236 | python-version: ["pypy3.10","pypy3.11"] 237 | outputs: 238 | python-key: ${{ steps.generate-python-key.outputs.key }} 239 | steps: 240 | - name: Check out code from GitHub 241 | uses: actions/checkout@v3 242 | with: 243 | fetch-depth: 0 244 | - name: Set up Python ${{ matrix.python-version }} 245 | id: python 246 | uses: actions/setup-python@v4 247 | with: 248 | python-version: ${{ matrix.python-version }} 249 | - name: Generate partial Python venv restore key 250 | id: generate-python-key 251 | run: >- 252 | echo "::set-output name=key::venv-${{ env.CACHE_VERSION }}-${{ 253 | hashFiles('poetry.lock', 'pyproject.toml') 254 | }}" 255 | - name: Restore Python virtual environment 256 | id: cache-venv 257 | uses: actions/cache@v3 258 | with: 259 | path: venv 260 | key: >- 261 | ${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{ 262 | steps.generate-python-key.outputs.key }} 263 | restore-keys: | 264 | ${{ runner.os }}-${{ steps.python.outputs.python-version }}-venv-${{ env.CACHE_VERSION }}- 265 | - name: Create Python virtual environment 266 | if: steps.cache-venv.outputs.cache-hit != 'true' 267 | run: | 268 | python -m venv venv 269 | . venv/bin/activate 270 | python -m pip install -U pip poetry 271 | poetry install 272 | 273 | pytest-pypy: 274 | name: Run tests Python ${{ matrix.python-version }} 275 | runs-on: ubuntu-latest 276 | needs: prepare-tests-pypy 277 | strategy: 278 | fail-fast: false 279 | matrix: 280 | python-version: ["pypy3.10","pypy3.11"] 281 | steps: 282 | - name: Check out code from GitHub 283 | uses: actions/checkout@v3 284 | - name: Set up Python ${{ matrix.python-version }} 285 | id: python 286 | uses: actions/setup-python@v4 287 | with: 288 | python-version: ${{ matrix.python-version }} 289 | - name: Restore Python virtual environment 290 | id: cache-venv 291 | uses: actions/cache@v3 292 | with: 293 | path: venv 294 | key: 295 | ${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{ 296 | needs.prepare-tests-pypy.outputs.python-key }} 297 | - name: Fail job if Python cache restore failed 298 | if: steps.cache-venv.outputs.cache-hit != 'true' 299 | run: | 300 | echo "Failed to restore Python venv from cache" 301 | exit 1 302 | - name: Run pytest 303 | run: | 304 | . venv/bin/activate 305 | pytest tests/ 306 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | Utilities and helpers for writing Pylint plugins 294 | Copyright (C) 2013 landscapeio 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | {signature of Ty Coon}, 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | -------------------------------------------------------------------------------- /poetry.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Poetry 2.1.3 and should not be changed by hand. 2 | 3 | [[package]] 4 | name = "astroid" 5 | version = "3.3.10" 6 | description = "An abstract syntax tree for Python with inference support." 7 | optional = false 8 | python-versions = ">=3.9.0" 9 | groups = ["main"] 10 | files = [ 11 | {file = "astroid-3.3.10-py3-none-any.whl", hash = "sha256:104fb9cb9b27ea95e847a94c003be03a9e039334a8ebca5ee27dafaf5c5711eb"}, 12 | {file = "astroid-3.3.10.tar.gz", hash = "sha256:c332157953060c6deb9caa57303ae0d20b0fbdb2e59b4a4f2a6ba49d0a7961ce"}, 13 | ] 14 | 15 | [package.dependencies] 16 | typing-extensions = {version = ">=4", markers = "python_version < \"3.11\""} 17 | 18 | [[package]] 19 | name = "cachetools" 20 | version = "6.1.0" 21 | description = "Extensible memoizing collections and decorators" 22 | optional = false 23 | python-versions = ">=3.9" 24 | groups = ["dev"] 25 | files = [ 26 | {file = "cachetools-6.1.0-py3-none-any.whl", hash = "sha256:1c7bb3cf9193deaf3508b7c5f2a79986c13ea38965c5adcff1f84519cf39163e"}, 27 | {file = "cachetools-6.1.0.tar.gz", hash = "sha256:b4c4f404392848db3ce7aac34950d17be4d864da4b8b66911008e430bc544587"}, 28 | ] 29 | 30 | [[package]] 31 | name = "cfgv" 32 | version = "3.4.0" 33 | description = "Validate configuration and produce human readable error messages." 34 | optional = false 35 | python-versions = ">=3.8" 36 | groups = ["dev"] 37 | files = [ 38 | {file = "cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9"}, 39 | {file = "cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560"}, 40 | ] 41 | 42 | [[package]] 43 | name = "chardet" 44 | version = "5.2.0" 45 | description = "Universal encoding detector for Python 3" 46 | optional = false 47 | python-versions = ">=3.7" 48 | groups = ["dev"] 49 | files = [ 50 | {file = "chardet-5.2.0-py3-none-any.whl", hash = "sha256:e1cf59446890a00105fe7b7912492ea04b6e6f06d4b742b2c788469e34c82970"}, 51 | {file = "chardet-5.2.0.tar.gz", hash = "sha256:1b3b6ff479a8c414bc3fa2c0852995695c4a026dcd6d0633b2dd092ca39c1cf7"}, 52 | ] 53 | 54 | [[package]] 55 | name = "colorama" 56 | version = "0.4.6" 57 | description = "Cross-platform colored terminal text." 58 | optional = false 59 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" 60 | groups = ["main", "dev"] 61 | files = [ 62 | {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, 63 | {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, 64 | ] 65 | markers = {main = "sys_platform == \"win32\""} 66 | 67 | [[package]] 68 | name = "coverage" 69 | version = "7.9.1" 70 | description = "Code coverage measurement for Python" 71 | optional = false 72 | python-versions = ">=3.9" 73 | groups = ["dev"] 74 | files = [ 75 | {file = "coverage-7.9.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:cc94d7c5e8423920787c33d811c0be67b7be83c705f001f7180c7b186dcf10ca"}, 76 | {file = "coverage-7.9.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:16aa0830d0c08a2c40c264cef801db8bc4fc0e1892782e45bcacbd5889270509"}, 77 | {file = "coverage-7.9.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cf95981b126f23db63e9dbe4cf65bd71f9a6305696fa5e2262693bc4e2183f5b"}, 78 | {file = "coverage-7.9.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f05031cf21699785cd47cb7485f67df619e7bcdae38e0fde40d23d3d0210d3c3"}, 79 | {file = "coverage-7.9.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bb4fbcab8764dc072cb651a4bcda4d11fb5658a1d8d68842a862a6610bd8cfa3"}, 80 | {file = "coverage-7.9.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:0f16649a7330ec307942ed27d06ee7e7a38417144620bb3d6e9a18ded8a2d3e5"}, 81 | {file = "coverage-7.9.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:cea0a27a89e6432705fffc178064503508e3c0184b4f061700e771a09de58187"}, 82 | {file = "coverage-7.9.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:e980b53a959fa53b6f05343afbd1e6f44a23ed6c23c4b4c56c6662bbb40c82ce"}, 83 | {file = "coverage-7.9.1-cp310-cp310-win32.whl", hash = "sha256:70760b4c5560be6ca70d11f8988ee6542b003f982b32f83d5ac0b72476607b70"}, 84 | {file = "coverage-7.9.1-cp310-cp310-win_amd64.whl", hash = "sha256:a66e8f628b71f78c0e0342003d53b53101ba4e00ea8dabb799d9dba0abbbcebe"}, 85 | {file = "coverage-7.9.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:95c765060e65c692da2d2f51a9499c5e9f5cf5453aeaf1420e3fc847cc060582"}, 86 | {file = "coverage-7.9.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ba383dc6afd5ec5b7a0d0c23d38895db0e15bcba7fb0fa8901f245267ac30d86"}, 87 | {file = "coverage-7.9.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:37ae0383f13cbdcf1e5e7014489b0d71cc0106458878ccde52e8a12ced4298ed"}, 88 | {file = "coverage-7.9.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:69aa417a030bf11ec46149636314c24c8d60fadb12fc0ee8f10fda0d918c879d"}, 89 | {file = "coverage-7.9.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0a4be2a28656afe279b34d4f91c3e26eccf2f85500d4a4ff0b1f8b54bf807338"}, 90 | {file = "coverage-7.9.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:382e7ddd5289f140259b610e5f5c58f713d025cb2f66d0eb17e68d0a94278875"}, 91 | {file = "coverage-7.9.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e5532482344186c543c37bfad0ee6069e8ae4fc38d073b8bc836fc8f03c9e250"}, 92 | {file = "coverage-7.9.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a39d18b3f50cc121d0ce3838d32d58bd1d15dab89c910358ebefc3665712256c"}, 93 | {file = "coverage-7.9.1-cp311-cp311-win32.whl", hash = "sha256:dd24bd8d77c98557880def750782df77ab2b6885a18483dc8588792247174b32"}, 94 | {file = "coverage-7.9.1-cp311-cp311-win_amd64.whl", hash = "sha256:6b55ad10a35a21b8015eabddc9ba31eb590f54adc9cd39bcf09ff5349fd52125"}, 95 | {file = "coverage-7.9.1-cp311-cp311-win_arm64.whl", hash = "sha256:6ad935f0016be24c0e97fc8c40c465f9c4b85cbbe6eac48934c0dc4d2568321e"}, 96 | {file = "coverage-7.9.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a8de12b4b87c20de895f10567639c0797b621b22897b0af3ce4b4e204a743626"}, 97 | {file = "coverage-7.9.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5add197315a054e92cee1b5f686a2bcba60c4c3e66ee3de77ace6c867bdee7cb"}, 98 | {file = "coverage-7.9.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:600a1d4106fe66f41e5d0136dfbc68fe7200a5cbe85610ddf094f8f22e1b0300"}, 99 | {file = "coverage-7.9.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2a876e4c3e5a2a1715a6608906aa5a2e0475b9c0f68343c2ada98110512ab1d8"}, 100 | {file = "coverage-7.9.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:81f34346dd63010453922c8e628a52ea2d2ccd73cb2487f7700ac531b247c8a5"}, 101 | {file = "coverage-7.9.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:888f8eee13f2377ce86d44f338968eedec3291876b0b8a7289247ba52cb984cd"}, 102 | {file = "coverage-7.9.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:9969ef1e69b8c8e1e70d591f91bbc37fc9a3621e447525d1602801a24ceda898"}, 103 | {file = "coverage-7.9.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:60c458224331ee3f1a5b472773e4a085cc27a86a0b48205409d364272d67140d"}, 104 | {file = "coverage-7.9.1-cp312-cp312-win32.whl", hash = "sha256:5f646a99a8c2b3ff4c6a6e081f78fad0dde275cd59f8f49dc4eab2e394332e74"}, 105 | {file = "coverage-7.9.1-cp312-cp312-win_amd64.whl", hash = "sha256:30f445f85c353090b83e552dcbbdad3ec84c7967e108c3ae54556ca69955563e"}, 106 | {file = "coverage-7.9.1-cp312-cp312-win_arm64.whl", hash = "sha256:af41da5dca398d3474129c58cb2b106a5d93bbb196be0d307ac82311ca234342"}, 107 | {file = "coverage-7.9.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:31324f18d5969feef7344a932c32428a2d1a3e50b15a6404e97cba1cc9b2c631"}, 108 | {file = "coverage-7.9.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0c804506d624e8a20fb3108764c52e0eef664e29d21692afa375e0dd98dc384f"}, 109 | {file = "coverage-7.9.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ef64c27bc40189f36fcc50c3fb8f16ccda73b6a0b80d9bd6e6ce4cffcd810bbd"}, 110 | {file = "coverage-7.9.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d4fe2348cc6ec372e25adec0219ee2334a68d2f5222e0cba9c0d613394e12d86"}, 111 | {file = "coverage-7.9.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:34ed2186fe52fcc24d4561041979a0dec69adae7bce2ae8d1c49eace13e55c43"}, 112 | {file = "coverage-7.9.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:25308bd3d00d5eedd5ae7d4357161f4df743e3c0240fa773ee1b0f75e6c7c0f1"}, 113 | {file = "coverage-7.9.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:73e9439310f65d55a5a1e0564b48e34f5369bee943d72c88378f2d576f5a5751"}, 114 | {file = "coverage-7.9.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:37ab6be0859141b53aa89412a82454b482c81cf750de4f29223d52268a86de67"}, 115 | {file = "coverage-7.9.1-cp313-cp313-win32.whl", hash = "sha256:64bdd969456e2d02a8b08aa047a92d269c7ac1f47e0c977675d550c9a0863643"}, 116 | {file = "coverage-7.9.1-cp313-cp313-win_amd64.whl", hash = "sha256:be9e3f68ca9edb897c2184ad0eee815c635565dbe7a0e7e814dc1f7cbab92c0a"}, 117 | {file = "coverage-7.9.1-cp313-cp313-win_arm64.whl", hash = "sha256:1c503289ffef1d5105d91bbb4d62cbe4b14bec4d13ca225f9c73cde9bb46207d"}, 118 | {file = "coverage-7.9.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0b3496922cb5f4215bf5caaef4cf12364a26b0be82e9ed6d050f3352cf2d7ef0"}, 119 | {file = "coverage-7.9.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:9565c3ab1c93310569ec0d86b017f128f027cab0b622b7af288696d7ed43a16d"}, 120 | {file = "coverage-7.9.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2241ad5dbf79ae1d9c08fe52b36d03ca122fb9ac6bca0f34439e99f8327ac89f"}, 121 | {file = "coverage-7.9.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3bb5838701ca68b10ebc0937dbd0eb81974bac54447c55cd58dea5bca8451029"}, 122 | {file = "coverage-7.9.1-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b30a25f814591a8c0c5372c11ac8967f669b97444c47fd794926e175c4047ece"}, 123 | {file = "coverage-7.9.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:2d04b16a6062516df97969f1ae7efd0de9c31eb6ebdceaa0d213b21c0ca1a683"}, 124 | {file = "coverage-7.9.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:7931b9e249edefb07cd6ae10c702788546341d5fe44db5b6108a25da4dca513f"}, 125 | {file = "coverage-7.9.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:52e92b01041151bf607ee858e5a56c62d4b70f4dac85b8c8cb7fb8a351ab2c10"}, 126 | {file = "coverage-7.9.1-cp313-cp313t-win32.whl", hash = "sha256:684e2110ed84fd1ca5f40e89aa44adf1729dc85444004111aa01866507adf363"}, 127 | {file = "coverage-7.9.1-cp313-cp313t-win_amd64.whl", hash = "sha256:437c576979e4db840539674e68c84b3cda82bc824dd138d56bead1435f1cb5d7"}, 128 | {file = "coverage-7.9.1-cp313-cp313t-win_arm64.whl", hash = "sha256:18a0912944d70aaf5f399e350445738a1a20b50fbea788f640751c2ed9208b6c"}, 129 | {file = "coverage-7.9.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6f424507f57878e424d9a95dc4ead3fbdd72fd201e404e861e465f28ea469951"}, 130 | {file = "coverage-7.9.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:535fde4001b2783ac80865d90e7cc7798b6b126f4cd8a8c54acfe76804e54e58"}, 131 | {file = "coverage-7.9.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:02532fd3290bb8fa6bec876520842428e2a6ed6c27014eca81b031c2d30e3f71"}, 132 | {file = "coverage-7.9.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:56f5eb308b17bca3bbff810f55ee26d51926d9f89ba92707ee41d3c061257e55"}, 133 | {file = "coverage-7.9.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bfa447506c1a52271f1b0de3f42ea0fa14676052549095e378d5bff1c505ff7b"}, 134 | {file = "coverage-7.9.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:9ca8e220006966b4a7b68e8984a6aee645a0384b0769e829ba60281fe61ec4f7"}, 135 | {file = "coverage-7.9.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:49f1d0788ba5b7ba65933f3a18864117c6506619f5ca80326b478f72acf3f385"}, 136 | {file = "coverage-7.9.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:68cd53aec6f45b8e4724c0950ce86eacb775c6be01ce6e3669fe4f3a21e768ed"}, 137 | {file = "coverage-7.9.1-cp39-cp39-win32.whl", hash = "sha256:95335095b6c7b1cc14c3f3f17d5452ce677e8490d101698562b2ffcacc304c8d"}, 138 | {file = "coverage-7.9.1-cp39-cp39-win_amd64.whl", hash = "sha256:e1b5191d1648acc439b24721caab2fd0c86679d8549ed2c84d5a7ec1bedcc244"}, 139 | {file = "coverage-7.9.1-pp39.pp310.pp311-none-any.whl", hash = "sha256:db0f04118d1db74db6c9e1cb1898532c7dcc220f1d2718f058601f7c3f499514"}, 140 | {file = "coverage-7.9.1-py3-none-any.whl", hash = "sha256:66b974b145aa189516b6bf2d8423e888b742517d37872f6ee4c5be0073bd9a3c"}, 141 | {file = "coverage-7.9.1.tar.gz", hash = "sha256:6cf43c78c4282708a28e466316935ec7489a9c487518a77fa68f716c67909cec"}, 142 | ] 143 | 144 | [package.dependencies] 145 | tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.11.0a6\" and extra == \"toml\""} 146 | 147 | [package.extras] 148 | toml = ["tomli ; python_full_version <= \"3.11.0a6\""] 149 | 150 | [[package]] 151 | name = "dill" 152 | version = "0.4.0" 153 | description = "serialize all of Python" 154 | optional = false 155 | python-versions = ">=3.8" 156 | groups = ["main"] 157 | files = [ 158 | {file = "dill-0.4.0-py3-none-any.whl", hash = "sha256:44f54bf6412c2c8464c14e8243eb163690a9800dbe2c367330883b19c7561049"}, 159 | {file = "dill-0.4.0.tar.gz", hash = "sha256:0633f1d2df477324f53a895b02c901fb961bdbf65a17122586ea7019292cbcf0"}, 160 | ] 161 | 162 | [package.extras] 163 | graph = ["objgraph (>=1.7.2)"] 164 | profile = ["gprof2dot (>=2022.7.29)"] 165 | 166 | [[package]] 167 | name = "distlib" 168 | version = "0.3.9" 169 | description = "Distribution utilities" 170 | optional = false 171 | python-versions = "*" 172 | groups = ["dev"] 173 | files = [ 174 | {file = "distlib-0.3.9-py2.py3-none-any.whl", hash = "sha256:47f8c22fd27c27e25a65601af709b38e4f0a45ea4fc2e710f65755fa8caaaf87"}, 175 | {file = "distlib-0.3.9.tar.gz", hash = "sha256:a60f20dea646b8a33f3e7772f74dc0b2d0772d2837ee1342a00645c81edf9403"}, 176 | ] 177 | 178 | [[package]] 179 | name = "exceptiongroup" 180 | version = "1.3.0" 181 | description = "Backport of PEP 654 (exception groups)" 182 | optional = false 183 | python-versions = ">=3.7" 184 | groups = ["dev"] 185 | markers = "python_version < \"3.11\"" 186 | files = [ 187 | {file = "exceptiongroup-1.3.0-py3-none-any.whl", hash = "sha256:4d111e6e0c13d0644cad6ddaa7ed0261a0b36971f6d23e7ec9b4b9097da78a10"}, 188 | {file = "exceptiongroup-1.3.0.tar.gz", hash = "sha256:b241f5885f560bc56a59ee63ca4c6a8bfa46ae4ad651af316d4e81817bb9fd88"}, 189 | ] 190 | 191 | [package.dependencies] 192 | typing-extensions = {version = ">=4.6.0", markers = "python_version < \"3.13\""} 193 | 194 | [package.extras] 195 | test = ["pytest (>=6)"] 196 | 197 | [[package]] 198 | name = "filelock" 199 | version = "3.18.0" 200 | description = "A platform independent file lock." 201 | optional = false 202 | python-versions = ">=3.9" 203 | groups = ["dev"] 204 | files = [ 205 | {file = "filelock-3.18.0-py3-none-any.whl", hash = "sha256:c401f4f8377c4464e6db25fff06205fd89bdd83b65eb0488ed1b160f780e21de"}, 206 | {file = "filelock-3.18.0.tar.gz", hash = "sha256:adbc88eabb99d2fec8c9c1b229b171f18afa655400173ddc653d5d01501fb9f2"}, 207 | ] 208 | 209 | [package.extras] 210 | docs = ["furo (>=2024.8.6)", "sphinx (>=8.1.3)", "sphinx-autodoc-typehints (>=3)"] 211 | testing = ["covdefaults (>=2.3)", "coverage (>=7.6.10)", "diff-cover (>=9.2.1)", "pytest (>=8.3.4)", "pytest-asyncio (>=0.25.2)", "pytest-cov (>=6)", "pytest-mock (>=3.14)", "pytest-timeout (>=2.3.1)", "virtualenv (>=20.28.1)"] 212 | typing = ["typing-extensions (>=4.12.2) ; python_version < \"3.11\""] 213 | 214 | [[package]] 215 | name = "identify" 216 | version = "2.6.12" 217 | description = "File identification library for Python" 218 | optional = false 219 | python-versions = ">=3.9" 220 | groups = ["dev"] 221 | files = [ 222 | {file = "identify-2.6.12-py2.py3-none-any.whl", hash = "sha256:ad9672d5a72e0d2ff7c5c8809b62dfa60458626352fb0eb7b55e69bdc45334a2"}, 223 | {file = "identify-2.6.12.tar.gz", hash = "sha256:d8de45749f1efb108badef65ee8386f0f7bb19a7f26185f74de6367bffbaf0e6"}, 224 | ] 225 | 226 | [package.extras] 227 | license = ["ukkonen"] 228 | 229 | [[package]] 230 | name = "iniconfig" 231 | version = "2.1.0" 232 | description = "brain-dead simple config-ini parsing" 233 | optional = false 234 | python-versions = ">=3.8" 235 | groups = ["dev"] 236 | files = [ 237 | {file = "iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760"}, 238 | {file = "iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7"}, 239 | ] 240 | 241 | [[package]] 242 | name = "isort" 243 | version = "6.0.1" 244 | description = "A Python utility / library to sort Python imports." 245 | optional = false 246 | python-versions = ">=3.9.0" 247 | groups = ["main"] 248 | files = [ 249 | {file = "isort-6.0.1-py3-none-any.whl", hash = "sha256:2dc5d7f65c9678d94c88dfc29161a320eec67328bc97aad576874cb4be1e9615"}, 250 | {file = "isort-6.0.1.tar.gz", hash = "sha256:1cb5df28dfbc742e490c5e41bad6da41b805b0a8be7bc93cd0fb2a8a890ac450"}, 251 | ] 252 | 253 | [package.extras] 254 | colors = ["colorama"] 255 | plugins = ["setuptools"] 256 | 257 | [[package]] 258 | name = "mccabe" 259 | version = "0.7.0" 260 | description = "McCabe checker, plugin for flake8" 261 | optional = false 262 | python-versions = ">=3.6" 263 | groups = ["main"] 264 | files = [ 265 | {file = "mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"}, 266 | {file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"}, 267 | ] 268 | 269 | [[package]] 270 | name = "nodeenv" 271 | version = "1.9.1" 272 | description = "Node.js virtual environment builder" 273 | optional = false 274 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" 275 | groups = ["dev"] 276 | files = [ 277 | {file = "nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9"}, 278 | {file = "nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f"}, 279 | ] 280 | 281 | [[package]] 282 | name = "packaging" 283 | version = "25.0" 284 | description = "Core utilities for Python packages" 285 | optional = false 286 | python-versions = ">=3.8" 287 | groups = ["dev"] 288 | files = [ 289 | {file = "packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484"}, 290 | {file = "packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f"}, 291 | ] 292 | 293 | [[package]] 294 | name = "platformdirs" 295 | version = "4.3.8" 296 | description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." 297 | optional = false 298 | python-versions = ">=3.9" 299 | groups = ["main", "dev"] 300 | files = [ 301 | {file = "platformdirs-4.3.8-py3-none-any.whl", hash = "sha256:ff7059bb7eb1179e2685604f4aaf157cfd9535242bd23742eadc3c13542139b4"}, 302 | {file = "platformdirs-4.3.8.tar.gz", hash = "sha256:3d512d96e16bcb959a814c9f348431070822a6496326a4be0911c40b5a74c2bc"}, 303 | ] 304 | 305 | [package.extras] 306 | docs = ["furo (>=2024.8.6)", "proselint (>=0.14)", "sphinx (>=8.1.3)", "sphinx-autodoc-typehints (>=3)"] 307 | test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=8.3.4)", "pytest-cov (>=6)", "pytest-mock (>=3.14)"] 308 | type = ["mypy (>=1.14.1)"] 309 | 310 | [[package]] 311 | name = "pluggy" 312 | version = "1.6.0" 313 | description = "plugin and hook calling mechanisms for python" 314 | optional = false 315 | python-versions = ">=3.9" 316 | groups = ["dev"] 317 | files = [ 318 | {file = "pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746"}, 319 | {file = "pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3"}, 320 | ] 321 | 322 | [package.extras] 323 | dev = ["pre-commit", "tox"] 324 | testing = ["coverage", "pytest", "pytest-benchmark"] 325 | 326 | [[package]] 327 | name = "pre-commit" 328 | version = "4.2.0" 329 | description = "A framework for managing and maintaining multi-language pre-commit hooks." 330 | optional = false 331 | python-versions = ">=3.9" 332 | groups = ["dev"] 333 | files = [ 334 | {file = "pre_commit-4.2.0-py2.py3-none-any.whl", hash = "sha256:a009ca7205f1eb497d10b845e52c838a98b6cdd2102a6c8e4540e94ee75c58bd"}, 335 | {file = "pre_commit-4.2.0.tar.gz", hash = "sha256:601283b9757afd87d40c4c4a9b2b5de9637a8ea02eaff7adc2d0fb4e04841146"}, 336 | ] 337 | 338 | [package.dependencies] 339 | cfgv = ">=2.0.0" 340 | identify = ">=1.0.0" 341 | nodeenv = ">=0.11.1" 342 | pyyaml = ">=5.1" 343 | virtualenv = ">=20.10.0" 344 | 345 | [[package]] 346 | name = "pygments" 347 | version = "2.19.2" 348 | description = "Pygments is a syntax highlighting package written in Python." 349 | optional = false 350 | python-versions = ">=3.8" 351 | groups = ["dev"] 352 | files = [ 353 | {file = "pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b"}, 354 | {file = "pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887"}, 355 | ] 356 | 357 | [package.extras] 358 | windows-terminal = ["colorama (>=0.4.6)"] 359 | 360 | [[package]] 361 | name = "pylint" 362 | version = "3.3.7" 363 | description = "python code static checker" 364 | optional = false 365 | python-versions = ">=3.9.0" 366 | groups = ["main"] 367 | files = [ 368 | {file = "pylint-3.3.7-py3-none-any.whl", hash = "sha256:43860aafefce92fca4cf6b61fe199cdc5ae54ea28f9bf4cd49de267b5195803d"}, 369 | {file = "pylint-3.3.7.tar.gz", hash = "sha256:2b11de8bde49f9c5059452e0c310c079c746a0a8eeaa789e5aa966ecc23e4559"}, 370 | ] 371 | 372 | [package.dependencies] 373 | astroid = ">=3.3.8,<=3.4.0.dev0" 374 | colorama = {version = ">=0.4.5", markers = "sys_platform == \"win32\""} 375 | dill = [ 376 | {version = ">=0.2", markers = "python_version < \"3.11\""}, 377 | {version = ">=0.3.7", markers = "python_version >= \"3.12\""}, 378 | {version = ">=0.3.6", markers = "python_version == \"3.11\""}, 379 | ] 380 | isort = ">=4.2.5,<5.13 || >5.13,<7" 381 | mccabe = ">=0.6,<0.8" 382 | platformdirs = ">=2.2" 383 | tomli = {version = ">=1.1", markers = "python_version < \"3.11\""} 384 | tomlkit = ">=0.10.1" 385 | typing-extensions = {version = ">=3.10", markers = "python_version < \"3.10\""} 386 | 387 | [package.extras] 388 | spelling = ["pyenchant (>=3.2,<4.0)"] 389 | testutils = ["gitpython (>3)"] 390 | 391 | [[package]] 392 | name = "pyproject-api" 393 | version = "1.9.1" 394 | description = "API to interact with the python pyproject.toml based projects" 395 | optional = false 396 | python-versions = ">=3.9" 397 | groups = ["dev"] 398 | files = [ 399 | {file = "pyproject_api-1.9.1-py3-none-any.whl", hash = "sha256:7d6238d92f8962773dd75b5f0c4a6a27cce092a14b623b811dba656f3b628948"}, 400 | {file = "pyproject_api-1.9.1.tar.gz", hash = "sha256:43c9918f49daab37e302038fc1aed54a8c7a91a9fa935d00b9a485f37e0f5335"}, 401 | ] 402 | 403 | [package.dependencies] 404 | packaging = ">=25" 405 | tomli = {version = ">=2.2.1", markers = "python_version < \"3.11\""} 406 | 407 | [package.extras] 408 | docs = ["furo (>=2024.8.6)", "sphinx-autodoc-typehints (>=3.2)"] 409 | testing = ["covdefaults (>=2.3)", "pytest (>=8.3.5)", "pytest-cov (>=6.1.1)", "pytest-mock (>=3.14)", "setuptools (>=80.3.1)"] 410 | 411 | [[package]] 412 | name = "pytest" 413 | version = "8.4.1" 414 | description = "pytest: simple powerful testing with Python" 415 | optional = false 416 | python-versions = ">=3.9" 417 | groups = ["dev"] 418 | files = [ 419 | {file = "pytest-8.4.1-py3-none-any.whl", hash = "sha256:539c70ba6fcead8e78eebbf1115e8b589e7565830d7d006a8723f19ac8a0afb7"}, 420 | {file = "pytest-8.4.1.tar.gz", hash = "sha256:7c67fd69174877359ed9371ec3af8a3d2b04741818c51e5e99cc1742251fa93c"}, 421 | ] 422 | 423 | [package.dependencies] 424 | colorama = {version = ">=0.4", markers = "sys_platform == \"win32\""} 425 | exceptiongroup = {version = ">=1", markers = "python_version < \"3.11\""} 426 | iniconfig = ">=1" 427 | packaging = ">=20" 428 | pluggy = ">=1.5,<2" 429 | pygments = ">=2.7.2" 430 | tomli = {version = ">=1", markers = "python_version < \"3.11\""} 431 | 432 | [package.extras] 433 | dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "requests", "setuptools", "xmlschema"] 434 | 435 | [[package]] 436 | name = "pytest-cov" 437 | version = "6.2.1" 438 | description = "Pytest plugin for measuring coverage." 439 | optional = false 440 | python-versions = ">=3.9" 441 | groups = ["dev"] 442 | files = [ 443 | {file = "pytest_cov-6.2.1-py3-none-any.whl", hash = "sha256:f5bc4c23f42f1cdd23c70b1dab1bbaef4fc505ba950d53e0081d0730dd7e86d5"}, 444 | {file = "pytest_cov-6.2.1.tar.gz", hash = "sha256:25cc6cc0a5358204b8108ecedc51a9b57b34cc6b8c967cc2c01a4e00d8a67da2"}, 445 | ] 446 | 447 | [package.dependencies] 448 | coverage = {version = ">=7.5", extras = ["toml"]} 449 | pluggy = ">=1.2" 450 | pytest = ">=6.2.5" 451 | 452 | [package.extras] 453 | testing = ["fields", "hunter", "process-tests", "pytest-xdist", "virtualenv"] 454 | 455 | [[package]] 456 | name = "pyyaml" 457 | version = "6.0.2" 458 | description = "YAML parser and emitter for Python" 459 | optional = false 460 | python-versions = ">=3.8" 461 | groups = ["dev"] 462 | files = [ 463 | {file = "PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086"}, 464 | {file = "PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf"}, 465 | {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237"}, 466 | {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b"}, 467 | {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed"}, 468 | {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180"}, 469 | {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68"}, 470 | {file = "PyYAML-6.0.2-cp310-cp310-win32.whl", hash = "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99"}, 471 | {file = "PyYAML-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e"}, 472 | {file = "PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774"}, 473 | {file = "PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee"}, 474 | {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c"}, 475 | {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317"}, 476 | {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85"}, 477 | {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4"}, 478 | {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e"}, 479 | {file = "PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5"}, 480 | {file = "PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44"}, 481 | {file = "PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab"}, 482 | {file = "PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725"}, 483 | {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5"}, 484 | {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425"}, 485 | {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476"}, 486 | {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48"}, 487 | {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b"}, 488 | {file = "PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4"}, 489 | {file = "PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8"}, 490 | {file = "PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba"}, 491 | {file = "PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1"}, 492 | {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133"}, 493 | {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484"}, 494 | {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5"}, 495 | {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc"}, 496 | {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652"}, 497 | {file = "PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183"}, 498 | {file = "PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563"}, 499 | {file = "PyYAML-6.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:24471b829b3bf607e04e88d79542a9d48bb037c2267d7927a874e6c205ca7e9a"}, 500 | {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7fded462629cfa4b685c5416b949ebad6cec74af5e2d42905d41e257e0869f5"}, 501 | {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d84a1718ee396f54f3a086ea0a66d8e552b2ab2017ef8b420e92edbc841c352d"}, 502 | {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9056c1ecd25795207ad294bcf39f2db3d845767be0ea6e6a34d856f006006083"}, 503 | {file = "PyYAML-6.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:82d09873e40955485746739bcb8b4586983670466c23382c19cffecbf1fd8706"}, 504 | {file = "PyYAML-6.0.2-cp38-cp38-win32.whl", hash = "sha256:43fa96a3ca0d6b1812e01ced1044a003533c47f6ee8aca31724f78e93ccc089a"}, 505 | {file = "PyYAML-6.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:01179a4a8559ab5de078078f37e5c1a30d76bb88519906844fd7bdea1b7729ff"}, 506 | {file = "PyYAML-6.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d"}, 507 | {file = "PyYAML-6.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f"}, 508 | {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290"}, 509 | {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12"}, 510 | {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19"}, 511 | {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e"}, 512 | {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725"}, 513 | {file = "PyYAML-6.0.2-cp39-cp39-win32.whl", hash = "sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631"}, 514 | {file = "PyYAML-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8"}, 515 | {file = "pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e"}, 516 | ] 517 | 518 | [[package]] 519 | name = "tomli" 520 | version = "2.2.1" 521 | description = "A lil' TOML parser" 522 | optional = false 523 | python-versions = ">=3.8" 524 | groups = ["main", "dev"] 525 | markers = "python_version < \"3.11\"" 526 | files = [ 527 | {file = "tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249"}, 528 | {file = "tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6"}, 529 | {file = "tomli-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a"}, 530 | {file = "tomli-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee"}, 531 | {file = "tomli-2.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e"}, 532 | {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4"}, 533 | {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106"}, 534 | {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8"}, 535 | {file = "tomli-2.2.1-cp311-cp311-win32.whl", hash = "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff"}, 536 | {file = "tomli-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b"}, 537 | {file = "tomli-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea"}, 538 | {file = "tomli-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8"}, 539 | {file = "tomli-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192"}, 540 | {file = "tomli-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222"}, 541 | {file = "tomli-2.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77"}, 542 | {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6"}, 543 | {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd"}, 544 | {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e"}, 545 | {file = "tomli-2.2.1-cp312-cp312-win32.whl", hash = "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98"}, 546 | {file = "tomli-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4"}, 547 | {file = "tomli-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7"}, 548 | {file = "tomli-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c"}, 549 | {file = "tomli-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13"}, 550 | {file = "tomli-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281"}, 551 | {file = "tomli-2.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272"}, 552 | {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140"}, 553 | {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2"}, 554 | {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744"}, 555 | {file = "tomli-2.2.1-cp313-cp313-win32.whl", hash = "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec"}, 556 | {file = "tomli-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69"}, 557 | {file = "tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc"}, 558 | {file = "tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff"}, 559 | ] 560 | 561 | [[package]] 562 | name = "tomlkit" 563 | version = "0.13.3" 564 | description = "Style preserving TOML library" 565 | optional = false 566 | python-versions = ">=3.8" 567 | groups = ["main"] 568 | files = [ 569 | {file = "tomlkit-0.13.3-py3-none-any.whl", hash = "sha256:c89c649d79ee40629a9fda55f8ace8c6a1b42deb912b2a8fd8d942ddadb606b0"}, 570 | {file = "tomlkit-0.13.3.tar.gz", hash = "sha256:430cf247ee57df2b94ee3fbe588e71d362a941ebb545dec29b53961d61add2a1"}, 571 | ] 572 | 573 | [[package]] 574 | name = "tox" 575 | version = "4.27.0" 576 | description = "tox is a generic virtualenv management and test command line tool" 577 | optional = false 578 | python-versions = ">=3.9" 579 | groups = ["dev"] 580 | files = [ 581 | {file = "tox-4.27.0-py3-none-any.whl", hash = "sha256:2b8a7fb986b82aa2c830c0615082a490d134e0626dbc9189986da46a313c4f20"}, 582 | {file = "tox-4.27.0.tar.gz", hash = "sha256:b97d5ecc0c0d5755bcc5348387fef793e1bfa68eb33746412f4c60881d7f5f57"}, 583 | ] 584 | 585 | [package.dependencies] 586 | cachetools = ">=5.5.1" 587 | chardet = ">=5.2" 588 | colorama = ">=0.4.6" 589 | filelock = ">=3.16.1" 590 | packaging = ">=24.2" 591 | platformdirs = ">=4.3.6" 592 | pluggy = ">=1.5" 593 | pyproject-api = ">=1.8" 594 | tomli = {version = ">=2.2.1", markers = "python_version < \"3.11\""} 595 | typing-extensions = {version = ">=4.12.2", markers = "python_version < \"3.11\""} 596 | virtualenv = ">=20.31" 597 | 598 | [package.extras] 599 | test = ["devpi-process (>=1.0.2)", "pytest (>=8.3.4)", "pytest-mock (>=3.14)"] 600 | 601 | [[package]] 602 | name = "typing-extensions" 603 | version = "4.14.0" 604 | description = "Backported and Experimental Type Hints for Python 3.9+" 605 | optional = false 606 | python-versions = ">=3.9" 607 | groups = ["main", "dev"] 608 | markers = "python_version < \"3.11\"" 609 | files = [ 610 | {file = "typing_extensions-4.14.0-py3-none-any.whl", hash = "sha256:a1514509136dd0b477638fc68d6a91497af5076466ad0fa6c338e44e359944af"}, 611 | {file = "typing_extensions-4.14.0.tar.gz", hash = "sha256:8676b788e32f02ab42d9e7c61324048ae4c6d844a399eebace3d4979d75ceef4"}, 612 | ] 613 | 614 | [[package]] 615 | name = "virtualenv" 616 | version = "20.31.2" 617 | description = "Virtual Python Environment builder" 618 | optional = false 619 | python-versions = ">=3.8" 620 | groups = ["dev"] 621 | files = [ 622 | {file = "virtualenv-20.31.2-py3-none-any.whl", hash = "sha256:36efd0d9650ee985f0cad72065001e66d49a6f24eb44d98980f630686243cf11"}, 623 | {file = "virtualenv-20.31.2.tar.gz", hash = "sha256:e10c0a9d02835e592521be48b332b6caee6887f332c111aa79a09b9e79efc2af"}, 624 | ] 625 | 626 | [package.dependencies] 627 | distlib = ">=0.3.7,<1" 628 | filelock = ">=3.12.2,<4" 629 | platformdirs = ">=3.9.1,<5" 630 | 631 | [package.extras] 632 | docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2,!=7.3)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"] 633 | test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8) ; platform_python_implementation == \"PyPy\" or platform_python_implementation == \"GraalVM\" or platform_python_implementation == \"CPython\" and sys_platform == \"win32\" and python_version >= \"3.13\"", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10) ; platform_python_implementation == \"CPython\""] 634 | 635 | [metadata] 636 | lock-version = "2.1" 637 | python-versions = ">=3.9,<4.0" 638 | content-hash = "9fca693849bea0cc692036275d8464446a81c86f610a718da70382e509946716" 639 | --------------------------------------------------------------------------------