├── src ├── tests │ ├── __init__.py │ ├── samples │ │ ├── func_pass.py │ │ ├── funcs.py │ │ ├── except_reraise_no_cause.py │ │ ├── log_object.py │ │ └── except_verbose_reraise.py │ ├── helpers.py │ ├── element_selector_test.py │ ├── attr_selector_test.py │ ├── drill_selector_test.py │ └── reference_selector_test.py └── ast_selector │ ├── __init__.py │ ├── exceptions.py │ ├── main.py │ └── selectors.py ├── .gitignore ├── bin └── lint ├── img └── logo.png ├── .github ├── FUNDING.yml └── workflows │ ├── release.yml │ └── ci.yml ├── .flake8 ├── .vscode ├── launch.json └── settings.json ├── .gitmessage ├── CHANGELOG.md ├── .pre-commit-config.yaml ├── LICENSE ├── pyproject.toml ├── docs └── CONTRIBUTING.md ├── README.md └── poetry.lock /src/tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | dist/ 3 | -------------------------------------------------------------------------------- /bin/lint: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | poetry run pre-commit run -a 4 | -------------------------------------------------------------------------------- /img/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guilatrova/ast_selector/HEAD/img/logo.png -------------------------------------------------------------------------------- /src/tests/samples/func_pass.py: -------------------------------------------------------------------------------- 1 | def func_pass(): 2 | pass 3 | 4 | 5 | def func_ellipsis(): 6 | ... 7 | -------------------------------------------------------------------------------- /src/ast_selector/__init__.py: -------------------------------------------------------------------------------- 1 | """Query AST elements by using CSS Selector-like syntax""" 2 | 3 | from .main import AstSelector 4 | 5 | __version__ = "0.2.0" 6 | __all__ = ["AstSelector"] 7 | -------------------------------------------------------------------------------- /src/tests/samples/funcs.py: -------------------------------------------------------------------------------- 1 | def main() -> str: 2 | return "hello AST Selector" 3 | 4 | 5 | def main_int() -> int: 6 | return 10 7 | 8 | 9 | def second_int() -> int: 10 | return 20 11 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | custom: https://www.paypal.com/donate/?business=SUQKVABPUHUUQ&no_recurring=0&item_name=Thank+you+very+much+for+considering+supporting+my+work.+%E2%9D%A4%EF%B8%8F+It+keeps+me+motivated+to+keep+producing+value+for+you.¤cy_code=USD 2 | -------------------------------------------------------------------------------- /.flake8: -------------------------------------------------------------------------------- 1 | [flake8] 2 | max-line-length=120 3 | extend-ignore= 4 | E203 # whitespace before : 5 | ANN101, ANN102 # type self and cls 6 | ANN204 # type __init__ 7 | per-file-ignores= 8 | src/tests/*:ANN201 9 | exclude= 10 | __init__.py, 11 | tests/samples/* 12 | -------------------------------------------------------------------------------- /src/tests/helpers.py: -------------------------------------------------------------------------------- 1 | import ast 2 | import os 3 | 4 | 5 | def read_sample(filename: str) -> ast.AST: 6 | ref_dir = f"{os.path.dirname(__file__)}/samples/" 7 | path = f"{ref_dir}{filename}.py" 8 | 9 | with open(path) as sample: 10 | content = sample.read() 11 | loaded = ast.parse(content) 12 | return loaded 13 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "Tests", 9 | "type": "python", 10 | "request": "launch", 11 | "module": "pytest", 12 | "justMyCode": true 13 | } 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /src/tests/samples/except_reraise_no_cause.py: -------------------------------------------------------------------------------- 1 | """ 2 | Violation: 3 | 4 | Reraise without using 'from' 5 | """ 6 | 7 | 8 | class MyException(Exception): 9 | pass 10 | 11 | 12 | def func(): 13 | try: 14 | a = 1 15 | except Exception: 16 | raise MyException() # exc is Call + cause is None 17 | 18 | 19 | def func_two(): 20 | try: 21 | a = 1 22 | except MyException as e: 23 | raise e # exc is Name 24 | except Exception: 25 | raise # exc is None 26 | -------------------------------------------------------------------------------- /src/ast_selector/exceptions.py: -------------------------------------------------------------------------------- 1 | class AstSelectorException(Exception): 2 | pass 3 | 4 | 5 | class UnableToFindElement(AstSelectorException): 6 | def __init__(self, query: str) -> None: 7 | self.query = query 8 | super().__init__(f"Unable to find element with query '{query}' in specified tree") 9 | 10 | 11 | class UnableToReferenceQuery(AstSelectorException): 12 | def __init__(self, query: str) -> None: 13 | self.query = query 14 | super().__init__(f"Unable to reference '{query}'") 15 | -------------------------------------------------------------------------------- /src/tests/samples/log_object.py: -------------------------------------------------------------------------------- 1 | """ 2 | Violation: 3 | 4 | Do not log exception object 5 | """ 6 | 7 | import logging 8 | 9 | logger = logging.getLogger(__name__) 10 | 11 | 12 | def func_fstr(): 13 | try: 14 | ... 15 | except Exception as ex: 16 | logger.exception(f"log message {ex}") 17 | 18 | 19 | def func_concat(): 20 | try: 21 | ... 22 | except Exception as ex: 23 | logger.exception("log message: " + str(ex)) 24 | 25 | 26 | def func_comma(): 27 | try: 28 | ... 29 | except Exception as ex: 30 | logger.exception("log message", ex) 31 | -------------------------------------------------------------------------------- /src/tests/samples/except_verbose_reraise.py: -------------------------------------------------------------------------------- 1 | """ 2 | Violation: 3 | 4 | Don't specify exception object again 5 | """ 6 | 7 | 8 | class MyException(Exception): 9 | pass 10 | 11 | 12 | def func(): 13 | try: 14 | a = 1 15 | except MyException: 16 | raise # This is fine 17 | except (NotImplementedError, ValueError) as ex: 18 | raise # This is fine 19 | except Exception as ex: 20 | raise ex # This is verbose 21 | 22 | 23 | def func(): 24 | try: 25 | a = 1 26 | except Exception as ex: 27 | if a == 1: 28 | raise ex # This is verbose 29 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "python.pythonPath": ".venv/bin/python", 3 | "python.linting.enabled": true, 4 | "python.linting.flake8Enabled": true, 5 | "python.formatting.provider": "black", 6 | "[python]": { 7 | "editor.rulers": [ 8 | 120 9 | ], 10 | "editor.formatOnSave": true, 11 | "editor.codeActionsOnSave": { 12 | "source.organizeImports": true 13 | } 14 | }, 15 | "python.testing.pytestArgs": [ 16 | "src/tests" 17 | ], 18 | "python.testing.unittestEnabled": false, 19 | "python.testing.pytestEnabled": true, 20 | } 21 | -------------------------------------------------------------------------------- /.gitmessage: -------------------------------------------------------------------------------- 1 | # Every commit in this project should follow https://www.conventionalcommits.org/en/v1.0.0/ 2 | # >>> Otherwise, you may be requested to rewrite your commit. 3 | 4 | # [optional scope]: 5 | # | | | 6 | # | | +-> Summary in present tense 7 | # | | 8 | # | +-> Refers to story/card/issue # 9 | # | 10 | # +-> Type: feat|chore|fix|refactor|ci|test 11 | # 12 | # Commit subject line should not exceed 50 characters. 13 | # Remainder of gitmessage should wrap at 72 characters. 14 | 15 | # [optional body] 16 | # Read docs/CONTRIBUTING.md for more details 17 | -------------------------------------------------------------------------------- /src/tests/element_selector_test.py: -------------------------------------------------------------------------------- 1 | from ast_selector import AstSelector 2 | 3 | from .helpers import read_sample 4 | 5 | 6 | def test_find_element(): 7 | tree = read_sample("except_reraise_no_cause") 8 | query = "Raise" 9 | 10 | selector = AstSelector(query, tree) 11 | 12 | assert selector.exists() is True 13 | 14 | 15 | def test_count_elements(): 16 | tree = read_sample("except_reraise_no_cause") 17 | query = "Raise" 18 | 19 | selector = AstSelector(query, tree) 20 | 21 | assert selector.count() == 3 22 | 23 | 24 | # TODO: Support element wildcard (*) 25 | # TODO: Support direct children (e.g. FunctionDef > FunctionDef using ast.iter_children instead of ast.walk) 26 | # TODO: Support OR operations 27 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | 4 | 5 | ## v0.2.0 (2022-06-09) 6 | ### Feature 7 | * Support drill and filter on array indexes ([`518239d`](https://github.com/guilatrova/ast_selector/commit/518239de531b579adc96227f2d6789cbc33f599f)) 8 | * Support deeper navigation and reference ([`a5bdf70`](https://github.com/guilatrova/ast_selector/commit/a5bdf7005bad92dec009115126f19775766874a3)) 9 | * Support drilling to lists ([`b161f59`](https://github.com/guilatrova/ast_selector/commit/b161f59037324d7f4670b7d05e5d99f66398949e)) 10 | 11 | ### Fix 12 | * Run semantic release through poetry ([`5f9a58e`](https://github.com/guilatrova/ast_selector/commit/5f9a58e56f49c87772c8cb3c24698e7791e997c3)) 13 | 14 | ### Documentation 15 | * Fix pip install cmd ([`7ee6357`](https://github.com/guilatrova/ast_selector/commit/7ee63579ffd4d09dc7e5c49cd91be1f68e7bf801)) 16 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/pre-commit/pre-commit-hooks 3 | rev: v3.4.0 4 | hooks: 5 | - id: end-of-file-fixer 6 | - id: trailing-whitespace 7 | - repo: https://github.com/psf/black 8 | rev: 22.3.0 9 | hooks: 10 | - id: black 11 | - repo: https://gitlab.com/pycqa/flake8 12 | rev: 4.0.1 13 | hooks: 14 | - id: flake8 15 | additional_dependencies: [flake8-annotations==2.9.0] 16 | exclude: tests/samples/ 17 | - repo: https://github.com/timothycrosley/isort 18 | rev: 5.10.1 19 | hooks: 20 | - id: isort 21 | - repo: https://github.com/guilatrova/tryceratops 22 | rev: v1.1.0 23 | hooks: 24 | - id: tryceratops 25 | exclude: tests/samples/ 26 | - repo: https://github.com/pre-commit/mirrors-mypy 27 | rev: v0.950 28 | hooks: 29 | - id: mypy 30 | exclude: tests/samples/ 31 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Semantic Release 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | release: 10 | runs-on: ubuntu-latest 11 | concurrency: release 12 | 13 | steps: 14 | - uses: actions/checkout@v2 15 | with: 16 | fetch-depth: 0 17 | token: ${{ secrets.AST_SELECTOR_GITHUB_TOKEN }} 18 | 19 | - name: Install poetry 20 | run: pipx install poetry 21 | 22 | - name: Set up Python 3.8 23 | uses: actions/setup-python@v3 24 | with: 25 | python-version: 3.8 26 | cache: poetry 27 | 28 | - name: Install dependencies 29 | run: poetry install 30 | 31 | - name: Python Semantic Release 32 | run: | 33 | git config --global user.name "github-actions" 34 | git config --global user.email "action@github.com" 35 | 36 | poetry run semantic-release publish -D commit_author="github-actions " 37 | env: 38 | GH_TOKEN: ${{secrets.AST_SELECTOR_GITHUB_TOKEN}} 39 | PYPI_TOKEN: ${{secrets.PYPI_TOKEN}} 40 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2021 Guilherme Latrova 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/tests/attr_selector_test.py: -------------------------------------------------------------------------------- 1 | import ast 2 | import pytest # noqa: F401 3 | 4 | from ast_selector import AstSelector 5 | 6 | from .helpers import read_sample 7 | 8 | 9 | def test_find_raise_with_single_attr(): 10 | tree = read_sample("except_reraise_no_cause") 11 | query = "Raise[exc is Call]" 12 | 13 | selector = AstSelector(query, tree) 14 | 15 | assert selector.exists() is True 16 | 17 | 18 | def test_find_raise_with_two_attrs(): 19 | tree = read_sample("except_reraise_no_cause") 20 | query = "Raise[exc is Call][cause is None]" 21 | 22 | selector = AstSelector(query, tree) 23 | 24 | assert selector.exists() is True 25 | assert selector.count() == 1 26 | 27 | 28 | def test_find_all_raise_below_except_handler(): 29 | tree = read_sample("except_reraise_no_cause") 30 | query = "ExceptHandler Raise[exc is Call]" 31 | 32 | selector = AstSelector(query, tree) 33 | found = selector.all() 34 | 35 | assert len(found) == 1 36 | assert isinstance(found[0], ast.Raise) 37 | 38 | 39 | def test_find_first_raise_below_except_handler(): 40 | tree = read_sample("except_reraise_no_cause") 41 | query = "ExceptHandler Raise[exc is Call][cause is None]" 42 | 43 | selector = AstSelector(query, tree) 44 | found = selector.first() 45 | 46 | assert isinstance(found, ast.Raise) 47 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | paths-ignore: 6 | - "docs/**" 7 | - "*.md" 8 | 9 | pull_request: 10 | paths-ignore: 11 | - "docs/**" 12 | - "*.md" 13 | 14 | jobs: 15 | test: 16 | # We want to run on external PRs, but not on our own internal PRs as they'll be run 17 | # by the push to the branch. Without this if check, checks are duplicated since 18 | # internal PRs match both the push and pull_request events. 19 | if: 20 | github.event_name == 'push' || github.event.pull_request.head.repo.full_name != 21 | github.repository 22 | 23 | strategy: 24 | fail-fast: false 25 | matrix: 26 | python-version: [3.8, 3.9] 27 | os: [ubuntu-latest, macOS-latest, windows-latest] 28 | 29 | runs-on: ${{ matrix.os }} 30 | 31 | steps: 32 | - uses: actions/checkout@v2 33 | 34 | - name: Install poetry 35 | run: pipx install poetry 36 | 37 | - name: Set up Python ${{ matrix.python-version }} 38 | uses: actions/setup-python@v3 39 | with: 40 | python-version: ${{ matrix.python-version }} 41 | cache: poetry 42 | 43 | - name: Install dependencies 44 | run: poetry install 45 | 46 | - name: Lint 47 | uses: pre-commit/action@v2.0.3 48 | 49 | - name: Unit tests 50 | run: poetry run pytest -vvv --cov=ast_selector --cov-report=xml 51 | 52 | # TODO: Set up code coverage 53 | -------------------------------------------------------------------------------- /src/tests/drill_selector_test.py: -------------------------------------------------------------------------------- 1 | import ast 2 | import pytest # noqa: F401 3 | 4 | from ast_selector import AstSelector 5 | 6 | from .helpers import read_sample 7 | 8 | 9 | def test_find_drill_properties(): 10 | """ 11 | Drill ability: 12 | 13 | if Expr has value and value is Call 14 | -> then take value from Expr 15 | """ 16 | tree = read_sample("log_object") 17 | query = "Expr[value is Call].value" 18 | 19 | selector = AstSelector(query, tree) 20 | found = selector.first() 21 | 22 | assert isinstance(found, ast.Call) 23 | 24 | 25 | def test_filter_drill_properties(): 26 | """ 27 | Drill ability: 28 | 29 | if Expr has value and value is Call 30 | -> then take value from Expr 31 | 32 | if Expr.value has func and func is Attribute 33 | -> then take func from Expr.value 34 | """ 35 | tree = read_sample("log_object") 36 | query = "Expr[value is Call].value[func is Attribute].func" 37 | 38 | selector = AstSelector(query, tree) 39 | found = selector.first() 40 | 41 | assert isinstance(found, ast.Attribute) 42 | 43 | 44 | def test_drill_filter_child_array(): 45 | tree = read_sample("func_pass") 46 | query = "FunctionDef.body[0 is Pass].0" 47 | 48 | selector = AstSelector(query, tree) 49 | found = selector.all() 50 | 51 | assert len(found) == 1 52 | assert isinstance(found[0], ast.Pass) 53 | 54 | 55 | # TODO: Support element wildcard (*) 56 | # TODO: Support deeper references (e.g. $FunctionDef.1 when FunctionDef(not this) FunctionDef(this)) 57 | # TODO: Support direct children (e.g. FunctionDef > FunctionDef using ast.iter_children instead of ast.walk) 58 | # TODO: Support OR operations 59 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "ast_selector" 3 | version = "0.2.0" 4 | description = "Query AST elements by using CSS Selector-like syntax" 5 | authors = ["Guilherme Latrova "] 6 | license = "MIT" 7 | keywords = ["ast"] 8 | readme = "README.md" 9 | homepage = "https://github.com/guilatrova/ast_selector" 10 | repository = "https://github.com/guilatrova/ast_selector" 11 | include = [ 12 | "LICENSE", 13 | ] 14 | classifiers = [ 15 | "Development Status :: 4 - Beta", 16 | "Intended Audience :: Developers", 17 | "Operating System :: OS Independent", 18 | "Topic :: Software Development :: Libraries :: Python Modules", 19 | ] 20 | packages = [ 21 | { include = "ast_selector", from = "src" }, 22 | ] 23 | 24 | [tool.poetry.urls] 25 | "Changelog" = "https://github.com/guilatrova/ast_selector/blob/main/CHANGELOG.md" 26 | 27 | [tool.semantic_release] 28 | version_variable = [ 29 | "src/ast_selector/__init__.py:__version__" 30 | ] 31 | version_toml = [ 32 | "pyproject.toml:tool.poetry.version" 33 | ] 34 | major_on_zero = false 35 | branch = "main" 36 | upload_to_pypi = true 37 | upload_to_release = true 38 | build_command = "pip install poetry && poetry build" 39 | 40 | [tool.poetry.dependencies] 41 | python = "^3.8" 42 | 43 | [tool.poetry.dev-dependencies] 44 | black = "^22.3.0" 45 | flake8 = "^4.0.1" 46 | flake8-annotations = "^2.9.0" 47 | isort = "^5.10.1" 48 | mypy = "^0.950" 49 | tryceratops = "^1.1.0" 50 | pre-commit = "^2.18.1" 51 | pytest = "^7.1.2" 52 | pytest-cov = "^3.0.0" 53 | python-semantic-release = "^7.29.1" 54 | 55 | [tool.black] 56 | line-length = 120 57 | 58 | [tool.isort] 59 | profile = "black" 60 | line_length = 120 61 | extra_standard_library = ["pytest"] 62 | 63 | [tool.mypy] 64 | python_version = 3.8 65 | warn_unused_configs = true 66 | namespace_packages = true 67 | explicit_package_bases = true 68 | ignore_missing_imports = true 69 | 70 | [tool.pytest.ini_options] 71 | testpaths = "src/tests" 72 | 73 | [build-system] 74 | requires = ["poetry-core>=1.0.0"] 75 | build-backend = "poetry.core.masonry.api" 76 | -------------------------------------------------------------------------------- /docs/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | - [Contributing](#contributing) 4 | - [Setup](#setup) 5 | - [Testing](#testing) 6 | - [Linting](#linting) 7 | - [Conventional Commits](#conventional-commits) 8 | 9 | ## Setup 10 | 11 | Install the dependency manager (if not already done): 12 | 13 | ```sh 14 | pip3 install poetry 15 | ``` 16 | 17 | Install all dependencies, pre-commit hooks, and git config: 18 | 19 | ```sh 20 | poetry install 21 | pre-commit install 22 | git config commit.template .gitmessage 23 | ``` 24 | 25 | ## Testing 26 | 27 | You can either run: 28 | 29 | ```sh 30 | ❯ poetry run ❯ pytest 31 | ============================================================= test session starts ============================================================= 32 | platform linux -- Python 3.10.4, pytest-7.1.2, pluggy-1.0.0 33 | rootdir: /home/gui/Desktop/poc/ast_selector, configfile: pyproject.toml, testpaths: src/tests 34 | plugins: cov-3.0.0 35 | collected 18 items 36 | 37 | src/tests/attr_selector_test.py .... [ 22%] 38 | src/tests/drill_selector_test.py ... [ 38%] 39 | src/tests/element_selector_test.py .. [ 50%] 40 | src/tests/reference_selector_test.py ......... [100%] 41 | 42 | ============================================================= 18 passed in 0.03s ============================================================== 43 | ``` 44 | 45 | ## Linting 46 | 47 | If you installed `pre-commit` it should ensure you're not commiting anything broken. 48 | 49 | You can run `./bin/lint` to check if there's any issue. 50 | 51 | ## Conventional Commits 52 | 53 | We automate the versioning and release process! It's only possible if we are consistent with the commit pattern and follow the conventional commits standards. 54 | 55 | Refer to [Conventional Commits here](https://www.conventionalcommits.org/en/v1.0.0/) and if you're curious to understand how the publishing works [check here](https://python-semantic-release.readthedocs.io/en/latest/). 56 | -------------------------------------------------------------------------------- /src/ast_selector/main.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import ast 4 | import itertools 5 | import re 6 | from typing import Generator, List, Optional 7 | 8 | from .exceptions import UnableToFindElement 9 | from .selectors import ElementSelector, NavigationReference, SelectorGroup 10 | 11 | 12 | class AstSelector: 13 | def __init__(self, query: str, tree: ast.AST) -> None: 14 | self.tree = tree 15 | self.query = query 16 | # TODO: Validate query 17 | 18 | def _resolve_query(self) -> List[ElementSelector]: 19 | PARENT_RE = r"(\.\.)?" 20 | DRILL_RE = r"(\.\w+)?" 21 | ATTR_RE = r"(\[[a-zA-Z0-9_= ]+\])?" 22 | NODE_RE = r"(\$?[A-Z]\w+)?" 23 | 24 | reg = re.findall(f"{PARENT_RE}{DRILL_RE}{ATTR_RE}{NODE_RE}", self.query) 25 | reggroup = [x for x in itertools.chain(*reg) if x] 26 | 27 | el_selector: Optional[ElementSelector] = None 28 | reference_table = NavigationReference() 29 | results = [] 30 | 31 | for g in reggroup: 32 | selector = SelectorGroup(reference_table, g) 33 | 34 | if selector.is_attribute_selector: 35 | if el_selector is not None: 36 | el_selector.append_attr_selector(selector) 37 | else: 38 | el_selector = selector.to_element_selector() 39 | results.append(el_selector) 40 | 41 | return results 42 | 43 | def _resolve(self) -> Generator[ast.AST, None, None]: 44 | groups = self._resolve_query() 45 | 46 | tree = iter([self.tree]) 47 | for group in groups: 48 | tree = group.find_nodes(tree) 49 | 50 | yield from tree 51 | 52 | def exists(self) -> bool: 53 | el = self._resolve() 54 | try: 55 | next(el) 56 | except StopIteration: 57 | return False 58 | else: 59 | return True 60 | 61 | def count(self) -> int: 62 | nodes = self._resolve() 63 | return len(list(nodes)) 64 | 65 | def all(self) -> List[ast.AST]: 66 | nodes = self._resolve() 67 | return list(nodes) 68 | 69 | def first(self) -> ast.AST: 70 | el = self._resolve() 71 | try: 72 | first = next(el) 73 | except StopIteration as e: 74 | raise UnableToFindElement(self.query) from e 75 | else: 76 | return first 77 | -------------------------------------------------------------------------------- /src/tests/reference_selector_test.py: -------------------------------------------------------------------------------- 1 | import ast 2 | import pytest # noqa: F401 3 | 4 | from ast_selector import AstSelector 5 | 6 | from .helpers import read_sample 7 | 8 | 9 | def test_drill_properties_reference_first(): 10 | """ 11 | Drill ability + Get origin: 12 | 13 | Drill 2 levels and get first reference 14 | """ 15 | tree = read_sample("log_object") 16 | query = "Expr[value is Call].value[func is Attribute].func $Expr" 17 | 18 | selector = AstSelector(query, tree) 19 | found = selector.first() 20 | 21 | assert isinstance(found, ast.Expr) 22 | assert isinstance(found.value, ast.Call) 23 | assert isinstance(found.value.func, ast.Attribute) 24 | 25 | 26 | def test_drill_properties_reference_second(): 27 | """ 28 | Drill ability + Get origin: 29 | 30 | Drill 2 levels and get second reference 31 | """ 32 | tree = read_sample("log_object") 33 | query = "Expr[value is Call].value[func is Attribute].func $Expr.value" 34 | 35 | selector = AstSelector(query, tree) 36 | found = selector.first() 37 | 38 | assert isinstance(found, ast.Call) 39 | assert isinstance(found.func, ast.Attribute) 40 | 41 | 42 | def test_filter_functions_returning_str(): 43 | tree = read_sample("funcs") 44 | query = "FunctionDef.returns[id=str].." 45 | 46 | selector = AstSelector(query, tree) 47 | found = selector.all() 48 | 49 | assert len(found) == 1 50 | assert isinstance(found[0], ast.FunctionDef) 51 | 52 | 53 | def test_filter_functions_returning_int(): 54 | tree = read_sample("funcs") 55 | query = "FunctionDef.returns[id=int] $FunctionDef" 56 | 57 | selector = AstSelector(query, tree) 58 | found = selector.all() 59 | 60 | assert len(found) == 2 61 | assert all(isinstance(x, ast.FunctionDef) for x in found) 62 | 63 | 64 | def test_filter_functions_returning_int_with_name(): 65 | tree = read_sample("funcs") 66 | query = "FunctionDef.returns[id=int] $FunctionDef[name=main_int]" 67 | 68 | selector = AstSelector(query, tree) 69 | found = selector.all() 70 | 71 | assert len(found) == 1 72 | assert found[0].name == "main_int" 73 | 74 | 75 | def test_drill_function_returning_int(): 76 | tree = read_sample("funcs") 77 | query = "FunctionDef.returns[id=int] $FunctionDef.args" 78 | 79 | selector = AstSelector(query, tree) 80 | found = selector.first() 81 | 82 | assert isinstance(found, ast.arguments) 83 | 84 | 85 | def test_reference_nonexisting_match(): 86 | tree = read_sample("funcs") 87 | query = "Raise $Raise" 88 | 89 | selector = AstSelector(query, tree) 90 | 91 | assert selector.exists() is False 92 | assert selector.count() == 0 93 | 94 | 95 | def test_drill_list_ast_from_reference(): 96 | """ 97 | 1. Drill from Expr, to value.func 98 | 2. Reference $Expr.value 99 | 3. Drill to .args 100 | """ 101 | tree = read_sample("log_object") 102 | query = "Expr[value is Call].value[func is Attribute].func[attr = exception] $Expr.value.args" 103 | 104 | selector = AstSelector(query, tree) 105 | found = selector.all() 106 | 107 | assert all(isinstance(x, ast.AST) for x in found) 108 | 109 | 110 | def test_long_navigation_get_back_to_original_reference(): 111 | tree = read_sample("log_object") 112 | query = "FunctionDef Expr[value is Call].value[func is Attribute].func[attr = exception] $FunctionDef" 113 | 114 | selector = AstSelector(query, tree) 115 | found = selector.first() 116 | 117 | assert isinstance(found, ast.FunctionDef) 118 | 119 | 120 | # TODO: Support deeper references (e.g. $FunctionDef.1 when FunctionDef(not this) FunctionDef(this)) 121 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 |

