├── tests ├── __init__.py └── test_cobhan.py ├── cobhan ├── __init__.py └── cobhan.py ├── CHANGELOG.md ├── .editorconfig ├── .github └── workflows │ ├── publish.yml │ └── codeql-analysis.yml ├── SECURITY.md ├── pyproject.toml ├── tox.ini ├── LICENSE ├── README.md ├── .gitignore ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md └── poetry.lock /tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /cobhan/__init__.py: -------------------------------------------------------------------------------- 1 | """Cobhan FFI""" 2 | 3 | from .cobhan import Cobhan 4 | 5 | __all__ = ["Cobhan"] 6 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## [0.3.0] - 2022-03-04 2 | 3 | - Changed `load_*` to return the libraries that have been loaded. 4 | 5 | ## [0.2.1] - 2022-03-04 6 | 7 | - Added `minimum_allocation` and `header_size` properties to the `Cobhan` class 8 | - Fixed `load_library` and added tests 9 | - Fixed allocation of empty buffers in `str_to_buf` and added tests 10 | 11 | ## [0.2.0] - 2022-03-03 12 | 13 | - Renamed the `_load_*` methods to `load_*` to make them part of the public API, 14 | rather than protected. 15 | - Added the `int_to_buf` and `buf_to_int` methods. 16 | 17 | ## [0.1.0] - 2022-02-28 18 | 19 | - Initial Release 20 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | 3 | root = true 4 | 5 | [*] 6 | indent_style = space 7 | indent_size = 4 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | end_of_line = lf 11 | charset = utf-8 12 | tab_width = 4 13 | max_line_length = 120 14 | 15 | # Use 2 spaces for the HTML files 16 | [*.html] 17 | indent_size = 2 18 | 19 | # The JSON files contain newlines inconsistently 20 | [*.json] 21 | indent_size = 2 22 | 23 | # Minified JavaScript files shouldn't be changed 24 | [**.min.js] 25 | 26 | # Makefiles always use tabs for indentation 27 | [Makefile] 28 | indent_style = tab 29 | 30 | # Batch files use tabs for indentation 31 | [*.bat] 32 | indent_style = tab 33 | 34 | [*.md] 35 | trim_trailing_whitespace = false 36 | 37 | [*.{yaml,yml}] 38 | indent_size = 2 39 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: publish 2 | 3 | on: 4 | release: 5 | types: [published] # Trigger when release is created 6 | 7 | jobs: 8 | publish: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f 12 | - name: Set up Python 3.7 13 | uses: actions/setup-python@3105fb18c05ddd93efea5f9e0bef7a03a6e9e7df 14 | with: 15 | python-version: 3.7 16 | - name: Install dependencies 17 | run: | 18 | pip install --upgrade pip 19 | pip install --upgrade poetry 20 | - name: Package and publish with Poetry 21 | run: | 22 | poetry config pypi-token.pypi $PYPI_TOKEN 23 | poetry publish --build 24 | env: 25 | PYPI_TOKEN: ${{ secrets.PYPI_TOKEN }} 26 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Reporting Security Issues 2 | 3 | We take security very seriously at GoDaddy. We appreciate your efforts to 4 | responsibly disclose your findings, and will make every effort to acknowledge 5 | your contributions. 6 | 7 | ## Where should I report security issues? 8 | 9 | In order to give the community time to respond and upgrade, we strongly urge you 10 | report all security issues privately. 11 | 12 | To report a security issue in one of our Open Source projects email us directly 13 | at **oss@godaddy.com** and include the word "SECURITY" in the subject line. 14 | 15 | This mail is delivered to our Open Source Security team. 16 | 17 | After the initial reply to your report, the team will keep you informed of the 18 | progress being made towards a fix and announcement, and may ask for additional 19 | information or guidance. 20 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "cobhan" 3 | version = "0.4.3" 4 | description = "Cobhan FFI" 5 | authors = [ 6 | "Jeremiah Gowdy ", 7 | "Joey Wilhelm " 8 | ] 9 | maintainers = ["GoDaddy "] 10 | license = "MIT" 11 | include = ["README.md"] 12 | keywords = ["ffi"] 13 | readme = "README.md" 14 | repository = "https://github.com/godaddy/cobhan-python/" 15 | 16 | [tool.poetry.dependencies] 17 | python = "^3.7" 18 | cffi = "^1.15.0" 19 | 20 | [tool.poetry.dev-dependencies] 21 | pytest-cov = "^3.0.0" 22 | coverage = "^6.3.2" 23 | pytest-sugar = "^0.9.4" 24 | pylint = "^2.12.2" 25 | pytest = "^7.0.1" 26 | black = "^22.1.0" 27 | tox = "^3.24.5" 28 | mypy = "^0.931" 29 | 30 | [build-system] 31 | requires = ["poetry-core>=1.0.0"] 32 | build-backend = "poetry.core.masonry.api" 33 | 34 | [tool.mypy] 35 | ignore_missing_imports = true 36 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | minversion = 3.7.0 3 | toxworkdir = {env:TOX_WORK_DIR:.tox} 4 | skip_missing_interpreters = True 5 | envlist = py{37,38,39,310},black,mypy,pylint 6 | parallel_show_output = True 7 | isolated_build = True 8 | 9 | [gh-actions] 10 | python = 11 | 3.7: py37 12 | 3.8: py38 13 | 3.9: py39 14 | 3.10: py310 15 | 16 | [testenv] 17 | whitelist_externals = 18 | poetry 19 | pytest 20 | setenv = 21 | PYTHONDONTWRITEBYTECODE=1 22 | PYTHONHASHSEED=0 23 | PYTHONWARNINGS=ignore 24 | commands = 25 | poetry install --no-root -v 26 | poetry run pytest {posargs} 27 | 28 | [testenv:black] 29 | basepython = python3.7 30 | commands = 31 | poetry install --no-root -v 32 | poetry run black --check . 33 | 34 | [testenv:mypy] 35 | basepython = python3.7 36 | commands = 37 | poetry install --no-root -v 38 | poetry run mypy . 39 | 40 | [testenv:pylint] 41 | basepython = python3.7 42 | commands = 43 | poetry install --no-root -v 44 | poetry run pylint cobhan/ tests/ 45 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2022 GoDaddy Operating Company, LLC. 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 all 13 | 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 THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Cobhan FFI 2 | 3 | Cobhan FFI is a proof of concept system for enabling shared code to be written in Rust or Go and consumed from all major languages/platforms in a safe and effective way, using easy helper functions to manage any unsafe data marshaling. 4 | 5 | ## Types 6 | 7 | * Supported types 8 | * int32 - 32bit signed integer 9 | * int64 - 64bit signed integer 10 | * float64 - double precision 64bit IEEE 754 floating point 11 | * Cobhan buffer - length delimited 8bit buffer (no null delimiters) 12 | * utf-8 encoded string 13 | * JSON 14 | * binary data 15 | * Cobhan buffer details 16 | * Callers provide the output buffer allocation and capacity 17 | * Called functions can transparently return larger values via temporary files 18 | * **Modern [tmpfs](https://en.wikipedia.org/wiki/Tmpfs) is entirely memory backed** 19 | * Return values 20 | * Functions that return scalar values can return the value directly 21 | * Functions *can* use special case and return maximum positive or maximum negative or zero values to 22 | represent error or overflow conditions 23 | * Functions *can* allow scalar values to wrap 24 | * Functions should document their overflow / underflow behavior 25 | 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | .python-version 86 | 87 | # pipenv 88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 91 | # install all needed dependencies. 92 | #Pipfile.lock 93 | 94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 95 | __pypackages__/ 96 | 97 | # Celery stuff 98 | celerybeat-schedule 99 | celerybeat.pid 100 | 101 | # SageMath parsed files 102 | *.sage.py 103 | 104 | # Environments 105 | .env 106 | .venv 107 | env/ 108 | venv/ 109 | ENV/ 110 | env.bak/ 111 | venv.bak/ 112 | 113 | # Spyder project settings 114 | .spyderproject 115 | .spyproject 116 | 117 | # Rope project settings 118 | .ropeproject 119 | 120 | # mkdocs documentation 121 | /site 122 | 123 | # mypy 124 | .mypy_cache/ 125 | .dmypy.json 126 | dmypy.json 127 | 128 | # Pyre type checker 129 | .pyre/ 130 | 131 | # Editors 132 | .vscode/ 133 | .idea/ 134 | *.swp 135 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [ main ] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: [ main ] 20 | schedule: 21 | - cron: '17 21 * * 6' 22 | 23 | jobs: 24 | analyze: 25 | name: Analyze 26 | runs-on: ubuntu-latest 27 | permissions: 28 | actions: read 29 | contents: read 30 | security-events: write 31 | 32 | strategy: 33 | fail-fast: false 34 | matrix: 35 | language: [ 'python' ] 36 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] 37 | # Learn more about CodeQL language support at https://git.io/codeql-language-support 38 | 39 | steps: 40 | - name: Checkout repository 41 | uses: actions/checkout@v2 42 | 43 | # Initializes the CodeQL tools for scanning. 44 | - name: Initialize CodeQL 45 | uses: github/codeql-action/init@v1 46 | with: 47 | languages: ${{ matrix.language }} 48 | # If you wish to specify custom queries, you can do so here or in a config file. 49 | # By default, queries listed here will override any specified in a config file. 50 | # Prefix the list here with "+" to use these queries and those in the config file. 51 | # queries: ./path/to/local/query, your-org/your-repo/queries@main 52 | 53 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 54 | # If this step fails, then you should remove it and run the build manually (see below) 55 | - name: Autobuild 56 | uses: github/codeql-action/autobuild@v1 57 | 58 | # ℹ️ Command-line programs to run using the OS shell. 59 | # 📚 https://git.io/JvXDl 60 | 61 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines 62 | # and modify them (or add more) to build your code if your project 63 | # uses a compiled language 64 | 65 | #- run: | 66 | # make bootstrap 67 | # make release 68 | 69 | - name: Perform CodeQL Analysis 70 | uses: github/codeql-action/analyze@v1 71 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies within all project spaces, and it also applies when 49 | an individual is representing the project or its community in public spaces. 50 | Examples of representing a project or community include using an official 51 | project e-mail address, posting via an official social media account, or acting 52 | as an appointed representative at an online or offline event. Representation of 53 | a project may be further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at oss@godaddy.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | 78 | -------------------------------------------------------------------------------- /tests/test_cobhan.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=missing-function-docstring,missing-class-docstring,missing-module-docstring 2 | 3 | from pathlib import Path 4 | from unittest import mock, TestCase 5 | 6 | from cobhan import Cobhan 7 | 8 | 9 | class LoadLibraryTests(TestCase): 10 | """Tests for Cobhan.load_library""" 11 | 12 | def setUp(self) -> None: 13 | self.ffi_patcher = mock.patch("cobhan.cobhan.FFI") 14 | self.platform_patcher = mock.patch("cobhan.cobhan.platform") 15 | 16 | self.mock_ffi = self.ffi_patcher.start() 17 | self.mock_dlopen = self.mock_ffi.return_value.dlopen 18 | self.mock_platform = self.platform_patcher.start() 19 | 20 | self.addCleanup(self.ffi_patcher.stop) 21 | self.addCleanup(self.platform_patcher.stop) 22 | 23 | self.cobhan = Cobhan() 24 | return super().setUp() 25 | 26 | def test_load_linux_x64(self): 27 | self.mock_platform.system.return_value = "Linux" 28 | self.mock_platform.machine.return_value = "x86_64" 29 | 30 | self.cobhan.load_library("libfoo", "libbar", "") 31 | self.mock_dlopen.assert_called_once_with( 32 | str(Path("libfoo/libbar-x64.so").resolve()) 33 | ) 34 | 35 | def test_load_linux_arm64(self): 36 | self.mock_platform.system.return_value = "Linux" 37 | self.mock_platform.machine.return_value = "arm64" 38 | 39 | self.cobhan.load_library("libfoo", "libbar", "") 40 | self.mock_dlopen.assert_called_once_with( 41 | str(Path("libfoo/libbar-arm64.so").resolve()) 42 | ) 43 | 44 | def test_load_macos_x64(self): 45 | self.mock_platform.system.return_value = "Darwin" 46 | self.mock_platform.machine.return_value = "x86_64" 47 | 48 | self.cobhan.load_library("libfoo", "libbar", "") 49 | self.mock_dlopen.assert_called_once_with( 50 | str(Path("libfoo/libbar-x64.dylib").resolve()) 51 | ) 52 | 53 | def test_load_macos_arm64(self): 54 | self.mock_platform.system.return_value = "Darwin" 55 | self.mock_platform.machine.return_value = "arm64" 56 | 57 | self.cobhan.load_library("libfoo", "libbar", "") 58 | self.mock_dlopen.assert_called_once_with( 59 | str(Path("libfoo/libbar-arm64.dylib").resolve()) 60 | ) 61 | 62 | def test_load_windows_x64(self): 63 | self.mock_platform.system.return_value = "Windows" 64 | self.mock_platform.machine.return_value = "x86_64" 65 | 66 | self.cobhan.load_library("libfoo", "libbar", "") 67 | self.mock_dlopen.assert_called_once_with( 68 | str(Path("libfoo/libbar-x64.dll").resolve()) 69 | ) 70 | 71 | def test_load_windows_arm64(self): 72 | self.mock_platform.system.return_value = "Windows" 73 | self.mock_platform.machine.return_value = "arm64" 74 | 75 | self.cobhan.load_library("libfoo", "libbar", "") 76 | self.mock_dlopen.assert_called_once_with( 77 | str(Path("libfoo/libbar-arm64.dll").resolve()) 78 | ) 79 | 80 | 81 | class StringTests(TestCase): 82 | def setUp(self) -> None: 83 | 84 | self.cobhan = Cobhan() 85 | return super().setUp() 86 | 87 | def test_minimum_allocation_is_enforced(self): 88 | buf = self.cobhan.str_to_buf("foo") 89 | self.assertEqual(len(buf), self.cobhan.minimum_allocation) 90 | 91 | def test_can_allocate_beyond_minimum(self): 92 | long_str = "foobar" * 1000 # This will be 6k characters in length 93 | buf = self.cobhan.str_to_buf(long_str) 94 | self.assertEqual(len(buf), (len(long_str) + self.cobhan.header_size)) 95 | 96 | def test_two_way_conversion_maintains_string(self): 97 | buf = self.cobhan.str_to_buf("foobar") 98 | result = self.cobhan.buf_to_str(buf) 99 | self.assertEqual(result, "foobar") 100 | 101 | def test_empty_string_returns_empty_buffer(self): 102 | buf = self.cobhan.str_to_buf("") 103 | self.assertEqual(len(buf), self.cobhan.minimum_allocation) 104 | 105 | def test_input_of_none_returns_empty_buffer(self): 106 | buf = self.cobhan.str_to_buf(None) 107 | self.assertEqual(len(buf), self.cobhan.minimum_allocation) 108 | 109 | 110 | class BytesTests(TestCase): 111 | def setUp(self) -> None: 112 | self.cobhan = Cobhan() 113 | return super().setUp() 114 | 115 | def test_minimum_allocation_is_enforced(self): 116 | data_bytes = "foo".encode("utf8") 117 | buf = self.cobhan.bytearray_to_buf(data_bytes) 118 | self.assertEqual(len(buf), self.cobhan.minimum_allocation) 119 | 120 | def test_can_allocate_beyond_minimum(self): 121 | long_str = "foobar" * 1000 # This will be 6k characters in length 122 | data_bytes = long_str.encode("utf8") 123 | buf = self.cobhan.bytearray_to_buf(data_bytes) 124 | self.assertEqual(len(buf), (len(long_str) + self.cobhan.header_size)) 125 | 126 | def test_two_way_conversion_maintains_string(self): 127 | data_bytes = "foobar".encode("utf8") 128 | buf = self.cobhan.bytearray_to_buf(data_bytes) 129 | result = self.cobhan.buf_to_bytearray(buf) 130 | self.assertEqual(result, data_bytes) 131 | 132 | def test_empty_string_returns_empty_buffer(self): 133 | data_bytes = "".encode("utf8") 134 | buf = self.cobhan.bytearray_to_buf(data_bytes) 135 | self.assertEqual(len(buf), self.cobhan.minimum_allocation) 136 | 137 | def test_input_of_none_returns_empty_buffer(self): 138 | buf = self.cobhan.bytearray_to_buf(None) 139 | self.assertEqual(len(buf), self.cobhan.minimum_allocation) 140 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Everyone is welcome to contribute to GoDaddy's Open Source Software. 4 | Contributing doesn’t just mean submitting pull requests. To get involved, 5 | you can report or triage bugs, and participate in discussions on the 6 | evolution of each project. 7 | 8 | No matter how you want to get involved, we ask that you first learn what’s 9 | expected of anyone who participates in the project by reading the Contribution 10 | Guidelines and our [Code of Conduct][coc]. 11 | 12 | ## Answering Questions 13 | 14 | One of the most important and immediate ways you can support this project is 15 | to answer questions on [Github][issues]. Whether you’re 16 | helping a newcomer understand a feature or troubleshooting an edge case with a 17 | seasoned developer, your knowledge and experience with a programming language 18 | can go a long way to help others. 19 | 20 | ## Reporting Bugs 21 | 22 | **Do not report potential security vulnerabilities here. Refer to 23 | [SECURITY.md](./SECURITY.md) for more details about the process of reporting 24 | security vulnerabilities.** 25 | 26 | Before submitting a ticket, please search our [Issue Tracker][issues] to make 27 | sure it does not already exist and have a simple replication of the behavior. If 28 | the issue is isolated to one of the dependencies of this project, please create 29 | a Github issue in that project. All dependencies should be open source software 30 | and can be found on Github. 31 | 32 | Submit a ticket for your issue, assuming one does not already exist: 33 | 34 | - Create it on the project's [issue Tracker][issues]. 35 | - Clearly describe the issue by following the template layout 36 | - Make sure to include steps to reproduce the bug. 37 | - A reproducible (unit) test could be helpful in solving the bug. 38 | - Describe the environment that (re)produced the problem. 39 | 40 | ## Triaging bugs or contributing code 41 | 42 | If you're triaging a bug, first make sure that you can reproduce it. Once a bug 43 | can be reproduced, reduce it to the smallest amount of code possible. Reasoning 44 | about a sample or unit test that reproduces a bug in just a few lines of code 45 | is easier than reasoning about a longer sample. 46 | 47 | From a practical perspective, contributions are as simple as: 48 | 49 | 1. Fork and clone the repo, [see Github's instructions if you need help.][fork] 50 | 1. Create a branch for your PR with `git checkout -b pr/your-branch-name` 51 | 1. Make changes on the branch of your forked repository. 52 | 1. When committing, reference your issue (if present) and include a note about 53 | the fix. 54 | 1. Please also add/update unit tests for your changes. 55 | 1. Push the changes to your fork and submit a pull request to the 'main 56 | development branch' branch of the projects' repository. 57 | 58 | If you are interested in making a large change and feel unsure about its overall 59 | effect, start with opening an Issue in the project's [Issue Tracker][issues] 60 | with a high-level proposal and discuss it with the core contributors through 61 | Github comments. After reaching a consensus with core 62 | contributors about the change, discuss the best way to go about implementing it. 63 | 64 | > Tip: Keep your main branch pointing at the original repository and make 65 | > pull requests from branches on your fork. To do this, run: 66 | > 67 | > ```sh 68 | > git remote add upstream https://github.com/godaddy/cobhan-python.git 69 | > git fetch upstream 70 | > git branch --set-upstream-to=upstream/main main 71 | > ``` 72 | > 73 | > This will add the original repository as a "remote" called "upstream," Then 74 | > fetch the git information from that remote, then set your local main 75 | > branch to use the upstream main branch whenever you run git pull. Then you 76 | > can make all of your pull request branches based on this main branch. 77 | > Whenever you want to update your version of main, do a regular git pull. 78 | 79 | ## Code Review 80 | 81 | Any open source project relies heavily on code review to improve software 82 | quality. All significant changes, by all developers, must be reviewed before 83 | they are committed to the repository. Code reviews are conducted on GitHub 84 | through comments on pull requests or commits. The developer responsible for a 85 | code change is also responsible for making all necessary review-related changes. 86 | 87 | Sometimes code reviews will take longer than you would hope for, especially for 88 | larger features. Here are some accepted ways to speed up review times for your 89 | patches: 90 | 91 | - Review other people’s changes. If you help out, others will more likely be 92 | willing to do the same for you. 93 | - Split your change into multiple smaller changes. The smaller your change, 94 | the higher the probability that somebody will take a quick look at it. 95 | 96 | **Note that anyone is welcome to review and give feedback on a change, but only 97 | people with commit access to the repository can approve it.** 98 | 99 | ## Attribution of Changes 100 | 101 | When contributors submit a change to this project, after that change is 102 | approved, other developers with commit access may commit it for the author. When 103 | doing so, it is important to retain correct attribution of the contribution. 104 | Generally speaking, Git handles attribution automatically. 105 | 106 | ## Code Style and Documentation 107 | 108 | Ensure that your contribution follows the standards set by the project's style 109 | guide with respect to patterns, naming, documentation and testing. 110 | 111 | # Additional Resources 112 | 113 | - [General GitHub Documentation](https://help.github.com/) 114 | - [GitHub Pull Request documentation](https://help.github.com/send-pull-requests/) 115 | 116 | [issues]: https://github.com/godaddy/cobhan-python/issues/ 117 | [coc]: ./CODE_OF_CONDUCT.md 118 | [fork]: https://help.github.com/en/articles/fork-a-repo 119 | -------------------------------------------------------------------------------- /cobhan/cobhan.py: -------------------------------------------------------------------------------- 1 | """Cobhan FFI primary functionality""" 2 | 3 | import json 4 | import os 5 | import pathlib 6 | import platform 7 | from io import UnsupportedOperation 8 | from typing import Any, ByteString, Optional 9 | 10 | from cffi import FFI 11 | 12 | # Pending https://github.com/python/typing/issues/593 13 | CBuf = bytearray # Cobhan buffer 14 | FFILibrary = Any 15 | 16 | 17 | class Cobhan: 18 | """Class representing the Cobhan translation layer""" 19 | 20 | def __init__(self): 21 | self._lib: Optional[FFILibrary] = None 22 | self.__ffi: FFI = FFI() 23 | self.__sizeof_int32: int = self.__ffi.sizeof("int32_t") 24 | self.__sizeof_int64: int = self.__ffi.sizeof("int64_t") 25 | self.__sizeof_header: int = self.__sizeof_int32 * 2 26 | self.__minimum_payload_size: int = 1024 27 | self.__int32_zero_bytes: bytes = int(0).to_bytes( 28 | self.__sizeof_int32, byteorder="little", signed=True 29 | ) 30 | 31 | @property 32 | def minimum_allocation(self): 33 | """The minimum buffer size, in bytes, that will be allocated for a string""" 34 | return self.__minimum_payload_size + self.header_size 35 | 36 | @property 37 | def header_size(self): 38 | """The size, in bytes, of a buffer's header""" 39 | return self.__sizeof_header 40 | 41 | def load_library( 42 | self, library_path: str, library_name: str, cdefines: str 43 | ) -> FFILibrary: 44 | """Locate and load a library based on the current platform. 45 | 46 | :param library_path: The filesystem path where the library is located 47 | :param library_name: The name of the library to be loaded 48 | :param cdefines: A declaration of the C types, functions, and globals 49 | globals needed to use the shared object. This must be valid C syntax, 50 | with one definition per line. 51 | :raises UnsupportedOperation: If the operating system or CPU arch are 52 | not supported 53 | """ 54 | self.__ffi.cdef(cdefines) 55 | 56 | system = platform.system() 57 | need_chdir = False 58 | if system == "Linux": 59 | if pathlib.Path("/lib").match("libc.musl*"): 60 | os_ext = "-musl.so" 61 | need_chdir = True 62 | else: 63 | os_ext = ".so" 64 | elif system == "Darwin": 65 | os_ext = ".dylib" 66 | elif system == "Windows": 67 | os_ext = ".dll" 68 | else: 69 | raise UnsupportedOperation("Unsupported operating system") 70 | 71 | machine = platform.machine() 72 | if machine in ("x86_64", "AMD64"): 73 | arch_part = "-x64" 74 | elif machine in ("arm64", "aarch64"): 75 | arch_part = "-arm64" 76 | else: 77 | raise UnsupportedOperation(f"Unsupported CPU: {machine}") 78 | 79 | # Get absolute library path 80 | resolved_library_path = pathlib.Path(library_path).resolve() 81 | 82 | # Build library path with file name 83 | library_file_path = os.path.join( 84 | str(resolved_library_path), f"{library_name}{arch_part}{os_ext}" 85 | ) 86 | 87 | if need_chdir: 88 | old_dir = os.getcwd() 89 | os.chdir(library_path) 90 | 91 | self._lib = self.__ffi.dlopen(library_file_path) 92 | 93 | if need_chdir: 94 | os.chdir(old_dir) 95 | 96 | return self._lib 97 | 98 | def load_library_direct(self, library_file_path: str, cdefines: str) -> FFILibrary: 99 | """Directly load a specific library file. 100 | 101 | Generally speaking, you probably don't want this. Instead, you probably 102 | want the `load_library` method which will load a platform-specific 103 | library for you. 104 | 105 | :param library_file_path: The full file path to the library 106 | :param cdefines: A declaration of the C types, functions, and globals 107 | globals needed to use the shared object. This must be valid C syntax, 108 | with one definition per line. 109 | """ 110 | self.__ffi.cdef(cdefines) 111 | self._lib = self.__ffi.dlopen(library_file_path) 112 | return self._lib 113 | 114 | def to_json_buf(self, obj: Any) -> CBuf: 115 | """Serialize an object into JSON in a Cobhan buffer. 116 | 117 | :param obj: The object to be serialized 118 | :returns: A new Cobhan buffer containing the JSON serialized object 119 | """ 120 | return self.str_to_buf(json.dumps(obj)) 121 | 122 | def from_json_buf(self, buf: CBuf) -> Any: 123 | """Deserialize a JSON serialized Cobhan buffer into an object. 124 | 125 | :param buf: The Cobhan buffer to be deserialized 126 | :returns: The deserialized object 127 | """ 128 | return json.loads(self.buf_to_str(buf)) 129 | 130 | def __get_length(self, buf: CBuf) -> int: 131 | length_buf = self.__ffi.unpack(buf, self.__sizeof_int32) 132 | return int.from_bytes(length_buf, byteorder="little", signed=True) 133 | 134 | def __set_header(self, buf: CBuf, length: int) -> None: 135 | """Create a header in a Cobhan buffer. 136 | 137 | :param buf: The Cobhan buffer in which to create a header 138 | :param length: The length of the header to be created 139 | """ 140 | length_bytes = length.to_bytes(self.__sizeof_int32, byteorder="little", signed=True) 141 | buf[0:self.__sizeof_int32] = length_bytes 142 | buf[self.__sizeof_int32:self.__sizeof_header] = self.__int32_zero_bytes 143 | 144 | def __get_payload_slice(self, buf: bytearray, length: int) -> bytearray: 145 | return buf[self.__sizeof_header:self.__sizeof_header + length] 146 | 147 | def __set_payload(self, buf: CBuf, payload: bytearray, length: int) -> None: 148 | """Copy a payload into a Cobhan buffer. 149 | 150 | :param buf: The Cobhan buffer to copy the payload into 151 | :param payload: The payload to be copied 152 | :param length: The length of the payload 153 | """ 154 | self.__set_header(buf, length) 155 | buf[self.__sizeof_header:self.__sizeof_header + length] = payload 156 | 157 | def bytearray_to_buf(self, payload: bytearray) -> Any: 158 | """Copy a bytearray to a Cobhan buffer. 159 | 160 | :param payload: The bytearray to be copied 161 | """ 162 | if payload is None: 163 | return self.allocate_buf(0) 164 | length = len(payload) 165 | buf = self.allocate_buf(length) 166 | self.__set_payload(buf, payload, length) 167 | return buf 168 | 169 | def str_to_buf(self, string: Optional[str]) -> Any: 170 | """Encode a string in utf8 and copy into a Cobhan buffer. 171 | 172 | :param string: The string to be copied 173 | :returns: A new Cobhan buffer containing the utf8 encoded string 174 | """ 175 | if not string: 176 | return self.allocate_buf(0) 177 | encoded_bytes = string.encode("utf8") 178 | length = len(encoded_bytes) 179 | buf = self.allocate_buf(length) 180 | self.__set_payload(buf, encoded_bytes, length) 181 | return buf 182 | 183 | def allocate_buf(self, buffer_len: int) -> CBuf: 184 | """Allocate a new Cobhan buffer. 185 | 186 | :param buffer_len: The length of the buffer to be allocated 187 | :returns: A new Cobhan buffer of the specified length 188 | """ 189 | length = max(buffer_len, self.__minimum_payload_size) 190 | buf = self.__ffi.new(f"char[{self.__sizeof_header + length}]") 191 | self.__set_header(buf, length) 192 | return buf 193 | 194 | def buf_to_str(self, buf: CBuf) -> str: 195 | """Read a Cobhan buffer into a string. 196 | 197 | :param buf: The Cobhan buffer to be read 198 | :returns: The string contents of the buffer 199 | """ 200 | encoded_bytes = self.buf_to_bytearray(buf) 201 | return encoded_bytes.decode("utf8") 202 | 203 | def buf_to_bytearray(self, buf: CBuf) -> bytearray: 204 | """Copy a Cobhan buffer into a bytearray. 205 | 206 | :param buf: The Cobhan buffer to be copied 207 | :returns: The bytearray contents of the buffer 208 | """ 209 | length = self.__get_length(buf) 210 | if length < 0: 211 | return self.__temp_to_bytearray(buf, length) 212 | 213 | payload = self.__ffi.unpack(buf[self.__sizeof_header:self.__sizeof_header + length] , length) 214 | 215 | return payload 216 | 217 | def __temp_to_str(self, buf: CBuf, length: int) -> str: 218 | """Copy a temporary file backed Cobhan buffer into a string. 219 | 220 | :param buf: The Cobhan buffer to be read from 221 | :param length: The length of the file name of the temporary file 222 | :returns: The string contents copied from the buffer 223 | """ 224 | encoded_bytes = self.__temp_to_bytearray(buf, length) 225 | return encoded_bytes.decode("utf8") 226 | 227 | def __temp_to_bytearray(self, buf: CBuf, length: int) -> bytearray: 228 | """Copy a temporary file backed Cobhan buffer into a bytearray. 229 | 230 | :param buf: The Cobhan buffer to be copied 231 | :param length: The length of the file name of the temporary file 232 | :returns: The bytearray contents copied from the buffer 233 | """ 234 | length = 0 - length 235 | payload = self.__ffi.unpack(buf[self.__sizeof_header:self.__sizeof_header + length], length) 236 | file_name = payload.decode("utf8") 237 | with open(file_name, "rb") as binaryfile: 238 | payload = bytearray(binaryfile.read()) 239 | os.remove(file_name) 240 | return payload 241 | 242 | def int_to_buf(self, num: int) -> CBuf: 243 | """Copy an integer into a Cobhan buffer. 244 | 245 | :param num: The integer to be copied 246 | :returns: A new Cobhan buffer containing the integer 247 | """ 248 | return bytearray(num.to_bytes(self.__sizeof_int64, byteorder="little", signed=True)) 249 | 250 | def buf_to_int(self, buf: CBuf) -> int: 251 | """Read a Cobhan buffer into an integer. 252 | 253 | :param buf: The Cobhan buffer to be read 254 | :returns: The integer contents of the buffer 255 | """ 256 | return int.from_bytes(buf[0:self.__sizeof_int64], byteorder="little", signed=True) 257 | -------------------------------------------------------------------------------- /poetry.lock: -------------------------------------------------------------------------------- 1 | [[package]] 2 | name = "astroid" 3 | version = "2.9.3" 4 | description = "An abstract syntax tree for Python with inference support." 5 | category = "dev" 6 | optional = false 7 | python-versions = ">=3.6.2" 8 | 9 | [package.dependencies] 10 | lazy-object-proxy = ">=1.4.0" 11 | typed-ast = {version = ">=1.4.0,<2.0", markers = "implementation_name == \"cpython\" and python_version < \"3.8\""} 12 | typing-extensions = {version = ">=3.10", markers = "python_version < \"3.10\""} 13 | wrapt = ">=1.11,<1.14" 14 | 15 | [[package]] 16 | name = "atomicwrites" 17 | version = "1.4.0" 18 | description = "Atomic file writes." 19 | category = "dev" 20 | optional = false 21 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 22 | 23 | [[package]] 24 | name = "attrs" 25 | version = "21.4.0" 26 | description = "Classes Without Boilerplate" 27 | category = "dev" 28 | optional = false 29 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 30 | 31 | [package.extras] 32 | tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "cloudpickle"] 33 | docs = ["furo", "sphinx", "zope.interface", "sphinx-notfound-page"] 34 | 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"] 35 | tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "cloudpickle"] 36 | 37 | [[package]] 38 | name = "black" 39 | version = "22.1.0" 40 | description = "The uncompromising code formatter." 41 | category = "dev" 42 | optional = false 43 | python-versions = ">=3.6.2" 44 | 45 | [package.dependencies] 46 | click = ">=8.0.0" 47 | mypy-extensions = ">=0.4.3" 48 | pathspec = ">=0.9.0" 49 | platformdirs = ">=2" 50 | tomli = ">=1.1.0" 51 | typed-ast = {version = ">=1.4.2", markers = "python_version < \"3.8\" and implementation_name == \"cpython\""} 52 | typing-extensions = {version = ">=3.10.0.0", markers = "python_version < \"3.10\""} 53 | 54 | [package.extras] 55 | jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] 56 | uvloop = ["uvloop (>=0.15.2)"] 57 | d = ["aiohttp (>=3.7.4)"] 58 | colorama = ["colorama (>=0.4.3)"] 59 | 60 | [[package]] 61 | name = "cffi" 62 | version = "1.15.0" 63 | description = "Foreign Function Interface for Python calling C code." 64 | category = "main" 65 | optional = false 66 | python-versions = "*" 67 | 68 | [package.dependencies] 69 | pycparser = "*" 70 | 71 | [[package]] 72 | name = "click" 73 | version = "8.0.4" 74 | description = "Composable command line interface toolkit" 75 | category = "dev" 76 | optional = false 77 | python-versions = ">=3.6" 78 | 79 | [package.dependencies] 80 | colorama = {version = "*", markers = "platform_system == \"Windows\""} 81 | importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} 82 | 83 | [[package]] 84 | name = "colorama" 85 | version = "0.4.4" 86 | description = "Cross-platform colored terminal text." 87 | category = "dev" 88 | optional = false 89 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 90 | 91 | [[package]] 92 | name = "coverage" 93 | version = "6.3.2" 94 | description = "Code coverage measurement for Python" 95 | category = "dev" 96 | optional = false 97 | python-versions = ">=3.7" 98 | 99 | [package.dependencies] 100 | tomli = {version = "*", optional = true, markers = "extra == \"toml\""} 101 | 102 | [package.extras] 103 | toml = ["tomli"] 104 | 105 | [[package]] 106 | name = "distlib" 107 | version = "0.3.4" 108 | description = "Distribution utilities" 109 | category = "dev" 110 | optional = false 111 | python-versions = "*" 112 | 113 | [[package]] 114 | name = "filelock" 115 | version = "3.6.0" 116 | description = "A platform independent file lock." 117 | category = "dev" 118 | optional = false 119 | python-versions = ">=3.7" 120 | 121 | [package.extras] 122 | docs = ["furo (>=2021.8.17b43)", "sphinx (>=4.1)", "sphinx-autodoc-typehints (>=1.12)"] 123 | testing = ["covdefaults (>=1.2.0)", "coverage (>=4)", "pytest (>=4)", "pytest-cov", "pytest-timeout (>=1.4.2)"] 124 | 125 | [[package]] 126 | name = "importlib-metadata" 127 | version = "4.11.2" 128 | description = "Read metadata from Python packages" 129 | category = "dev" 130 | optional = false 131 | python-versions = ">=3.7" 132 | 133 | [package.dependencies] 134 | typing-extensions = {version = ">=3.6.4", markers = "python_version < \"3.8\""} 135 | zipp = ">=0.5" 136 | 137 | [package.extras] 138 | docs = ["sphinx", "jaraco.packaging (>=9)", "rst.linker (>=1.9)"] 139 | perf = ["ipython"] 140 | testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "packaging", "pyfakefs", "flufl.flake8", "pytest-perf (>=0.9.2)", "pytest-black (>=0.3.7)", "pytest-mypy (>=0.9.1)", "importlib-resources (>=1.3)"] 141 | 142 | [[package]] 143 | name = "iniconfig" 144 | version = "1.1.1" 145 | description = "iniconfig: brain-dead simple config-ini parsing" 146 | category = "dev" 147 | optional = false 148 | python-versions = "*" 149 | 150 | [[package]] 151 | name = "isort" 152 | version = "5.10.1" 153 | description = "A Python utility / library to sort Python imports." 154 | category = "dev" 155 | optional = false 156 | python-versions = ">=3.6.1,<4.0" 157 | 158 | [package.extras] 159 | pipfile_deprecated_finder = ["pipreqs", "requirementslib"] 160 | requirements_deprecated_finder = ["pipreqs", "pip-api"] 161 | plugins = ["setuptools"] 162 | colors = ["colorama (>=0.4.3,<0.5.0)"] 163 | 164 | [[package]] 165 | name = "lazy-object-proxy" 166 | version = "1.7.1" 167 | description = "A fast and thorough lazy object proxy." 168 | category = "dev" 169 | optional = false 170 | python-versions = ">=3.6" 171 | 172 | [[package]] 173 | name = "mccabe" 174 | version = "0.6.1" 175 | description = "McCabe checker, plugin for flake8" 176 | category = "dev" 177 | optional = false 178 | python-versions = "*" 179 | 180 | [[package]] 181 | name = "mypy" 182 | version = "0.931" 183 | description = "Optional static typing for Python" 184 | category = "dev" 185 | optional = false 186 | python-versions = ">=3.6" 187 | 188 | [package.dependencies] 189 | mypy-extensions = ">=0.4.3" 190 | tomli = ">=1.1.0" 191 | typed-ast = {version = ">=1.4.0,<2", markers = "python_version < \"3.8\""} 192 | typing-extensions = ">=3.10" 193 | 194 | [package.extras] 195 | dmypy = ["psutil (>=4.0)"] 196 | python2 = ["typed-ast (>=1.4.0,<2)"] 197 | 198 | [[package]] 199 | name = "mypy-extensions" 200 | version = "0.4.3" 201 | description = "Experimental type system extensions for programs checked with the mypy typechecker." 202 | category = "dev" 203 | optional = false 204 | python-versions = "*" 205 | 206 | [[package]] 207 | name = "packaging" 208 | version = "21.3" 209 | description = "Core utilities for Python packages" 210 | category = "dev" 211 | optional = false 212 | python-versions = ">=3.6" 213 | 214 | [package.dependencies] 215 | pyparsing = ">=2.0.2,<3.0.5 || >3.0.5" 216 | 217 | [[package]] 218 | name = "pathspec" 219 | version = "0.9.0" 220 | description = "Utility library for gitignore style pattern matching of file paths." 221 | category = "dev" 222 | optional = false 223 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" 224 | 225 | [[package]] 226 | name = "platformdirs" 227 | version = "2.5.1" 228 | description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." 229 | category = "dev" 230 | optional = false 231 | python-versions = ">=3.7" 232 | 233 | [package.extras] 234 | test = ["appdirs (==1.4.4)", "pytest (>=6)", "pytest-cov (>=2.7)", "pytest-mock (>=3.6)"] 235 | docs = ["Sphinx (>=4)", "furo (>=2021.7.5b38)", "proselint (>=0.10.2)", "sphinx-autodoc-typehints (>=1.12)"] 236 | 237 | [[package]] 238 | name = "pluggy" 239 | version = "1.0.0" 240 | description = "plugin and hook calling mechanisms for python" 241 | category = "dev" 242 | optional = false 243 | python-versions = ">=3.6" 244 | 245 | [package.dependencies] 246 | importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} 247 | 248 | [package.extras] 249 | dev = ["pre-commit", "tox"] 250 | testing = ["pytest", "pytest-benchmark"] 251 | 252 | [[package]] 253 | name = "py" 254 | version = "1.11.0" 255 | description = "library with cross-python path, ini-parsing, io, code, log facilities" 256 | category = "dev" 257 | optional = false 258 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 259 | 260 | [[package]] 261 | name = "pycparser" 262 | version = "2.21" 263 | description = "C parser in Python" 264 | category = "main" 265 | optional = false 266 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 267 | 268 | [[package]] 269 | name = "pylint" 270 | version = "2.12.2" 271 | description = "python code static checker" 272 | category = "dev" 273 | optional = false 274 | python-versions = ">=3.6.2" 275 | 276 | [package.dependencies] 277 | astroid = ">=2.9.0,<2.10" 278 | colorama = {version = "*", markers = "sys_platform == \"win32\""} 279 | isort = ">=4.2.5,<6" 280 | mccabe = ">=0.6,<0.7" 281 | platformdirs = ">=2.2.0" 282 | toml = ">=0.9.2" 283 | typing-extensions = {version = ">=3.10.0", markers = "python_version < \"3.10\""} 284 | 285 | [[package]] 286 | name = "pyparsing" 287 | version = "3.0.7" 288 | description = "Python parsing module" 289 | category = "dev" 290 | optional = false 291 | python-versions = ">=3.6" 292 | 293 | [package.extras] 294 | diagrams = ["jinja2", "railroad-diagrams"] 295 | 296 | [[package]] 297 | name = "pytest" 298 | version = "7.0.1" 299 | description = "pytest: simple powerful testing with Python" 300 | category = "dev" 301 | optional = false 302 | python-versions = ">=3.6" 303 | 304 | [package.dependencies] 305 | atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""} 306 | attrs = ">=19.2.0" 307 | colorama = {version = "*", markers = "sys_platform == \"win32\""} 308 | importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} 309 | iniconfig = "*" 310 | packaging = "*" 311 | pluggy = ">=0.12,<2.0" 312 | py = ">=1.8.2" 313 | tomli = ">=1.0.0" 314 | 315 | [package.extras] 316 | testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "xmlschema"] 317 | 318 | [[package]] 319 | name = "pytest-cov" 320 | version = "3.0.0" 321 | description = "Pytest plugin for measuring coverage." 322 | category = "dev" 323 | optional = false 324 | python-versions = ">=3.6" 325 | 326 | [package.dependencies] 327 | coverage = {version = ">=5.2.1", extras = ["toml"]} 328 | pytest = ">=4.6" 329 | 330 | [package.extras] 331 | testing = ["fields", "hunter", "process-tests", "six", "pytest-xdist", "virtualenv"] 332 | 333 | [[package]] 334 | name = "pytest-sugar" 335 | version = "0.9.4" 336 | description = "pytest-sugar is a plugin for pytest that changes the default look and feel of pytest (e.g. progressbar, show tests that fail instantly)." 337 | category = "dev" 338 | optional = false 339 | python-versions = "*" 340 | 341 | [package.dependencies] 342 | packaging = ">=14.1" 343 | pytest = ">=2.9" 344 | termcolor = ">=1.1.0" 345 | 346 | [[package]] 347 | name = "six" 348 | version = "1.16.0" 349 | description = "Python 2 and 3 compatibility utilities" 350 | category = "dev" 351 | optional = false 352 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" 353 | 354 | [[package]] 355 | name = "termcolor" 356 | version = "1.1.0" 357 | description = "ANSII Color formatting for output in terminal." 358 | category = "dev" 359 | optional = false 360 | python-versions = "*" 361 | 362 | [[package]] 363 | name = "toml" 364 | version = "0.10.2" 365 | description = "Python Library for Tom's Obvious, Minimal Language" 366 | category = "dev" 367 | optional = false 368 | python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" 369 | 370 | [[package]] 371 | name = "tomli" 372 | version = "2.0.1" 373 | description = "A lil' TOML parser" 374 | category = "dev" 375 | optional = false 376 | python-versions = ">=3.7" 377 | 378 | [[package]] 379 | name = "tox" 380 | version = "3.24.5" 381 | description = "tox is a generic virtualenv management and test command line tool" 382 | category = "dev" 383 | optional = false 384 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" 385 | 386 | [package.dependencies] 387 | colorama = {version = ">=0.4.1", markers = "platform_system == \"Windows\""} 388 | filelock = ">=3.0.0" 389 | importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} 390 | packaging = ">=14" 391 | pluggy = ">=0.12.0" 392 | py = ">=1.4.17" 393 | six = ">=1.14.0" 394 | toml = ">=0.9.4" 395 | virtualenv = ">=16.0.0,<20.0.0 || >20.0.0,<20.0.1 || >20.0.1,<20.0.2 || >20.0.2,<20.0.3 || >20.0.3,<20.0.4 || >20.0.4,<20.0.5 || >20.0.5,<20.0.6 || >20.0.6,<20.0.7 || >20.0.7" 396 | 397 | [package.extras] 398 | docs = ["pygments-github-lexers (>=0.0.5)", "sphinx (>=2.0.0)", "sphinxcontrib-autoprogram (>=0.1.5)", "towncrier (>=18.5.0)"] 399 | testing = ["flaky (>=3.4.0)", "freezegun (>=0.3.11)", "pytest (>=4.0.0)", "pytest-cov (>=2.5.1)", "pytest-mock (>=1.10.0)", "pytest-randomly (>=1.0.0)", "psutil (>=5.6.1)", "pathlib2 (>=2.3.3)"] 400 | 401 | [[package]] 402 | name = "typed-ast" 403 | version = "1.5.2" 404 | description = "a fork of Python 2 and 3 ast modules with type comment support" 405 | category = "dev" 406 | optional = false 407 | python-versions = ">=3.6" 408 | 409 | [[package]] 410 | name = "typing-extensions" 411 | version = "4.1.1" 412 | description = "Backported and Experimental Type Hints for Python 3.6+" 413 | category = "dev" 414 | optional = false 415 | python-versions = ">=3.6" 416 | 417 | [[package]] 418 | name = "virtualenv" 419 | version = "20.13.2" 420 | description = "Virtual Python Environment builder" 421 | category = "dev" 422 | optional = false 423 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" 424 | 425 | [package.dependencies] 426 | distlib = ">=0.3.1,<1" 427 | filelock = ">=3.2,<4" 428 | importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} 429 | platformdirs = ">=2,<3" 430 | six = ">=1.9.0,<2" 431 | 432 | [package.extras] 433 | docs = ["proselint (>=0.10.2)", "sphinx (>=3)", "sphinx-argparse (>=0.2.5)", "sphinx-rtd-theme (>=0.4.3)", "towncrier (>=21.3)"] 434 | 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)"] 435 | 436 | [[package]] 437 | name = "wrapt" 438 | version = "1.13.3" 439 | description = "Module for decorators, wrappers and monkey patching." 440 | category = "dev" 441 | optional = false 442 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" 443 | 444 | [[package]] 445 | name = "zipp" 446 | version = "3.7.0" 447 | description = "Backport of pathlib-compatible object wrapper for zip files" 448 | category = "dev" 449 | optional = false 450 | python-versions = ">=3.7" 451 | 452 | [package.extras] 453 | docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] 454 | 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"] 455 | 456 | [metadata] 457 | lock-version = "1.1" 458 | python-versions = "^3.7" 459 | content-hash = "6fa17f30b9aea6c3aee85e0a6eb61c206a3595cc84607877952f80ba5badab0b" 460 | 461 | [metadata.files] 462 | astroid = [ 463 | {file = "astroid-2.9.3-py3-none-any.whl", hash = "sha256:506daabe5edffb7e696ad82483ad0228245a9742ed7d2d8c9cdb31537decf9f6"}, 464 | {file = "astroid-2.9.3.tar.gz", hash = "sha256:1efdf4e867d4d8ba4a9f6cf9ce07cd182c4c41de77f23814feb27ca93ca9d877"}, 465 | ] 466 | atomicwrites = [ 467 | {file = "atomicwrites-1.4.0-py2.py3-none-any.whl", hash = "sha256:6d1784dea7c0c8d4a5172b6c620f40b6e4cbfdf96d783691f2e1302a7b88e197"}, 468 | {file = "atomicwrites-1.4.0.tar.gz", hash = "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a"}, 469 | ] 470 | attrs = [ 471 | {file = "attrs-21.4.0-py2.py3-none-any.whl", hash = "sha256:2d27e3784d7a565d36ab851fe94887c5eccd6a463168875832a1be79c82828b4"}, 472 | {file = "attrs-21.4.0.tar.gz", hash = "sha256:626ba8234211db98e869df76230a137c4c40a12d72445c45d5f5b716f076e2fd"}, 473 | ] 474 | black = [ 475 | {file = "black-22.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:1297c63b9e1b96a3d0da2d85d11cd9bf8664251fd69ddac068b98dc4f34f73b6"}, 476 | {file = "black-22.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2ff96450d3ad9ea499fc4c60e425a1439c2120cbbc1ab959ff20f7c76ec7e866"}, 477 | {file = "black-22.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0e21e1f1efa65a50e3960edd068b6ae6d64ad6235bd8bfea116a03b21836af71"}, 478 | {file = "black-22.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e2f69158a7d120fd641d1fa9a921d898e20d52e44a74a6fbbcc570a62a6bc8ab"}, 479 | {file = "black-22.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:228b5ae2c8e3d6227e4bde5920d2fc66cc3400fde7bcc74f480cb07ef0b570d5"}, 480 | {file = "black-22.1.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:b1a5ed73ab4c482208d20434f700d514f66ffe2840f63a6252ecc43a9bc77e8a"}, 481 | {file = "black-22.1.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:35944b7100af4a985abfcaa860b06af15590deb1f392f06c8683b4381e8eeaf0"}, 482 | {file = "black-22.1.0-cp36-cp36m-win_amd64.whl", hash = "sha256:7835fee5238fc0a0baf6c9268fb816b5f5cd9b8793423a75e8cd663c48d073ba"}, 483 | {file = "black-22.1.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:dae63f2dbf82882fa3b2a3c49c32bffe144970a573cd68d247af6560fc493ae1"}, 484 | {file = "black-22.1.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5fa1db02410b1924b6749c245ab38d30621564e658297484952f3d8a39fce7e8"}, 485 | {file = "black-22.1.0-cp37-cp37m-win_amd64.whl", hash = "sha256:c8226f50b8c34a14608b848dc23a46e5d08397d009446353dad45e04af0c8e28"}, 486 | {file = "black-22.1.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:2d6f331c02f0f40aa51a22e479c8209d37fcd520c77721c034517d44eecf5912"}, 487 | {file = "black-22.1.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:742ce9af3086e5bd07e58c8feb09dbb2b047b7f566eb5f5bc63fd455814979f3"}, 488 | {file = "black-22.1.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:fdb8754b453fb15fad3f72cd9cad3e16776f0964d67cf30ebcbf10327a3777a3"}, 489 | {file = "black-22.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5660feab44c2e3cb24b2419b998846cbb01c23c7fe645fee45087efa3da2d61"}, 490 | {file = "black-22.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:6f2f01381f91c1efb1451998bd65a129b3ed6f64f79663a55fe0e9b74a5f81fd"}, 491 | {file = "black-22.1.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:efbadd9b52c060a8fc3b9658744091cb33c31f830b3f074422ed27bad2b18e8f"}, 492 | {file = "black-22.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8871fcb4b447206904932b54b567923e5be802b9b19b744fdff092bd2f3118d0"}, 493 | {file = "black-22.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ccad888050f5393f0d6029deea2a33e5ae371fd182a697313bdbd835d3edaf9c"}, 494 | {file = "black-22.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:07e5c049442d7ca1a2fc273c79d1aecbbf1bc858f62e8184abe1ad175c4f7cc2"}, 495 | {file = "black-22.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:373922fc66676133ddc3e754e4509196a8c392fec3f5ca4486673e685a421321"}, 496 | {file = "black-22.1.0-py3-none-any.whl", hash = "sha256:3524739d76b6b3ed1132422bf9d82123cd1705086723bc3e235ca39fd21c667d"}, 497 | {file = "black-22.1.0.tar.gz", hash = "sha256:a7c0192d35635f6fc1174be575cb7915e92e5dd629ee79fdaf0dcfa41a80afb5"}, 498 | ] 499 | cffi = [ 500 | {file = "cffi-1.15.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:c2502a1a03b6312837279c8c1bd3ebedf6c12c4228ddbad40912d671ccc8a962"}, 501 | {file = "cffi-1.15.0-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:23cfe892bd5dd8941608f93348c0737e369e51c100d03718f108bf1add7bd6d0"}, 502 | {file = "cffi-1.15.0-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:41d45de54cd277a7878919867c0f08b0cf817605e4eb94093e7516505d3c8d14"}, 503 | {file = "cffi-1.15.0-cp27-cp27m-win32.whl", hash = "sha256:4a306fa632e8f0928956a41fa8e1d6243c71e7eb59ffbd165fc0b41e316b2474"}, 504 | {file = "cffi-1.15.0-cp27-cp27m-win_amd64.whl", hash = "sha256:e7022a66d9b55e93e1a845d8c9eba2a1bebd4966cd8bfc25d9cd07d515b33fa6"}, 505 | {file = "cffi-1.15.0-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:14cd121ea63ecdae71efa69c15c5543a4b5fbcd0bbe2aad864baca0063cecf27"}, 506 | {file = "cffi-1.15.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:d4d692a89c5cf08a8557fdeb329b82e7bf609aadfaed6c0d79f5a449a3c7c023"}, 507 | {file = "cffi-1.15.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0104fb5ae2391d46a4cb082abdd5c69ea4eab79d8d44eaaf79f1b1fd806ee4c2"}, 508 | {file = "cffi-1.15.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:91ec59c33514b7c7559a6acda53bbfe1b283949c34fe7440bcf917f96ac0723e"}, 509 | {file = "cffi-1.15.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:f5c7150ad32ba43a07c4479f40241756145a1f03b43480e058cfd862bf5041c7"}, 510 | {file = "cffi-1.15.0-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:00c878c90cb53ccfaae6b8bc18ad05d2036553e6d9d1d9dbcf323bbe83854ca3"}, 511 | {file = "cffi-1.15.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:abb9a20a72ac4e0fdb50dae135ba5e77880518e742077ced47eb1499e29a443c"}, 512 | {file = "cffi-1.15.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a5263e363c27b653a90078143adb3d076c1a748ec9ecc78ea2fb916f9b861962"}, 513 | {file = "cffi-1.15.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f54a64f8b0c8ff0b64d18aa76675262e1700f3995182267998c31ae974fbc382"}, 514 | {file = "cffi-1.15.0-cp310-cp310-win32.whl", hash = "sha256:c21c9e3896c23007803a875460fb786118f0cdd4434359577ea25eb556e34c55"}, 515 | {file = "cffi-1.15.0-cp310-cp310-win_amd64.whl", hash = "sha256:5e069f72d497312b24fcc02073d70cb989045d1c91cbd53979366077959933e0"}, 516 | {file = "cffi-1.15.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:64d4ec9f448dfe041705426000cc13e34e6e5bb13736e9fd62e34a0b0c41566e"}, 517 | {file = "cffi-1.15.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2756c88cbb94231c7a147402476be2c4df2f6078099a6f4a480d239a8817ae39"}, 518 | {file = "cffi-1.15.0-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3b96a311ac60a3f6be21d2572e46ce67f09abcf4d09344c49274eb9e0bf345fc"}, 519 | {file = "cffi-1.15.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:75e4024375654472cc27e91cbe9eaa08567f7fbdf822638be2814ce059f58032"}, 520 | {file = "cffi-1.15.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:59888172256cac5629e60e72e86598027aca6bf01fa2465bdb676d37636573e8"}, 521 | {file = "cffi-1.15.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:27c219baf94952ae9d50ec19651a687b826792055353d07648a5695413e0c605"}, 522 | {file = "cffi-1.15.0-cp36-cp36m-win32.whl", hash = "sha256:4958391dbd6249d7ad855b9ca88fae690783a6be9e86df65865058ed81fc860e"}, 523 | {file = "cffi-1.15.0-cp36-cp36m-win_amd64.whl", hash = "sha256:f6f824dc3bce0edab5f427efcfb1d63ee75b6fcb7282900ccaf925be84efb0fc"}, 524 | {file = "cffi-1.15.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:06c48159c1abed75c2e721b1715c379fa3200c7784271b3c46df01383b593636"}, 525 | {file = "cffi-1.15.0-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:c2051981a968d7de9dd2d7b87bcb9c939c74a34626a6e2f8181455dd49ed69e4"}, 526 | {file = "cffi-1.15.0-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:fd8a250edc26254fe5b33be00402e6d287f562b6a5b2152dec302fa15bb3e997"}, 527 | {file = "cffi-1.15.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:91d77d2a782be4274da750752bb1650a97bfd8f291022b379bb8e01c66b4e96b"}, 528 | {file = "cffi-1.15.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:45db3a33139e9c8f7c09234b5784a5e33d31fd6907800b316decad50af323ff2"}, 529 | {file = "cffi-1.15.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:263cc3d821c4ab2213cbe8cd8b355a7f72a8324577dc865ef98487c1aeee2bc7"}, 530 | {file = "cffi-1.15.0-cp37-cp37m-win32.whl", hash = "sha256:17771976e82e9f94976180f76468546834d22a7cc404b17c22df2a2c81db0c66"}, 531 | {file = "cffi-1.15.0-cp37-cp37m-win_amd64.whl", hash = "sha256:3415c89f9204ee60cd09b235810be700e993e343a408693e80ce7f6a40108029"}, 532 | {file = "cffi-1.15.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:4238e6dab5d6a8ba812de994bbb0a79bddbdf80994e4ce802b6f6f3142fcc880"}, 533 | {file = "cffi-1.15.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:0808014eb713677ec1292301ea4c81ad277b6cdf2fdd90fd540af98c0b101d20"}, 534 | {file = "cffi-1.15.0-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:57e9ac9ccc3101fac9d6014fba037473e4358ef4e89f8e181f8951a2c0162024"}, 535 | {file = "cffi-1.15.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b6c2ea03845c9f501ed1313e78de148cd3f6cad741a75d43a29b43da27f2e1e"}, 536 | {file = "cffi-1.15.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:10dffb601ccfb65262a27233ac273d552ddc4d8ae1bf93b21c94b8511bffe728"}, 537 | {file = "cffi-1.15.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:786902fb9ba7433aae840e0ed609f45c7bcd4e225ebb9c753aa39725bb3e6ad6"}, 538 | {file = "cffi-1.15.0-cp38-cp38-win32.whl", hash = "sha256:da5db4e883f1ce37f55c667e5c0de439df76ac4cb55964655906306918e7363c"}, 539 | {file = "cffi-1.15.0-cp38-cp38-win_amd64.whl", hash = "sha256:181dee03b1170ff1969489acf1c26533710231c58f95534e3edac87fff06c443"}, 540 | {file = "cffi-1.15.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:45e8636704eacc432a206ac7345a5d3d2c62d95a507ec70d62f23cd91770482a"}, 541 | {file = "cffi-1.15.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:31fb708d9d7c3f49a60f04cf5b119aeefe5644daba1cd2a0fe389b674fd1de37"}, 542 | {file = "cffi-1.15.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:6dc2737a3674b3e344847c8686cf29e500584ccad76204efea14f451d4cc669a"}, 543 | {file = "cffi-1.15.0-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:74fdfdbfdc48d3f47148976f49fab3251e550a8720bebc99bf1483f5bfb5db3e"}, 544 | {file = "cffi-1.15.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffaa5c925128e29efbde7301d8ecaf35c8c60ffbcd6a1ffd3a552177c8e5e796"}, 545 | {file = "cffi-1.15.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3f7d084648d77af029acb79a0ff49a0ad7e9d09057a9bf46596dac9514dc07df"}, 546 | {file = "cffi-1.15.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ef1f279350da2c586a69d32fc8733092fd32cc8ac95139a00377841f59a3f8d8"}, 547 | {file = "cffi-1.15.0-cp39-cp39-win32.whl", hash = "sha256:2a23af14f408d53d5e6cd4e3d9a24ff9e05906ad574822a10563efcef137979a"}, 548 | {file = "cffi-1.15.0-cp39-cp39-win_amd64.whl", hash = "sha256:3773c4d81e6e818df2efbc7dd77325ca0dcb688116050fb2b3011218eda36139"}, 549 | {file = "cffi-1.15.0.tar.gz", hash = "sha256:920f0d66a896c2d99f0adbb391f990a84091179542c205fa53ce5787aff87954"}, 550 | ] 551 | click = [ 552 | {file = "click-8.0.4-py3-none-any.whl", hash = "sha256:6a7a62563bbfabfda3a38f3023a1db4a35978c0abd76f6c9605ecd6554d6d9b1"}, 553 | {file = "click-8.0.4.tar.gz", hash = "sha256:8458d7b1287c5fb128c90e23381cf99dcde74beaf6c7ff6384ce84d6fe090adb"}, 554 | ] 555 | colorama = [ 556 | {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"}, 557 | {file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"}, 558 | ] 559 | coverage = [ 560 | {file = "coverage-6.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9b27d894748475fa858f9597c0ee1d4829f44683f3813633aaf94b19cb5453cf"}, 561 | {file = "coverage-6.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:37d1141ad6b2466a7b53a22e08fe76994c2d35a5b6b469590424a9953155afac"}, 562 | {file = "coverage-6.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f9987b0354b06d4df0f4d3e0ec1ae76d7ce7cbca9a2f98c25041eb79eec766f1"}, 563 | {file = "coverage-6.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:26e2deacd414fc2f97dd9f7676ee3eaecd299ca751412d89f40bc01557a6b1b4"}, 564 | {file = "coverage-6.3.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4dd8bafa458b5c7d061540f1ee9f18025a68e2d8471b3e858a9dad47c8d41903"}, 565 | {file = "coverage-6.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:46191097ebc381fbf89bdce207a6c107ac4ec0890d8d20f3360345ff5976155c"}, 566 | {file = "coverage-6.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6f89d05e028d274ce4fa1a86887b071ae1755082ef94a6740238cd7a8178804f"}, 567 | {file = "coverage-6.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:58303469e9a272b4abdb9e302a780072c0633cdcc0165db7eec0f9e32f901e05"}, 568 | {file = "coverage-6.3.2-cp310-cp310-win32.whl", hash = "sha256:2fea046bfb455510e05be95e879f0e768d45c10c11509e20e06d8fcaa31d9e39"}, 569 | {file = "coverage-6.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:a2a8b8bcc399edb4347a5ca8b9b87e7524c0967b335fbb08a83c8421489ddee1"}, 570 | {file = "coverage-6.3.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:f1555ea6d6da108e1999b2463ea1003fe03f29213e459145e70edbaf3e004aaa"}, 571 | {file = "coverage-6.3.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e5f4e1edcf57ce94e5475fe09e5afa3e3145081318e5fd1a43a6b4539a97e518"}, 572 | {file = "coverage-6.3.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7a15dc0a14008f1da3d1ebd44bdda3e357dbabdf5a0b5034d38fcde0b5c234b7"}, 573 | {file = "coverage-6.3.2-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21b7745788866028adeb1e0eca3bf1101109e2dc58456cb49d2d9b99a8c516e6"}, 574 | {file = "coverage-6.3.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:8ce257cac556cb03be4a248d92ed36904a59a4a5ff55a994e92214cde15c5bad"}, 575 | {file = "coverage-6.3.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:b0be84e5a6209858a1d3e8d1806c46214e867ce1b0fd32e4ea03f4bd8b2e3359"}, 576 | {file = "coverage-6.3.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:acf53bc2cf7282ab9b8ba346746afe703474004d9e566ad164c91a7a59f188a4"}, 577 | {file = "coverage-6.3.2-cp37-cp37m-win32.whl", hash = "sha256:8bdde1177f2311ee552f47ae6e5aa7750c0e3291ca6b75f71f7ffe1f1dab3dca"}, 578 | {file = "coverage-6.3.2-cp37-cp37m-win_amd64.whl", hash = "sha256:b31651d018b23ec463e95cf10070d0b2c548aa950a03d0b559eaa11c7e5a6fa3"}, 579 | {file = "coverage-6.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:07e6db90cd9686c767dcc593dff16c8c09f9814f5e9c51034066cad3373b914d"}, 580 | {file = "coverage-6.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2c6dbb42f3ad25760010c45191e9757e7dce981cbfb90e42feef301d71540059"}, 581 | {file = "coverage-6.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c76aeef1b95aff3905fb2ae2d96e319caca5b76fa41d3470b19d4e4a3a313512"}, 582 | {file = "coverage-6.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8cf5cfcb1521dc3255d845d9dca3ff204b3229401994ef8d1984b32746bb45ca"}, 583 | {file = "coverage-6.3.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8fbbdc8d55990eac1b0919ca69eb5a988a802b854488c34b8f37f3e2025fa90d"}, 584 | {file = "coverage-6.3.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:ec6bc7fe73a938933d4178c9b23c4e0568e43e220aef9472c4f6044bfc6dd0f0"}, 585 | {file = "coverage-6.3.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:9baff2a45ae1f17c8078452e9e5962e518eab705e50a0aa8083733ea7d45f3a6"}, 586 | {file = "coverage-6.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:fd9e830e9d8d89b20ab1e5af09b32d33e1a08ef4c4e14411e559556fd788e6b2"}, 587 | {file = "coverage-6.3.2-cp38-cp38-win32.whl", hash = "sha256:f7331dbf301b7289013175087636bbaf5b2405e57259dd2c42fdcc9fcc47325e"}, 588 | {file = "coverage-6.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:68353fe7cdf91f109fc7d474461b46e7f1f14e533e911a2a2cbb8b0fc8613cf1"}, 589 | {file = "coverage-6.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b78e5afb39941572209f71866aa0b206c12f0109835aa0d601e41552f9b3e620"}, 590 | {file = "coverage-6.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4e21876082ed887baed0146fe222f861b5815455ada3b33b890f4105d806128d"}, 591 | {file = "coverage-6.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:34626a7eee2a3da12af0507780bb51eb52dca0e1751fd1471d0810539cefb536"}, 592 | {file = "coverage-6.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1ebf730d2381158ecf3dfd4453fbca0613e16eaa547b4170e2450c9707665ce7"}, 593 | {file = "coverage-6.3.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd6fe30bd519694b356cbfcaca9bd5c1737cddd20778c6a581ae20dc8c04def2"}, 594 | {file = "coverage-6.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:96f8a1cb43ca1422f36492bebe63312d396491a9165ed3b9231e778d43a7fca4"}, 595 | {file = "coverage-6.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:dd035edafefee4d573140a76fdc785dc38829fe5a455c4bb12bac8c20cfc3d69"}, 596 | {file = "coverage-6.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5ca5aeb4344b30d0bec47481536b8ba1181d50dbe783b0e4ad03c95dc1296684"}, 597 | {file = "coverage-6.3.2-cp39-cp39-win32.whl", hash = "sha256:f5fa5803f47e095d7ad8443d28b01d48c0359484fec1b9d8606d0e3282084bc4"}, 598 | {file = "coverage-6.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:9548f10d8be799551eb3a9c74bbf2b4934ddb330e08a73320123c07f95cc2d92"}, 599 | {file = "coverage-6.3.2-pp36.pp37.pp38-none-any.whl", hash = "sha256:18d520c6860515a771708937d2f78f63cc47ab3b80cb78e86573b0a760161faf"}, 600 | {file = "coverage-6.3.2.tar.gz", hash = "sha256:03e2a7826086b91ef345ff18742ee9fc47a6839ccd517061ef8fa1976e652ce9"}, 601 | ] 602 | distlib = [ 603 | {file = "distlib-0.3.4-py2.py3-none-any.whl", hash = "sha256:6564fe0a8f51e734df6333d08b8b94d4ea8ee6b99b5ed50613f731fd4089f34b"}, 604 | {file = "distlib-0.3.4.zip", hash = "sha256:e4b58818180336dc9c529bfb9a0b58728ffc09ad92027a3f30b7cd91e3458579"}, 605 | ] 606 | filelock = [ 607 | {file = "filelock-3.6.0-py3-none-any.whl", hash = "sha256:f8314284bfffbdcfa0ff3d7992b023d4c628ced6feb957351d4c48d059f56bc0"}, 608 | {file = "filelock-3.6.0.tar.gz", hash = "sha256:9cd540a9352e432c7246a48fe4e8712b10acb1df2ad1f30e8c070b82ae1fed85"}, 609 | ] 610 | importlib-metadata = [ 611 | {file = "importlib_metadata-4.11.2-py3-none-any.whl", hash = "sha256:d16e8c1deb60de41b8e8ed21c1a7b947b0bc62fab7e1d470bcdf331cea2e6735"}, 612 | {file = "importlib_metadata-4.11.2.tar.gz", hash = "sha256:b36ffa925fe3139b2f6ff11d6925ffd4fa7bc47870165e3ac260ac7b4f91e6ac"}, 613 | ] 614 | iniconfig = [ 615 | {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, 616 | {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"}, 617 | ] 618 | isort = [ 619 | {file = "isort-5.10.1-py3-none-any.whl", hash = "sha256:6f62d78e2f89b4500b080fe3a81690850cd254227f27f75c3a0c491a1f351ba7"}, 620 | {file = "isort-5.10.1.tar.gz", hash = "sha256:e8443a5e7a020e9d7f97f1d7d9cd17c88bcb3bc7e218bf9cf5095fe550be2951"}, 621 | ] 622 | lazy-object-proxy = [ 623 | {file = "lazy-object-proxy-1.7.1.tar.gz", hash = "sha256:d609c75b986def706743cdebe5e47553f4a5a1da9c5ff66d76013ef396b5a8a4"}, 624 | {file = "lazy_object_proxy-1.7.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bb8c5fd1684d60a9902c60ebe276da1f2281a318ca16c1d0a96db28f62e9166b"}, 625 | {file = "lazy_object_proxy-1.7.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a57d51ed2997e97f3b8e3500c984db50a554bb5db56c50b5dab1b41339b37e36"}, 626 | {file = "lazy_object_proxy-1.7.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd45683c3caddf83abbb1249b653a266e7069a09f486daa8863fb0e7496a9fdb"}, 627 | {file = "lazy_object_proxy-1.7.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:8561da8b3dd22d696244d6d0d5330618c993a215070f473b699e00cf1f3f6443"}, 628 | {file = "lazy_object_proxy-1.7.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fccdf7c2c5821a8cbd0a9440a456f5050492f2270bd54e94360cac663398739b"}, 629 | {file = "lazy_object_proxy-1.7.1-cp310-cp310-win32.whl", hash = "sha256:898322f8d078f2654d275124a8dd19b079080ae977033b713f677afcfc88e2b9"}, 630 | {file = "lazy_object_proxy-1.7.1-cp310-cp310-win_amd64.whl", hash = "sha256:85b232e791f2229a4f55840ed54706110c80c0a210d076eee093f2b2e33e1bfd"}, 631 | {file = "lazy_object_proxy-1.7.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:46ff647e76f106bb444b4533bb4153c7370cdf52efc62ccfc1a28bdb3cc95442"}, 632 | {file = "lazy_object_proxy-1.7.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:12f3bb77efe1367b2515f8cb4790a11cffae889148ad33adad07b9b55e0ab22c"}, 633 | {file = "lazy_object_proxy-1.7.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c19814163728941bb871240d45c4c30d33b8a2e85972c44d4e63dd7107faba44"}, 634 | {file = "lazy_object_proxy-1.7.1-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:e40f2013d96d30217a51eeb1db28c9ac41e9d0ee915ef9d00da639c5b63f01a1"}, 635 | {file = "lazy_object_proxy-1.7.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:2052837718516a94940867e16b1bb10edb069ab475c3ad84fd1e1a6dd2c0fcfc"}, 636 | {file = "lazy_object_proxy-1.7.1-cp36-cp36m-win32.whl", hash = "sha256:6a24357267aa976abab660b1d47a34aaf07259a0c3859a34e536f1ee6e76b5bb"}, 637 | {file = "lazy_object_proxy-1.7.1-cp36-cp36m-win_amd64.whl", hash = "sha256:6aff3fe5de0831867092e017cf67e2750c6a1c7d88d84d2481bd84a2e019ec35"}, 638 | {file = "lazy_object_proxy-1.7.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:6a6e94c7b02641d1311228a102607ecd576f70734dc3d5e22610111aeacba8a0"}, 639 | {file = "lazy_object_proxy-1.7.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c4ce15276a1a14549d7e81c243b887293904ad2d94ad767f42df91e75fd7b5b6"}, 640 | {file = "lazy_object_proxy-1.7.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e368b7f7eac182a59ff1f81d5f3802161932a41dc1b1cc45c1f757dc876b5d2c"}, 641 | {file = "lazy_object_proxy-1.7.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:6ecbb350991d6434e1388bee761ece3260e5228952b1f0c46ffc800eb313ff42"}, 642 | {file = "lazy_object_proxy-1.7.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:553b0f0d8dbf21890dd66edd771f9b1b5f51bd912fa5f26de4449bfc5af5e029"}, 643 | {file = "lazy_object_proxy-1.7.1-cp37-cp37m-win32.whl", hash = "sha256:c7a683c37a8a24f6428c28c561c80d5f4fd316ddcf0c7cab999b15ab3f5c5c69"}, 644 | {file = "lazy_object_proxy-1.7.1-cp37-cp37m-win_amd64.whl", hash = "sha256:df2631f9d67259dc9620d831384ed7732a198eb434eadf69aea95ad18c587a28"}, 645 | {file = "lazy_object_proxy-1.7.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:07fa44286cda977bd4803b656ffc1c9b7e3bc7dff7d34263446aec8f8c96f88a"}, 646 | {file = "lazy_object_proxy-1.7.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4dca6244e4121c74cc20542c2ca39e5c4a5027c81d112bfb893cf0790f96f57e"}, 647 | {file = "lazy_object_proxy-1.7.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:91ba172fc5b03978764d1df5144b4ba4ab13290d7bab7a50f12d8117f8630c38"}, 648 | {file = "lazy_object_proxy-1.7.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:043651b6cb706eee4f91854da4a089816a6606c1428fd391573ef8cb642ae4f7"}, 649 | {file = "lazy_object_proxy-1.7.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b9e89b87c707dd769c4ea91f7a31538888aad05c116a59820f28d59b3ebfe25a"}, 650 | {file = "lazy_object_proxy-1.7.1-cp38-cp38-win32.whl", hash = "sha256:9d166602b525bf54ac994cf833c385bfcc341b364e3ee71e3bf5a1336e677b55"}, 651 | {file = "lazy_object_proxy-1.7.1-cp38-cp38-win_amd64.whl", hash = "sha256:8f3953eb575b45480db6568306893f0bd9d8dfeeebd46812aa09ca9579595148"}, 652 | {file = "lazy_object_proxy-1.7.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:dd7ed7429dbb6c494aa9bc4e09d94b778a3579be699f9d67da7e6804c422d3de"}, 653 | {file = "lazy_object_proxy-1.7.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:70ed0c2b380eb6248abdef3cd425fc52f0abd92d2b07ce26359fcbc399f636ad"}, 654 | {file = "lazy_object_proxy-1.7.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7096a5e0c1115ec82641afbdd70451a144558ea5cf564a896294e346eb611be1"}, 655 | {file = "lazy_object_proxy-1.7.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:f769457a639403073968d118bc70110e7dce294688009f5c24ab78800ae56dc8"}, 656 | {file = "lazy_object_proxy-1.7.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:39b0e26725c5023757fc1ab2a89ef9d7ab23b84f9251e28f9cc114d5b59c1b09"}, 657 | {file = "lazy_object_proxy-1.7.1-cp39-cp39-win32.whl", hash = "sha256:2130db8ed69a48a3440103d4a520b89d8a9405f1b06e2cc81640509e8bf6548f"}, 658 | {file = "lazy_object_proxy-1.7.1-cp39-cp39-win_amd64.whl", hash = "sha256:677ea950bef409b47e51e733283544ac3d660b709cfce7b187f5ace137960d61"}, 659 | {file = "lazy_object_proxy-1.7.1-pp37.pp38-none-any.whl", hash = "sha256:d66906d5785da8e0be7360912e99c9188b70f52c422f9fc18223347235691a84"}, 660 | ] 661 | mccabe = [ 662 | {file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"}, 663 | {file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"}, 664 | ] 665 | mypy = [ 666 | {file = "mypy-0.931-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:3c5b42d0815e15518b1f0990cff7a705805961613e701db60387e6fb663fe78a"}, 667 | {file = "mypy-0.931-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c89702cac5b302f0c5d33b172d2b55b5df2bede3344a2fbed99ff96bddb2cf00"}, 668 | {file = "mypy-0.931-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:300717a07ad09525401a508ef5d105e6b56646f7942eb92715a1c8d610149714"}, 669 | {file = "mypy-0.931-cp310-cp310-win_amd64.whl", hash = "sha256:7b3f6f557ba4afc7f2ce6d3215d5db279bcf120b3cfd0add20a5d4f4abdae5bc"}, 670 | {file = "mypy-0.931-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:1bf752559797c897cdd2c65f7b60c2b6969ffe458417b8d947b8340cc9cec08d"}, 671 | {file = "mypy-0.931-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:4365c60266b95a3f216a3047f1d8e3f895da6c7402e9e1ddfab96393122cc58d"}, 672 | {file = "mypy-0.931-cp36-cp36m-win_amd64.whl", hash = "sha256:1b65714dc296a7991000b6ee59a35b3f550e0073411ac9d3202f6516621ba66c"}, 673 | {file = "mypy-0.931-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:e839191b8da5b4e5d805f940537efcaa13ea5dd98418f06dc585d2891d228cf0"}, 674 | {file = "mypy-0.931-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:50c7346a46dc76a4ed88f3277d4959de8a2bd0a0fa47fa87a4cde36fe247ac05"}, 675 | {file = "mypy-0.931-cp37-cp37m-win_amd64.whl", hash = "sha256:d8f1ff62f7a879c9fe5917b3f9eb93a79b78aad47b533911b853a757223f72e7"}, 676 | {file = "mypy-0.931-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f9fe20d0872b26c4bba1c1be02c5340de1019530302cf2dcc85c7f9fc3252ae0"}, 677 | {file = "mypy-0.931-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:1b06268df7eb53a8feea99cbfff77a6e2b205e70bf31743e786678ef87ee8069"}, 678 | {file = "mypy-0.931-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:8c11003aaeaf7cc2d0f1bc101c1cc9454ec4cc9cb825aef3cafff8a5fdf4c799"}, 679 | {file = "mypy-0.931-cp38-cp38-win_amd64.whl", hash = "sha256:d9d2b84b2007cea426e327d2483238f040c49405a6bf4074f605f0156c91a47a"}, 680 | {file = "mypy-0.931-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ff3bf387c14c805ab1388185dd22d6b210824e164d4bb324b195ff34e322d166"}, 681 | {file = "mypy-0.931-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5b56154f8c09427bae082b32275a21f500b24d93c88d69a5e82f3978018a0266"}, 682 | {file = "mypy-0.931-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:8ca7f8c4b1584d63c9a0f827c37ba7a47226c19a23a753d52e5b5eddb201afcd"}, 683 | {file = "mypy-0.931-cp39-cp39-win_amd64.whl", hash = "sha256:74f7eccbfd436abe9c352ad9fb65872cc0f1f0a868e9d9c44db0893440f0c697"}, 684 | {file = "mypy-0.931-py3-none-any.whl", hash = "sha256:1171f2e0859cfff2d366da2c7092b06130f232c636a3f7301e3feb8b41f6377d"}, 685 | {file = "mypy-0.931.tar.gz", hash = "sha256:0038b21890867793581e4cb0d810829f5fd4441aa75796b53033af3aa30430ce"}, 686 | ] 687 | mypy-extensions = [ 688 | {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"}, 689 | {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"}, 690 | ] 691 | packaging = [ 692 | {file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"}, 693 | {file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"}, 694 | ] 695 | pathspec = [ 696 | {file = "pathspec-0.9.0-py2.py3-none-any.whl", hash = "sha256:7d15c4ddb0b5c802d161efc417ec1a2558ea2653c2e8ad9c19098201dc1c993a"}, 697 | {file = "pathspec-0.9.0.tar.gz", hash = "sha256:e564499435a2673d586f6b2130bb5b95f04a3ba06f81b8f895b651a3c76aabb1"}, 698 | ] 699 | platformdirs = [ 700 | {file = "platformdirs-2.5.1-py3-none-any.whl", hash = "sha256:bcae7cab893c2d310a711b70b24efb93334febe65f8de776ee320b517471e227"}, 701 | {file = "platformdirs-2.5.1.tar.gz", hash = "sha256:7535e70dfa32e84d4b34996ea99c5e432fa29a708d0f4e394bbcb2a8faa4f16d"}, 702 | ] 703 | pluggy = [ 704 | {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"}, 705 | {file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"}, 706 | ] 707 | py = [ 708 | {file = "py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"}, 709 | {file = "py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719"}, 710 | ] 711 | pycparser = [ 712 | {file = "pycparser-2.21-py2.py3-none-any.whl", hash = "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9"}, 713 | {file = "pycparser-2.21.tar.gz", hash = "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"}, 714 | ] 715 | pylint = [ 716 | {file = "pylint-2.12.2-py3-none-any.whl", hash = "sha256:daabda3f7ed9d1c60f52d563b1b854632fd90035bcf01443e234d3dc794e3b74"}, 717 | {file = "pylint-2.12.2.tar.gz", hash = "sha256:9d945a73640e1fec07ee34b42f5669b770c759acd536ec7b16d7e4b87a9c9ff9"}, 718 | ] 719 | pyparsing = [ 720 | {file = "pyparsing-3.0.7-py3-none-any.whl", hash = "sha256:a6c06a88f252e6c322f65faf8f418b16213b51bdfaece0524c1c1bc30c63c484"}, 721 | {file = "pyparsing-3.0.7.tar.gz", hash = "sha256:18ee9022775d270c55187733956460083db60b37d0d0fb357445f3094eed3eea"}, 722 | ] 723 | pytest = [ 724 | {file = "pytest-7.0.1-py3-none-any.whl", hash = "sha256:9ce3ff477af913ecf6321fe337b93a2c0dcf2a0a1439c43f5452112c1e4280db"}, 725 | {file = "pytest-7.0.1.tar.gz", hash = "sha256:e30905a0c131d3d94b89624a1cc5afec3e0ba2fbdb151867d8e0ebd49850f171"}, 726 | ] 727 | pytest-cov = [ 728 | {file = "pytest-cov-3.0.0.tar.gz", hash = "sha256:e7f0f5b1617d2210a2cabc266dfe2f4c75a8d32fb89eafb7ad9d06f6d076d470"}, 729 | {file = "pytest_cov-3.0.0-py3-none-any.whl", hash = "sha256:578d5d15ac4a25e5f961c938b85a05b09fdaae9deef3bb6de9a6e766622ca7a6"}, 730 | ] 731 | pytest-sugar = [ 732 | {file = "pytest-sugar-0.9.4.tar.gz", hash = "sha256:b1b2186b0a72aada6859bea2a5764145e3aaa2c1cfbb23c3a19b5f7b697563d3"}, 733 | ] 734 | six = [ 735 | {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, 736 | {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, 737 | ] 738 | termcolor = [ 739 | {file = "termcolor-1.1.0.tar.gz", hash = "sha256:1d6d69ce66211143803fbc56652b41d73b4a400a2891d7bf7a1cdf4c02de613b"}, 740 | ] 741 | toml = [ 742 | {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, 743 | {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, 744 | ] 745 | tomli = [ 746 | {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, 747 | {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, 748 | ] 749 | tox = [ 750 | {file = "tox-3.24.5-py2.py3-none-any.whl", hash = "sha256:be3362472a33094bce26727f5f771ca0facf6dafa217f65875314e9a6600c95c"}, 751 | {file = "tox-3.24.5.tar.gz", hash = "sha256:67e0e32c90e278251fea45b696d0fef3879089ccbe979b0c556d35d5a70e2993"}, 752 | ] 753 | typed-ast = [ 754 | {file = "typed_ast-1.5.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:183b183b7771a508395d2cbffd6db67d6ad52958a5fdc99f450d954003900266"}, 755 | {file = "typed_ast-1.5.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:676d051b1da67a852c0447621fdd11c4e104827417bf216092ec3e286f7da596"}, 756 | {file = "typed_ast-1.5.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bc2542e83ac8399752bc16e0b35e038bdb659ba237f4222616b4e83fb9654985"}, 757 | {file = "typed_ast-1.5.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:74cac86cc586db8dfda0ce65d8bcd2bf17b58668dfcc3652762f3ef0e6677e76"}, 758 | {file = "typed_ast-1.5.2-cp310-cp310-win_amd64.whl", hash = "sha256:18fe320f354d6f9ad3147859b6e16649a0781425268c4dde596093177660e71a"}, 759 | {file = "typed_ast-1.5.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:31d8c6b2df19a777bc8826770b872a45a1f30cfefcfd729491baa5237faae837"}, 760 | {file = "typed_ast-1.5.2-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:963a0ccc9a4188524e6e6d39b12c9ca24cc2d45a71cfdd04a26d883c922b4b78"}, 761 | {file = "typed_ast-1.5.2-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:0eb77764ea470f14fcbb89d51bc6bbf5e7623446ac4ed06cbd9ca9495b62e36e"}, 762 | {file = "typed_ast-1.5.2-cp36-cp36m-win_amd64.whl", hash = "sha256:294a6903a4d087db805a7656989f613371915fc45c8cc0ddc5c5a0a8ad9bea4d"}, 763 | {file = "typed_ast-1.5.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:26a432dc219c6b6f38be20a958cbe1abffcc5492821d7e27f08606ef99e0dffd"}, 764 | {file = "typed_ast-1.5.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c7407cfcad702f0b6c0e0f3e7ab876cd1d2c13b14ce770e412c0c4b9728a0f88"}, 765 | {file = "typed_ast-1.5.2-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f30ddd110634c2d7534b2d4e0e22967e88366b0d356b24de87419cc4410c41b7"}, 766 | {file = "typed_ast-1.5.2-cp37-cp37m-win_amd64.whl", hash = "sha256:8c08d6625bb258179b6e512f55ad20f9dfef019bbfbe3095247401e053a3ea30"}, 767 | {file = "typed_ast-1.5.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:90904d889ab8e81a956f2c0935a523cc4e077c7847a836abee832f868d5c26a4"}, 768 | {file = "typed_ast-1.5.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:bbebc31bf11762b63bf61aaae232becb41c5bf6b3461b80a4df7e791fabb3aca"}, 769 | {file = "typed_ast-1.5.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c29dd9a3a9d259c9fa19d19738d021632d673f6ed9b35a739f48e5f807f264fb"}, 770 | {file = "typed_ast-1.5.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:58ae097a325e9bb7a684572d20eb3e1809802c5c9ec7108e85da1eb6c1a3331b"}, 771 | {file = "typed_ast-1.5.2-cp38-cp38-win_amd64.whl", hash = "sha256:da0a98d458010bf4fe535f2d1e367a2e2060e105978873c04c04212fb20543f7"}, 772 | {file = "typed_ast-1.5.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:33b4a19ddc9fc551ebabca9765d54d04600c4a50eda13893dadf67ed81d9a098"}, 773 | {file = "typed_ast-1.5.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1098df9a0592dd4c8c0ccfc2e98931278a6c6c53cb3a3e2cf7e9ee3b06153344"}, 774 | {file = "typed_ast-1.5.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42c47c3b43fe3a39ddf8de1d40dbbfca60ac8530a36c9b198ea5b9efac75c09e"}, 775 | {file = "typed_ast-1.5.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f290617f74a610849bd8f5514e34ae3d09eafd521dceaa6cf68b3f4414266d4e"}, 776 | {file = "typed_ast-1.5.2-cp39-cp39-win_amd64.whl", hash = "sha256:df05aa5b241e2e8045f5f4367a9f6187b09c4cdf8578bb219861c4e27c443db5"}, 777 | {file = "typed_ast-1.5.2.tar.gz", hash = "sha256:525a2d4088e70a9f75b08b3f87a51acc9cde640e19cc523c7e41aa355564ae27"}, 778 | ] 779 | typing-extensions = [ 780 | {file = "typing_extensions-4.1.1-py3-none-any.whl", hash = "sha256:21c85e0fe4b9a155d0799430b0ad741cdce7e359660ccbd8b530613e8df88ce2"}, 781 | {file = "typing_extensions-4.1.1.tar.gz", hash = "sha256:1a9462dcc3347a79b1f1c0271fbe79e844580bb598bafa1ed208b94da3cdcd42"}, 782 | ] 783 | virtualenv = [ 784 | {file = "virtualenv-20.13.2-py2.py3-none-any.whl", hash = "sha256:e7b34c9474e6476ee208c43a4d9ac1510b041c68347eabfe9a9ea0c86aa0a46b"}, 785 | {file = "virtualenv-20.13.2.tar.gz", hash = "sha256:01f5f80744d24a3743ce61858123488e91cb2dd1d3bdf92adaf1bba39ffdedf0"}, 786 | ] 787 | wrapt = [ 788 | {file = "wrapt-1.13.3-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:e05e60ff3b2b0342153be4d1b597bbcfd8330890056b9619f4ad6b8d5c96a81a"}, 789 | {file = "wrapt-1.13.3-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:85148f4225287b6a0665eef08a178c15097366d46b210574a658c1ff5b377489"}, 790 | {file = "wrapt-1.13.3-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:2dded5496e8f1592ec27079b28b6ad2a1ef0b9296d270f77b8e4a3a796cf6909"}, 791 | {file = "wrapt-1.13.3-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:e94b7d9deaa4cc7bac9198a58a7240aaf87fe56c6277ee25fa5b3aa1edebd229"}, 792 | {file = "wrapt-1.13.3-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:498e6217523111d07cd67e87a791f5e9ee769f9241fcf8a379696e25806965af"}, 793 | {file = "wrapt-1.13.3-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:ec7e20258ecc5174029a0f391e1b948bf2906cd64c198a9b8b281b811cbc04de"}, 794 | {file = "wrapt-1.13.3-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:87883690cae293541e08ba2da22cacaae0a092e0ed56bbba8d018cc486fbafbb"}, 795 | {file = "wrapt-1.13.3-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:f99c0489258086308aad4ae57da9e8ecf9e1f3f30fa35d5e170b4d4896554d80"}, 796 | {file = "wrapt-1.13.3-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:6a03d9917aee887690aa3f1747ce634e610f6db6f6b332b35c2dd89412912bca"}, 797 | {file = "wrapt-1.13.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:936503cb0a6ed28dbfa87e8fcd0a56458822144e9d11a49ccee6d9a8adb2ac44"}, 798 | {file = "wrapt-1.13.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:f9c51d9af9abb899bd34ace878fbec8bf357b3194a10c4e8e0a25512826ef056"}, 799 | {file = "wrapt-1.13.3-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:220a869982ea9023e163ba915077816ca439489de6d2c09089b219f4e11b6785"}, 800 | {file = "wrapt-1.13.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:0877fe981fd76b183711d767500e6b3111378ed2043c145e21816ee589d91096"}, 801 | {file = "wrapt-1.13.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:43e69ffe47e3609a6aec0fe723001c60c65305784d964f5007d5b4fb1bc6bf33"}, 802 | {file = "wrapt-1.13.3-cp310-cp310-win32.whl", hash = "sha256:78dea98c81915bbf510eb6a3c9c24915e4660302937b9ae05a0947164248020f"}, 803 | {file = "wrapt-1.13.3-cp310-cp310-win_amd64.whl", hash = "sha256:ea3e746e29d4000cd98d572f3ee2a6050a4f784bb536f4ac1f035987fc1ed83e"}, 804 | {file = "wrapt-1.13.3-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:8c73c1a2ec7c98d7eaded149f6d225a692caa1bd7b2401a14125446e9e90410d"}, 805 | {file = "wrapt-1.13.3-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:086218a72ec7d986a3eddb7707c8c4526d677c7b35e355875a0fe2918b059179"}, 806 | {file = "wrapt-1.13.3-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:e92d0d4fa68ea0c02d39f1e2f9cb5bc4b4a71e8c442207433d8db47ee79d7aa3"}, 807 | {file = "wrapt-1.13.3-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:d4a5f6146cfa5c7ba0134249665acd322a70d1ea61732723c7d3e8cc0fa80755"}, 808 | {file = "wrapt-1.13.3-cp35-cp35m-win32.whl", hash = "sha256:8aab36778fa9bba1a8f06a4919556f9f8c7b33102bd71b3ab307bb3fecb21851"}, 809 | {file = "wrapt-1.13.3-cp35-cp35m-win_amd64.whl", hash = "sha256:944b180f61f5e36c0634d3202ba8509b986b5fbaf57db3e94df11abee244ba13"}, 810 | {file = "wrapt-1.13.3-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:2ebdde19cd3c8cdf8df3fc165bc7827334bc4e353465048b36f7deeae8ee0918"}, 811 | {file = "wrapt-1.13.3-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:610f5f83dd1e0ad40254c306f4764fcdc846641f120c3cf424ff57a19d5f7ade"}, 812 | {file = "wrapt-1.13.3-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:5601f44a0f38fed36cc07db004f0eedeaadbdcec90e4e90509480e7e6060a5bc"}, 813 | {file = "wrapt-1.13.3-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:e6906d6f48437dfd80464f7d7af1740eadc572b9f7a4301e7dd3d65db285cacf"}, 814 | {file = "wrapt-1.13.3-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:766b32c762e07e26f50d8a3468e3b4228b3736c805018e4b0ec8cc01ecd88125"}, 815 | {file = "wrapt-1.13.3-cp36-cp36m-win32.whl", hash = "sha256:5f223101f21cfd41deec8ce3889dc59f88a59b409db028c469c9b20cfeefbe36"}, 816 | {file = "wrapt-1.13.3-cp36-cp36m-win_amd64.whl", hash = "sha256:f122ccd12fdc69628786d0c947bdd9cb2733be8f800d88b5a37c57f1f1d73c10"}, 817 | {file = "wrapt-1.13.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:46f7f3af321a573fc0c3586612db4decb7eb37172af1bc6173d81f5b66c2e068"}, 818 | {file = "wrapt-1.13.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:778fd096ee96890c10ce96187c76b3e99b2da44e08c9e24d5652f356873f6709"}, 819 | {file = "wrapt-1.13.3-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:0cb23d36ed03bf46b894cfec777eec754146d68429c30431c99ef28482b5c1df"}, 820 | {file = "wrapt-1.13.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:96b81ae75591a795d8c90edc0bfaab44d3d41ffc1aae4d994c5aa21d9b8e19a2"}, 821 | {file = "wrapt-1.13.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:7dd215e4e8514004c8d810a73e342c536547038fb130205ec4bba9f5de35d45b"}, 822 | {file = "wrapt-1.13.3-cp37-cp37m-win32.whl", hash = "sha256:47f0a183743e7f71f29e4e21574ad3fa95676136f45b91afcf83f6a050914829"}, 823 | {file = "wrapt-1.13.3-cp37-cp37m-win_amd64.whl", hash = "sha256:fd76c47f20984b43d93de9a82011bb6e5f8325df6c9ed4d8310029a55fa361ea"}, 824 | {file = "wrapt-1.13.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b73d4b78807bd299b38e4598b8e7bd34ed55d480160d2e7fdaabd9931afa65f9"}, 825 | {file = "wrapt-1.13.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:ec9465dd69d5657b5d2fa6133b3e1e989ae27d29471a672416fd729b429eb554"}, 826 | {file = "wrapt-1.13.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:dd91006848eb55af2159375134d724032a2d1d13bcc6f81cd8d3ed9f2b8e846c"}, 827 | {file = "wrapt-1.13.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:ae9de71eb60940e58207f8e71fe113c639da42adb02fb2bcbcaccc1ccecd092b"}, 828 | {file = "wrapt-1.13.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:51799ca950cfee9396a87f4a1240622ac38973b6df5ef7a41e7f0b98797099ce"}, 829 | {file = "wrapt-1.13.3-cp38-cp38-win32.whl", hash = "sha256:4b9c458732450ec42578b5642ac53e312092acf8c0bfce140ada5ca1ac556f79"}, 830 | {file = "wrapt-1.13.3-cp38-cp38-win_amd64.whl", hash = "sha256:7dde79d007cd6dfa65afe404766057c2409316135cb892be4b1c768e3f3a11cb"}, 831 | {file = "wrapt-1.13.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:981da26722bebb9247a0601e2922cedf8bb7a600e89c852d063313102de6f2cb"}, 832 | {file = "wrapt-1.13.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:705e2af1f7be4707e49ced9153f8d72131090e52be9278b5dbb1498c749a1e32"}, 833 | {file = "wrapt-1.13.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:25b1b1d5df495d82be1c9d2fad408f7ce5ca8a38085e2da41bb63c914baadff7"}, 834 | {file = "wrapt-1.13.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:77416e6b17926d953b5c666a3cb718d5945df63ecf922af0ee576206d7033b5e"}, 835 | {file = "wrapt-1.13.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:865c0b50003616f05858b22174c40ffc27a38e67359fa1495605f96125f76640"}, 836 | {file = "wrapt-1.13.3-cp39-cp39-win32.whl", hash = "sha256:0a017a667d1f7411816e4bf214646d0ad5b1da2c1ea13dec6c162736ff25a374"}, 837 | {file = "wrapt-1.13.3-cp39-cp39-win_amd64.whl", hash = "sha256:81bd7c90d28a4b2e1df135bfbd7c23aee3050078ca6441bead44c42483f9ebfb"}, 838 | {file = "wrapt-1.13.3.tar.gz", hash = "sha256:1fea9cd438686e6682271d36f3481a9f3636195578bab9ca3382e2f5f01fc185"}, 839 | ] 840 | zipp = [ 841 | {file = "zipp-3.7.0-py3-none-any.whl", hash = "sha256:b47250dd24f92b7dd6a0a8fc5244da14608f3ca90a5efcd37a3b1642fac9a375"}, 842 | {file = "zipp-3.7.0.tar.gz", hash = "sha256:9f50f446828eb9d45b267433fd3e9da8d801f614129124863f9c51ebceafb87d"}, 843 | ] 844 | --------------------------------------------------------------------------------