├── .github └── workflows │ └── python-package.yml ├── .gitignore ├── .travis.yml ├── CHANGELIST ├── FUNDING.yml ├── LICENSE ├── README.md ├── doc_sproc.py ├── docs ├── extra.css └── index.md ├── mkdocs.yml ├── poetry.lock ├── pyproject.toml ├── sproc ├── __init__.py └── py.typed ├── test_sproc.py └── tox.ini /.github/workflows/python-package.yml: -------------------------------------------------------------------------------- 1 | jobs: 2 | build: 3 | runs-on: ubuntu-latest 4 | steps: 5 | - {uses: actions/checkout@v3} 6 | - name: Set up Python ${{ matrix.python-version }} 7 | uses: actions/setup-python@v3 8 | with: {python-version: '${{ matrix.python-version }}'} 9 | - name: cache poetry install 10 | uses: actions/cache@v2 11 | with: {key: poetry-1.7.1-0, path: ~/.local} 12 | - name: Install Poetry 13 | uses: snok/install-poetry@v1 14 | with: {version: 1.7.1, virtualenvs-create: true, virtualenvs-in-project: true} 15 | - id: cache-deps 16 | name: cache deps 17 | uses: actions/cache@v2 18 | with: {key: "pydeps-${{ hashFiles('**/poetry.lock') }}", path: .venv} 19 | - {if: steps.cache-deps.outputs.cache-hit != 'true', run: poetry install --no-interaction 20 | --no-root} 21 | - {run: poetry install --no-interaction} 22 | - {run: poetry run mypy sproc} 23 | - {run: poetry run ruff check --select I --fix sproc test*} 24 | - {run: poetry run ruff format sproc test*} 25 | - {run: poetry run pytest} 26 | strategy: 27 | fail-fast: false 28 | matrix: 29 | python-version: ['3.8', '3.12'] 30 | name: Python package 31 | on: 32 | pull_request: 33 | branches: [main] 34 | push: 35 | branches: [main] 36 | -------------------------------------------------------------------------------- /.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 | # direnv 132 | .direnv/ 133 | .envrc 134 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | 3 | python: 4 | - '3.5' 5 | 6 | matrix: 7 | include: 8 | - python: 3.9-dev 9 | dist: xenial 10 | 11 | 12 | branches: 13 | only: 14 | - master 15 | - /^_gitz_stripe_.*$/ 16 | 17 | 18 | install: 19 | - pip install -r requirements.txt 20 | - pip install -r test_requirements.txt 21 | 22 | script: 23 | - flake8 24 | - pytest 25 | -------------------------------------------------------------------------------- /CHANGELIST: -------------------------------------------------------------------------------- 1 | ## v2.0.3 - 20/10/07 2 | 3 | * Enable Python 3.9 4 | 5 | ## v2.0.2 - 20/09/06 6 | 7 | * Add MANIFEST.in 8 | * Sort import statements with simp 9 | * Refactored by Sourcery 10 | * New function sproc.call_async() 11 | * Extract Sub._start_thread() method 12 | * More documentation 13 | * Make Sub.returncode into a property 14 | 15 | ## v2.0.1 - 2020-04-28 16 | 17 | * Mark as release version 18 | 19 | ## v2.0.0 - 2020-04-28 20 | 21 | * Rename subprocessor to sproc 22 | 23 | ## v1.0.0 - 2020-04-28 24 | 25 | * First release 26 | 27 | ## v0.9.2 - 2020-04-28 28 | 29 | * Rewrite to use multiple threads 30 | * More testing 31 | * More documentation 32 | 33 | ## v0.9.1 - 2020-03-30 34 | 35 | * Check stderr before stdout 36 | * More documentation 37 | 38 | ## v0.9.0 - 2020-03-01 39 | 40 | * Initial port from gitz 41 | * Tests and more features 42 | * Refactor into an iterable class 43 | -------------------------------------------------------------------------------- /FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: rec 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Tom Swirly 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 | # ⛏️sproc: subprocesseses for subhumanses ⛏ 2 | 3 | Run a command in a subprocess and yield lines of text from `stdout` and 4 | `stderr` independently. 5 | 6 | Useful for handling long-running proceesses that write to both `stdout` and 7 | `stderr`. 8 | 9 | ### Simple Example 10 | 11 | import sproc 12 | 13 | CMD = 'my-unix-command "My Cool File.txt" No-file.txt' 14 | 15 | for ok, line in sproc.Sub(CMD) as sp: 16 | if ok: 17 | print(' ', line) 18 | else: 19 | print('!', line) 20 | 21 | if sp.returncode: 22 | print('Error code', sp.returncode) 23 | 24 | # Return two lists of text lines and a returncode 25 | out_lines, err_lines, returncode = sproc.run(CMD) 26 | 27 | # Call callback functions with lines of text read from stdout and stderr 28 | returncode = sproc.call(CMD, save_results, print_errors) 29 | 30 | # Log stdout and stderr, with prefixes 31 | returncode = sproc.log(CMD) 32 | 33 | 34 | ### [API Documentation](https://rec.github.io/sproc#sproc--api-documentation) 35 | -------------------------------------------------------------------------------- /doc_sproc.py: -------------------------------------------------------------------------------- 1 | from sproc import Sub 2 | import inspect 3 | import sproc 4 | 5 | README_FILE = 'README.rst' 6 | _, *ALL = sproc.__all__ 7 | 8 | assert _ == 'Sub' 9 | 10 | 11 | def main(): 12 | with open(README_FILE, 'w') as fp: 13 | fp.write(make_doc()) 14 | 15 | 16 | def make_doc(): 17 | def header(s, c='-'): 18 | return '%s\n%s' % (s, c * (len(s) + s.count('`'))) 19 | 20 | def sig(name, thing): 21 | return '`%s%s`' % (name, str(inspect.signature(thing))) 22 | 23 | def indent(s, indent=' '): 24 | for i in s.splitlines(): 25 | yield indent + i if i.strip() else i 26 | yield '' 27 | 28 | def bold(s, c='**'): 29 | return c + s.replace(c, '\\' + c) + c 30 | 31 | def sub_class(): 32 | yield from (sproc.__doc__, '***', 'API', '***', '') 33 | yield header('Methods on `class sproc.Sub:`', '=') 34 | yield '' 35 | yield header('`Sub.__init__(self, cmd, **kwds)`') 36 | yield from indent(Sub.__doc__) 37 | 38 | for name in ['__iter__'] + ALL: 39 | method = getattr(Sub, name) 40 | yield header(sig('Sub.' + name, method)) 41 | yield from indent(method.__doc__) 42 | 43 | def apis(): 44 | yield from sub_class() 45 | yield '' 46 | yield header('Functions', '=') 47 | 48 | for name in ALL: 49 | function = getattr(sproc, name) 50 | yield '' 51 | yield header(sig('sproc.' + name, function)) 52 | yield from indent(function.__doc__) 53 | 54 | return '\n'.join(apis()).strip().replace('`', '``') 55 | 56 | 57 | if __name__ == '__main__': 58 | main() 59 | -------------------------------------------------------------------------------- /docs/extra.css: -------------------------------------------------------------------------------- 1 | .md-typeset h1 { 2 | font-size: 28px; 3 | } 4 | 5 | main { 6 | margin-top: -35px; 7 | } 8 | 9 | div .doc-children { 10 | margin-top: -20px; 11 | } 12 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | # ⛏: `sproc`: Subprocesseses for subhumanses ⛏ 2 | 3 | ::: sproc 4 | 5 | ## About this project 6 | 7 | * [ Source code ]( https://github.com/rec/sproc ) 8 | * [ More by this author ]( https://github.com/rec ) 9 | * [ Sponsors ]( https://github.com/sponsors/rec ) 10 | -------------------------------------------------------------------------------- /mkdocs.yml: -------------------------------------------------------------------------------- 1 | site_name: '⛏: `sproc`: Subprocesseses for subhumanses ⛏' 2 | 3 | theme: 4 | name: material 5 | 6 | palette: 7 | primary: 8 | 'blue' 9 | 10 | font: 11 | text: 12 | Roboto 13 | code: 14 | Roboto Mono 15 | 16 | extra_css: [extra.css] 17 | 18 | plugins: 19 | - mkdocstrings 20 | -------------------------------------------------------------------------------- /poetry.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand. 2 | 3 | [[package]] 4 | name = "click" 5 | version = "8.1.7" 6 | description = "Composable command line interface toolkit" 7 | optional = false 8 | python-versions = ">=3.7" 9 | files = [ 10 | {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"}, 11 | {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"}, 12 | ] 13 | 14 | [package.dependencies] 15 | colorama = {version = "*", markers = "platform_system == \"Windows\""} 16 | 17 | [[package]] 18 | name = "colorama" 19 | version = "0.4.6" 20 | description = "Cross-platform colored terminal text." 21 | optional = false 22 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" 23 | files = [ 24 | {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, 25 | {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, 26 | ] 27 | 28 | [[package]] 29 | name = "coverage" 30 | version = "7.4.1" 31 | description = "Code coverage measurement for Python" 32 | optional = false 33 | python-versions = ">=3.8" 34 | files = [ 35 | {file = "coverage-7.4.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:077d366e724f24fc02dbfe9d946534357fda71af9764ff99d73c3c596001bbd7"}, 36 | {file = "coverage-7.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0193657651f5399d433c92f8ae264aff31fc1d066deee4b831549526433f3f61"}, 37 | {file = "coverage-7.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d17bbc946f52ca67adf72a5ee783cd7cd3477f8f8796f59b4974a9b59cacc9ee"}, 38 | {file = "coverage-7.4.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a3277f5fa7483c927fe3a7b017b39351610265308f5267ac6d4c2b64cc1d8d25"}, 39 | {file = "coverage-7.4.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6dceb61d40cbfcf45f51e59933c784a50846dc03211054bd76b421a713dcdf19"}, 40 | {file = "coverage-7.4.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:6008adeca04a445ea6ef31b2cbaf1d01d02986047606f7da266629afee982630"}, 41 | {file = "coverage-7.4.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:c61f66d93d712f6e03369b6a7769233bfda880b12f417eefdd4f16d1deb2fc4c"}, 42 | {file = "coverage-7.4.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b9bb62fac84d5f2ff523304e59e5c439955fb3b7f44e3d7b2085184db74d733b"}, 43 | {file = "coverage-7.4.1-cp310-cp310-win32.whl", hash = "sha256:f86f368e1c7ce897bf2457b9eb61169a44e2ef797099fb5728482b8d69f3f016"}, 44 | {file = "coverage-7.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:869b5046d41abfea3e381dd143407b0d29b8282a904a19cb908fa24d090cc018"}, 45 | {file = "coverage-7.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b8ffb498a83d7e0305968289441914154fb0ef5d8b3157df02a90c6695978295"}, 46 | {file = "coverage-7.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3cacfaefe6089d477264001f90f55b7881ba615953414999c46cc9713ff93c8c"}, 47 | {file = "coverage-7.4.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d6850e6e36e332d5511a48a251790ddc545e16e8beaf046c03985c69ccb2676"}, 48 | {file = "coverage-7.4.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:18e961aa13b6d47f758cc5879383d27b5b3f3dcd9ce8cdbfdc2571fe86feb4dd"}, 49 | {file = "coverage-7.4.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dfd1e1b9f0898817babf840b77ce9fe655ecbe8b1b327983df485b30df8cc011"}, 50 | {file = "coverage-7.4.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:6b00e21f86598b6330f0019b40fb397e705135040dbedc2ca9a93c7441178e74"}, 51 | {file = "coverage-7.4.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:536d609c6963c50055bab766d9951b6c394759190d03311f3e9fcf194ca909e1"}, 52 | {file = "coverage-7.4.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:7ac8f8eb153724f84885a1374999b7e45734bf93a87d8df1e7ce2146860edef6"}, 53 | {file = "coverage-7.4.1-cp311-cp311-win32.whl", hash = "sha256:f3771b23bb3675a06f5d885c3630b1d01ea6cac9e84a01aaf5508706dba546c5"}, 54 | {file = "coverage-7.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:9d2f9d4cc2a53b38cabc2d6d80f7f9b7e3da26b2f53d48f05876fef7956b6968"}, 55 | {file = "coverage-7.4.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f68ef3660677e6624c8cace943e4765545f8191313a07288a53d3da188bd8581"}, 56 | {file = "coverage-7.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:23b27b8a698e749b61809fb637eb98ebf0e505710ec46a8aa6f1be7dc0dc43a6"}, 57 | {file = "coverage-7.4.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e3424c554391dc9ef4a92ad28665756566a28fecf47308f91841f6c49288e66"}, 58 | {file = "coverage-7.4.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e0860a348bf7004c812c8368d1fc7f77fe8e4c095d661a579196a9533778e156"}, 59 | {file = "coverage-7.4.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe558371c1bdf3b8fa03e097c523fb9645b8730399c14fe7721ee9c9e2a545d3"}, 60 | {file = "coverage-7.4.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:3468cc8720402af37b6c6e7e2a9cdb9f6c16c728638a2ebc768ba1ef6f26c3a1"}, 61 | {file = "coverage-7.4.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:02f2edb575d62172aa28fe00efe821ae31f25dc3d589055b3fb64d51e52e4ab1"}, 62 | {file = "coverage-7.4.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:ca6e61dc52f601d1d224526360cdeab0d0712ec104a2ce6cc5ccef6ed9a233bc"}, 63 | {file = "coverage-7.4.1-cp312-cp312-win32.whl", hash = "sha256:ca7b26a5e456a843b9b6683eada193fc1f65c761b3a473941efe5a291f604c74"}, 64 | {file = "coverage-7.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:85ccc5fa54c2ed64bd91ed3b4a627b9cce04646a659512a051fa82a92c04a448"}, 65 | {file = "coverage-7.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8bdb0285a0202888d19ec6b6d23d5990410decb932b709f2b0dfe216d031d218"}, 66 | {file = "coverage-7.4.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:918440dea04521f499721c039863ef95433314b1db00ff826a02580c1f503e45"}, 67 | {file = "coverage-7.4.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:379d4c7abad5afbe9d88cc31ea8ca262296480a86af945b08214eb1a556a3e4d"}, 68 | {file = "coverage-7.4.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b094116f0b6155e36a304ff912f89bbb5067157aff5f94060ff20bbabdc8da06"}, 69 | {file = "coverage-7.4.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2f5968608b1fe2a1d00d01ad1017ee27efd99b3437e08b83ded9b7af3f6f766"}, 70 | {file = "coverage-7.4.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:10e88e7f41e6197ea0429ae18f21ff521d4f4490aa33048f6c6f94c6045a6a75"}, 71 | {file = "coverage-7.4.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a4a3907011d39dbc3e37bdc5df0a8c93853c369039b59efa33a7b6669de04c60"}, 72 | {file = "coverage-7.4.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6d224f0c4c9c98290a6990259073f496fcec1b5cc613eecbd22786d398ded3ad"}, 73 | {file = "coverage-7.4.1-cp38-cp38-win32.whl", hash = "sha256:23f5881362dcb0e1a92b84b3c2809bdc90db892332daab81ad8f642d8ed55042"}, 74 | {file = "coverage-7.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:a07f61fc452c43cd5328b392e52555f7d1952400a1ad09086c4a8addccbd138d"}, 75 | {file = "coverage-7.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8e738a492b6221f8dcf281b67129510835461132b03024830ac0e554311a5c54"}, 76 | {file = "coverage-7.4.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:46342fed0fff72efcda77040b14728049200cbba1279e0bf1188f1f2078c1d70"}, 77 | {file = "coverage-7.4.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9641e21670c68c7e57d2053ddf6c443e4f0a6e18e547e86af3fad0795414a628"}, 78 | {file = "coverage-7.4.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aeb2c2688ed93b027eb0d26aa188ada34acb22dceea256d76390eea135083950"}, 79 | {file = "coverage-7.4.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d12c923757de24e4e2110cf8832d83a886a4cf215c6e61ed506006872b43a6d1"}, 80 | {file = "coverage-7.4.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0491275c3b9971cdbd28a4595c2cb5838f08036bca31765bad5e17edf900b2c7"}, 81 | {file = "coverage-7.4.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:8dfc5e195bbef80aabd81596ef52a1277ee7143fe419efc3c4d8ba2754671756"}, 82 | {file = "coverage-7.4.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:1a78b656a4d12b0490ca72651fe4d9f5e07e3c6461063a9b6265ee45eb2bdd35"}, 83 | {file = "coverage-7.4.1-cp39-cp39-win32.whl", hash = "sha256:f90515974b39f4dea2f27c0959688621b46d96d5a626cf9c53dbc653a895c05c"}, 84 | {file = "coverage-7.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:64e723ca82a84053dd7bfcc986bdb34af8d9da83c521c19d6b472bc6880e191a"}, 85 | {file = "coverage-7.4.1-pp38.pp39.pp310-none-any.whl", hash = "sha256:32a8d985462e37cfdab611a6f95b09d7c091d07668fdc26e47a725ee575fe166"}, 86 | {file = "coverage-7.4.1.tar.gz", hash = "sha256:1ed4b95480952b1a26d863e546fa5094564aa0065e1e5f0d4d0041f293251d04"}, 87 | ] 88 | 89 | [package.extras] 90 | toml = ["tomli"] 91 | 92 | [[package]] 93 | name = "docutils" 94 | version = "0.20.1" 95 | description = "Docutils -- Python Documentation Utilities" 96 | optional = false 97 | python-versions = ">=3.7" 98 | files = [ 99 | {file = "docutils-0.20.1-py3-none-any.whl", hash = "sha256:96f387a2c5562db4476f09f13bbab2192e764cac08ebbf3a34a95d9b1e4a59d6"}, 100 | {file = "docutils-0.20.1.tar.gz", hash = "sha256:f08a4e276c3a1583a86dce3e34aba3fe04d02bba2dd51ed16106244e8a923e3b"}, 101 | ] 102 | 103 | [[package]] 104 | name = "exceptiongroup" 105 | version = "1.2.0" 106 | description = "Backport of PEP 654 (exception groups)" 107 | optional = false 108 | python-versions = ">=3.7" 109 | files = [ 110 | {file = "exceptiongroup-1.2.0-py3-none-any.whl", hash = "sha256:4bfd3996ac73b41e9b9628b04e079f193850720ea5945fc96a08633c66912f14"}, 111 | {file = "exceptiongroup-1.2.0.tar.gz", hash = "sha256:91f5c769735f051a4290d52edd0858999b57e5876e9f85937691bd4c9fa3ed68"}, 112 | ] 113 | 114 | [package.extras] 115 | test = ["pytest (>=6)"] 116 | 117 | [[package]] 118 | name = "ghp-import" 119 | version = "2.1.0" 120 | description = "Copy your docs directly to the gh-pages branch." 121 | optional = false 122 | python-versions = "*" 123 | files = [ 124 | {file = "ghp-import-2.1.0.tar.gz", hash = "sha256:9c535c4c61193c2df8871222567d7fd7e5014d835f97dc7b7439069e2413d343"}, 125 | {file = "ghp_import-2.1.0-py3-none-any.whl", hash = "sha256:8337dd7b50877f163d4c0289bc1f1c7f127550241988d568c1db512c4324a619"}, 126 | ] 127 | 128 | [package.dependencies] 129 | python-dateutil = ">=2.8.1" 130 | 131 | [package.extras] 132 | dev = ["flake8", "markdown", "twine", "wheel"] 133 | 134 | [[package]] 135 | name = "importlib-metadata" 136 | version = "7.0.1" 137 | description = "Read metadata from Python packages" 138 | optional = false 139 | python-versions = ">=3.8" 140 | files = [ 141 | {file = "importlib_metadata-7.0.1-py3-none-any.whl", hash = "sha256:4805911c3a4ec7c3966410053e9ec6a1fecd629117df5adee56dfc9432a1081e"}, 142 | {file = "importlib_metadata-7.0.1.tar.gz", hash = "sha256:f238736bb06590ae52ac1fab06a3a9ef1d8dce2b7a35b5ab329371d6c8f5d2cc"}, 143 | ] 144 | 145 | [package.dependencies] 146 | zipp = ">=0.5" 147 | 148 | [package.extras] 149 | docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-lint"] 150 | perf = ["ipython"] 151 | testing = ["flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)", "pytest-ruff"] 152 | 153 | [[package]] 154 | name = "iniconfig" 155 | version = "2.0.0" 156 | description = "brain-dead simple config-ini parsing" 157 | optional = false 158 | python-versions = ">=3.7" 159 | files = [ 160 | {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, 161 | {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, 162 | ] 163 | 164 | [[package]] 165 | name = "jinja2" 166 | version = "3.1.3" 167 | description = "A very fast and expressive template engine." 168 | optional = false 169 | python-versions = ">=3.7" 170 | files = [ 171 | {file = "Jinja2-3.1.3-py3-none-any.whl", hash = "sha256:7d6d50dd97d52cbc355597bd845fabfbac3f551e1f99619e39a35ce8c370b5fa"}, 172 | {file = "Jinja2-3.1.3.tar.gz", hash = "sha256:ac8bd6544d4bb2c9792bf3a159e80bba8fda7f07e81bc3aed565432d5925ba90"}, 173 | ] 174 | 175 | [package.dependencies] 176 | MarkupSafe = ">=2.0" 177 | 178 | [package.extras] 179 | i18n = ["Babel (>=2.7)"] 180 | 181 | [[package]] 182 | name = "markdown" 183 | version = "3.5.2" 184 | description = "Python implementation of John Gruber's Markdown." 185 | optional = false 186 | python-versions = ">=3.8" 187 | files = [ 188 | {file = "Markdown-3.5.2-py3-none-any.whl", hash = "sha256:d43323865d89fc0cb9b20c75fc8ad313af307cc087e84b657d9eec768eddeadd"}, 189 | {file = "Markdown-3.5.2.tar.gz", hash = "sha256:e1ac7b3dc550ee80e602e71c1d168002f062e49f1b11e26a36264dafd4df2ef8"}, 190 | ] 191 | 192 | [package.dependencies] 193 | importlib-metadata = {version = ">=4.4", markers = "python_version < \"3.10\""} 194 | 195 | [package.extras] 196 | docs = ["mdx-gh-links (>=0.2)", "mkdocs (>=1.5)", "mkdocs-gen-files", "mkdocs-literate-nav", "mkdocs-nature (>=0.6)", "mkdocs-section-index", "mkdocstrings[python]"] 197 | testing = ["coverage", "pyyaml"] 198 | 199 | [[package]] 200 | name = "markupsafe" 201 | version = "2.1.5" 202 | description = "Safely add untrusted strings to HTML/XML markup." 203 | optional = false 204 | python-versions = ">=3.7" 205 | files = [ 206 | {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a17a92de5231666cfbe003f0e4b9b3a7ae3afb1ec2845aadc2bacc93ff85febc"}, 207 | {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:72b6be590cc35924b02c78ef34b467da4ba07e4e0f0454a2c5907f473fc50ce5"}, 208 | {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e61659ba32cf2cf1481e575d0462554625196a1f2fc06a1c777d3f48e8865d46"}, 209 | {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2174c595a0d73a3080ca3257b40096db99799265e1c27cc5a610743acd86d62f"}, 210 | {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae2ad8ae6ebee9d2d94b17fb62763125f3f374c25618198f40cbb8b525411900"}, 211 | {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:075202fa5b72c86ad32dc7d0b56024ebdbcf2048c0ba09f1cde31bfdd57bcfff"}, 212 | {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:598e3276b64aff0e7b3451b72e94fa3c238d452e7ddcd893c3ab324717456bad"}, 213 | {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fce659a462a1be54d2ffcacea5e3ba2d74daa74f30f5f143fe0c58636e355fdd"}, 214 | {file = "MarkupSafe-2.1.5-cp310-cp310-win32.whl", hash = "sha256:d9fad5155d72433c921b782e58892377c44bd6252b5af2f67f16b194987338a4"}, 215 | {file = "MarkupSafe-2.1.5-cp310-cp310-win_amd64.whl", hash = "sha256:bf50cd79a75d181c9181df03572cdce0fbb75cc353bc350712073108cba98de5"}, 216 | {file = "MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:629ddd2ca402ae6dbedfceeba9c46d5f7b2a61d9749597d4307f943ef198fc1f"}, 217 | {file = "MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5b7b716f97b52c5a14bffdf688f971b2d5ef4029127f1ad7a513973cfd818df2"}, 218 | {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ec585f69cec0aa07d945b20805be741395e28ac1627333b1c5b0105962ffced"}, 219 | {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b91c037585eba9095565a3556f611e3cbfaa42ca1e865f7b8015fe5c7336d5a5"}, 220 | {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7502934a33b54030eaf1194c21c692a534196063db72176b0c4028e140f8f32c"}, 221 | {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0e397ac966fdf721b2c528cf028494e86172b4feba51d65f81ffd65c63798f3f"}, 222 | {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c061bb86a71b42465156a3ee7bd58c8c2ceacdbeb95d05a99893e08b8467359a"}, 223 | {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3a57fdd7ce31c7ff06cdfbf31dafa96cc533c21e443d57f5b1ecc6cdc668ec7f"}, 224 | {file = "MarkupSafe-2.1.5-cp311-cp311-win32.whl", hash = "sha256:397081c1a0bfb5124355710fe79478cdbeb39626492b15d399526ae53422b906"}, 225 | {file = "MarkupSafe-2.1.5-cp311-cp311-win_amd64.whl", hash = "sha256:2b7c57a4dfc4f16f7142221afe5ba4e093e09e728ca65c51f5620c9aaeb9a617"}, 226 | {file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:8dec4936e9c3100156f8a2dc89c4b88d5c435175ff03413b443469c7c8c5f4d1"}, 227 | {file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:3c6b973f22eb18a789b1460b4b91bf04ae3f0c4234a0a6aa6b0a92f6f7b951d4"}, 228 | {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac07bad82163452a6884fe8fa0963fb98c2346ba78d779ec06bd7a6262132aee"}, 229 | {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5dfb42c4604dddc8e4305050aa6deb084540643ed5804d7455b5df8fe16f5e5"}, 230 | {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ea3d8a3d18833cf4304cd2fc9cbb1efe188ca9b5efef2bdac7adc20594a0e46b"}, 231 | {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d050b3361367a06d752db6ead6e7edeb0009be66bc3bae0ee9d97fb326badc2a"}, 232 | {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:bec0a414d016ac1a18862a519e54b2fd0fc8bbfd6890376898a6c0891dd82e9f"}, 233 | {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:58c98fee265677f63a4385256a6d7683ab1832f3ddd1e66fe948d5880c21a169"}, 234 | {file = "MarkupSafe-2.1.5-cp312-cp312-win32.whl", hash = "sha256:8590b4ae07a35970728874632fed7bd57b26b0102df2d2b233b6d9d82f6c62ad"}, 235 | {file = "MarkupSafe-2.1.5-cp312-cp312-win_amd64.whl", hash = "sha256:823b65d8706e32ad2df51ed89496147a42a2a6e01c13cfb6ffb8b1e92bc910bb"}, 236 | {file = "MarkupSafe-2.1.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c8b29db45f8fe46ad280a7294f5c3ec36dbac9491f2d1c17345be8e69cc5928f"}, 237 | {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ec6a563cff360b50eed26f13adc43e61bc0c04d94b8be985e6fb24b81f6dcfdf"}, 238 | {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a549b9c31bec33820e885335b451286e2969a2d9e24879f83fe904a5ce59d70a"}, 239 | {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4f11aa001c540f62c6166c7726f71f7573b52c68c31f014c25cc7901deea0b52"}, 240 | {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7b2e5a267c855eea6b4283940daa6e88a285f5f2a67f2220203786dfa59b37e9"}, 241 | {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:2d2d793e36e230fd32babe143b04cec8a8b3eb8a3122d2aceb4a371e6b09b8df"}, 242 | {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ce409136744f6521e39fd8e2a24c53fa18ad67aa5bc7c2cf83645cce5b5c4e50"}, 243 | {file = "MarkupSafe-2.1.5-cp37-cp37m-win32.whl", hash = "sha256:4096e9de5c6fdf43fb4f04c26fb114f61ef0bf2e5604b6ee3019d51b69e8c371"}, 244 | {file = "MarkupSafe-2.1.5-cp37-cp37m-win_amd64.whl", hash = "sha256:4275d846e41ecefa46e2015117a9f491e57a71ddd59bbead77e904dc02b1bed2"}, 245 | {file = "MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:656f7526c69fac7f600bd1f400991cc282b417d17539a1b228617081106feb4a"}, 246 | {file = "MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:97cafb1f3cbcd3fd2b6fbfb99ae11cdb14deea0736fc2b0952ee177f2b813a46"}, 247 | {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f3fbcb7ef1f16e48246f704ab79d79da8a46891e2da03f8783a5b6fa41a9532"}, 248 | {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fa9db3f79de01457b03d4f01b34cf91bc0048eb2c3846ff26f66687c2f6d16ab"}, 249 | {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffee1f21e5ef0d712f9033568f8344d5da8cc2869dbd08d87c84656e6a2d2f68"}, 250 | {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:5dedb4db619ba5a2787a94d877bc8ffc0566f92a01c0ef214865e54ecc9ee5e0"}, 251 | {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:30b600cf0a7ac9234b2638fbc0fb6158ba5bdcdf46aeb631ead21248b9affbc4"}, 252 | {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8dd717634f5a044f860435c1d8c16a270ddf0ef8588d4887037c5028b859b0c3"}, 253 | {file = "MarkupSafe-2.1.5-cp38-cp38-win32.whl", hash = "sha256:daa4ee5a243f0f20d528d939d06670a298dd39b1ad5f8a72a4275124a7819eff"}, 254 | {file = "MarkupSafe-2.1.5-cp38-cp38-win_amd64.whl", hash = "sha256:619bc166c4f2de5caa5a633b8b7326fbe98e0ccbfacabd87268a2b15ff73a029"}, 255 | {file = "MarkupSafe-2.1.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7a68b554d356a91cce1236aa7682dc01df0edba8d043fd1ce607c49dd3c1edcf"}, 256 | {file = "MarkupSafe-2.1.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:db0b55e0f3cc0be60c1f19efdde9a637c32740486004f20d1cff53c3c0ece4d2"}, 257 | {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e53af139f8579a6d5f7b76549125f0d94d7e630761a2111bc431fd820e163b8"}, 258 | {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:17b950fccb810b3293638215058e432159d2b71005c74371d784862b7e4683f3"}, 259 | {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4c31f53cdae6ecfa91a77820e8b151dba54ab528ba65dfd235c80b086d68a465"}, 260 | {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:bff1b4290a66b490a2f4719358c0cdcd9bafb6b8f061e45c7a2460866bf50c2e"}, 261 | {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:bc1667f8b83f48511b94671e0e441401371dfd0f0a795c7daa4a3cd1dde55bea"}, 262 | {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5049256f536511ee3f7e1b3f87d1d1209d327e818e6ae1365e8653d7e3abb6a6"}, 263 | {file = "MarkupSafe-2.1.5-cp39-cp39-win32.whl", hash = "sha256:00e046b6dd71aa03a41079792f8473dc494d564611a8f89bbbd7cb93295ebdcf"}, 264 | {file = "MarkupSafe-2.1.5-cp39-cp39-win_amd64.whl", hash = "sha256:fa173ec60341d6bb97a89f5ea19c85c5643c1e7dedebc22f5181eb73573142c5"}, 265 | {file = "MarkupSafe-2.1.5.tar.gz", hash = "sha256:d283d37a890ba4c1ae73ffadf8046435c76e7bc2247bbb63c00bd1a709c6544b"}, 266 | ] 267 | 268 | [[package]] 269 | name = "mergedeep" 270 | version = "1.3.4" 271 | description = "A deep merge function for 🐍." 272 | optional = false 273 | python-versions = ">=3.6" 274 | files = [ 275 | {file = "mergedeep-1.3.4-py3-none-any.whl", hash = "sha256:70775750742b25c0d8f36c55aed03d24c3384d17c951b3175d898bd778ef0307"}, 276 | {file = "mergedeep-1.3.4.tar.gz", hash = "sha256:0096d52e9dad9939c3d975a774666af186eda617e6ca84df4c94dec30004f2a8"}, 277 | ] 278 | 279 | [[package]] 280 | name = "mkdocs" 281 | version = "1.5.3" 282 | description = "Project documentation with Markdown." 283 | optional = false 284 | python-versions = ">=3.7" 285 | files = [ 286 | {file = "mkdocs-1.5.3-py3-none-any.whl", hash = "sha256:3b3a78e736b31158d64dbb2f8ba29bd46a379d0c6e324c2246c3bc3d2189cfc1"}, 287 | {file = "mkdocs-1.5.3.tar.gz", hash = "sha256:eb7c99214dcb945313ba30426c2451b735992c73c2e10838f76d09e39ff4d0e2"}, 288 | ] 289 | 290 | [package.dependencies] 291 | click = ">=7.0" 292 | colorama = {version = ">=0.4", markers = "platform_system == \"Windows\""} 293 | ghp-import = ">=1.0" 294 | importlib-metadata = {version = ">=4.3", markers = "python_version < \"3.10\""} 295 | jinja2 = ">=2.11.1" 296 | markdown = ">=3.2.1" 297 | markupsafe = ">=2.0.1" 298 | mergedeep = ">=1.3.4" 299 | packaging = ">=20.5" 300 | pathspec = ">=0.11.1" 301 | platformdirs = ">=2.2.0" 302 | pyyaml = ">=5.1" 303 | pyyaml-env-tag = ">=0.1" 304 | watchdog = ">=2.0" 305 | 306 | [package.extras] 307 | i18n = ["babel (>=2.9.0)"] 308 | min-versions = ["babel (==2.9.0)", "click (==7.0)", "colorama (==0.4)", "ghp-import (==1.0)", "importlib-metadata (==4.3)", "jinja2 (==2.11.1)", "markdown (==3.2.1)", "markupsafe (==2.0.1)", "mergedeep (==1.3.4)", "packaging (==20.5)", "pathspec (==0.11.1)", "platformdirs (==2.2.0)", "pyyaml (==5.1)", "pyyaml-env-tag (==0.1)", "typing-extensions (==3.10)", "watchdog (==2.0)"] 309 | 310 | [[package]] 311 | name = "mypy" 312 | version = "1.8.0" 313 | description = "Optional static typing for Python" 314 | optional = false 315 | python-versions = ">=3.8" 316 | files = [ 317 | {file = "mypy-1.8.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:485a8942f671120f76afffff70f259e1cd0f0cfe08f81c05d8816d958d4577d3"}, 318 | {file = "mypy-1.8.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:df9824ac11deaf007443e7ed2a4a26bebff98d2bc43c6da21b2b64185da011c4"}, 319 | {file = "mypy-1.8.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2afecd6354bbfb6e0160f4e4ad9ba6e4e003b767dd80d85516e71f2e955ab50d"}, 320 | {file = "mypy-1.8.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8963b83d53ee733a6e4196954502b33567ad07dfd74851f32be18eb932fb1cb9"}, 321 | {file = "mypy-1.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:e46f44b54ebddbeedbd3d5b289a893219065ef805d95094d16a0af6630f5d410"}, 322 | {file = "mypy-1.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:855fe27b80375e5c5878492f0729540db47b186509c98dae341254c8f45f42ae"}, 323 | {file = "mypy-1.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4c886c6cce2d070bd7df4ec4a05a13ee20c0aa60cb587e8d1265b6c03cf91da3"}, 324 | {file = "mypy-1.8.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d19c413b3c07cbecf1f991e2221746b0d2a9410b59cb3f4fb9557f0365a1a817"}, 325 | {file = "mypy-1.8.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9261ed810972061388918c83c3f5cd46079d875026ba97380f3e3978a72f503d"}, 326 | {file = "mypy-1.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:51720c776d148bad2372ca21ca29256ed483aa9a4cdefefcef49006dff2a6835"}, 327 | {file = "mypy-1.8.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:52825b01f5c4c1c4eb0db253ec09c7aa17e1a7304d247c48b6f3599ef40db8bd"}, 328 | {file = "mypy-1.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f5ac9a4eeb1ec0f1ccdc6f326bcdb464de5f80eb07fb38b5ddd7b0de6bc61e55"}, 329 | {file = "mypy-1.8.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:afe3fe972c645b4632c563d3f3eff1cdca2fa058f730df2b93a35e3b0c538218"}, 330 | {file = "mypy-1.8.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:42c6680d256ab35637ef88891c6bd02514ccb7e1122133ac96055ff458f93fc3"}, 331 | {file = "mypy-1.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:720a5ca70e136b675af3af63db533c1c8c9181314d207568bbe79051f122669e"}, 332 | {file = "mypy-1.8.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:028cf9f2cae89e202d7b6593cd98db6759379f17a319b5faf4f9978d7084cdc6"}, 333 | {file = "mypy-1.8.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4e6d97288757e1ddba10dd9549ac27982e3e74a49d8d0179fc14d4365c7add66"}, 334 | {file = "mypy-1.8.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f1478736fcebb90f97e40aff11a5f253af890c845ee0c850fe80aa060a267c6"}, 335 | {file = "mypy-1.8.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:42419861b43e6962a649068a61f4a4839205a3ef525b858377a960b9e2de6e0d"}, 336 | {file = "mypy-1.8.0-cp38-cp38-win_amd64.whl", hash = "sha256:2b5b6c721bd4aabaadead3a5e6fa85c11c6c795e0c81a7215776ef8afc66de02"}, 337 | {file = "mypy-1.8.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5c1538c38584029352878a0466f03a8ee7547d7bd9f641f57a0f3017a7c905b8"}, 338 | {file = "mypy-1.8.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4ef4be7baf08a203170f29e89d79064463b7fc7a0908b9d0d5114e8009c3a259"}, 339 | {file = "mypy-1.8.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7178def594014aa6c35a8ff411cf37d682f428b3b5617ca79029d8ae72f5402b"}, 340 | {file = "mypy-1.8.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ab3c84fa13c04aeeeabb2a7f67a25ef5d77ac9d6486ff33ded762ef353aa5592"}, 341 | {file = "mypy-1.8.0-cp39-cp39-win_amd64.whl", hash = "sha256:99b00bc72855812a60d253420d8a2eae839b0afa4938f09f4d2aa9bb4654263a"}, 342 | {file = "mypy-1.8.0-py3-none-any.whl", hash = "sha256:538fd81bb5e430cc1381a443971c0475582ff9f434c16cd46d2c66763ce85d9d"}, 343 | {file = "mypy-1.8.0.tar.gz", hash = "sha256:6ff8b244d7085a0b425b56d327b480c3b29cafbd2eff27316a004f9a7391ae07"}, 344 | ] 345 | 346 | [package.dependencies] 347 | mypy-extensions = ">=1.0.0" 348 | tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} 349 | typing-extensions = ">=4.1.0" 350 | 351 | [package.extras] 352 | dmypy = ["psutil (>=4.0)"] 353 | install-types = ["pip"] 354 | mypyc = ["setuptools (>=50)"] 355 | reports = ["lxml"] 356 | 357 | [[package]] 358 | name = "mypy-extensions" 359 | version = "1.0.0" 360 | description = "Type system extensions for programs checked with the mypy type checker." 361 | optional = false 362 | python-versions = ">=3.5" 363 | files = [ 364 | {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, 365 | {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, 366 | ] 367 | 368 | [[package]] 369 | name = "nh3" 370 | version = "0.2.15" 371 | description = "Python bindings to the ammonia HTML sanitization library." 372 | optional = false 373 | python-versions = "*" 374 | files = [ 375 | {file = "nh3-0.2.15-cp37-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:9c0d415f6b7f2338f93035bba5c0d8c1b464e538bfbb1d598acd47d7969284f0"}, 376 | {file = "nh3-0.2.15-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:6f42f99f0cf6312e470b6c09e04da31f9abaadcd3eb591d7d1a88ea931dca7f3"}, 377 | {file = "nh3-0.2.15-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac19c0d68cd42ecd7ead91a3a032fdfff23d29302dbb1311e641a130dfefba97"}, 378 | {file = "nh3-0.2.15-cp37-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5f0d77272ce6d34db6c87b4f894f037d55183d9518f948bba236fe81e2bb4e28"}, 379 | {file = "nh3-0.2.15-cp37-abi3-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:8d595df02413aa38586c24811237e95937ef18304e108b7e92c890a06793e3bf"}, 380 | {file = "nh3-0.2.15-cp37-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:86e447a63ca0b16318deb62498db4f76fc60699ce0a1231262880b38b6cff911"}, 381 | {file = "nh3-0.2.15-cp37-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3277481293b868b2715907310c7be0f1b9d10491d5adf9fce11756a97e97eddf"}, 382 | {file = "nh3-0.2.15-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:60684857cfa8fdbb74daa867e5cad3f0c9789415aba660614fe16cd66cbb9ec7"}, 383 | {file = "nh3-0.2.15-cp37-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3b803a5875e7234907f7d64777dfde2b93db992376f3d6d7af7f3bc347deb305"}, 384 | {file = "nh3-0.2.15-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:0d02d0ff79dfd8208ed25a39c12cbda092388fff7f1662466e27d97ad011b770"}, 385 | {file = "nh3-0.2.15-cp37-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:f3b53ba93bb7725acab1e030bc2ecd012a817040fd7851b332f86e2f9bb98dc6"}, 386 | {file = "nh3-0.2.15-cp37-abi3-musllinux_1_2_i686.whl", hash = "sha256:b1e97221cedaf15a54f5243f2c5894bb12ca951ae4ddfd02a9d4ea9df9e1a29d"}, 387 | {file = "nh3-0.2.15-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a5167a6403d19c515217b6bcaaa9be420974a6ac30e0da9e84d4fc67a5d474c5"}, 388 | {file = "nh3-0.2.15-cp37-abi3-win32.whl", hash = "sha256:427fecbb1031db085eaac9931362adf4a796428ef0163070c484b5a768e71601"}, 389 | {file = "nh3-0.2.15-cp37-abi3-win_amd64.whl", hash = "sha256:bc2d086fb540d0fa52ce35afaded4ea526b8fc4d3339f783db55c95de40ef02e"}, 390 | {file = "nh3-0.2.15.tar.gz", hash = "sha256:d1e30ff2d8d58fb2a14961f7aac1bbb1c51f9bdd7da727be35c63826060b0bf3"}, 391 | ] 392 | 393 | [[package]] 394 | name = "packaging" 395 | version = "23.2" 396 | description = "Core utilities for Python packages" 397 | optional = false 398 | python-versions = ">=3.7" 399 | files = [ 400 | {file = "packaging-23.2-py3-none-any.whl", hash = "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7"}, 401 | {file = "packaging-23.2.tar.gz", hash = "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5"}, 402 | ] 403 | 404 | [[package]] 405 | name = "pathspec" 406 | version = "0.12.1" 407 | description = "Utility library for gitignore style pattern matching of file paths." 408 | optional = false 409 | python-versions = ">=3.8" 410 | files = [ 411 | {file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"}, 412 | {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"}, 413 | ] 414 | 415 | [[package]] 416 | name = "platformdirs" 417 | version = "4.2.0" 418 | description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." 419 | optional = false 420 | python-versions = ">=3.8" 421 | files = [ 422 | {file = "platformdirs-4.2.0-py3-none-any.whl", hash = "sha256:0614df2a2f37e1a662acbd8e2b25b92ccf8632929bc6d43467e17fe89c75e068"}, 423 | {file = "platformdirs-4.2.0.tar.gz", hash = "sha256:ef0cc731df711022c174543cb70a9b5bd22e5a9337c8624ef2c2ceb8ddad8768"}, 424 | ] 425 | 426 | [package.extras] 427 | docs = ["furo (>=2023.9.10)", "proselint (>=0.13)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.25.2)"] 428 | test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)"] 429 | 430 | [[package]] 431 | name = "pluggy" 432 | version = "1.4.0" 433 | description = "plugin and hook calling mechanisms for python" 434 | optional = false 435 | python-versions = ">=3.8" 436 | files = [ 437 | {file = "pluggy-1.4.0-py3-none-any.whl", hash = "sha256:7db9f7b503d67d1c5b95f59773ebb58a8c1c288129a88665838012cfb07b8981"}, 438 | {file = "pluggy-1.4.0.tar.gz", hash = "sha256:8c85c2876142a764e5b7548e7d9a0e0ddb46f5185161049a79b7e974454223be"}, 439 | ] 440 | 441 | [package.extras] 442 | dev = ["pre-commit", "tox"] 443 | testing = ["pytest", "pytest-benchmark"] 444 | 445 | [[package]] 446 | name = "py-cpuinfo" 447 | version = "9.0.0" 448 | description = "Get CPU info with pure Python" 449 | optional = false 450 | python-versions = "*" 451 | files = [ 452 | {file = "py-cpuinfo-9.0.0.tar.gz", hash = "sha256:3cdbbf3fac90dc6f118bfd64384f309edeadd902d7c8fb17f02ffa1fc3f49690"}, 453 | {file = "py_cpuinfo-9.0.0-py3-none-any.whl", hash = "sha256:859625bc251f64e21f077d099d4162689c762b5d6a4c3c97553d56241c9674d5"}, 454 | ] 455 | 456 | [[package]] 457 | name = "pygments" 458 | version = "2.17.2" 459 | description = "Pygments is a syntax highlighting package written in Python." 460 | optional = false 461 | python-versions = ">=3.7" 462 | files = [ 463 | {file = "pygments-2.17.2-py3-none-any.whl", hash = "sha256:b27c2826c47d0f3219f29554824c30c5e8945175d888647acd804ddd04af846c"}, 464 | {file = "pygments-2.17.2.tar.gz", hash = "sha256:da46cec9fd2de5be3a8a784f434e4c4ab670b4ff54d605c4c2717e9d49c4c367"}, 465 | ] 466 | 467 | [package.extras] 468 | plugins = ["importlib-metadata"] 469 | windows-terminal = ["colorama (>=0.4.6)"] 470 | 471 | [[package]] 472 | name = "pytest" 473 | version = "8.0.0" 474 | description = "pytest: simple powerful testing with Python" 475 | optional = false 476 | python-versions = ">=3.8" 477 | files = [ 478 | {file = "pytest-8.0.0-py3-none-any.whl", hash = "sha256:50fb9cbe836c3f20f0dfa99c565201fb75dc54c8d76373cd1bde06b06657bdb6"}, 479 | {file = "pytest-8.0.0.tar.gz", hash = "sha256:249b1b0864530ba251b7438274c4d251c58d868edaaec8762893ad4a0d71c36c"}, 480 | ] 481 | 482 | [package.dependencies] 483 | colorama = {version = "*", markers = "sys_platform == \"win32\""} 484 | exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} 485 | iniconfig = "*" 486 | packaging = "*" 487 | pluggy = ">=1.3.0,<2.0" 488 | tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""} 489 | 490 | [package.extras] 491 | testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] 492 | 493 | [[package]] 494 | name = "pytest-benchmark" 495 | version = "4.0.0" 496 | description = "A ``pytest`` fixture for benchmarking code. It will group the tests into rounds that are calibrated to the chosen timer." 497 | optional = false 498 | python-versions = ">=3.7" 499 | files = [ 500 | {file = "pytest-benchmark-4.0.0.tar.gz", hash = "sha256:fb0785b83efe599a6a956361c0691ae1dbb5318018561af10f3e915caa0048d1"}, 501 | {file = "pytest_benchmark-4.0.0-py3-none-any.whl", hash = "sha256:fdb7db64e31c8b277dff9850d2a2556d8b60bcb0ea6524e36e28ffd7c87f71d6"}, 502 | ] 503 | 504 | [package.dependencies] 505 | py-cpuinfo = "*" 506 | pytest = ">=3.8" 507 | 508 | [package.extras] 509 | aspect = ["aspectlib"] 510 | elasticsearch = ["elasticsearch"] 511 | histogram = ["pygal", "pygaljs"] 512 | 513 | [[package]] 514 | name = "python-dateutil" 515 | version = "2.8.2" 516 | description = "Extensions to the standard Python datetime module" 517 | optional = false 518 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" 519 | files = [ 520 | {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"}, 521 | {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"}, 522 | ] 523 | 524 | [package.dependencies] 525 | six = ">=1.5" 526 | 527 | [[package]] 528 | name = "pyyaml" 529 | version = "6.0.1" 530 | description = "YAML parser and emitter for Python" 531 | optional = false 532 | python-versions = ">=3.6" 533 | files = [ 534 | {file = "PyYAML-6.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d858aa552c999bc8a8d57426ed01e40bef403cd8ccdd0fc5f6f04a00414cac2a"}, 535 | {file = "PyYAML-6.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f"}, 536 | {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"}, 537 | {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"}, 538 | {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"}, 539 | {file = "PyYAML-6.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290"}, 540 | {file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"}, 541 | {file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"}, 542 | {file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"}, 543 | {file = "PyYAML-6.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f003ed9ad21d6a4713f0a9b5a7a0a79e08dd0f221aff4525a2be4c346ee60aab"}, 544 | {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"}, 545 | {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"}, 546 | {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"}, 547 | {file = "PyYAML-6.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b"}, 548 | {file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"}, 549 | {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, 550 | {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, 551 | {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, 552 | {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, 553 | {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, 554 | {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, 555 | {file = "PyYAML-6.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df"}, 556 | {file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"}, 557 | {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"}, 558 | {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"}, 559 | {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:afd7e57eddb1a54f0f1a974bc4391af8bcce0b444685d936840f125cf046d5bd"}, 560 | {file = "PyYAML-6.0.1-cp36-cp36m-win32.whl", hash = "sha256:fca0e3a251908a499833aa292323f32437106001d436eca0e6e7833256674585"}, 561 | {file = "PyYAML-6.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:f22ac1c3cac4dbc50079e965eba2c1058622631e526bd9afd45fedd49ba781fa"}, 562 | {file = "PyYAML-6.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b1275ad35a5d18c62a7220633c913e1b42d44b46ee12554e5fd39c70a243d6a3"}, 563 | {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:18aeb1bf9a78867dc38b259769503436b7c72f7a1f1f4c93ff9a17de54319b27"}, 564 | {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:596106435fa6ad000c2991a98fa58eeb8656ef2325d7e158344fb33864ed87e3"}, 565 | {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:baa90d3f661d43131ca170712d903e6295d1f7a0f595074f151c0aed377c9b9c"}, 566 | {file = "PyYAML-6.0.1-cp37-cp37m-win32.whl", hash = "sha256:9046c58c4395dff28dd494285c82ba00b546adfc7ef001486fbf0324bc174fba"}, 567 | {file = "PyYAML-6.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:4fb147e7a67ef577a588a0e2c17b6db51dda102c71de36f8549b6816a96e1867"}, 568 | {file = "PyYAML-6.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1d4c7e777c441b20e32f52bd377e0c409713e8bb1386e1099c2415f26e479595"}, 569 | {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"}, 570 | {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"}, 571 | {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"}, 572 | {file = "PyYAML-6.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6"}, 573 | {file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"}, 574 | {file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"}, 575 | {file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"}, 576 | {file = "PyYAML-6.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c8098ddcc2a85b61647b2590f825f3db38891662cfc2fc776415143f599bb859"}, 577 | {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"}, 578 | {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"}, 579 | {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"}, 580 | {file = "PyYAML-6.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5"}, 581 | {file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"}, 582 | {file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"}, 583 | {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, 584 | ] 585 | 586 | [[package]] 587 | name = "pyyaml-env-tag" 588 | version = "0.1" 589 | description = "A custom YAML tag for referencing environment variables in YAML files. " 590 | optional = false 591 | python-versions = ">=3.6" 592 | files = [ 593 | {file = "pyyaml_env_tag-0.1-py3-none-any.whl", hash = "sha256:af31106dec8a4d68c60207c1886031cbf839b68aa7abccdb19868200532c2069"}, 594 | {file = "pyyaml_env_tag-0.1.tar.gz", hash = "sha256:70092675bda14fdec33b31ba77e7543de9ddc88f2e5b99160396572d11525bdb"}, 595 | ] 596 | 597 | [package.dependencies] 598 | pyyaml = "*" 599 | 600 | [[package]] 601 | name = "readme-renderer" 602 | version = "42.0" 603 | description = "readme_renderer is a library for rendering readme descriptions for Warehouse" 604 | optional = false 605 | python-versions = ">=3.8" 606 | files = [ 607 | {file = "readme_renderer-42.0-py3-none-any.whl", hash = "sha256:13d039515c1f24de668e2c93f2e877b9dbe6c6c32328b90a40a49d8b2b85f36d"}, 608 | {file = "readme_renderer-42.0.tar.gz", hash = "sha256:2d55489f83be4992fe4454939d1a051c33edbab778e82761d060c9fc6b308cd1"}, 609 | ] 610 | 611 | [package.dependencies] 612 | docutils = ">=0.13.1" 613 | nh3 = ">=0.2.14" 614 | Pygments = ">=2.5.1" 615 | 616 | [package.extras] 617 | md = ["cmarkgfm (>=0.8.0)"] 618 | 619 | [[package]] 620 | name = "ruff" 621 | version = "0.2.1" 622 | description = "An extremely fast Python linter and code formatter, written in Rust." 623 | optional = false 624 | python-versions = ">=3.7" 625 | files = [ 626 | {file = "ruff-0.2.1-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:dd81b911d28925e7e8b323e8d06951554655021df8dd4ac3045d7212ac4ba080"}, 627 | {file = "ruff-0.2.1-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:dc586724a95b7d980aa17f671e173df00f0a2eef23f8babbeee663229a938fec"}, 628 | {file = "ruff-0.2.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c92db7101ef5bfc18e96777ed7bc7c822d545fa5977e90a585accac43d22f18a"}, 629 | {file = "ruff-0.2.1-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:13471684694d41ae0f1e8e3a7497e14cd57ccb7dd72ae08d56a159d6c9c3e30e"}, 630 | {file = "ruff-0.2.1-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a11567e20ea39d1f51aebd778685582d4c56ccb082c1161ffc10f79bebe6df35"}, 631 | {file = "ruff-0.2.1-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:00a818e2db63659570403e44383ab03c529c2b9678ba4ba6c105af7854008105"}, 632 | {file = "ruff-0.2.1-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:be60592f9d218b52f03384d1325efa9d3b41e4c4d55ea022cd548547cc42cd2b"}, 633 | {file = "ruff-0.2.1-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fbd2288890b88e8aab4499e55148805b58ec711053588cc2f0196a44f6e3d855"}, 634 | {file = "ruff-0.2.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f3ef052283da7dec1987bba8d8733051c2325654641dfe5877a4022108098683"}, 635 | {file = "ruff-0.2.1-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:7022d66366d6fded4ba3889f73cd791c2d5621b2ccf34befc752cb0df70f5fad"}, 636 | {file = "ruff-0.2.1-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:0a725823cb2a3f08ee743a534cb6935727d9e47409e4ad72c10a3faf042ad5ba"}, 637 | {file = "ruff-0.2.1-py3-none-musllinux_1_2_i686.whl", hash = "sha256:0034d5b6323e6e8fe91b2a1e55b02d92d0b582d2953a2b37a67a2d7dedbb7acc"}, 638 | {file = "ruff-0.2.1-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:e5cb5526d69bb9143c2e4d2a115d08ffca3d8e0fddc84925a7b54931c96f5c02"}, 639 | {file = "ruff-0.2.1-py3-none-win32.whl", hash = "sha256:6b95ac9ce49b4fb390634d46d6ece32ace3acdd52814671ccaf20b7f60adb232"}, 640 | {file = "ruff-0.2.1-py3-none-win_amd64.whl", hash = "sha256:e3affdcbc2afb6f5bd0eb3130139ceedc5e3f28d206fe49f63073cb9e65988e0"}, 641 | {file = "ruff-0.2.1-py3-none-win_arm64.whl", hash = "sha256:efababa8e12330aa94a53e90a81eb6e2d55f348bc2e71adbf17d9cad23c03ee6"}, 642 | {file = "ruff-0.2.1.tar.gz", hash = "sha256:3b42b5d8677cd0c72b99fcaf068ffc62abb5a19e71b4a3b9cfa50658a0af02f1"}, 643 | ] 644 | 645 | [[package]] 646 | name = "six" 647 | version = "1.16.0" 648 | description = "Python 2 and 3 compatibility utilities" 649 | optional = false 650 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" 651 | files = [ 652 | {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, 653 | {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, 654 | ] 655 | 656 | [[package]] 657 | name = "tomli" 658 | version = "2.0.1" 659 | description = "A lil' TOML parser" 660 | optional = false 661 | python-versions = ">=3.7" 662 | files = [ 663 | {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, 664 | {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, 665 | ] 666 | 667 | [[package]] 668 | name = "typing-extensions" 669 | version = "4.9.0" 670 | description = "Backported and Experimental Type Hints for Python 3.8+" 671 | optional = false 672 | python-versions = ">=3.8" 673 | files = [ 674 | {file = "typing_extensions-4.9.0-py3-none-any.whl", hash = "sha256:af72aea155e91adfc61c3ae9e0e342dbc0cba726d6cba4b6c72c1f34e47291cd"}, 675 | {file = "typing_extensions-4.9.0.tar.gz", hash = "sha256:23478f88c37f27d76ac8aee6c905017a143b0b1b886c3c9f66bc2fd94f9f5783"}, 676 | ] 677 | 678 | [[package]] 679 | name = "watchdog" 680 | version = "4.0.0" 681 | description = "Filesystem events monitoring" 682 | optional = false 683 | python-versions = ">=3.8" 684 | files = [ 685 | {file = "watchdog-4.0.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:39cb34b1f1afbf23e9562501673e7146777efe95da24fab5707b88f7fb11649b"}, 686 | {file = "watchdog-4.0.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c522392acc5e962bcac3b22b9592493ffd06d1fc5d755954e6be9f4990de932b"}, 687 | {file = "watchdog-4.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6c47bdd680009b11c9ac382163e05ca43baf4127954c5f6d0250e7d772d2b80c"}, 688 | {file = "watchdog-4.0.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8350d4055505412a426b6ad8c521bc7d367d1637a762c70fdd93a3a0d595990b"}, 689 | {file = "watchdog-4.0.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c17d98799f32e3f55f181f19dd2021d762eb38fdd381b4a748b9f5a36738e935"}, 690 | {file = "watchdog-4.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4986db5e8880b0e6b7cd52ba36255d4793bf5cdc95bd6264806c233173b1ec0b"}, 691 | {file = "watchdog-4.0.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:11e12fafb13372e18ca1bbf12d50f593e7280646687463dd47730fd4f4d5d257"}, 692 | {file = "watchdog-4.0.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:5369136a6474678e02426bd984466343924d1df8e2fd94a9b443cb7e3aa20d19"}, 693 | {file = "watchdog-4.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:76ad8484379695f3fe46228962017a7e1337e9acadafed67eb20aabb175df98b"}, 694 | {file = "watchdog-4.0.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:45cc09cc4c3b43fb10b59ef4d07318d9a3ecdbff03abd2e36e77b6dd9f9a5c85"}, 695 | {file = "watchdog-4.0.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:eed82cdf79cd7f0232e2fdc1ad05b06a5e102a43e331f7d041e5f0e0a34a51c4"}, 696 | {file = "watchdog-4.0.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:ba30a896166f0fee83183cec913298151b73164160d965af2e93a20bbd2ab605"}, 697 | {file = "watchdog-4.0.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:d18d7f18a47de6863cd480734613502904611730f8def45fc52a5d97503e5101"}, 698 | {file = "watchdog-4.0.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2895bf0518361a9728773083908801a376743bcc37dfa252b801af8fd281b1ca"}, 699 | {file = "watchdog-4.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:87e9df830022488e235dd601478c15ad73a0389628588ba0b028cb74eb72fed8"}, 700 | {file = "watchdog-4.0.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:6e949a8a94186bced05b6508faa61b7adacc911115664ccb1923b9ad1f1ccf7b"}, 701 | {file = "watchdog-4.0.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:6a4db54edea37d1058b08947c789a2354ee02972ed5d1e0dca9b0b820f4c7f92"}, 702 | {file = "watchdog-4.0.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:d31481ccf4694a8416b681544c23bd271f5a123162ab603c7d7d2dd7dd901a07"}, 703 | {file = "watchdog-4.0.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:8fec441f5adcf81dd240a5fe78e3d83767999771630b5ddfc5867827a34fa3d3"}, 704 | {file = "watchdog-4.0.0-py3-none-manylinux2014_armv7l.whl", hash = "sha256:6a9c71a0b02985b4b0b6d14b875a6c86ddea2fdbebd0c9a720a806a8bbffc69f"}, 705 | {file = "watchdog-4.0.0-py3-none-manylinux2014_i686.whl", hash = "sha256:557ba04c816d23ce98a06e70af6abaa0485f6d94994ec78a42b05d1c03dcbd50"}, 706 | {file = "watchdog-4.0.0-py3-none-manylinux2014_ppc64.whl", hash = "sha256:d0f9bd1fd919134d459d8abf954f63886745f4660ef66480b9d753a7c9d40927"}, 707 | {file = "watchdog-4.0.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:f9b2fdca47dc855516b2d66eef3c39f2672cbf7e7a42e7e67ad2cbfcd6ba107d"}, 708 | {file = "watchdog-4.0.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:73c7a935e62033bd5e8f0da33a4dcb763da2361921a69a5a95aaf6c93aa03a87"}, 709 | {file = "watchdog-4.0.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:6a80d5cae8c265842c7419c560b9961561556c4361b297b4c431903f8c33b269"}, 710 | {file = "watchdog-4.0.0-py3-none-win32.whl", hash = "sha256:8f9a542c979df62098ae9c58b19e03ad3df1c9d8c6895d96c0d51da17b243b1c"}, 711 | {file = "watchdog-4.0.0-py3-none-win_amd64.whl", hash = "sha256:f970663fa4f7e80401a7b0cbeec00fa801bf0287d93d48368fc3e6fa32716245"}, 712 | {file = "watchdog-4.0.0-py3-none-win_ia64.whl", hash = "sha256:9a03e16e55465177d416699331b0f3564138f1807ecc5f2de9d55d8f188d08c7"}, 713 | {file = "watchdog-4.0.0.tar.gz", hash = "sha256:e3e7065cbdabe6183ab82199d7a4f6b3ba0a438c5a512a68559846ccb76a78ec"}, 714 | ] 715 | 716 | [package.extras] 717 | watchmedo = ["PyYAML (>=3.10)"] 718 | 719 | [[package]] 720 | name = "zipp" 721 | version = "3.17.0" 722 | description = "Backport of pathlib-compatible object wrapper for zip files" 723 | optional = false 724 | python-versions = ">=3.8" 725 | files = [ 726 | {file = "zipp-3.17.0-py3-none-any.whl", hash = "sha256:0e923e726174922dce09c53c59ad483ff7bbb8e572e00c7f7c46b88556409f31"}, 727 | {file = "zipp-3.17.0.tar.gz", hash = "sha256:84e64a1c28cf7e91ed2078bb8cc8c259cb19b76942096c8d7b84947690cabaf0"}, 728 | ] 729 | 730 | [package.extras] 731 | docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-lint"] 732 | testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-ignore-flaky", "pytest-mypy (>=0.9.1)", "pytest-ruff"] 733 | 734 | [metadata] 735 | lock-version = "2.0" 736 | python-versions = ">=3.8" 737 | content-hash = "9b89cd2c097eec981f461ae82a4aebdd0a1256ea9c2d4b3220802e49d58780d0" 738 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "sproc" 3 | version = "2.4.1" 4 | description = "⛏ Subprocesseses for subhumanses ⛏" 5 | authors = ["Tom Ritchford "] 6 | license = "MIT" 7 | readme = "README.md" 8 | repository = "https://github.com/rec/sproc" 9 | homepage = "https://github.com/rec/sproc" 10 | documentation = "https://rec.github.io/sproc" 11 | 12 | [tool.poetry.dependencies] 13 | python = ">=3.8" 14 | 15 | [tool.poetry.group.dev.dependencies] 16 | coverage = "*" 17 | pytest = "*" 18 | mkdocs = "*" 19 | mypy = "*" 20 | ruff = "*" 21 | pytest-benchmark = "^4.0.0" 22 | readme-renderer = "*" 23 | 24 | 25 | [tool.coverage] 26 | [tool.coverage.run] 27 | branch = true 28 | source = ["sproc"] 29 | 30 | [tool.coverage.report] 31 | fail_under = 81 32 | skip_covered = true 33 | 34 | [tool.ruff] 35 | line-length = 88 36 | 37 | [tool.ruff.format] 38 | quote-style = "single" 39 | 40 | [tool.mypy] 41 | strict = true 42 | [build-system] 43 | requires = ["poetry-core"] 44 | build-backend = "poetry.core.masonry.api" 45 | -------------------------------------------------------------------------------- /sproc/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | # ⛏️sproc: subprocesseses for subhumanses ⛏ 3 | 4 | Run a command in a subprocess and yield lines of text from `stdout` and 5 | `stderr` independently. 6 | 7 | Useful for handling long-running proceesses that write to both `stdout` and 8 | `stderr`. 9 | 10 | ### Simple Example 11 | 12 | import sproc 13 | 14 | CMD = 'my-unix-command "My Cool File.txt" No-file.txt' 15 | 16 | for ok, line in sproc.Sub(CMD) as sp: 17 | if ok: 18 | print(' ', line) 19 | else: 20 | print('!', line) 21 | 22 | if sp.returncode: 23 | print('Error code', sp.returncode) 24 | 25 | # Return two lists of text lines and a returncode 26 | out_lines, err_lines, returncode = sproc.run(CMD) 27 | 28 | # Call callback functions with lines of text read from stdout and stderr 29 | returncode = sproc.call(CMD, save_results, print_errors) 30 | 31 | # Log stdout and stderr, with prefixes 32 | returncode = sproc.log(CMD) 33 | """ 34 | 35 | import functools 36 | import shlex 37 | import subprocess 38 | import typing as t 39 | from queue import Queue 40 | from threading import Thread 41 | from typing import Callable, List, Mapping, Optional, Sequence, Union 42 | 43 | __all__ = 'Sub', 'call', 'call_in_thread', 'run', 'log' 44 | 45 | DEFAULTS = {'stderr': subprocess.PIPE, 'stdout': subprocess.PIPE} 46 | 47 | Callback = Optional[t.Callable[..., t.Any]] 48 | Cmd = Union[str, Sequence[str]] 49 | 50 | 51 | class Sub: 52 | """ 53 | Sub is a class to Iterate over lines or chunks of text from a subprocess. 54 | 55 | If `by_lines` is true, use readline() to get each new item; 56 | if false, use read1(). 57 | 58 | Args: 59 | cmd: The command to run in a subprocess 60 | 61 | by_lines: If `by_lines` is true, `Sub` uses readline() to get each new 62 | item; otherwise, it uses read1() to get each chunk as it comes. 63 | 64 | kwargs: The arguments to subprocess.Popen. 65 | 66 | If `kwargs['shell']` is true, `Popen` expects a string, 67 | and so if `cmd` is not a string, it is joined using `shlex`. 68 | 69 | If `kwargs['shell']` is false, `Popen` expects a list of strings, 70 | and so if `cmd` is a string, it is split using `shlex`. 71 | """ 72 | 73 | @functools.wraps(subprocess.Popen) 74 | def __init__(self, cmd: Cmd, *, by_lines: bool = True, **kwargs: t.Any) -> None: 75 | if 'stdout' in kwargs or 'stderr' in kwargs: 76 | raise ValueError('Cannot set stdout or stderr') 77 | 78 | self.cmd = cmd 79 | self.by_lines = by_lines 80 | self.kwargs = dict(kwargs, **DEFAULTS) 81 | self._threads: List[Thread] = [] 82 | 83 | shell = kwargs.get('shell', False) 84 | if isinstance(cmd, str): 85 | if not shell: 86 | self.cmd = shlex.split(cmd) 87 | else: 88 | if shell: 89 | self.cmd = shlex.join(cmd) 90 | 91 | @property 92 | def returncode(self) -> int: 93 | return self.proc.returncode if self.proc else 0 94 | 95 | def __iter__(self) -> t.Iterator[t.Tuple[bool, str]]: 96 | """ 97 | Yields a sequence of `ok, line` pairs from `stdout` and `stderr` of 98 | a subprocess, where `ok` is `True` if `line` came from `stdout` 99 | and `False` if it came from `stderr`. 100 | 101 | After iteration is done, the `.returncode` property contains 102 | the error code from the subprocess, an integer where 0 means no error. 103 | """ 104 | queue: Queue[t.Tuple[bool, t.Optional[str]]] = Queue() 105 | 106 | with subprocess.Popen(self.cmd, **self.kwargs) as self.proc: 107 | for ok in False, True: 108 | self._start_thread(ok, lambda o, s: queue.put((o, s))) 109 | 110 | finished = 0 111 | while finished < 2: 112 | ok, line = queue.get() 113 | if line: 114 | yield ok, line 115 | else: 116 | finished += 1 117 | 118 | def call(self, out: Callback = None, err: Callback = None) -> int: 119 | """ 120 | Run the subprocess, and call function `out` with lines from 121 | `stdout` and function `err` with lines from `stderr`. 122 | 123 | Blocks until the subprocess is complete: the callbacks to `out` and 124 | 'err` are on the current thread. 125 | 126 | Args: 127 | out: if not None, `out` is called for each line from the 128 | subprocess's stdout 129 | 130 | err: if not None, `err` is called for each line from the 131 | subprocess's stderr, 132 | """ 133 | callback = self._callback(out, err) 134 | for ok, line in self: 135 | callback(ok, line) 136 | 137 | return self.returncode 138 | 139 | def call_async(self, out: Callback = None, err: Callback = None) -> None: 140 | # DEPRECATED: now called "call_in_thread" 141 | return self.call_in_thread(out, err) 142 | 143 | def call_in_thread(self, out: Callback = None, err: Callback = None) -> None: 144 | """ 145 | Run the subprocess, and asynchronously call function `out` with lines 146 | from `stdout`, and function `err` with lines from `stderr`. 147 | 148 | Does not block - immediately returns. 149 | 150 | Args: 151 | out: If not None, `out` is called for each line from the 152 | subprocess's stdout 153 | 154 | err: If not None, `err` is called for each line from the 155 | subprocess's stderr, 156 | """ 157 | with subprocess.Popen(self.cmd, **self.kwargs) as self.proc: 158 | callback = self._callback(out, err) 159 | for ok in False, True: 160 | self._start_thread(ok, callback) 161 | 162 | def run(self) -> t.Tuple[t.List[str], t.List[str], int]: 163 | """ 164 | Reads lines from `stdout` and `stderr` into two lists `out` and `err`, 165 | then returns a tuple `(out, err, returncode)` 166 | """ 167 | out: t.List[str] = [] 168 | err: t.List[str] = [] 169 | 170 | self.call(out.append, err.append) 171 | return out, err, self.returncode 172 | 173 | def log( 174 | self, out: str = ' ', err: str = '! ', print: t.Callable[..., None] = print 175 | ) -> int: 176 | """ 177 | Read lines from `stdin` and `stderr` and prints them with prefixes 178 | 179 | Returns the shell integer error code from the subprocess, where 0 means 180 | no error. 181 | 182 | Args: 183 | out: The contents of `out` prepends strings from stdout 184 | err: The contents of `err` prepends strings from stderr 185 | print: A function that accepts individual strings 186 | """ 187 | return self.call(lambda x: print(out + x), lambda x: print(err + x)) 188 | 189 | def join(self, timeout: Optional[int] = None) -> None: 190 | """Join the stream handling threads""" 191 | for t in self._threads: 192 | t.join(timeout) 193 | 194 | def kill(self) -> None: 195 | """Kill the running process, if any""" 196 | if self.proc: 197 | self.proc.kill() 198 | 199 | def _start_thread( 200 | self, ok: bool, callback: t.Callable[[bool, t.Optional[str]], None] 201 | ) -> None: 202 | def read_stream() -> None: 203 | try: 204 | stream = self.proc.stdout if ok else self.proc.stderr 205 | assert stream is not None 206 | line = '.' 207 | while line or self.proc.poll() is None: 208 | if self.by_lines: 209 | line = stream.readline() 210 | else: 211 | line = stream.read() 212 | 213 | if line: 214 | if not isinstance(line, str): 215 | line = line.decode('utf8') 216 | callback(ok, line) 217 | finally: 218 | callback(ok, None) 219 | 220 | th = Thread(target=read_stream, daemon=True) 221 | th.start() 222 | self._threads.append(th) 223 | 224 | def _callback( 225 | self, 226 | out: t.Optional[t.Callable[..., t.Any]], 227 | err: t.Optional[t.Callable[..., t.Any]], 228 | ) -> t.Callable[[bool, t.Optional[str]], t.Any]: 229 | if out and err: 230 | return lambda ok, line: line and (out(line) if ok else err(line)) 231 | if out: 232 | return lambda ok, line: line and ok and out(line) 233 | if err: 234 | return lambda ok, line: line and not ok and err(line) 235 | else: 236 | return lambda ok, line: None 237 | 238 | 239 | def call(cmd: Cmd, out: Callback = None, err: Callback = None, **kwargs: t.Any) -> int: 240 | """ 241 | Args: 242 | cmd: The command to run in a subprocess 243 | 244 | out: if not None, `out` is called for each line from the 245 | subprocess's stdout 246 | 247 | err: if not None, `err` is called for each line from the 248 | subprocess's stderr, 249 | 250 | kwargs: The arguments to subprocess.Popen. 251 | """ 252 | return Sub(cmd, **kwargs).call(out, err) 253 | 254 | 255 | def call_in_thread( 256 | cmd: Cmd, out: Callback = None, err: Callback = None, **kwargs: t.Any 257 | ) -> None: 258 | """ 259 | Args: 260 | cmd: The command to run in a subprocess 261 | 262 | out: if not None, `out` is called for each line from the 263 | subprocess's stdout 264 | 265 | err: if not None, `err` is called for each line from the 266 | subprocess's stderr, 267 | 268 | kwargs: The arguments to subprocess.Popen. 269 | """ 270 | return Sub(cmd, **kwargs).call_in_thread(out, err) 271 | 272 | 273 | call_async = call_in_thread 274 | 275 | 276 | @functools.wraps(Sub.__init__) 277 | def run(cmd: Cmd, **kwargs: t.Any) -> t.Tuple[t.List[str], t.List[str], int]: 278 | return Sub(cmd, **kwargs).run() 279 | 280 | 281 | def log( 282 | cmd: Cmd, 283 | out: str = ' ', 284 | err: str = '! ', 285 | print: t.Callable[..., None] = print, 286 | **kwargs: t.Any, 287 | ) -> int: 288 | """ 289 | Args: 290 | cmd: The command to run in a subprocess 291 | out: The contents of `out` prepends strings from stdout 292 | err: The contents of `err` prepends strings from stderr 293 | print: A function that accepts individual strings 294 | """ 295 | return Sub(cmd, **kwargs).log(out, err, print) 296 | -------------------------------------------------------------------------------- /sproc/py.typed: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rec/sproc/ba0ccb5164d52b9de743b5a1d0b405641bb053ea/sproc/py.typed -------------------------------------------------------------------------------- /test_sproc.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | import sproc 4 | 5 | 6 | class RunTest(unittest.TestCase): 7 | def setUp(self): 8 | self.lines = [] 9 | 10 | def sub(self, cmd, **kwds): 11 | return sproc.call(cmd, self.lines.append, self.lines.append, **kwds) 12 | 13 | def test_simple(self): 14 | for shell in False, True: 15 | self.lines.clear() 16 | error = self.sub('ls', shell=shell) 17 | 18 | assert error == 0 19 | assert 'sproc\n' in self.lines 20 | assert len(self.lines) >= 10 21 | 22 | def test_simple_by_chunks(self): 23 | for shell in False, True: 24 | self.lines.clear() 25 | error = self.sub('ls', shell=shell, by_lines=False) 26 | 27 | assert error == 0 28 | lines = ''.join(self.lines).splitlines() 29 | assert 'sproc' in lines 30 | assert len(lines) >= 10 31 | 32 | def test_error(self): 33 | for shell in False, True: 34 | self.lines.clear() 35 | error = self.sub('ls foo pyproject.toml bar', shell=shell) 36 | 37 | assert error 38 | assert 'pyproject.toml\n' in self.lines 39 | 40 | for _ in 'foo', 'bar': 41 | assert sum(i.endswith(_NO_SUCH) for i in self.lines) == 2 42 | 43 | def test_log(self): 44 | cmd = 'ls foo pyproject.toml bar' 45 | for shell in False, True: 46 | self.lines.clear() 47 | error = sproc.log(cmd, shell=shell, print=self.lines.append) 48 | 49 | assert error 50 | assert len(self.lines) == 3 51 | assert ' pyproject.toml\n' in self.lines 52 | 53 | for f in 'foo', 'bar': 54 | assert sum(f in i for i in self.lines) == 1 55 | 56 | assert sum(i.startswith('! ') for i in self.lines) == 2 57 | 58 | def test_run(self): 59 | cmd = 'ls foo pyproject.toml bar' 60 | for shell in False, True: 61 | out, err, error_code = sproc.run(cmd, shell=shell) 62 | 63 | assert len(err) == 2 64 | assert error_code 65 | assert 'pyproject.toml\n' in out 66 | 67 | for f in 'bar', 'foo': 68 | assert sum(f in i for i in err) == 1 69 | 70 | assert all(i.endswith(_NO_SUCH) for i in err) 71 | 72 | def test_async(self): 73 | for shell in False, True: 74 | lines, errors = [], [] 75 | sub = sproc.Sub('ls pyproject.toml MISSING', shell=shell) 76 | sub.call_async(lines.append, errors.append) 77 | sub.join() 78 | assert sub.returncode != 0 79 | 80 | assert 'pyproject.toml\n' in lines 81 | assert len(lines) == 1 82 | assert len(errors) == 1 83 | 84 | 85 | _NO_SUCH = 'No such file or directory\n' 86 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [flake8] 2 | ignore = 3 | # whitespace before ':'. See https://github.com/python/black/issues/315 4 | E203, 5 | 6 | # line break before binary operator 7 | W503 8 | 9 | exclude = build/*, *.ini, *.in, MANIFEST*, *.md, .eggs, doc/* 10 | max-complexity = -1 11 | --------------------------------------------------------------------------------