4 | 5 |

Query AST elements by using CSS Selector-like syntax

6 | 7 |

8 | Actions Status 9 | PyPI 10 | 11 | Semantic Release 12 | GitHub 13 | Downloads 14 | Code style: black 15 | try/except style: tryceratops 16 | Follow guilatrova 17 |

18 | 19 | > "Query AST elements 🌲 by using CSS Selector-like 💅 syntax." 20 | 21 | **Summary** 22 | - [Installation and usage](#installation-and-usage) 23 | - [Installation](#installation) 24 | - [Quick Start](#quick-start) 25 | - [Examples](#examples) 26 | - [Query by AST type](#query-by-ast-type) 27 | - [Filter AST by property type](#filter-ast-by-property-type) 28 | - [Drill to AST property](#drill-to-ast-property) 29 | - [Filter AST by property value](#filter-ast-by-property-value) 30 | - [Filter AST by multiple conditions](#filter-ast-by-multiple-conditions) 31 | - [Drill down but take previous reference](#drill-down-but-take-previous-reference) 32 | - [Filter and drill through references](#filter-and-drill-through-references) 33 | - [Count nodes](#count-nodes) 34 | - [Take first node only](#take-first-node-only) 35 | - [Check if node exists](#check-if-node-exists) 36 | - [Contributing](#contributing) 37 | - [Change log](#change-log) 38 | - [License](#license) 39 | - [Credits](#credits) 40 | 41 | ## Installation and usage 42 | 43 | ### Installation 44 | 45 | ``` 46 | pip install ast-selector 47 | ``` 48 | 49 | ### Quick Start 50 | 51 | Query all functions that raises at least an exception: 52 | 53 | ```py 54 | from ast_selector import AstSelector 55 | 56 | tree = load_python_code_as_ast_tree() 57 | query = "FunctionDef Raise $FunctionDef" 58 | 59 | functions_raising_exceptions = AstSelector(query, tree).all() 60 | ``` 61 | 62 | ## Examples 63 | 64 | ### Query by AST type 65 | 66 | Simply use the AST type. Note it should have the proper casing. 67 | 68 | ```py 69 | AstSelector("FunctionDef", tree).all() # Any Ast.FunctionDef 70 | AstSelector("Raise", tree).all() # Any ast.Raise 71 | AstSelector("Expr", tree).all() # Any ast.Expr 72 | ``` 73 | 74 | ### Filter AST by property type 75 | 76 | You can filter property types by writing like: `[Prop is Type]`. 77 | 78 | Condition: Any `ast.Expr` that contains `.value` prop and that prop is an instance of `ast.Call` 79 | 80 | Result: List of `ast.Expr` that fulfills the condition. 81 | 82 | ```py 83 | AstSelector("Expr[value is Call]", tree).all() 84 | ``` 85 | 86 | ### Drill to AST property 87 | 88 | You can navigate as you filter the elements you want by using `.prop`. 89 | 90 | Condition: Any `ast.Expr` that contains `.value` prop and that prop is an instance of `ast.Call`, take `.value`. 91 | 92 | Result: List of `ast.Call` that fulfills the condition. 93 | 94 | ```py 95 | AstSelector("Expr[value is Call].value", tree).all() 96 | ``` 97 | 98 | ### Filter AST by property value 99 | 100 | You can filter property values by writing like: `[Prop = Value]`. 101 | 102 | Condition: Any `ast.FunctionDef`, take `returns` as long as it contains `id` equals to `int`. 103 | 104 | Result: List of `ast.Name` that fulfills the condition. 105 | 106 | ```py 107 | AstSelector("FunctionDef.returns[id=int]", tree).all() 108 | ``` 109 | 110 | ### Filter AST by multiple conditions 111 | 112 | You can keep appending `[Cond1][Cond2][Cond3]` as you wish: 113 | 114 | Condition: Any `ast.Raise` that has `exc` of type `ast.Call` **AND** that has `cause` as `None`. 115 | 116 | Result: List of `ast.Raise` that fulfills the condition. 117 | 118 | ```py 119 | AstSelector("Raise[exc is Call][cause is None]", tree).all() 120 | ``` 121 | 122 | ### Drill down but take previous reference 123 | 124 | You can keep drilling down, but take a previous value as your result by using `$[Placeholder]` syntax: 125 | 126 | Condition: Any `ast.FunctionDef`, take `returns` as long as it has `id` equals to `int`, then take the original `FunctionDef`. 127 | 128 | Result: List of `ast.FunctionDef` that fulfills the condition. 129 | 130 | ```py 131 | AstSelector("FunctionDef.returns[id=int] $FunctionDef", tree).all() 132 | ``` 133 | 134 | ### Filter and drill through references 135 | 136 | You can keep filtering and drilling references as you would normally. 137 | 138 | Drill `$Expr` and take `args` as result: 139 | 140 | ```py 141 | AstSelector("Expr[value is Call].value[func is Attribute].func[attr = exception] $Expr.value.args", tree).all() 142 | ``` 143 | 144 | Drill `$FunctionDef` (redundant) and filter functions named `main_int` as result: 145 | 146 | ```py 147 | AstSelector("FunctionDef.returns[id=int] $FunctionDef[name=main_int]", tree).all() 148 | ``` 149 | 150 | ### Count nodes 151 | 152 | ```py 153 | AstSelector(query, tree).count() 154 | ``` 155 | 156 | ### Take first node only 157 | 158 | ```py 159 | # Raises exception if None 160 | AstSelector(query, tree).first() 161 | ``` 162 | 163 | ### Check if node exists 164 | 165 | ```py 166 | AstSelector(query, tree).exists() 167 | ``` 168 | 169 | ## Contributing 170 | 171 | Thank you for considering making AST Selector better for everyone! 172 | 173 | Refer to [Contributing docs](docs/CONTRIBUTING.md). 174 | 175 | ## Change log 176 | 177 | See [CHANGELOG](CHANGELOG.md). 178 | 179 | ## License 180 | 181 | MIT 182 | 183 | ## Credits 184 | 185 | It's extremely hard to keep hacking on open source like that while keeping a full-time job. I thank God from the bottom of my heart for both the inspiration and the energy. 186 | -------------------------------------------------------------------------------- /src/ast_selector/selectors.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import ast 4 | from collections import defaultdict 5 | from dataclasses import dataclass, field 6 | from enum import Enum 7 | from typing import Any, Dict, Generator, Iterator, List, Optional, Type, Union 8 | 9 | from .exceptions import UnableToReferenceQuery 10 | 11 | 12 | @dataclass 13 | class NavigationReference: 14 | reference_table: Dict[str, List[ast.AST]] = field(default_factory=lambda: defaultdict(list)) 15 | 16 | def _build_reference(self, selector_group: SelectorGroup) -> str: 17 | if selector_group.is_element_selector: 18 | return f"${selector_group.query}" 19 | 20 | elif selector_group.is_drill_selector: 21 | last_ref = list(self.reference_table.keys())[-1] 22 | return f"${last_ref}.{selector_group.query}" 23 | 24 | raise UnableToReferenceQuery(selector_group.query) 25 | 26 | def get_last_entry(self) -> List[ast.AST]: 27 | return list(self.reference_table.values())[-1] 28 | 29 | def append(self, selector_group: SelectorGroup, val: ast.AST) -> None: 30 | ref = self._build_reference(selector_group) 31 | self.reference_table[ref].append(val) 32 | 33 | def get_reference(self, ref: str) -> Optional[List[ast.AST]]: 34 | k = list(reversed(self.reference_table.keys())) 35 | if ref in k: 36 | return self.reference_table[ref] 37 | 38 | return None 39 | 40 | 41 | @dataclass 42 | class SelectorGroup: 43 | navigation: NavigationReference 44 | query: str 45 | referentiable = True 46 | 47 | @property 48 | def is_attribute_selector(self) -> bool: 49 | return self.query.startswith("[") 50 | 51 | @property 52 | def is_drill_selector(self) -> bool: 53 | return self.query.startswith(".") 54 | 55 | @property 56 | def is_reference_selector(self) -> bool: 57 | return self.query.startswith("$") 58 | 59 | @property 60 | def is_parent_selector(self) -> bool: 61 | return self.query == ".." 62 | 63 | @property 64 | def is_element_selector(self) -> bool: 65 | return all( 66 | [ 67 | not self.is_attribute_selector, 68 | not self.is_drill_selector, 69 | not self.is_reference_selector, 70 | not self.is_parent_selector, 71 | ] 72 | ) 73 | 74 | def to_element_selector(self) -> ElementSelector: 75 | if self.is_element_selector: 76 | return ElementSelector(self.navigation, self.query) 77 | 78 | if self.is_parent_selector: 79 | return ParentSelector(self.navigation, self.query) 80 | 81 | if self.is_drill_selector: 82 | return DrillSelector(self.navigation, self.query) 83 | 84 | if self.is_reference_selector: 85 | return ReferenceSelector(self.navigation, self.query) 86 | 87 | raise TypeError() 88 | 89 | def to_attribute_selector(self) -> AttributeSelector: 90 | return AttributeSelector(self.navigation, self.query) 91 | 92 | def _find_nodes(self, branches: Union[Iterator[ast.AST], ast.AST]) -> Generator[ast.AST, None, None]: 93 | raise NotImplementedError() 94 | 95 | def find_nodes(self, branches: Union[Iterator[ast.AST], ast.AST]) -> Generator[ast.AST, None, None]: 96 | for node in self._find_nodes(branches): 97 | if self.referentiable: 98 | self.navigation.append(self, node) 99 | 100 | yield node 101 | 102 | 103 | @dataclass 104 | class ElementSelector(SelectorGroup): 105 | element_type: Type[ast.AST] = field(init=False) 106 | attr_selectors: List[AttributeSelector] = field(default_factory=list) 107 | 108 | def __post_init__(self) -> None: 109 | self.element_type = getattr(ast, self.query) 110 | 111 | def append_attr_selector(self, selector: SelectorGroup) -> None: 112 | self.attr_selectors.append(selector.to_attribute_selector()) 113 | 114 | def _matches(self, node: ast.AST) -> bool: 115 | return all(attr.matches(node) for attr in self.attr_selectors) 116 | 117 | def _find_nodes(self, branches: Union[Iterator[ast.AST], ast.AST]) -> Generator[ast.AST, None, None]: 118 | if not isinstance(branches, Iterator): 119 | branches = iter([branches]) 120 | 121 | for tree in branches: 122 | for node in ast.walk(tree): 123 | node.parent = getattr(node, "parent", tree) # type: ignore 124 | 125 | for child in ast.iter_child_nodes(node): 126 | child.parent = node # type: ignore 127 | 128 | if isinstance(node, self.element_type): 129 | if self._matches(node): 130 | yield node 131 | 132 | 133 | @dataclass 134 | class DrillSelector(ElementSelector): 135 | iteration: dict[Any, int] = field(default_factory=lambda: defaultdict(int)) 136 | 137 | def __post_init__(self) -> None: 138 | self.element_type = ast.AST 139 | self.query = self.query.lstrip(".") 140 | 141 | def _get_drilled_instances(self, parent: ast.AST, node: Any) -> Generator[ast.AST, None, None]: 142 | drilled_instances: list[ast.AST] = [] 143 | 144 | if isinstance(node, ast.AST): 145 | drilled_instances = [node] 146 | elif isinstance(node, list): 147 | drilled_instances = [valid for valid in node if isinstance(valid, ast.AST)] 148 | 149 | for drilled in drilled_instances: 150 | if self._matches(drilled): 151 | drilled.parent = parent # type: ignore 152 | yield drilled 153 | 154 | def _find_nodes(self, branches: Union[Iterator[ast.AST], ast.AST]) -> Generator[ast.AST, None, None]: 155 | if not isinstance(branches, Iterator): 156 | branches = iter([branches]) 157 | 158 | for node in list(branches): 159 | if self.query.isnumeric(): 160 | attr_idx = int(self.query) 161 | if attr_idx == self.iteration[node.parent]: # type: ignore 162 | yield node 163 | 164 | elif drilled := getattr(node, self.query, False): 165 | yield from self._get_drilled_instances(node, drilled) 166 | 167 | self.iteration[node.parent] += 1 # type: ignore 168 | 169 | 170 | class AttributeSelectorComparator(str, Enum): 171 | INSTANCE = " is " 172 | EQUALS = "=" 173 | 174 | 175 | @dataclass 176 | class AttributeSelector(SelectorGroup): 177 | attr: str = field(init=False) 178 | condition: AttributeSelectorComparator = field(init=False) 179 | val: str = field(init=False) 180 | iteration: dict[Any, int] = field(default_factory=lambda: defaultdict(int)) 181 | 182 | def __post_init__(self) -> None: 183 | self.query = self.query.lstrip("[").rstrip("]") 184 | if AttributeSelectorComparator.EQUALS in self.query: 185 | self.condition = AttributeSelectorComparator.EQUALS 186 | else: 187 | self.condition = AttributeSelectorComparator.INSTANCE 188 | 189 | self.attr, self.val = [x.strip() for x in self.query.split(self.condition)] 190 | 191 | def _match_equals(self, node: ast.AST) -> bool: 192 | attr_val = str(getattr(node, self.attr)) 193 | expected_val = self.val 194 | 195 | return attr_val == expected_val 196 | 197 | def _match_instance(self, node: ast.AST) -> bool: 198 | if self.attr.isdigit(): 199 | attr_idx = int(self.attr) 200 | if attr_idx != self.iteration[node.parent]: # type: ignore 201 | return False 202 | 203 | attr_val = node 204 | else: 205 | attr_val = getattr(node, self.attr) 206 | 207 | expected_val_type = getattr(ast, self.val, type(None)) 208 | 209 | if isinstance(attr_val, expected_val_type): 210 | return True 211 | 212 | return False 213 | 214 | def matches(self, node: ast.AST) -> bool: 215 | if self.condition == AttributeSelectorComparator.INSTANCE: 216 | result = self._match_instance(node) 217 | else: 218 | result = self._match_equals(node) 219 | 220 | self.iteration[node.parent] += 1 # type: ignore 221 | return result 222 | 223 | 224 | @dataclass 225 | class ReferenceSelector(ElementSelector): 226 | referentiable = False 227 | 228 | def __post_init__(self) -> None: 229 | self.element_type = ast.AST 230 | 231 | def _walk_parent(self, node: ast.AST) -> Generator[ast.AST, None, None]: 232 | cur_node = node 233 | while cur_node.parent and cur_node != cur_node.parent: # type: ignore 234 | yield cur_node.parent # type: ignore 235 | cur_node = cur_node.parent # type: ignore 236 | 237 | def _find_nodes(self, branches: Union[Iterator[ast.AST], ast.AST]) -> Generator[ast.AST, None, None]: 238 | if not isinstance(branches, Iterator): 239 | branches = iter([branches]) 240 | 241 | tree = list(branches) # Force generators to build reference table until this point 242 | references = self.navigation.get_reference(self.query) or [] 243 | 244 | for ref in references: 245 | if any((node for node in tree if ref in self._walk_parent(node))): 246 | if self._matches(ref): 247 | yield ref 248 | 249 | 250 | @dataclass 251 | class ParentSelector(ReferenceSelector): 252 | def _find_nodes(self, branches: Union[Iterator[ast.AST], ast.AST]) -> Generator[ast.AST, None, None]: 253 | if not isinstance(branches, Iterator): 254 | branches = iter([branches]) 255 | 256 | for node in branches: 257 | yield node.parent # type: ignore 258 | -------------------------------------------------------------------------------- /poetry.lock: -------------------------------------------------------------------------------- 1 | [[package]] 2 | name = "atomicwrites" 3 | version = "1.4.0" 4 | description = "Atomic file writes." 5 | category = "dev" 6 | optional = false 7 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 8 | 9 | [[package]] 10 | name = "attrs" 11 | version = "21.4.0" 12 | description = "Classes Without Boilerplate" 13 | category = "dev" 14 | optional = false 15 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 16 | 17 | [package.extras] 18 | dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "furo", "sphinx", "sphinx-notfound-page", "pre-commit", "cloudpickle"] 19 | docs = ["furo", "sphinx", "zope.interface", "sphinx-notfound-page"] 20 | tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "cloudpickle"] 21 | tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "cloudpickle"] 22 | 23 | [[package]] 24 | name = "black" 25 | version = "22.3.0" 26 | description = "The uncompromising code formatter." 27 | category = "dev" 28 | optional = false 29 | python-versions = ">=3.6.2" 30 | 31 | [package.dependencies] 32 | click = ">=8.0.0" 33 | mypy-extensions = ">=0.4.3" 34 | pathspec = ">=0.9.0" 35 | platformdirs = ">=2" 36 | tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} 37 | typing-extensions = {version = ">=3.10.0.0", markers = "python_version < \"3.10\""} 38 | 39 | [package.extras] 40 | colorama = ["colorama (>=0.4.3)"] 41 | d = ["aiohttp (>=3.7.4)"] 42 | jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] 43 | uvloop = ["uvloop (>=0.15.2)"] 44 | 45 | [[package]] 46 | name = "bleach" 47 | version = "5.0.0" 48 | description = "An easy safelist-based HTML-sanitizing tool." 49 | category = "dev" 50 | optional = false 51 | python-versions = ">=3.7" 52 | 53 | [package.dependencies] 54 | six = ">=1.9.0" 55 | webencodings = "*" 56 | 57 | [package.extras] 58 | css = ["tinycss2 (>=1.1.0)"] 59 | dev = ["pip-tools (==6.5.1)", "pytest (==7.1.1)", "flake8 (==4.0.1)", "tox (==3.24.5)", "sphinx (==4.3.2)", "twine (==4.0.0)", "wheel (==0.37.1)", "hashin (==0.17.0)", "black (==22.3.0)", "mypy (==0.942)"] 60 | 61 | [[package]] 62 | name = "certifi" 63 | version = "2022.5.18.1" 64 | description = "Python package for providing Mozilla's CA Bundle." 65 | category = "dev" 66 | optional = false 67 | python-versions = ">=3.6" 68 | 69 | [[package]] 70 | name = "cffi" 71 | version = "1.15.0" 72 | description = "Foreign Function Interface for Python calling C code." 73 | category = "dev" 74 | optional = false 75 | python-versions = "*" 76 | 77 | [package.dependencies] 78 | pycparser = "*" 79 | 80 | [[package]] 81 | name = "cfgv" 82 | version = "3.3.1" 83 | description = "Validate configuration and produce human readable error messages." 84 | category = "dev" 85 | optional = false 86 | python-versions = ">=3.6.1" 87 | 88 | [[package]] 89 | name = "charset-normalizer" 90 | version = "2.0.12" 91 | description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." 92 | category = "dev" 93 | optional = false 94 | python-versions = ">=3.5.0" 95 | 96 | [package.extras] 97 | unicode_backport = ["unicodedata2"] 98 | 99 | [[package]] 100 | name = "click" 101 | version = "8.1.3" 102 | description = "Composable command line interface toolkit" 103 | category = "dev" 104 | optional = false 105 | python-versions = ">=3.7" 106 | 107 | [package.dependencies] 108 | colorama = {version = "*", markers = "platform_system == \"Windows\""} 109 | 110 | [[package]] 111 | name = "click-log" 112 | version = "0.4.0" 113 | description = "Logging integration for Click" 114 | category = "dev" 115 | optional = false 116 | python-versions = "*" 117 | 118 | [package.dependencies] 119 | click = "*" 120 | 121 | [[package]] 122 | name = "colorama" 123 | version = "0.4.4" 124 | description = "Cross-platform colored terminal text." 125 | category = "dev" 126 | optional = false 127 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 128 | 129 | [[package]] 130 | name = "commonmark" 131 | version = "0.9.1" 132 | description = "Python parser for the CommonMark Markdown spec" 133 | category = "dev" 134 | optional = false 135 | python-versions = "*" 136 | 137 | [package.extras] 138 | test = ["flake8 (==3.7.8)", "hypothesis (==3.55.3)"] 139 | 140 | [[package]] 141 | name = "coverage" 142 | version = "6.4.1" 143 | description = "Code coverage measurement for Python" 144 | category = "dev" 145 | optional = false 146 | python-versions = ">=3.7" 147 | 148 | [package.dependencies] 149 | tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.11.0a6\" and extra == \"toml\""} 150 | 151 | [package.extras] 152 | toml = ["tomli"] 153 | 154 | [[package]] 155 | name = "cryptography" 156 | version = "37.0.2" 157 | description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." 158 | category = "dev" 159 | optional = false 160 | python-versions = ">=3.6" 161 | 162 | [package.dependencies] 163 | cffi = ">=1.12" 164 | 165 | [package.extras] 166 | docs = ["sphinx (>=1.6.5,!=1.8.0,!=3.1.0,!=3.1.1)", "sphinx-rtd-theme"] 167 | docstest = ["pyenchant (>=1.6.11)", "twine (>=1.12.0)", "sphinxcontrib-spelling (>=4.0.1)"] 168 | pep8test = ["black", "flake8", "flake8-import-order", "pep8-naming"] 169 | sdist = ["setuptools_rust (>=0.11.4)"] 170 | ssh = ["bcrypt (>=3.1.5)"] 171 | test = ["pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-subtests", "pytest-xdist", "pretend", "iso8601", "pytz", "hypothesis (>=1.11.4,!=3.79.2)"] 172 | 173 | [[package]] 174 | name = "distlib" 175 | version = "0.3.4" 176 | description = "Distribution utilities" 177 | category = "dev" 178 | optional = false 179 | python-versions = "*" 180 | 181 | [[package]] 182 | name = "docutils" 183 | version = "0.18.1" 184 | description = "Docutils -- Python Documentation Utilities" 185 | category = "dev" 186 | optional = false 187 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 188 | 189 | [[package]] 190 | name = "dotty-dict" 191 | version = "1.3.0" 192 | description = "Dictionary wrapper for quick access to deeply nested keys." 193 | category = "dev" 194 | optional = false 195 | python-versions = "*" 196 | 197 | [package.dependencies] 198 | setuptools_scm = "*" 199 | 200 | [[package]] 201 | name = "filelock" 202 | version = "3.7.1" 203 | description = "A platform independent file lock." 204 | category = "dev" 205 | optional = false 206 | python-versions = ">=3.7" 207 | 208 | [package.extras] 209 | docs = ["furo (>=2021.8.17b43)", "sphinx (>=4.1)", "sphinx-autodoc-typehints (>=1.12)"] 210 | testing = ["covdefaults (>=1.2.0)", "coverage (>=4)", "pytest (>=4)", "pytest-cov", "pytest-timeout (>=1.4.2)"] 211 | 212 | [[package]] 213 | name = "flake8" 214 | version = "4.0.1" 215 | description = "the modular source code checker: pep8 pyflakes and co" 216 | category = "dev" 217 | optional = false 218 | python-versions = ">=3.6" 219 | 220 | [package.dependencies] 221 | mccabe = ">=0.6.0,<0.7.0" 222 | pycodestyle = ">=2.8.0,<2.9.0" 223 | pyflakes = ">=2.4.0,<2.5.0" 224 | 225 | [[package]] 226 | name = "flake8-annotations" 227 | version = "2.9.0" 228 | description = "Flake8 Type Annotation Checks" 229 | category = "dev" 230 | optional = false 231 | python-versions = ">=3.7,<4.0" 232 | 233 | [package.dependencies] 234 | attrs = ">=21.4,<22.0" 235 | flake8 = ">=3.7" 236 | 237 | [[package]] 238 | name = "gitdb" 239 | version = "4.0.9" 240 | description = "Git Object Database" 241 | category = "dev" 242 | optional = false 243 | python-versions = ">=3.6" 244 | 245 | [package.dependencies] 246 | smmap = ">=3.0.1,<6" 247 | 248 | [[package]] 249 | name = "gitpython" 250 | version = "3.1.27" 251 | description = "GitPython is a python library used to interact with Git repositories" 252 | category = "dev" 253 | optional = false 254 | python-versions = ">=3.7" 255 | 256 | [package.dependencies] 257 | gitdb = ">=4.0.1,<5" 258 | 259 | [[package]] 260 | name = "identify" 261 | version = "2.5.1" 262 | description = "File identification library for Python" 263 | category = "dev" 264 | optional = false 265 | python-versions = ">=3.7" 266 | 267 | [package.extras] 268 | license = ["ukkonen"] 269 | 270 | [[package]] 271 | name = "idna" 272 | version = "3.3" 273 | description = "Internationalized Domain Names in Applications (IDNA)" 274 | category = "dev" 275 | optional = false 276 | python-versions = ">=3.5" 277 | 278 | [[package]] 279 | name = "importlib-metadata" 280 | version = "4.2.0" 281 | description = "Read metadata from Python packages" 282 | category = "dev" 283 | optional = false 284 | python-versions = ">=3.6" 285 | 286 | [package.dependencies] 287 | zipp = ">=0.5" 288 | 289 | [package.extras] 290 | docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] 291 | testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "packaging", "pep517", "pyfakefs", "flufl.flake8", "pytest-black (>=0.3.7)", "pytest-mypy", "importlib-resources (>=1.3)"] 292 | 293 | [[package]] 294 | name = "iniconfig" 295 | version = "1.1.1" 296 | description = "iniconfig: brain-dead simple config-ini parsing" 297 | category = "dev" 298 | optional = false 299 | python-versions = "*" 300 | 301 | [[package]] 302 | name = "invoke" 303 | version = "1.7.1" 304 | description = "Pythonic task execution" 305 | category = "dev" 306 | optional = false 307 | python-versions = "*" 308 | 309 | [[package]] 310 | name = "isort" 311 | version = "5.10.1" 312 | description = "A Python utility / library to sort Python imports." 313 | category = "dev" 314 | optional = false 315 | python-versions = ">=3.6.1,<4.0" 316 | 317 | [package.extras] 318 | pipfile_deprecated_finder = ["pipreqs", "requirementslib"] 319 | requirements_deprecated_finder = ["pipreqs", "pip-api"] 320 | colors = ["colorama (>=0.4.3,<0.5.0)"] 321 | plugins = ["setuptools"] 322 | 323 | [[package]] 324 | name = "jeepney" 325 | version = "0.8.0" 326 | description = "Low-level, pure Python DBus protocol wrapper." 327 | category = "dev" 328 | optional = false 329 | python-versions = ">=3.7" 330 | 331 | [package.extras] 332 | test = ["pytest", "pytest-trio", "pytest-asyncio (>=0.17)", "testpath", "trio", "async-timeout"] 333 | trio = ["trio", "async-generator"] 334 | 335 | [[package]] 336 | name = "keyring" 337 | version = "23.6.0" 338 | description = "Store and access your passwords safely." 339 | category = "dev" 340 | optional = false 341 | python-versions = ">=3.7" 342 | 343 | [package.dependencies] 344 | importlib-metadata = {version = ">=3.6", markers = "python_version < \"3.10\""} 345 | jeepney = {version = ">=0.4.2", markers = "sys_platform == \"linux\""} 346 | pywin32-ctypes = {version = "<0.1.0 || >0.1.0,<0.1.1 || >0.1.1", markers = "sys_platform == \"win32\""} 347 | SecretStorage = {version = ">=3.2", markers = "sys_platform == \"linux\""} 348 | 349 | [package.extras] 350 | docs = ["sphinx", "jaraco.packaging (>=9)", "rst.linker (>=1.9)", "jaraco.tidelift (>=1.4)"] 351 | testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "pytest-black (>=0.3.7)", "pytest-mypy (>=0.9.1)"] 352 | 353 | [[package]] 354 | name = "mccabe" 355 | version = "0.6.1" 356 | description = "McCabe checker, plugin for flake8" 357 | category = "dev" 358 | optional = false 359 | python-versions = "*" 360 | 361 | [[package]] 362 | name = "mypy" 363 | version = "0.950" 364 | description = "Optional static typing for Python" 365 | category = "dev" 366 | optional = false 367 | python-versions = ">=3.6" 368 | 369 | [package.dependencies] 370 | mypy-extensions = ">=0.4.3" 371 | tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} 372 | typing-extensions = ">=3.10" 373 | 374 | [package.extras] 375 | dmypy = ["psutil (>=4.0)"] 376 | python2 = ["typed-ast (>=1.4.0,<2)"] 377 | reports = ["lxml"] 378 | 379 | [[package]] 380 | name = "mypy-extensions" 381 | version = "0.4.3" 382 | description = "Experimental type system extensions for programs checked with the mypy typechecker." 383 | category = "dev" 384 | optional = false 385 | python-versions = "*" 386 | 387 | [[package]] 388 | name = "nodeenv" 389 | version = "1.6.0" 390 | description = "Node.js virtual environment builder" 391 | category = "dev" 392 | optional = false 393 | python-versions = "*" 394 | 395 | [[package]] 396 | name = "packaging" 397 | version = "21.3" 398 | description = "Core utilities for Python packages" 399 | category = "dev" 400 | optional = false 401 | python-versions = ">=3.6" 402 | 403 | [package.dependencies] 404 | pyparsing = ">=2.0.2,<3.0.5 || >3.0.5" 405 | 406 | [[package]] 407 | name = "pathspec" 408 | version = "0.9.0" 409 | description = "Utility library for gitignore style pattern matching of file paths." 410 | category = "dev" 411 | optional = false 412 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" 413 | 414 | [[package]] 415 | name = "pkginfo" 416 | version = "1.8.3" 417 | description = "Query metadatdata from sdists / bdists / installed packages." 418 | category = "dev" 419 | optional = false 420 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" 421 | 422 | [package.extras] 423 | testing = ["nose", "coverage"] 424 | 425 | [[package]] 426 | name = "platformdirs" 427 | version = "2.5.2" 428 | description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." 429 | category = "dev" 430 | optional = false 431 | python-versions = ">=3.7" 432 | 433 | [package.extras] 434 | docs = ["furo (>=2021.7.5b38)", "proselint (>=0.10.2)", "sphinx-autodoc-typehints (>=1.12)", "sphinx (>=4)"] 435 | test = ["appdirs (==1.4.4)", "pytest-cov (>=2.7)", "pytest-mock (>=3.6)", "pytest (>=6)"] 436 | 437 | [[package]] 438 | name = "pluggy" 439 | version = "1.0.0" 440 | description = "plugin and hook calling mechanisms for python" 441 | category = "dev" 442 | optional = false 443 | python-versions = ">=3.6" 444 | 445 | [package.extras] 446 | dev = ["pre-commit", "tox"] 447 | testing = ["pytest", "pytest-benchmark"] 448 | 449 | [[package]] 450 | name = "pre-commit" 451 | version = "2.19.0" 452 | description = "A framework for managing and maintaining multi-language pre-commit hooks." 453 | category = "dev" 454 | optional = false 455 | python-versions = ">=3.7" 456 | 457 | [package.dependencies] 458 | cfgv = ">=2.0.0" 459 | identify = ">=1.0.0" 460 | nodeenv = ">=0.11.1" 461 | pyyaml = ">=5.1" 462 | toml = "*" 463 | virtualenv = ">=20.0.8" 464 | 465 | [[package]] 466 | name = "py" 467 | version = "1.11.0" 468 | description = "library with cross-python path, ini-parsing, io, code, log facilities" 469 | category = "dev" 470 | optional = false 471 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 472 | 473 | [[package]] 474 | name = "pycodestyle" 475 | version = "2.8.0" 476 | description = "Python style guide checker" 477 | category = "dev" 478 | optional = false 479 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 480 | 481 | [[package]] 482 | name = "pycparser" 483 | version = "2.21" 484 | description = "C parser in Python" 485 | category = "dev" 486 | optional = false 487 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 488 | 489 | [[package]] 490 | name = "pyflakes" 491 | version = "2.4.0" 492 | description = "passive checker of Python programs" 493 | category = "dev" 494 | optional = false 495 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 496 | 497 | [[package]] 498 | name = "pygments" 499 | version = "2.12.0" 500 | description = "Pygments is a syntax highlighting package written in Python." 501 | category = "dev" 502 | optional = false 503 | python-versions = ">=3.6" 504 | 505 | [[package]] 506 | name = "pyparsing" 507 | version = "3.0.9" 508 | description = "pyparsing module - Classes and methods to define and execute parsing grammars" 509 | category = "dev" 510 | optional = false 511 | python-versions = ">=3.6.8" 512 | 513 | [package.extras] 514 | diagrams = ["railroad-diagrams", "jinja2"] 515 | 516 | [[package]] 517 | name = "pytest" 518 | version = "7.1.2" 519 | description = "pytest: simple powerful testing with Python" 520 | category = "dev" 521 | optional = false 522 | python-versions = ">=3.7" 523 | 524 | [package.dependencies] 525 | atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""} 526 | attrs = ">=19.2.0" 527 | colorama = {version = "*", markers = "sys_platform == \"win32\""} 528 | iniconfig = "*" 529 | packaging = "*" 530 | pluggy = ">=0.12,<2.0" 531 | py = ">=1.8.2" 532 | tomli = ">=1.0.0" 533 | 534 | [package.extras] 535 | testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "xmlschema"] 536 | 537 | [[package]] 538 | name = "pytest-cov" 539 | version = "3.0.0" 540 | description = "Pytest plugin for measuring coverage." 541 | category = "dev" 542 | optional = false 543 | python-versions = ">=3.6" 544 | 545 | [package.dependencies] 546 | coverage = {version = ">=5.2.1", extras = ["toml"]} 547 | pytest = ">=4.6" 548 | 549 | [package.extras] 550 | testing = ["fields", "hunter", "process-tests", "six", "pytest-xdist", "virtualenv"] 551 | 552 | [[package]] 553 | name = "python-gitlab" 554 | version = "3.5.0" 555 | description = "Interact with GitLab API" 556 | category = "dev" 557 | optional = false 558 | python-versions = ">=3.7.0" 559 | 560 | [package.dependencies] 561 | requests = ">=2.25.0" 562 | requests-toolbelt = ">=0.9.1" 563 | 564 | [package.extras] 565 | autocompletion = ["argcomplete (>=1.10.0,<3)"] 566 | yaml = ["PyYaml (>=5.2)"] 567 | 568 | [[package]] 569 | name = "python-semantic-release" 570 | version = "7.29.1" 571 | description = "Automatic Semantic Versioning for Python projects" 572 | category = "dev" 573 | optional = false 574 | python-versions = "*" 575 | 576 | [package.dependencies] 577 | click = ">=7,<9" 578 | click-log = ">=0.3,<1" 579 | dotty-dict = ">=1.3.0,<2" 580 | gitpython = ">=3.0.8,<4" 581 | invoke = ">=1.4.1,<2" 582 | python-gitlab = ">=2,<4" 583 | requests = ">=2.25,<3" 584 | semver = ">=2.10,<3" 585 | tomlkit = ">=0.10.0,<0.11.0" 586 | twine = ">=3,<4" 587 | 588 | [package.extras] 589 | dev = ["tox", "isort", "black"] 590 | docs = ["Sphinx (==1.3.6)", "Jinja2 (==3.0.3)"] 591 | mypy = ["mypy", "types-requests"] 592 | test = ["coverage (>=5,<6)", "pytest (>=5,<6)", "pytest-xdist (>=1,<2)", "pytest-mock (>=2,<3)", "responses (==0.13.3)", "mock (==1.3.0)"] 593 | 594 | [[package]] 595 | name = "pywin32-ctypes" 596 | version = "0.2.0" 597 | description = "" 598 | category = "dev" 599 | optional = false 600 | python-versions = "*" 601 | 602 | [[package]] 603 | name = "pyyaml" 604 | version = "6.0" 605 | description = "YAML parser and emitter for Python" 606 | category = "dev" 607 | optional = false 608 | python-versions = ">=3.6" 609 | 610 | [[package]] 611 | name = "readme-renderer" 612 | version = "35.0" 613 | description = "readme_renderer is a library for rendering \"readme\" descriptions for Warehouse" 614 | category = "dev" 615 | optional = false 616 | python-versions = ">=3.7" 617 | 618 | [package.dependencies] 619 | bleach = ">=2.1.0" 620 | docutils = ">=0.13.1" 621 | Pygments = ">=2.5.1" 622 | 623 | [package.extras] 624 | md = ["cmarkgfm (>=0.8.0)"] 625 | 626 | [[package]] 627 | name = "requests" 628 | version = "2.28.0" 629 | description = "Python HTTP for Humans." 630 | category = "dev" 631 | optional = false 632 | python-versions = ">=3.7, <4" 633 | 634 | [package.dependencies] 635 | certifi = ">=2017.4.17" 636 | charset-normalizer = ">=2.0.0,<2.1.0" 637 | idna = ">=2.5,<4" 638 | urllib3 = ">=1.21.1,<1.27" 639 | 640 | [package.extras] 641 | socks = ["PySocks (>=1.5.6,!=1.5.7)"] 642 | use_chardet_on_py3 = ["chardet (>=3.0.2,<5)"] 643 | 644 | [[package]] 645 | name = "requests-toolbelt" 646 | version = "0.9.1" 647 | description = "A utility belt for advanced users of python-requests" 648 | category = "dev" 649 | optional = false 650 | python-versions = "*" 651 | 652 | [package.dependencies] 653 | requests = ">=2.0.1,<3.0.0" 654 | 655 | [[package]] 656 | name = "rfc3986" 657 | version = "2.0.0" 658 | description = "Validating URI References per RFC 3986" 659 | category = "dev" 660 | optional = false 661 | python-versions = ">=3.7" 662 | 663 | [package.extras] 664 | idna2008 = ["idna"] 665 | 666 | [[package]] 667 | name = "rich" 668 | version = "12.4.4" 669 | description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" 670 | category = "dev" 671 | optional = false 672 | python-versions = ">=3.6.3,<4.0.0" 673 | 674 | [package.dependencies] 675 | commonmark = ">=0.9.0,<0.10.0" 676 | pygments = ">=2.6.0,<3.0.0" 677 | typing-extensions = {version = ">=4.0.0,<5.0", markers = "python_version < \"3.9\""} 678 | 679 | [package.extras] 680 | jupyter = ["ipywidgets (>=7.5.1,<8.0.0)"] 681 | 682 | [[package]] 683 | name = "secretstorage" 684 | version = "3.3.2" 685 | description = "Python bindings to FreeDesktop.org Secret Service API" 686 | category = "dev" 687 | optional = false 688 | python-versions = ">=3.6" 689 | 690 | [package.dependencies] 691 | cryptography = ">=2.0" 692 | jeepney = ">=0.6" 693 | 694 | [[package]] 695 | name = "semver" 696 | version = "2.13.0" 697 | description = "Python helper for Semantic Versioning (http://semver.org/)" 698 | category = "dev" 699 | optional = false 700 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 701 | 702 | [[package]] 703 | name = "setuptools-scm" 704 | version = "6.4.2" 705 | description = "the blessed package to manage your versions by scm tags" 706 | category = "dev" 707 | optional = false 708 | python-versions = ">=3.6" 709 | 710 | [package.dependencies] 711 | packaging = ">=20.0" 712 | tomli = ">=1.0.0" 713 | 714 | [package.extras] 715 | test = ["pytest (>=6.2)", "virtualenv (>20)"] 716 | toml = ["setuptools (>=42)"] 717 | 718 | [[package]] 719 | name = "six" 720 | version = "1.16.0" 721 | description = "Python 2 and 3 compatibility utilities" 722 | category = "dev" 723 | optional = false 724 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" 725 | 726 | [[package]] 727 | name = "smmap" 728 | version = "5.0.0" 729 | description = "A pure Python implementation of a sliding window memory map manager" 730 | category = "dev" 731 | optional = false 732 | python-versions = ">=3.6" 733 | 734 | [[package]] 735 | name = "toml" 736 | version = "0.10.2" 737 | description = "Python Library for Tom's Obvious, Minimal Language" 738 | category = "dev" 739 | optional = false 740 | python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" 741 | 742 | [[package]] 743 | name = "tomli" 744 | version = "2.0.1" 745 | description = "A lil' TOML parser" 746 | category = "dev" 747 | optional = false 748 | python-versions = ">=3.7" 749 | 750 | [[package]] 751 | name = "tomlkit" 752 | version = "0.10.2" 753 | description = "Style preserving TOML library" 754 | category = "dev" 755 | optional = false 756 | python-versions = ">=3.6,<4.0" 757 | 758 | [[package]] 759 | name = "tqdm" 760 | version = "4.64.0" 761 | description = "Fast, Extensible Progress Meter" 762 | category = "dev" 763 | optional = false 764 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" 765 | 766 | [package.dependencies] 767 | colorama = {version = "*", markers = "platform_system == \"Windows\""} 768 | 769 | [package.extras] 770 | dev = ["py-make (>=0.1.0)", "twine", "wheel"] 771 | notebook = ["ipywidgets (>=6)"] 772 | slack = ["slack-sdk"] 773 | telegram = ["requests"] 774 | 775 | [[package]] 776 | name = "tryceratops" 777 | version = "1.1.0" 778 | description = "A linter to manage your exception like a PRO!" 779 | category = "dev" 780 | optional = false 781 | python-versions = ">=3.8,<4.0" 782 | 783 | [package.dependencies] 784 | click = ">=7" 785 | rich = ">=10.14.0" 786 | toml = ">=0.10.2" 787 | 788 | [[package]] 789 | name = "twine" 790 | version = "3.8.0" 791 | description = "Collection of utilities for publishing packages on PyPI" 792 | category = "dev" 793 | optional = false 794 | python-versions = ">=3.6" 795 | 796 | [package.dependencies] 797 | colorama = ">=0.4.3" 798 | importlib-metadata = ">=3.6" 799 | keyring = ">=15.1" 800 | pkginfo = ">=1.8.1" 801 | readme-renderer = ">=21.0" 802 | requests = ">=2.20" 803 | requests-toolbelt = ">=0.8.0,<0.9.0 || >0.9.0" 804 | rfc3986 = ">=1.4.0" 805 | tqdm = ">=4.14" 806 | urllib3 = ">=1.26.0" 807 | 808 | [[package]] 809 | name = "typing-extensions" 810 | version = "4.2.0" 811 | description = "Backported and Experimental Type Hints for Python 3.7+" 812 | category = "dev" 813 | optional = false 814 | python-versions = ">=3.7" 815 | 816 | [[package]] 817 | name = "urllib3" 818 | version = "1.26.9" 819 | description = "HTTP library with thread-safe connection pooling, file post, and more." 820 | category = "dev" 821 | optional = false 822 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" 823 | 824 | [package.extras] 825 | brotli = ["brotlicffi (>=0.8.0)", "brotli (>=1.0.9)", "brotlipy (>=0.6.0)"] 826 | secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"] 827 | socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] 828 | 829 | [[package]] 830 | name = "virtualenv" 831 | version = "20.14.1" 832 | description = "Virtual Python Environment builder" 833 | category = "dev" 834 | optional = false 835 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" 836 | 837 | [package.dependencies] 838 | distlib = ">=0.3.1,<1" 839 | filelock = ">=3.2,<4" 840 | platformdirs = ">=2,<3" 841 | six = ">=1.9.0,<2" 842 | 843 | [package.extras] 844 | docs = ["proselint (>=0.10.2)", "sphinx (>=3)", "sphinx-argparse (>=0.2.5)", "sphinx-rtd-theme (>=0.4.3)", "towncrier (>=21.3)"] 845 | testing = ["coverage (>=4)", "coverage-enable-subprocess (>=1)", "flaky (>=3)", "pytest (>=4)", "pytest-env (>=0.6.2)", "pytest-freezegun (>=0.4.1)", "pytest-mock (>=2)", "pytest-randomly (>=1)", "pytest-timeout (>=1)", "packaging (>=20.0)"] 846 | 847 | [[package]] 848 | name = "webencodings" 849 | version = "0.5.1" 850 | description = "Character encoding aliases for legacy web content" 851 | category = "dev" 852 | optional = false 853 | python-versions = "*" 854 | 855 | [[package]] 856 | name = "zipp" 857 | version = "3.8.0" 858 | description = "Backport of pathlib-compatible object wrapper for zip files" 859 | category = "dev" 860 | optional = false 861 | python-versions = ">=3.7" 862 | 863 | [package.extras] 864 | docs = ["sphinx", "jaraco.packaging (>=9)", "rst.linker (>=1.9)"] 865 | testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy (>=0.9.1)"] 866 | 867 | [metadata] 868 | lock-version = "1.1" 869 | python-versions = "^3.8" 870 | content-hash = "38630fdf59a99f504c6a149fa119e0cb77ab345a47e0366d7f639a82c6ab3fa3" 871 | 872 | [metadata.files] 873 | atomicwrites = [ 874 | {file = "atomicwrites-1.4.0-py2.py3-none-any.whl", hash = "sha256:6d1784dea7c0c8d4a5172b6c620f40b6e4cbfdf96d783691f2e1302a7b88e197"}, 875 | {file = "atomicwrites-1.4.0.tar.gz", hash = "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a"}, 876 | ] 877 | attrs = [ 878 | {file = "attrs-21.4.0-py2.py3-none-any.whl", hash = "sha256:2d27e3784d7a565d36ab851fe94887c5eccd6a463168875832a1be79c82828b4"}, 879 | {file = "attrs-21.4.0.tar.gz", hash = "sha256:626ba8234211db98e869df76230a137c4c40a12d72445c45d5f5b716f076e2fd"}, 880 | ] 881 | black = [ 882 | {file = "black-22.3.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:2497f9c2386572e28921fa8bec7be3e51de6801f7459dffd6e62492531c47e09"}, 883 | {file = "black-22.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5795a0375eb87bfe902e80e0c8cfaedf8af4d49694d69161e5bd3206c18618bb"}, 884 | {file = "black-22.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e3556168e2e5c49629f7b0f377070240bd5511e45e25a4497bb0073d9dda776a"}, 885 | {file = "black-22.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:67c8301ec94e3bcc8906740fe071391bce40a862b7be0b86fb5382beefecd968"}, 886 | {file = "black-22.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:fd57160949179ec517d32ac2ac898b5f20d68ed1a9c977346efbac9c2f1e779d"}, 887 | {file = "black-22.3.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:cc1e1de68c8e5444e8f94c3670bb48a2beef0e91dddfd4fcc29595ebd90bb9ce"}, 888 | {file = "black-22.3.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d2fc92002d44746d3e7db7cf9313cf4452f43e9ea77a2c939defce3b10b5c82"}, 889 | {file = "black-22.3.0-cp36-cp36m-win_amd64.whl", hash = "sha256:a6342964b43a99dbc72f72812bf88cad8f0217ae9acb47c0d4f141a6416d2d7b"}, 890 | {file = "black-22.3.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:328efc0cc70ccb23429d6be184a15ce613f676bdfc85e5fe8ea2a9354b4e9015"}, 891 | {file = "black-22.3.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:06f9d8846f2340dfac80ceb20200ea5d1b3f181dd0556b47af4e8e0b24fa0a6b"}, 892 | {file = "black-22.3.0-cp37-cp37m-win_amd64.whl", hash = "sha256:ad4efa5fad66b903b4a5f96d91461d90b9507a812b3c5de657d544215bb7877a"}, 893 | {file = "black-22.3.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e8477ec6bbfe0312c128e74644ac8a02ca06bcdb8982d4ee06f209be28cdf163"}, 894 | {file = "black-22.3.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:637a4014c63fbf42a692d22b55d8ad6968a946b4a6ebc385c5505d9625b6a464"}, 895 | {file = "black-22.3.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:863714200ada56cbc366dc9ae5291ceb936573155f8bf8e9de92aef51f3ad0f0"}, 896 | {file = "black-22.3.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10dbe6e6d2988049b4655b2b739f98785a884d4d6b85bc35133a8fb9a2233176"}, 897 | {file = "black-22.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:cee3e11161dde1b2a33a904b850b0899e0424cc331b7295f2a9698e79f9a69a0"}, 898 | {file = "black-22.3.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:5891ef8abc06576985de8fa88e95ab70641de6c1fca97e2a15820a9b69e51b20"}, 899 | {file = "black-22.3.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:30d78ba6bf080eeaf0b7b875d924b15cd46fec5fd044ddfbad38c8ea9171043a"}, 900 | {file = "black-22.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ee8f1f7228cce7dffc2b464f07ce769f478968bfb3dd1254a4c2eeed84928aad"}, 901 | {file = "black-22.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6ee227b696ca60dd1c507be80a6bc849a5a6ab57ac7352aad1ffec9e8b805f21"}, 902 | {file = "black-22.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:9b542ced1ec0ceeff5b37d69838106a6348e60db7b8fdd245294dc1d26136265"}, 903 | {file = "black-22.3.0-py3-none-any.whl", hash = "sha256:bc58025940a896d7e5356952228b68f793cf5fcb342be703c3a2669a1488cb72"}, 904 | {file = "black-22.3.0.tar.gz", hash = "sha256:35020b8886c022ced9282b51b5a875b6d1ab0c387b31a065b84db7c33085ca79"}, 905 | ] 906 | bleach = [ 907 | {file = "bleach-5.0.0-py3-none-any.whl", hash = "sha256:08a1fe86d253b5c88c92cc3d810fd8048a16d15762e1e5b74d502256e5926aa1"}, 908 | {file = "bleach-5.0.0.tar.gz", hash = "sha256:c6d6cc054bdc9c83b48b8083e236e5f00f238428666d2ce2e083eaa5fd568565"}, 909 | ] 910 | certifi = [ 911 | {file = "certifi-2022.5.18.1-py3-none-any.whl", hash = "sha256:f1d53542ee8cbedbe2118b5686372fb33c297fcd6379b050cca0ef13a597382a"}, 912 | {file = "certifi-2022.5.18.1.tar.gz", hash = "sha256:9c5705e395cd70084351dd8ad5c41e65655e08ce46f2ec9cf6c2c08390f71eb7"}, 913 | ] 914 | cffi = [ 915 | {file = "cffi-1.15.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:c2502a1a03b6312837279c8c1bd3ebedf6c12c4228ddbad40912d671ccc8a962"}, 916 | {file = "cffi-1.15.0-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:23cfe892bd5dd8941608f93348c0737e369e51c100d03718f108bf1add7bd6d0"}, 917 | {file = "cffi-1.15.0-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:41d45de54cd277a7878919867c0f08b0cf817605e4eb94093e7516505d3c8d14"}, 918 | {file = "cffi-1.15.0-cp27-cp27m-win32.whl", hash = "sha256:4a306fa632e8f0928956a41fa8e1d6243c71e7eb59ffbd165fc0b41e316b2474"}, 919 | {file = "cffi-1.15.0-cp27-cp27m-win_amd64.whl", hash = "sha256:e7022a66d9b55e93e1a845d8c9eba2a1bebd4966cd8bfc25d9cd07d515b33fa6"}, 920 | {file = "cffi-1.15.0-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:14cd121ea63ecdae71efa69c15c5543a4b5fbcd0bbe2aad864baca0063cecf27"}, 921 | {file = "cffi-1.15.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:d4d692a89c5cf08a8557fdeb329b82e7bf609aadfaed6c0d79f5a449a3c7c023"}, 922 | {file = "cffi-1.15.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0104fb5ae2391d46a4cb082abdd5c69ea4eab79d8d44eaaf79f1b1fd806ee4c2"}, 923 | {file = "cffi-1.15.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:91ec59c33514b7c7559a6acda53bbfe1b283949c34fe7440bcf917f96ac0723e"}, 924 | {file = "cffi-1.15.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:f5c7150ad32ba43a07c4479f40241756145a1f03b43480e058cfd862bf5041c7"}, 925 | {file = "cffi-1.15.0-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:00c878c90cb53ccfaae6b8bc18ad05d2036553e6d9d1d9dbcf323bbe83854ca3"}, 926 | {file = "cffi-1.15.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:abb9a20a72ac4e0fdb50dae135ba5e77880518e742077ced47eb1499e29a443c"}, 927 | {file = "cffi-1.15.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a5263e363c27b653a90078143adb3d076c1a748ec9ecc78ea2fb916f9b861962"}, 928 | {file = "cffi-1.15.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f54a64f8b0c8ff0b64d18aa76675262e1700f3995182267998c31ae974fbc382"}, 929 | {file = "cffi-1.15.0-cp310-cp310-win32.whl", hash = "sha256:c21c9e3896c23007803a875460fb786118f0cdd4434359577ea25eb556e34c55"}, 930 | {file = "cffi-1.15.0-cp310-cp310-win_amd64.whl", hash = "sha256:5e069f72d497312b24fcc02073d70cb989045d1c91cbd53979366077959933e0"}, 931 | {file = "cffi-1.15.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:64d4ec9f448dfe041705426000cc13e34e6e5bb13736e9fd62e34a0b0c41566e"}, 932 | {file = "cffi-1.15.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2756c88cbb94231c7a147402476be2c4df2f6078099a6f4a480d239a8817ae39"}, 933 | {file = "cffi-1.15.0-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3b96a311ac60a3f6be21d2572e46ce67f09abcf4d09344c49274eb9e0bf345fc"}, 934 | {file = "cffi-1.15.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:75e4024375654472cc27e91cbe9eaa08567f7fbdf822638be2814ce059f58032"}, 935 | {file = "cffi-1.15.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:59888172256cac5629e60e72e86598027aca6bf01fa2465bdb676d37636573e8"}, 936 | {file = "cffi-1.15.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:27c219baf94952ae9d50ec19651a687b826792055353d07648a5695413e0c605"}, 937 | {file = "cffi-1.15.0-cp36-cp36m-win32.whl", hash = "sha256:4958391dbd6249d7ad855b9ca88fae690783a6be9e86df65865058ed81fc860e"}, 938 | {file = "cffi-1.15.0-cp36-cp36m-win_amd64.whl", hash = "sha256:f6f824dc3bce0edab5f427efcfb1d63ee75b6fcb7282900ccaf925be84efb0fc"}, 939 | {file = "cffi-1.15.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:06c48159c1abed75c2e721b1715c379fa3200c7784271b3c46df01383b593636"}, 940 | {file = "cffi-1.15.0-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:c2051981a968d7de9dd2d7b87bcb9c939c74a34626a6e2f8181455dd49ed69e4"}, 941 | {file = "cffi-1.15.0-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:fd8a250edc26254fe5b33be00402e6d287f562b6a5b2152dec302fa15bb3e997"}, 942 | {file = "cffi-1.15.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:91d77d2a782be4274da750752bb1650a97bfd8f291022b379bb8e01c66b4e96b"}, 943 | {file = "cffi-1.15.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:45db3a33139e9c8f7c09234b5784a5e33d31fd6907800b316decad50af323ff2"}, 944 | {file = "cffi-1.15.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:263cc3d821c4ab2213cbe8cd8b355a7f72a8324577dc865ef98487c1aeee2bc7"}, 945 | {file = "cffi-1.15.0-cp37-cp37m-win32.whl", hash = "sha256:17771976e82e9f94976180f76468546834d22a7cc404b17c22df2a2c81db0c66"}, 946 | {file = "cffi-1.15.0-cp37-cp37m-win_amd64.whl", hash = "sha256:3415c89f9204ee60cd09b235810be700e993e343a408693e80ce7f6a40108029"}, 947 | {file = "cffi-1.15.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:4238e6dab5d6a8ba812de994bbb0a79bddbdf80994e4ce802b6f6f3142fcc880"}, 948 | {file = "cffi-1.15.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:0808014eb713677ec1292301ea4c81ad277b6cdf2fdd90fd540af98c0b101d20"}, 949 | {file = "cffi-1.15.0-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:57e9ac9ccc3101fac9d6014fba037473e4358ef4e89f8e181f8951a2c0162024"}, 950 | {file = "cffi-1.15.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b6c2ea03845c9f501ed1313e78de148cd3f6cad741a75d43a29b43da27f2e1e"}, 951 | {file = "cffi-1.15.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:10dffb601ccfb65262a27233ac273d552ddc4d8ae1bf93b21c94b8511bffe728"}, 952 | {file = "cffi-1.15.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:786902fb9ba7433aae840e0ed609f45c7bcd4e225ebb9c753aa39725bb3e6ad6"}, 953 | {file = "cffi-1.15.0-cp38-cp38-win32.whl", hash = "sha256:da5db4e883f1ce37f55c667e5c0de439df76ac4cb55964655906306918e7363c"}, 954 | {file = "cffi-1.15.0-cp38-cp38-win_amd64.whl", hash = "sha256:181dee03b1170ff1969489acf1c26533710231c58f95534e3edac87fff06c443"}, 955 | {file = "cffi-1.15.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:45e8636704eacc432a206ac7345a5d3d2c62d95a507ec70d62f23cd91770482a"}, 956 | {file = "cffi-1.15.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:31fb708d9d7c3f49a60f04cf5b119aeefe5644daba1cd2a0fe389b674fd1de37"}, 957 | {file = "cffi-1.15.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:6dc2737a3674b3e344847c8686cf29e500584ccad76204efea14f451d4cc669a"}, 958 | {file = "cffi-1.15.0-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:74fdfdbfdc48d3f47148976f49fab3251e550a8720bebc99bf1483f5bfb5db3e"}, 959 | {file = "cffi-1.15.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffaa5c925128e29efbde7301d8ecaf35c8c60ffbcd6a1ffd3a552177c8e5e796"}, 960 | {file = "cffi-1.15.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3f7d084648d77af029acb79a0ff49a0ad7e9d09057a9bf46596dac9514dc07df"}, 961 | {file = "cffi-1.15.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ef1f279350da2c586a69d32fc8733092fd32cc8ac95139a00377841f59a3f8d8"}, 962 | {file = "cffi-1.15.0-cp39-cp39-win32.whl", hash = "sha256:2a23af14f408d53d5e6cd4e3d9a24ff9e05906ad574822a10563efcef137979a"}, 963 | {file = "cffi-1.15.0-cp39-cp39-win_amd64.whl", hash = "sha256:3773c4d81e6e818df2efbc7dd77325ca0dcb688116050fb2b3011218eda36139"}, 964 | {file = "cffi-1.15.0.tar.gz", hash = "sha256:920f0d66a896c2d99f0adbb391f990a84091179542c205fa53ce5787aff87954"}, 965 | ] 966 | cfgv = [ 967 | {file = "cfgv-3.3.1-py2.py3-none-any.whl", hash = "sha256:c6a0883f3917a037485059700b9e75da2464e6c27051014ad85ba6aaa5884426"}, 968 | {file = "cfgv-3.3.1.tar.gz", hash = "sha256:f5a830efb9ce7a445376bb66ec94c638a9787422f96264c98edc6bdeed8ab736"}, 969 | ] 970 | charset-normalizer = [ 971 | {file = "charset-normalizer-2.0.12.tar.gz", hash = "sha256:2857e29ff0d34db842cd7ca3230549d1a697f96ee6d3fb071cfa6c7393832597"}, 972 | {file = "charset_normalizer-2.0.12-py3-none-any.whl", hash = "sha256:6881edbebdb17b39b4eaaa821b438bf6eddffb4468cf344f09f89def34a8b1df"}, 973 | ] 974 | click = [ 975 | {file = "click-8.1.3-py3-none-any.whl", hash = "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"}, 976 | {file = "click-8.1.3.tar.gz", hash = "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e"}, 977 | ] 978 | click-log = [ 979 | {file = "click-log-0.4.0.tar.gz", hash = "sha256:3970f8570ac54491237bcdb3d8ab5e3eef6c057df29f8c3d1151a51a9c23b975"}, 980 | {file = "click_log-0.4.0-py2.py3-none-any.whl", hash = "sha256:a43e394b528d52112af599f2fc9e4b7cf3c15f94e53581f74fa6867e68c91756"}, 981 | ] 982 | colorama = [ 983 | {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"}, 984 | {file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"}, 985 | ] 986 | commonmark = [ 987 | {file = "commonmark-0.9.1-py2.py3-none-any.whl", hash = "sha256:da2f38c92590f83de410ba1a3cbceafbc74fee9def35f9251ba9a971d6d66fd9"}, 988 | {file = "commonmark-0.9.1.tar.gz", hash = "sha256:452f9dc859be7f06631ddcb328b6919c67984aca654e5fefb3914d54691aed60"}, 989 | ] 990 | coverage = [ 991 | {file = "coverage-6.4.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f1d5aa2703e1dab4ae6cf416eb0095304f49d004c39e9db1d86f57924f43006b"}, 992 | {file = "coverage-6.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4ce1b258493cbf8aec43e9b50d89982346b98e9ffdfaae8ae5793bc112fb0068"}, 993 | {file = "coverage-6.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:83c4e737f60c6936460c5be330d296dd5b48b3963f48634c53b3f7deb0f34ec4"}, 994 | {file = "coverage-6.4.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:84e65ef149028516c6d64461b95a8dbcfce95cfd5b9eb634320596173332ea84"}, 995 | {file = "coverage-6.4.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f69718750eaae75efe506406c490d6fc5a6161d047206cc63ce25527e8a3adad"}, 996 | {file = "coverage-6.4.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e57816f8ffe46b1df8f12e1b348f06d164fd5219beba7d9433ba79608ef011cc"}, 997 | {file = "coverage-6.4.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:01c5615d13f3dd3aa8543afc069e5319cfa0c7d712f6e04b920431e5c564a749"}, 998 | {file = "coverage-6.4.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:75ab269400706fab15981fd4bd5080c56bd5cc07c3bccb86aab5e1d5a88dc8f4"}, 999 | {file = "coverage-6.4.1-cp310-cp310-win32.whl", hash = "sha256:a7f3049243783df2e6cc6deafc49ea123522b59f464831476d3d1448e30d72df"}, 1000 | {file = "coverage-6.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:ee2ddcac99b2d2aec413e36d7a429ae9ebcadf912946b13ffa88e7d4c9b712d6"}, 1001 | {file = "coverage-6.4.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:fb73e0011b8793c053bfa85e53129ba5f0250fdc0392c1591fd35d915ec75c46"}, 1002 | {file = "coverage-6.4.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:106c16dfe494de3193ec55cac9640dd039b66e196e4641fa8ac396181578b982"}, 1003 | {file = "coverage-6.4.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:87f4f3df85aa39da00fd3ec4b5abeb7407e82b68c7c5ad181308b0e2526da5d4"}, 1004 | {file = "coverage-6.4.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:961e2fb0680b4f5ad63234e0bf55dfb90d302740ae9c7ed0120677a94a1590cb"}, 1005 | {file = "coverage-6.4.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:cec3a0f75c8f1031825e19cd86ee787e87cf03e4fd2865c79c057092e69e3a3b"}, 1006 | {file = "coverage-6.4.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:129cd05ba6f0d08a766d942a9ed4b29283aff7b2cccf5b7ce279d50796860bb3"}, 1007 | {file = "coverage-6.4.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:bf5601c33213d3cb19d17a796f8a14a9eaa5e87629a53979a5981e3e3ae166f6"}, 1008 | {file = "coverage-6.4.1-cp37-cp37m-win32.whl", hash = "sha256:269eaa2c20a13a5bf17558d4dc91a8d078c4fa1872f25303dddcbba3a813085e"}, 1009 | {file = "coverage-6.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:f02cbbf8119db68455b9d763f2f8737bb7db7e43720afa07d8eb1604e5c5ae28"}, 1010 | {file = "coverage-6.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ffa9297c3a453fba4717d06df579af42ab9a28022444cae7fa605af4df612d54"}, 1011 | {file = "coverage-6.4.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:145f296d00441ca703a659e8f3eb48ae39fb083baba2d7ce4482fb2723e050d9"}, 1012 | {file = "coverage-6.4.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d67d44996140af8b84284e5e7d398e589574b376fb4de8ccd28d82ad8e3bea13"}, 1013 | {file = "coverage-6.4.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2bd9a6fc18aab8d2e18f89b7ff91c0f34ff4d5e0ba0b33e989b3cd4194c81fd9"}, 1014 | {file = "coverage-6.4.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3384f2a3652cef289e38100f2d037956194a837221edd520a7ee5b42d00cc605"}, 1015 | {file = "coverage-6.4.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:9b3e07152b4563722be523e8cd0b209e0d1a373022cfbde395ebb6575bf6790d"}, 1016 | {file = "coverage-6.4.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:1480ff858b4113db2718848d7b2d1b75bc79895a9c22e76a221b9d8d62496428"}, 1017 | {file = "coverage-6.4.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:865d69ae811a392f4d06bde506d531f6a28a00af36f5c8649684a9e5e4a85c83"}, 1018 | {file = "coverage-6.4.1-cp38-cp38-win32.whl", hash = "sha256:664a47ce62fe4bef9e2d2c430306e1428ecea207ffd68649e3b942fa8ea83b0b"}, 1019 | {file = "coverage-6.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:26dff09fb0d82693ba9e6231248641d60ba606150d02ed45110f9ec26404ed1c"}, 1020 | {file = "coverage-6.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d9c80df769f5ec05ad21ea34be7458d1dc51ff1fb4b2219e77fe24edf462d6df"}, 1021 | {file = "coverage-6.4.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:39ee53946bf009788108b4dd2894bf1349b4e0ca18c2016ffa7d26ce46b8f10d"}, 1022 | {file = "coverage-6.4.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f5b66caa62922531059bc5ac04f836860412f7f88d38a476eda0a6f11d4724f4"}, 1023 | {file = "coverage-6.4.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fd180ed867e289964404051a958f7cccabdeed423f91a899829264bb7974d3d3"}, 1024 | {file = "coverage-6.4.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:84631e81dd053e8a0d4967cedab6db94345f1c36107c71698f746cb2636c63e3"}, 1025 | {file = "coverage-6.4.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:8c08da0bd238f2970230c2a0d28ff0e99961598cb2e810245d7fc5afcf1254e8"}, 1026 | {file = "coverage-6.4.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:d42c549a8f41dc103a8004b9f0c433e2086add8a719da00e246e17cbe4056f72"}, 1027 | {file = "coverage-6.4.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:309ce4a522ed5fca432af4ebe0f32b21d6d7ccbb0f5fcc99290e71feba67c264"}, 1028 | {file = "coverage-6.4.1-cp39-cp39-win32.whl", hash = "sha256:fdb6f7bd51c2d1714cea40718f6149ad9be6a2ee7d93b19e9f00934c0f2a74d9"}, 1029 | {file = "coverage-6.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:342d4aefd1c3e7f620a13f4fe563154d808b69cccef415415aece4c786665397"}, 1030 | {file = "coverage-6.4.1-pp36.pp37.pp38-none-any.whl", hash = "sha256:4803e7ccf93230accb928f3a68f00ffa80a88213af98ed338a57ad021ef06815"}, 1031 | {file = "coverage-6.4.1.tar.gz", hash = "sha256:4321f075095a096e70aff1d002030ee612b65a205a0a0f5b815280d5dc58100c"}, 1032 | ] 1033 | cryptography = [ 1034 | {file = "cryptography-37.0.2-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:ef15c2df7656763b4ff20a9bc4381d8352e6640cfeb95c2972c38ef508e75181"}, 1035 | {file = "cryptography-37.0.2-cp36-abi3-macosx_10_10_x86_64.whl", hash = "sha256:3c81599befb4d4f3d7648ed3217e00d21a9341a9a688ecdd615ff72ffbed7336"}, 1036 | {file = "cryptography-37.0.2-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:2bd1096476aaac820426239ab534b636c77d71af66c547b9ddcd76eb9c79e004"}, 1037 | {file = "cryptography-37.0.2-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:31fe38d14d2e5f787e0aecef831457da6cec68e0bb09a35835b0b44ae8b988fe"}, 1038 | {file = "cryptography-37.0.2-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:093cb351031656d3ee2f4fa1be579a8c69c754cf874206be1d4cf3b542042804"}, 1039 | {file = "cryptography-37.0.2-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:59b281eab51e1b6b6afa525af2bd93c16d49358404f814fe2c2410058623928c"}, 1040 | {file = "cryptography-37.0.2-cp36-abi3-manylinux_2_24_x86_64.whl", hash = "sha256:0cc20f655157d4cfc7bada909dc5cc228211b075ba8407c46467f63597c78178"}, 1041 | {file = "cryptography-37.0.2-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:f8ec91983e638a9bcd75b39f1396e5c0dc2330cbd9ce4accefe68717e6779e0a"}, 1042 | {file = "cryptography-37.0.2-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:46f4c544f6557a2fefa7ac8ac7d1b17bf9b647bd20b16decc8fbcab7117fbc15"}, 1043 | {file = "cryptography-37.0.2-cp36-abi3-win32.whl", hash = "sha256:731c8abd27693323b348518ed0e0705713a36d79fdbd969ad968fbef0979a7e0"}, 1044 | {file = "cryptography-37.0.2-cp36-abi3-win_amd64.whl", hash = "sha256:471e0d70201c069f74c837983189949aa0d24bb2d751b57e26e3761f2f782b8d"}, 1045 | {file = "cryptography-37.0.2-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a68254dd88021f24a68b613d8c51d5c5e74d735878b9e32cc0adf19d1f10aaf9"}, 1046 | {file = "cryptography-37.0.2-pp37-pypy37_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:a7d5137e556cc0ea418dca6186deabe9129cee318618eb1ffecbd35bee55ddc1"}, 1047 | {file = "cryptography-37.0.2-pp38-pypy38_pp73-macosx_10_10_x86_64.whl", hash = "sha256:aeaba7b5e756ea52c8861c133c596afe93dd716cbcacae23b80bc238202dc023"}, 1048 | {file = "cryptography-37.0.2-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95e590dd70642eb2079d280420a888190aa040ad20f19ec8c6e097e38aa29e06"}, 1049 | {file = "cryptography-37.0.2-pp38-pypy38_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:1b9362d34363f2c71b7853f6251219298124aa4cc2075ae2932e64c91a3e2717"}, 1050 | {file = "cryptography-37.0.2-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:e53258e69874a306fcecb88b7534d61820db8a98655662a3dd2ec7f1afd9132f"}, 1051 | {file = "cryptography-37.0.2-pp39-pypy39_pp73-macosx_10_10_x86_64.whl", hash = "sha256:1f3bfbd611db5cb58ca82f3deb35e83af34bb8cf06043fa61500157d50a70982"}, 1052 | {file = "cryptography-37.0.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:419c57d7b63f5ec38b1199a9521d77d7d1754eb97827bbb773162073ccd8c8d4"}, 1053 | {file = "cryptography-37.0.2-pp39-pypy39_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:dc26bb134452081859aa21d4990474ddb7e863aa39e60d1592800a8865a702de"}, 1054 | {file = "cryptography-37.0.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:3b8398b3d0efc420e777c40c16764d6870bcef2eb383df9c6dbb9ffe12c64452"}, 1055 | {file = "cryptography-37.0.2.tar.gz", hash = "sha256:f224ad253cc9cea7568f49077007d2263efa57396a2f2f78114066fd54b5c68e"}, 1056 | ] 1057 | distlib = [ 1058 | {file = "distlib-0.3.4-py2.py3-none-any.whl", hash = "sha256:6564fe0a8f51e734df6333d08b8b94d4ea8ee6b99b5ed50613f731fd4089f34b"}, 1059 | {file = "distlib-0.3.4.zip", hash = "sha256:e4b58818180336dc9c529bfb9a0b58728ffc09ad92027a3f30b7cd91e3458579"}, 1060 | ] 1061 | docutils = [ 1062 | {file = "docutils-0.18.1-py2.py3-none-any.whl", hash = "sha256:23010f129180089fbcd3bc08cfefccb3b890b0050e1ca00c867036e9d161b98c"}, 1063 | {file = "docutils-0.18.1.tar.gz", hash = "sha256:679987caf361a7539d76e584cbeddc311e3aee937877c87346f31debc63e9d06"}, 1064 | ] 1065 | dotty-dict = [ 1066 | {file = "dotty_dict-1.3.0.tar.gz", hash = "sha256:eb0035a3629ecd84397a68f1f42f1e94abd1c34577a19cd3eacad331ee7cbaf0"}, 1067 | ] 1068 | filelock = [ 1069 | {file = "filelock-3.7.1-py3-none-any.whl", hash = "sha256:37def7b658813cda163b56fc564cdc75e86d338246458c4c28ae84cabefa2404"}, 1070 | {file = "filelock-3.7.1.tar.gz", hash = "sha256:3a0fd85166ad9dbab54c9aec96737b744106dc5f15c0b09a6744a445299fcf04"}, 1071 | ] 1072 | flake8 = [ 1073 | {file = "flake8-4.0.1-py2.py3-none-any.whl", hash = "sha256:479b1304f72536a55948cb40a32dce8bb0ffe3501e26eaf292c7e60eb5e0428d"}, 1074 | {file = "flake8-4.0.1.tar.gz", hash = "sha256:806e034dda44114815e23c16ef92f95c91e4c71100ff52813adf7132a6ad870d"}, 1075 | ] 1076 | flake8-annotations = [ 1077 | {file = "flake8-annotations-2.9.0.tar.gz", hash = "sha256:63fb3f538970b6a8dfd84125cf5af16f7b22e52d5032acb3b7eb23645ecbda9b"}, 1078 | {file = "flake8_annotations-2.9.0-py3-none-any.whl", hash = "sha256:84f46de2964cb18fccea968d9eafce7cf857e34d913d515120795b9af6498d56"}, 1079 | ] 1080 | gitdb = [ 1081 | {file = "gitdb-4.0.9-py3-none-any.whl", hash = "sha256:8033ad4e853066ba6ca92050b9df2f89301b8fc8bf7e9324d412a63f8bf1a8fd"}, 1082 | {file = "gitdb-4.0.9.tar.gz", hash = "sha256:bac2fd45c0a1c9cf619e63a90d62bdc63892ef92387424b855792a6cabe789aa"}, 1083 | ] 1084 | gitpython = [ 1085 | {file = "GitPython-3.1.27-py3-none-any.whl", hash = "sha256:5b68b000463593e05ff2b261acff0ff0972df8ab1b70d3cdbd41b546c8b8fc3d"}, 1086 | {file = "GitPython-3.1.27.tar.gz", hash = "sha256:1c885ce809e8ba2d88a29befeb385fcea06338d3640712b59ca623c220bb5704"}, 1087 | ] 1088 | identify = [ 1089 | {file = "identify-2.5.1-py2.py3-none-any.whl", hash = "sha256:0dca2ea3e4381c435ef9c33ba100a78a9b40c0bab11189c7cf121f75815efeaa"}, 1090 | {file = "identify-2.5.1.tar.gz", hash = "sha256:3d11b16f3fe19f52039fb7e39c9c884b21cb1b586988114fbe42671f03de3e82"}, 1091 | ] 1092 | idna = [ 1093 | {file = "idna-3.3-py3-none-any.whl", hash = "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff"}, 1094 | {file = "idna-3.3.tar.gz", hash = "sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d"}, 1095 | ] 1096 | importlib-metadata = [ 1097 | {file = "importlib_metadata-4.2.0-py3-none-any.whl", hash = "sha256:057e92c15bc8d9e8109738a48db0ccb31b4d9d5cfbee5a8670879a30be66304b"}, 1098 | {file = "importlib_metadata-4.2.0.tar.gz", hash = "sha256:b7e52a1f8dec14a75ea73e0891f3060099ca1d8e6a462a4dff11c3e119ea1b31"}, 1099 | ] 1100 | iniconfig = [ 1101 | {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, 1102 | {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"}, 1103 | ] 1104 | invoke = [ 1105 | {file = "invoke-1.7.1-py3-none-any.whl", hash = "sha256:2dc975b4f92be0c0a174ad2d063010c8a1fdb5e9389d69871001118b4fcac4fb"}, 1106 | {file = "invoke-1.7.1.tar.gz", hash = "sha256:7b6deaf585eee0a848205d0b8c0014b9bf6f287a8eb798818a642dff1df14b19"}, 1107 | ] 1108 | isort = [ 1109 | {file = "isort-5.10.1-py3-none-any.whl", hash = "sha256:6f62d78e2f89b4500b080fe3a81690850cd254227f27f75c3a0c491a1f351ba7"}, 1110 | {file = "isort-5.10.1.tar.gz", hash = "sha256:e8443a5e7a020e9d7f97f1d7d9cd17c88bcb3bc7e218bf9cf5095fe550be2951"}, 1111 | ] 1112 | jeepney = [ 1113 | {file = "jeepney-0.8.0-py3-none-any.whl", hash = "sha256:c0a454ad016ca575060802ee4d590dd912e35c122fa04e70306de3d076cce755"}, 1114 | {file = "jeepney-0.8.0.tar.gz", hash = "sha256:5efe48d255973902f6badc3ce55e2aa6c5c3b3bc642059ef3a91247bcfcc5806"}, 1115 | ] 1116 | keyring = [ 1117 | {file = "keyring-23.6.0-py3-none-any.whl", hash = "sha256:372ff2fc43ab779e3f87911c26e6c7acc8bb440cbd82683e383ca37594cb0617"}, 1118 | {file = "keyring-23.6.0.tar.gz", hash = "sha256:3ac00c26e4c93739e19103091a9986a9f79665a78cf15a4df1dba7ea9ac8da2f"}, 1119 | ] 1120 | mccabe = [ 1121 | {file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"}, 1122 | {file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"}, 1123 | ] 1124 | mypy = [ 1125 | {file = "mypy-0.950-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cf9c261958a769a3bd38c3e133801ebcd284ffb734ea12d01457cb09eacf7d7b"}, 1126 | {file = "mypy-0.950-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b5b5bd0ffb11b4aba2bb6d31b8643902c48f990cc92fda4e21afac658044f0c0"}, 1127 | {file = "mypy-0.950-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5e7647df0f8fc947388e6251d728189cfadb3b1e558407f93254e35abc026e22"}, 1128 | {file = "mypy-0.950-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:eaff8156016487c1af5ffa5304c3e3fd183edcb412f3e9c72db349faf3f6e0eb"}, 1129 | {file = "mypy-0.950-cp310-cp310-win_amd64.whl", hash = "sha256:563514c7dc504698fb66bb1cf897657a173a496406f1866afae73ab5b3cdb334"}, 1130 | {file = "mypy-0.950-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:dd4d670eee9610bf61c25c940e9ade2d0ed05eb44227275cce88701fee014b1f"}, 1131 | {file = "mypy-0.950-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:ca75ecf2783395ca3016a5e455cb322ba26b6d33b4b413fcdedfc632e67941dc"}, 1132 | {file = "mypy-0.950-cp36-cp36m-win_amd64.whl", hash = "sha256:6003de687c13196e8a1243a5e4bcce617d79b88f83ee6625437e335d89dfebe2"}, 1133 | {file = "mypy-0.950-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:4c653e4846f287051599ed8f4b3c044b80e540e88feec76b11044ddc5612ffed"}, 1134 | {file = "mypy-0.950-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:e19736af56947addedce4674c0971e5dceef1b5ec7d667fe86bcd2b07f8f9075"}, 1135 | {file = "mypy-0.950-cp37-cp37m-win_amd64.whl", hash = "sha256:ef7beb2a3582eb7a9f37beaf38a28acfd801988cde688760aea9e6cc4832b10b"}, 1136 | {file = "mypy-0.950-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:0112752a6ff07230f9ec2f71b0d3d4e088a910fdce454fdb6553e83ed0eced7d"}, 1137 | {file = "mypy-0.950-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ee0a36edd332ed2c5208565ae6e3a7afc0eabb53f5327e281f2ef03a6bc7687a"}, 1138 | {file = "mypy-0.950-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:77423570c04aca807508a492037abbd72b12a1fb25a385847d191cd50b2c9605"}, 1139 | {file = "mypy-0.950-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:5ce6a09042b6da16d773d2110e44f169683d8cc8687e79ec6d1181a72cb028d2"}, 1140 | {file = "mypy-0.950-cp38-cp38-win_amd64.whl", hash = "sha256:5b231afd6a6e951381b9ef09a1223b1feabe13625388db48a8690f8daa9b71ff"}, 1141 | {file = "mypy-0.950-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:0384d9f3af49837baa92f559d3fa673e6d2652a16550a9ee07fc08c736f5e6f8"}, 1142 | {file = "mypy-0.950-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1fdeb0a0f64f2a874a4c1f5271f06e40e1e9779bf55f9567f149466fc7a55038"}, 1143 | {file = "mypy-0.950-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:61504b9a5ae166ba5ecfed9e93357fd51aa693d3d434b582a925338a2ff57fd2"}, 1144 | {file = "mypy-0.950-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:a952b8bc0ae278fc6316e6384f67bb9a396eb30aced6ad034d3a76120ebcc519"}, 1145 | {file = "mypy-0.950-cp39-cp39-win_amd64.whl", hash = "sha256:eaea21d150fb26d7b4856766e7addcf929119dd19fc832b22e71d942835201ef"}, 1146 | {file = "mypy-0.950-py3-none-any.whl", hash = "sha256:a4d9898f46446bfb6405383b57b96737dcfd0a7f25b748e78ef3e8c576bba3cb"}, 1147 | {file = "mypy-0.950.tar.gz", hash = "sha256:1b333cfbca1762ff15808a0ef4f71b5d3eed8528b23ea1c3fb50543c867d68de"}, 1148 | ] 1149 | mypy-extensions = [ 1150 | {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"}, 1151 | {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"}, 1152 | ] 1153 | nodeenv = [ 1154 | {file = "nodeenv-1.6.0-py2.py3-none-any.whl", hash = "sha256:621e6b7076565ddcacd2db0294c0381e01fd28945ab36bcf00f41c5daf63bef7"}, 1155 | {file = "nodeenv-1.6.0.tar.gz", hash = "sha256:3ef13ff90291ba2a4a7a4ff9a979b63ffdd00a464dbe04acf0ea6471517a4c2b"}, 1156 | ] 1157 | packaging = [ 1158 | {file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"}, 1159 | {file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"}, 1160 | ] 1161 | pathspec = [ 1162 | {file = "pathspec-0.9.0-py2.py3-none-any.whl", hash = "sha256:7d15c4ddb0b5c802d161efc417ec1a2558ea2653c2e8ad9c19098201dc1c993a"}, 1163 | {file = "pathspec-0.9.0.tar.gz", hash = "sha256:e564499435a2673d586f6b2130bb5b95f04a3ba06f81b8f895b651a3c76aabb1"}, 1164 | ] 1165 | pkginfo = [ 1166 | {file = "pkginfo-1.8.3-py2.py3-none-any.whl", hash = "sha256:848865108ec99d4901b2f7e84058b6e7660aae8ae10164e015a6dcf5b242a594"}, 1167 | {file = "pkginfo-1.8.3.tar.gz", hash = "sha256:a84da4318dd86f870a9447a8c98340aa06216bfc6f2b7bdc4b8766984ae1867c"}, 1168 | ] 1169 | platformdirs = [ 1170 | {file = "platformdirs-2.5.2-py3-none-any.whl", hash = "sha256:027d8e83a2d7de06bbac4e5ef7e023c02b863d7ea5d079477e722bb41ab25788"}, 1171 | {file = "platformdirs-2.5.2.tar.gz", hash = "sha256:58c8abb07dcb441e6ee4b11d8df0ac856038f944ab98b7be6b27b2a3c7feef19"}, 1172 | ] 1173 | pluggy = [ 1174 | {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"}, 1175 | {file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"}, 1176 | ] 1177 | pre-commit = [ 1178 | {file = "pre_commit-2.19.0-py2.py3-none-any.whl", hash = "sha256:10c62741aa5704faea2ad69cb550ca78082efe5697d6f04e5710c3c229afdd10"}, 1179 | {file = "pre_commit-2.19.0.tar.gz", hash = "sha256:4233a1e38621c87d9dda9808c6606d7e7ba0e087cd56d3fe03202a01d2919615"}, 1180 | ] 1181 | py = [ 1182 | {file = "py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"}, 1183 | {file = "py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719"}, 1184 | ] 1185 | pycodestyle = [ 1186 | {file = "pycodestyle-2.8.0-py2.py3-none-any.whl", hash = "sha256:720f8b39dde8b293825e7ff02c475f3077124006db4f440dcbc9a20b76548a20"}, 1187 | {file = "pycodestyle-2.8.0.tar.gz", hash = "sha256:eddd5847ef438ea1c7870ca7eb78a9d47ce0cdb4851a5523949f2601d0cbbe7f"}, 1188 | ] 1189 | pycparser = [ 1190 | {file = "pycparser-2.21-py2.py3-none-any.whl", hash = "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9"}, 1191 | {file = "pycparser-2.21.tar.gz", hash = "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"}, 1192 | ] 1193 | pyflakes = [ 1194 | {file = "pyflakes-2.4.0-py2.py3-none-any.whl", hash = "sha256:3bb3a3f256f4b7968c9c788781e4ff07dce46bdf12339dcda61053375426ee2e"}, 1195 | {file = "pyflakes-2.4.0.tar.gz", hash = "sha256:05a85c2872edf37a4ed30b0cce2f6093e1d0581f8c19d7393122da7e25b2b24c"}, 1196 | ] 1197 | pygments = [ 1198 | {file = "Pygments-2.12.0-py3-none-any.whl", hash = "sha256:dc9c10fb40944260f6ed4c688ece0cd2048414940f1cea51b8b226318411c519"}, 1199 | {file = "Pygments-2.12.0.tar.gz", hash = "sha256:5eb116118f9612ff1ee89ac96437bb6b49e8f04d8a13b514ba26f620208e26eb"}, 1200 | ] 1201 | pyparsing = [ 1202 | {file = "pyparsing-3.0.9-py3-none-any.whl", hash = "sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc"}, 1203 | {file = "pyparsing-3.0.9.tar.gz", hash = "sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb"}, 1204 | ] 1205 | pytest = [ 1206 | {file = "pytest-7.1.2-py3-none-any.whl", hash = "sha256:13d0e3ccfc2b6e26be000cb6568c832ba67ba32e719443bfe725814d3c42433c"}, 1207 | {file = "pytest-7.1.2.tar.gz", hash = "sha256:a06a0425453864a270bc45e71f783330a7428defb4230fb5e6a731fde06ecd45"}, 1208 | ] 1209 | pytest-cov = [ 1210 | {file = "pytest-cov-3.0.0.tar.gz", hash = "sha256:e7f0f5b1617d2210a2cabc266dfe2f4c75a8d32fb89eafb7ad9d06f6d076d470"}, 1211 | {file = "pytest_cov-3.0.0-py3-none-any.whl", hash = "sha256:578d5d15ac4a25e5f961c938b85a05b09fdaae9deef3bb6de9a6e766622ca7a6"}, 1212 | ] 1213 | python-gitlab = [ 1214 | {file = "python-gitlab-3.5.0.tar.gz", hash = "sha256:29ae7fb9b8c9aeb2e6e19bd2fd04867e93ecd7af719978ce68fac0cf116ab30d"}, 1215 | {file = "python_gitlab-3.5.0-py3-none-any.whl", hash = "sha256:73b5aa6502efa557ee1a51227cceb0243fac5529627da34f08c5f265bf50417c"}, 1216 | ] 1217 | python-semantic-release = [ 1218 | {file = "python-semantic-release-7.29.1.tar.gz", hash = "sha256:9bf10e1f9593259d9c139fb3dd2f54b7c61e8abdce62e4aada5b50253dd60dec"}, 1219 | {file = "python_semantic_release-7.29.1-py3-none-any.whl", hash = "sha256:459ed4c09b4aa9ea94b5ceb240211cb7967069d635b1dd9b97b44a9893ec73fb"}, 1220 | ] 1221 | pywin32-ctypes = [ 1222 | {file = "pywin32-ctypes-0.2.0.tar.gz", hash = "sha256:24ffc3b341d457d48e8922352130cf2644024a4ff09762a2261fd34c36ee5942"}, 1223 | {file = "pywin32_ctypes-0.2.0-py2.py3-none-any.whl", hash = "sha256:9dc2d991b3479cc2df15930958b674a48a227d5361d413827a4cfd0b5876fc98"}, 1224 | ] 1225 | pyyaml = [ 1226 | {file = "PyYAML-6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53"}, 1227 | {file = "PyYAML-6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c"}, 1228 | {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc"}, 1229 | {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b"}, 1230 | {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"}, 1231 | {file = "PyYAML-6.0-cp310-cp310-win32.whl", hash = "sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513"}, 1232 | {file = "PyYAML-6.0-cp310-cp310-win_amd64.whl", hash = "sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a"}, 1233 | {file = "PyYAML-6.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86"}, 1234 | {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f"}, 1235 | {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92"}, 1236 | {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"}, 1237 | {file = "PyYAML-6.0-cp36-cp36m-win32.whl", hash = "sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293"}, 1238 | {file = "PyYAML-6.0-cp36-cp36m-win_amd64.whl", hash = "sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57"}, 1239 | {file = "PyYAML-6.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c"}, 1240 | {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0"}, 1241 | {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0ce82d761c532fe4ec3f87fc45688bdd3a4c1dc5e0b4a19814b9009a29baefd4"}, 1242 | {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"}, 1243 | {file = "PyYAML-6.0-cp37-cp37m-win32.whl", hash = "sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737"}, 1244 | {file = "PyYAML-6.0-cp37-cp37m-win_amd64.whl", hash = "sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d"}, 1245 | {file = "PyYAML-6.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0b4624f379dab24d3725ffde76559cff63d9ec94e1736b556dacdfebe5ab6d4b"}, 1246 | {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:213c60cd50106436cc818accf5baa1aba61c0189ff610f64f4a3e8c6726218ba"}, 1247 | {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34"}, 1248 | {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"}, 1249 | {file = "PyYAML-6.0-cp38-cp38-win32.whl", hash = "sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78"}, 1250 | {file = "PyYAML-6.0-cp38-cp38-win_amd64.whl", hash = "sha256:1e4747bc279b4f613a09eb64bba2ba602d8a6664c6ce6396a4d0cd413a50ce07"}, 1251 | {file = "PyYAML-6.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b"}, 1252 | {file = "PyYAML-6.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174"}, 1253 | {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803"}, 1254 | {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3"}, 1255 | {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"}, 1256 | {file = "PyYAML-6.0-cp39-cp39-win32.whl", hash = "sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb"}, 1257 | {file = "PyYAML-6.0-cp39-cp39-win_amd64.whl", hash = "sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c"}, 1258 | {file = "PyYAML-6.0.tar.gz", hash = "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2"}, 1259 | ] 1260 | readme-renderer = [ 1261 | {file = "readme_renderer-35.0-py3-none-any.whl", hash = "sha256:73b84905d091c31f36e50b4ae05ae2acead661f6a09a9abb4df7d2ddcdb6a698"}, 1262 | {file = "readme_renderer-35.0.tar.gz", hash = "sha256:a727999acfc222fc21d82a12ed48c957c4989785e5865807c65a487d21677497"}, 1263 | ] 1264 | requests = [ 1265 | {file = "requests-2.28.0-py3-none-any.whl", hash = "sha256:bc7861137fbce630f17b03d3ad02ad0bf978c844f3536d0edda6499dafce2b6f"}, 1266 | {file = "requests-2.28.0.tar.gz", hash = "sha256:d568723a7ebd25875d8d1eaf5dfa068cd2fc8194b2e483d7b1f7c81918dbec6b"}, 1267 | ] 1268 | requests-toolbelt = [ 1269 | {file = "requests-toolbelt-0.9.1.tar.gz", hash = "sha256:968089d4584ad4ad7c171454f0a5c6dac23971e9472521ea3b6d49d610aa6fc0"}, 1270 | {file = "requests_toolbelt-0.9.1-py2.py3-none-any.whl", hash = "sha256:380606e1d10dc85c3bd47bf5a6095f815ec007be7a8b69c878507068df059e6f"}, 1271 | ] 1272 | rfc3986 = [ 1273 | {file = "rfc3986-2.0.0-py2.py3-none-any.whl", hash = "sha256:50b1502b60e289cb37883f3dfd34532b8873c7de9f49bb546641ce9cbd256ebd"}, 1274 | {file = "rfc3986-2.0.0.tar.gz", hash = "sha256:97aacf9dbd4bfd829baad6e6309fa6573aaf1be3f6fa735c8ab05e46cecb261c"}, 1275 | ] 1276 | rich = [ 1277 | {file = "rich-12.4.4-py3-none-any.whl", hash = "sha256:d2bbd99c320a2532ac71ff6a3164867884357da3e3301f0240090c5d2fdac7ec"}, 1278 | {file = "rich-12.4.4.tar.gz", hash = "sha256:4c586de507202505346f3e32d1363eb9ed6932f0c2f63184dea88983ff4971e2"}, 1279 | ] 1280 | secretstorage = [ 1281 | {file = "SecretStorage-3.3.2-py3-none-any.whl", hash = "sha256:755dc845b6ad76dcbcbc07ea3da75ae54bb1ea529eb72d15f83d26499a5df319"}, 1282 | {file = "SecretStorage-3.3.2.tar.gz", hash = "sha256:0a8eb9645b320881c222e827c26f4cfcf55363e8b374a021981ef886657a912f"}, 1283 | ] 1284 | semver = [ 1285 | {file = "semver-2.13.0-py2.py3-none-any.whl", hash = "sha256:ced8b23dceb22134307c1b8abfa523da14198793d9787ac838e70e29e77458d4"}, 1286 | {file = "semver-2.13.0.tar.gz", hash = "sha256:fa0fe2722ee1c3f57eac478820c3a5ae2f624af8264cbdf9000c980ff7f75e3f"}, 1287 | ] 1288 | setuptools-scm = [ 1289 | {file = "setuptools_scm-6.4.2-py3-none-any.whl", hash = "sha256:acea13255093849de7ccb11af9e1fb8bde7067783450cee9ef7a93139bddf6d4"}, 1290 | {file = "setuptools_scm-6.4.2.tar.gz", hash = "sha256:6833ac65c6ed9711a4d5d2266f8024cfa07c533a0e55f4c12f6eff280a5a9e30"}, 1291 | ] 1292 | six = [ 1293 | {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, 1294 | {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, 1295 | ] 1296 | smmap = [ 1297 | {file = "smmap-5.0.0-py3-none-any.whl", hash = "sha256:2aba19d6a040e78d8b09de5c57e96207b09ed71d8e55ce0959eeee6c8e190d94"}, 1298 | {file = "smmap-5.0.0.tar.gz", hash = "sha256:c840e62059cd3be204b0c9c9f74be2c09d5648eddd4580d9314c3ecde0b30936"}, 1299 | ] 1300 | toml = [ 1301 | {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, 1302 | {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, 1303 | ] 1304 | tomli = [ 1305 | {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, 1306 | {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, 1307 | ] 1308 | tomlkit = [ 1309 | {file = "tomlkit-0.10.2-py3-none-any.whl", hash = "sha256:905cf92c2111ef80d355708f47ac24ad1b6fc2adc5107455940088c9bbecaedb"}, 1310 | {file = "tomlkit-0.10.2.tar.gz", hash = "sha256:30d54c0b914e595f3d10a87888599eab5321a2a69abc773bbefff51599b72db6"}, 1311 | ] 1312 | tqdm = [ 1313 | {file = "tqdm-4.64.0-py2.py3-none-any.whl", hash = "sha256:74a2cdefe14d11442cedf3ba4e21a3b84ff9a2dbdc6cfae2c34addb2a14a5ea6"}, 1314 | {file = "tqdm-4.64.0.tar.gz", hash = "sha256:40be55d30e200777a307a7585aee69e4eabb46b4ec6a4b4a5f2d9f11e7d5408d"}, 1315 | ] 1316 | tryceratops = [ 1317 | {file = "tryceratops-1.1.0-py3-none-any.whl", hash = "sha256:f4770960782a2ae87cad7a7fd1206476468d0f41383c9fd7a49c6d537534015e"}, 1318 | {file = "tryceratops-1.1.0.tar.gz", hash = "sha256:58e56b33eeb7a9cfd643957069bb1872ebe26c1eeac7abb1877c9535b7e473db"}, 1319 | ] 1320 | twine = [ 1321 | {file = "twine-3.8.0-py3-none-any.whl", hash = "sha256:d0550fca9dc19f3d5e8eadfce0c227294df0a2a951251a4385797c8a6198b7c8"}, 1322 | {file = "twine-3.8.0.tar.gz", hash = "sha256:8efa52658e0ae770686a13b675569328f1fba9837e5de1867bfe5f46a9aefe19"}, 1323 | ] 1324 | typing-extensions = [ 1325 | {file = "typing_extensions-4.2.0-py3-none-any.whl", hash = "sha256:6657594ee297170d19f67d55c05852a874e7eb634f4f753dbd667855e07c1708"}, 1326 | {file = "typing_extensions-4.2.0.tar.gz", hash = "sha256:f1c24655a0da0d1b67f07e17a5e6b2a105894e6824b92096378bb3668ef02376"}, 1327 | ] 1328 | urllib3 = [ 1329 | {file = "urllib3-1.26.9-py2.py3-none-any.whl", hash = "sha256:44ece4d53fb1706f667c9bd1c648f5469a2ec925fcf3a776667042d645472c14"}, 1330 | {file = "urllib3-1.26.9.tar.gz", hash = "sha256:aabaf16477806a5e1dd19aa41f8c2b7950dd3c746362d7e3223dbe6de6ac448e"}, 1331 | ] 1332 | virtualenv = [ 1333 | {file = "virtualenv-20.14.1-py2.py3-none-any.whl", hash = "sha256:e617f16e25b42eb4f6e74096b9c9e37713cf10bf30168fb4a739f3fa8f898a3a"}, 1334 | {file = "virtualenv-20.14.1.tar.gz", hash = "sha256:ef589a79795589aada0c1c5b319486797c03b67ac3984c48c669c0e4f50df3a5"}, 1335 | ] 1336 | webencodings = [ 1337 | {file = "webencodings-0.5.1-py2.py3-none-any.whl", hash = "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78"}, 1338 | {file = "webencodings-0.5.1.tar.gz", hash = "sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923"}, 1339 | ] 1340 | zipp = [ 1341 | {file = "zipp-3.8.0-py3-none-any.whl", hash = "sha256:c4f6e5bbf48e74f7a38e7cc5b0480ff42b0ae5178957d564d18932525d5cf099"}, 1342 | {file = "zipp-3.8.0.tar.gz", hash = "sha256:56bf8aadb83c24db6c4b577e13de374ccfb67da2078beba1d037c17980bf43ad"}, 1343 | ] 1344 | --------------------------------------------------------------------------------