diff --git a/.github/dependabot.yaml b/.github/dependabot.yaml new file mode 100644 index 00000000..e0e0e33a --- /dev/null +++ b/.github/dependabot.yaml @@ -0,0 +1,11 @@ +# To get started with Dependabot version updates, you'll need to specify which +# package ecosystems to update and where the package manifests are located. +# Please see the documentation for all configuration options: +# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates + +version: 2 +updates: +- package-ecosystem: github-actions # https://docs.github.com/en/code-security/dependabot/working-with-dependabot/keeping-your-actions-up-to-date-with-dependabot + directory: / + schedule: + interval: monthly diff --git a/.github/workflows/pythonpublish.yml b/.github/workflows/pythonpublish.yml index b8b4fadd..e3f693aa 100644 --- a/.github/workflows/pythonpublish.yml +++ b/.github/workflows/pythonpublish.yml @@ -17,15 +17,15 @@ jobs: - name: Set up Python uses: actions/setup-python@v1 with: - python-version: '3.x' + python-version: 3.x - name: Install dependencies run: | python -m pip install --upgrade pip - pip install setuptools wheel twine + pip install poetry - name: Build and publish env: - TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }} - TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }} + PYPI_PASSWORD: ${{ secrets.PYPI_PASSWORD }} run: | - python setup.py sdist bdist_wheel - twine upload dist/* + poetry config pypi-token.pypi "${PYPI_PASSWORD}" + poetry build + poetry publish diff --git a/.github/workflows/test_cpac.yml b/.github/workflows/test_cpac.yml index 700bbaec..41b630c1 100644 --- a/.github/workflows/test_cpac.yml +++ b/.github/workflows/test_cpac.yml @@ -7,7 +7,6 @@ on: push: paths-ignore: - README.rst - pull_request: jobs: test_cpac: @@ -22,7 +21,7 @@ jobs: platform: [docker] tag: [latest, nightly] go: [1.14] - python: [3.7, 3.8, 3.9, "3.10", 3.11] + python: [3.8, 3.9, '3.10', 3.11, 3.12] singularity: [3.6.4] steps: @@ -45,20 +44,8 @@ jobs: - name: Prepare Singularity cache and tmp directories if: ${{ matrix.platform == 'singularity' }} run: mkdir -p ${SINGULARITY_CACHEDIR} && mkdir -p ${SINGULARITY_TMPDIR} - - name: Install dependencies - run: | - sudo apt-get install libffi-dev \ - flawfinder \ - libgpgme11-dev \ - libseccomp-dev \ - squashfs-tools \ - libssl1.1 libssl-dev \ - libuuid1 uuid-dev - pip install -r requirements.txt - pip install -r requirements-dev.txt - pip install coverage coveralls nipype - name: Install cpac - run: cd $GITHUB_WORKSPACE && pip install -e . + run: cd $GITHUB_WORKSPACE && pip install -e ".[dev,testing]" - name: Test cpac, platform and tag specified run: | coverage run --append -m pytest --basetemp=${PWD}/tmp --doctest-modules --platform ${{ matrix.platform }} --tag ${{ matrix.tag }} . @@ -81,12 +68,12 @@ jobs: - name: Report coverage uses: AndreMiras/coveralls-python-action@v20201129 with: - parallel: True + parallel: true flag-name: Test cpac ${{ matrix.platform }} with Python ${{ matrix.python }} continue-on-error: true finalize_coverage-report: - needs: - - test_cpac + needs: + - test_cpac runs-on: ubuntu-latest steps: - name: Finalize coverage report @@ -97,7 +84,7 @@ jobs: update_README: # Only update README if tests succeed needs: - - test_cpac + - test_cpac runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 00000000..21930ad2 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,57 @@ +fail_fast: false + +repos: +- repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.1.8 + hooks: + - id: ruff + args: [--fix] + - id: ruff-format + +- repo: https://github.com/pre-commit/mirrors-mypy + rev: v1.4.1 + hooks: + - id: mypy + additional_dependencies: + - types-tabulate + - types-PyYAML + args: [--python-version=3.8, --ignore-missing-imports] +- repo: https://github.com/macisamuele/language-formatters-pre-commit-hooks + rev: v2.11.0 + hooks: + - id: pretty-format-yaml + args: + - --autofix + - --indent=2 + - id: pretty-format-toml + exclude: ^poetry.lock$ + args: + - --autofix + - --indent=2 + - --no-sort + +- repo: https://github.com/python-poetry/poetry + rev: 1.6.0 + hooks: + - id: poetry-check + - id: poetry-lock + args: [--no-update] + +- repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.1.0 + hooks: + - id: check-case-conflict + - id: end-of-file-fixer + - id: mixed-line-ending + args: + - --fix=lf + - id: trailing-whitespace + - id: pretty-format-json + args: + - --autofix + - --indent=4 + - --no-sort-keys + - id: check-merge-conflict + - id: check-yaml + - id: check-json + - id: check-toml diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 74e78352..c7ad2cdb 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,11 +1,17 @@ ========= Changelog ========= -`Version 0.6.0: Support for C-PAC v1.8.5 `_ -=============== + +`Version 1.8.5: Support for C-PAC v1.8.5 `_ +===================================================================================================== + +* Changes versioning scheme to match supported C-PAC version * Handles relocation of default pipeline in C-PAC 1.8.5 * Fixes a bug preventing default bindings in Mac OS ≥ 10.15 * Replaces ``setup.cfg`` with ``pyproject.toml`` +* Replaces ``setuptools`` with ``poetry`` +* Adds pre-commit hooks for linting and formatting +* Require Python ≥ 3.8 `Version 0.5.0: Parse Resources `_ ======================================================================================== @@ -50,11 +56,11 @@ Changelog ======================================================================== * 📚 Update the main usage string to better articulate functionality * 📢🐳 Provide a clearer error message if package cannot connect to Docker. -* 🐳 Fix a bug introduced in `v0.2.4 ` where some crashfiles would print for ``cpac --platform singularity crash`` but not for ``cpac --platform docker crash`` -* 🚑 Fix some installation issues: +* 🐳 Fix a bug introduced in `v0.2.4 ` where some crashfiles would print for ``cpac --platform singularity crash`` but not for ``cpac --platform docker crash`` +* 🚑 Fix some installation issues: * All required packages are now installed with ``pip install cpac`` * Version is now set correctly -* 🐳 Fix a bug introduced in `v0.2.4 `_ where some crashfiles would print for ``cpac --platform singularity crash`` but not for ``cpac --platform docker crash`` +* 🐳 Fix a bug introduced in `v0.2.4 `_ where some crashfiles would print for ``cpac --platform singularity crash`` but not for ``cpac --platform docker crash`` * 🔬 Set `coverage reports `_ to report local paths `Version 0.2.4 `_ diff --git a/README.rst b/README.rst index 44105026..658f9ac7 100644 --- a/README.rst +++ b/README.rst @@ -10,12 +10,16 @@ A Python package that wraps `C-PAC `_, enabling users Description =========== -C-PAC Python Package is a lightweight Python package that handles interfacing a user's machine and a C-PAC container through a command line interface. +cpac Python Package is a lightweight Python package that handles interfacing a user's machine and a C-PAC container through a command line interface. + +.. admonition:: Note about cpac versioning + + This package's versioning scheme changed in version 1.8.5 to match C-PAC's versioning. From cpac v1.8.5 forward, the version of cpac indicates the newest supported version of C-PAC. Dependencies ============ -* `Python `_ ≥3.7 +* `Python `_ ≥ 3.8 * `pip `_ * At least one of: @@ -86,6 +90,8 @@ Usage enter (bash, shell) Enter a new C-PAC container via BASH. parse-resources (parse_resources) + . + When provided with a `callback.log` file, this utility can sort through the memory `runtime` usage, `estimate`, and associated `efficiency`, to identify the `n` tasks with the `highest` or `lowest` of each of these @@ -145,4 +151,3 @@ Usage .. |upload| image:: https://github.com/FCP-INDI/cpac/workflows/Upload%20Python%20Package/badge.svg :target: https://pypi.org/project/cpac/ :alt: upload Python package to PyPI - diff --git a/docs/conf.py b/docs/conf.py index 7a77b9c0..af040d5a 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -7,19 +7,20 @@ # # All configuration values have a default; values that are commented out # serve to show the default. - -import os -import sys +# mypy: ignore-errors import inspect +import os import shutil +import sys -__location__ = os.path.join(os.getcwd(), os.path.dirname( - inspect.getfile(inspect.currentframe()))) +__location__ = os.path.join( + os.getcwd(), os.path.dirname(inspect.getfile(inspect.currentframe())) +) # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. -sys.path.insert(0, os.path.join(__location__, '../src')) +sys.path.insert(0, os.path.join(__location__, "../src")) # -- Run sphinx-apidoc ------------------------------------------------------ # This hack is necessary since RTD does not issue `sphinx-apidoc` before running @@ -42,14 +43,14 @@ pass try: - import sphinx from semver.version import Version + import sphinx cmd_line_template = "sphinx-apidoc -f -o {outputdir} {moduledir}" cmd_line = cmd_line_template.format(outputdir=output_dir, moduledir=module_dir) args = cmd_line.split(" ") - if Version(*sphinx.__version__.split('.')).compare(Version('1','7')): + if Version(*sphinx.__version__.split(".")).compare(Version("1", "7")): args = args[1:] apidoc.main(args) @@ -63,35 +64,43 @@ # Add any Sphinx extension module names here, as strings. They can be extensions # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. -extensions = ['sphinx.ext.autodoc', 'sphinx.ext.intersphinx', 'sphinx.ext.todo', - 'sphinx.ext.autosummary', 'sphinx.ext.viewcode', 'sphinx.ext.coverage', - 'sphinx.ext.doctest', 'sphinx.ext.ifconfig', 'sphinx.ext.mathjax', - 'sphinx.ext.napoleon'] +extensions = [ + "sphinx.ext.autodoc", + "sphinx.ext.intersphinx", + "sphinx.ext.todo", + "sphinx.ext.autosummary", + "sphinx.ext.viewcode", + "sphinx.ext.coverage", + "sphinx.ext.doctest", + "sphinx.ext.ifconfig", + "sphinx.ext.mathjax", + "sphinx.ext.napoleon", +] # Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] +templates_path = ["_templates"] # The suffix of source filenames. -source_suffix = '.rst' +source_suffix = ".rst" # The encoding of source files. # source_encoding = 'utf-8-sig' # The master toctree document. -master_doc = 'index' +master_doc = "index" # General information about the project. -project = u'cpac' -copyright = u'2019, anibalsolon; 2020 C-PAC Team' +project = "cpac" +copyright = "2019, anibalsolon; 2020 C-PAC Team" # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The short X.Y version. -version = '' # Is set by calling `setup.py docs` +version = "" # Is set by calling `setup.py docs` # The full version, including alpha/beta/rc tags. -release = '' # Is set by calling `setup.py docs` +release = "" # Is set by calling `setup.py docs` # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. @@ -105,7 +114,7 @@ # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. -exclude_patterns = ['_build'] +exclude_patterns = ["_build"] # The reST default role (used for this markup: `text`) to use for all documents. # default_role = None @@ -122,7 +131,7 @@ # show_authors = False # The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'sphinx' +pygments_style = "sphinx" # A list of ignored prefixes for module index sorting. # modindex_common_prefix = [] @@ -135,15 +144,12 @@ # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. -html_theme = 'alabaster' +html_theme = "alabaster" # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. -html_theme_options = { - 'sidebar_width': '300px', - 'page_width': '1200px' -} +html_theme_options = {"sidebar_width": "300px", "page_width": "1200px"} # Add any paths that contain custom themes here, relative to this directory. # html_theme_path = [] @@ -172,7 +178,7 @@ # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['_static'] +html_static_path = ["_static"] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. @@ -216,27 +222,24 @@ # html_file_suffix = None # Output file base name for HTML help builder. -htmlhelp_basename = 'cpac-doc' +htmlhelp_basename = "cpac-doc" # -- Options for LaTeX output -------------------------------------------------- latex_elements = { -# The paper size ('letterpaper' or 'a4paper'). -# 'papersize': 'letterpaper', - -# The font size ('10pt', '11pt' or '12pt'). -# 'pointsize': '10pt', - -# Additional stuff for the LaTeX preamble. -# 'preamble': '', + # The paper size ('letterpaper' or 'a4paper'). + # 'papersize': 'letterpaper', + # The font size ('10pt', '11pt' or '12pt'). + # 'pointsize': '10pt', + # Additional stuff for the LaTeX preamble. + # 'preamble': '', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass [howto/manual]). latex_documents = [ - ('index', 'user_guide.tex', u'cpac Documentation', - u'anibalsolon', 'manual'), + ("index", "user_guide.tex", "cpac Documentation", "anibalsolon", "manual"), ] # The name of an image file (relative to this directory) to place at the top of @@ -260,13 +263,13 @@ # latex_domain_indices = True # -- External mapping ------------------------------------------------------------ -python_version = '.'.join(map(str, sys.version_info[0:2])) +python_version = ".".join(map(str, sys.version_info[0:2])) intersphinx_mapping = { - 'sphinx': ('http://www.sphinx-doc.org/en/stable', None), - 'python': ('https://docs.python.org/' + python_version, None), - 'matplotlib': ('https://matplotlib.org', None), - 'numpy': ('https://docs.scipy.org/doc/numpy', None), - 'sklearn': ('http://scikit-learn.org/stable', None), - 'pandas': ('http://pandas.pydata.org/pandas-docs/stable', None), - 'scipy': ('https://docs.scipy.org/doc/scipy/reference', None), + "sphinx": ("http://www.sphinx-doc.org/en/stable", None), + "python": ("https://docs.python.org/" + python_version, None), + "matplotlib": ("https://matplotlib.org", None), + "numpy": ("https://docs.scipy.org/doc/numpy", None), + "sklearn": ("http://scikit-learn.org/stable", None), + "pandas": ("http://pandas.pydata.org/pandas-docs/stable", None), + "scipy": ("https://docs.scipy.org/doc/scipy/reference", None), } diff --git a/poetry.lock b/poetry.lock new file mode 100644 index 00000000..9d7fba4a --- /dev/null +++ b/poetry.lock @@ -0,0 +1,1665 @@ +# This file is automatically @generated by Poetry 1.5.1 and should not be changed by hand. + +[[package]] +name = "alabaster" +version = "0.7.13" +description = "A configurable sidebar-enabled Sphinx theme" +optional = true +python-versions = ">=3.6" +files = [ + {file = "alabaster-0.7.13-py3-none-any.whl", hash = "sha256:1ee19aca801bbabb5ba3f5f258e4422dfa86f82f3e9cefb0859b283cdd7f62a3"}, + {file = "alabaster-0.7.13.tar.gz", hash = "sha256:a27a4a084d5e690e16e01e03ad2b2e552c61a65469419b907243193de1a84ae2"}, +] + +[[package]] +name = "attrs" +version = "23.2.0" +description = "Classes Without Boilerplate" +optional = false +python-versions = ">=3.7" +files = [ + {file = "attrs-23.2.0-py3-none-any.whl", hash = "sha256:99b87a485a5820b23b879f04c2305b44b951b502fd64be915879d77a7e8fc6f1"}, + {file = "attrs-23.2.0.tar.gz", hash = "sha256:935dc3b529c262f6cf76e50877d35a4bd3c1de194fd41f47a2b7ae8f19971f30"}, +] + +[package.extras] +cov = ["attrs[tests]", "coverage[toml] (>=5.3)"] +dev = ["attrs[tests]", "pre-commit"] +docs = ["furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier", "zope-interface"] +tests = ["attrs[tests-no-zope]", "zope-interface"] +tests-mypy = ["mypy (>=1.6)", "pytest-mypy-plugins"] +tests-no-zope = ["attrs[tests-mypy]", "cloudpickle", "hypothesis", "pympler", "pytest (>=4.3.0)", "pytest-xdist[psutil]"] + +[[package]] +name = "babel" +version = "2.14.0" +description = "Internationalization utilities" +optional = true +python-versions = ">=3.7" +files = [ + {file = "Babel-2.14.0-py3-none-any.whl", hash = "sha256:efb1a25b7118e67ce3a259bed20545c29cb68be8ad2c784c83689981b7a57287"}, + {file = "Babel-2.14.0.tar.gz", hash = "sha256:6919867db036398ba21eb5c7a0f6b28ab8cbc3ae7a73a44ebe34ae74a4e7d363"}, +] + +[package.dependencies] +pytz = {version = ">=2015.7", markers = "python_version < \"3.9\""} + +[package.extras] +dev = ["freezegun (>=1.0,<2.0)", "pytest (>=6.0)", "pytest-cov"] + +[[package]] +name = "cachecontrol" +version = "0.12.14" +description = "httplib2 caching for requests" +optional = false +python-versions = ">=3.6" +files = [ + {file = "CacheControl-0.12.14-py2.py3-none-any.whl", hash = "sha256:1c2939be362a70c4e5f02c6249462b3b7a24441e4f1ced5e9ef028172edf356a"}, + {file = "CacheControl-0.12.14.tar.gz", hash = "sha256:d1087f45781c0e00616479bfd282c78504371ca71da017b49df9f5365a95feba"}, +] + +[package.dependencies] +lockfile = {version = ">=0.9", optional = true, markers = "extra == \"filecache\""} +msgpack = ">=0.5.2" +requests = "*" + +[package.extras] +filecache = ["lockfile (>=0.9)"] +redis = ["redis (>=2.10.5)"] + +[[package]] +name = "cachy" +version = "0.3.0" +description = "Cachy provides a simple yet effective caching library." +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ + {file = "cachy-0.3.0-py2.py3-none-any.whl", hash = "sha256:338ca09c8860e76b275aff52374330efedc4d5a5e45dc1c5b539c1ead0786fe7"}, + {file = "cachy-0.3.0.tar.gz", hash = "sha256:186581f4ceb42a0bbe040c407da73c14092379b1e4c0e327fdb72ae4a9b269b1"}, +] + +[package.extras] +memcached = ["python-memcached (>=1.59,<2.0)"] +msgpack = ["msgpack-python (>=0.5,<0.6)"] +redis = ["redis (>=3.3.6,<4.0.0)"] + +[[package]] +name = "certifi" +version = "2023.11.17" +description = "Python package for providing Mozilla's CA Bundle." +optional = false +python-versions = ">=3.6" +files = [ + {file = "certifi-2023.11.17-py3-none-any.whl", hash = "sha256:e036ab49d5b79556f99cfc2d9320b34cfbe5be05c5871b51de9329f0603b0474"}, + {file = "certifi-2023.11.17.tar.gz", hash = "sha256:9b469f3a900bf28dc19b8cfbf8019bf47f7fdd1a65a1d4ffb98fc14166beb4d1"}, +] + +[[package]] +name = "cffi" +version = "1.16.0" +description = "Foreign Function Interface for Python calling C code." +optional = false +python-versions = ">=3.8" +files = [ + {file = "cffi-1.16.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6b3d6606d369fc1da4fd8c357d026317fbb9c9b75d36dc16e90e84c26854b088"}, + {file = "cffi-1.16.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ac0f5edd2360eea2f1daa9e26a41db02dd4b0451b48f7c318e217ee092a213e9"}, + {file = "cffi-1.16.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7e61e3e4fa664a8588aa25c883eab612a188c725755afff6289454d6362b9673"}, + {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a72e8961a86d19bdb45851d8f1f08b041ea37d2bd8d4fd19903bc3083d80c896"}, + {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5b50bf3f55561dac5438f8e70bfcdfd74543fd60df5fa5f62d94e5867deca684"}, + {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7651c50c8c5ef7bdb41108b7b8c5a83013bfaa8a935590c5d74627c047a583c7"}, + {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4108df7fe9b707191e55f33efbcb2d81928e10cea45527879a4749cbe472614"}, + {file = "cffi-1.16.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:32c68ef735dbe5857c810328cb2481e24722a59a2003018885514d4c09af9743"}, + {file = "cffi-1.16.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:673739cb539f8cdaa07d92d02efa93c9ccf87e345b9a0b556e3ecc666718468d"}, + {file = "cffi-1.16.0-cp310-cp310-win32.whl", hash = "sha256:9f90389693731ff1f659e55c7d1640e2ec43ff725cc61b04b2f9c6d8d017df6a"}, + {file = "cffi-1.16.0-cp310-cp310-win_amd64.whl", hash = "sha256:e6024675e67af929088fda399b2094574609396b1decb609c55fa58b028a32a1"}, + {file = "cffi-1.16.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b84834d0cf97e7d27dd5b7f3aca7b6e9263c56308ab9dc8aae9784abb774d404"}, + {file = "cffi-1.16.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1b8ebc27c014c59692bb2664c7d13ce7a6e9a629be20e54e7271fa696ff2b417"}, + {file = "cffi-1.16.0-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ee07e47c12890ef248766a6e55bd38ebfb2bb8edd4142d56db91b21ea68b7627"}, + {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8a9d3ebe49f084ad71f9269834ceccbf398253c9fac910c4fd7053ff1386936"}, + {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e70f54f1796669ef691ca07d046cd81a29cb4deb1e5f942003f401c0c4a2695d"}, + {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5bf44d66cdf9e893637896c7faa22298baebcd18d1ddb6d2626a6e39793a1d56"}, + {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7b78010e7b97fef4bee1e896df8a4bbb6712b7f05b7ef630f9d1da00f6444d2e"}, + {file = "cffi-1.16.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c6a164aa47843fb1b01e941d385aab7215563bb8816d80ff3a363a9f8448a8dc"}, + {file = "cffi-1.16.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e09f3ff613345df5e8c3667da1d918f9149bd623cd9070c983c013792a9a62eb"}, + {file = "cffi-1.16.0-cp311-cp311-win32.whl", hash = "sha256:2c56b361916f390cd758a57f2e16233eb4f64bcbeee88a4881ea90fca14dc6ab"}, + {file = "cffi-1.16.0-cp311-cp311-win_amd64.whl", hash = "sha256:db8e577c19c0fda0beb7e0d4e09e0ba74b1e4c092e0e40bfa12fe05b6f6d75ba"}, + {file = "cffi-1.16.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:fa3a0128b152627161ce47201262d3140edb5a5c3da88d73a1b790a959126956"}, + {file = "cffi-1.16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:68e7c44931cc171c54ccb702482e9fc723192e88d25a0e133edd7aff8fcd1f6e"}, + {file = "cffi-1.16.0-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:abd808f9c129ba2beda4cfc53bde801e5bcf9d6e0f22f095e45327c038bfe68e"}, + {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88e2b3c14bdb32e440be531ade29d3c50a1a59cd4e51b1dd8b0865c54ea5d2e2"}, + {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fcc8eb6d5902bb1cf6dc4f187ee3ea80a1eba0a89aba40a5cb20a5087d961357"}, + {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b7be2d771cdba2942e13215c4e340bfd76398e9227ad10402a8767ab1865d2e6"}, + {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e715596e683d2ce000574bae5d07bd522c781a822866c20495e52520564f0969"}, + {file = "cffi-1.16.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:2d92b25dbf6cae33f65005baf472d2c245c050b1ce709cc4588cdcdd5495b520"}, + {file = "cffi-1.16.0-cp312-cp312-win32.whl", hash = "sha256:b2ca4e77f9f47c55c194982e10f058db063937845bb2b7a86c84a6cfe0aefa8b"}, + {file = "cffi-1.16.0-cp312-cp312-win_amd64.whl", hash = "sha256:68678abf380b42ce21a5f2abde8efee05c114c2fdb2e9eef2efdb0257fba1235"}, + {file = "cffi-1.16.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0c9ef6ff37e974b73c25eecc13952c55bceed9112be2d9d938ded8e856138bcc"}, + {file = "cffi-1.16.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a09582f178759ee8128d9270cd1344154fd473bb77d94ce0aeb2a93ebf0feaf0"}, + {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e760191dd42581e023a68b758769e2da259b5d52e3103c6060ddc02c9edb8d7b"}, + {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:80876338e19c951fdfed6198e70bc88f1c9758b94578d5a7c4c91a87af3cf31c"}, + {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a6a14b17d7e17fa0d207ac08642c8820f84f25ce17a442fd15e27ea18d67c59b"}, + {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6602bc8dc6f3a9e02b6c22c4fc1e47aa50f8f8e6d3f78a5e16ac33ef5fefa324"}, + {file = "cffi-1.16.0-cp38-cp38-win32.whl", hash = "sha256:131fd094d1065b19540c3d72594260f118b231090295d8c34e19a7bbcf2e860a"}, + {file = "cffi-1.16.0-cp38-cp38-win_amd64.whl", hash = "sha256:31d13b0f99e0836b7ff893d37af07366ebc90b678b6664c955b54561fc36ef36"}, + {file = "cffi-1.16.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:582215a0e9adbe0e379761260553ba11c58943e4bbe9c36430c4ca6ac74b15ed"}, + {file = "cffi-1.16.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b29ebffcf550f9da55bec9e02ad430c992a87e5f512cd63388abb76f1036d8d2"}, + {file = "cffi-1.16.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dc9b18bf40cc75f66f40a7379f6a9513244fe33c0e8aa72e2d56b0196a7ef872"}, + {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cb4a35b3642fc5c005a6755a5d17c6c8b6bcb6981baf81cea8bfbc8903e8ba8"}, + {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b86851a328eedc692acf81fb05444bdf1891747c25af7529e39ddafaf68a4f3f"}, + {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c0f31130ebc2d37cdd8e44605fb5fa7ad59049298b3f745c74fa74c62fbfcfc4"}, + {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f8e709127c6c77446a8c0a8c8bf3c8ee706a06cd44b1e827c3e6a2ee6b8c098"}, + {file = "cffi-1.16.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:748dcd1e3d3d7cd5443ef03ce8685043294ad6bd7c02a38d1bd367cfd968e000"}, + {file = "cffi-1.16.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8895613bcc094d4a1b2dbe179d88d7fb4a15cee43c052e8885783fac397d91fe"}, + {file = "cffi-1.16.0-cp39-cp39-win32.whl", hash = "sha256:ed86a35631f7bfbb28e108dd96773b9d5a6ce4811cf6ea468bb6a359b256b1e4"}, + {file = "cffi-1.16.0-cp39-cp39-win_amd64.whl", hash = "sha256:3686dffb02459559c74dd3d81748269ffb0eb027c39a6fc99502de37d501faa8"}, + {file = "cffi-1.16.0.tar.gz", hash = "sha256:bcb3ef43e58665bbda2fb198698fcae6776483e0c4a631aa5647806c25e02cc0"}, +] + +[package.dependencies] +pycparser = "*" + +[[package]] +name = "charset-normalizer" +version = "3.3.2" +description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." +optional = false +python-versions = ">=3.7.0" +files = [ + {file = "charset-normalizer-3.3.2.tar.gz", hash = "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-win32.whl", hash = "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-win32.whl", hash = "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-win32.whl", hash = "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-win32.whl", hash = "sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-win_amd64.whl", hash = "sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-win32.whl", hash = "sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-win32.whl", hash = "sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d"}, + {file = "charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc"}, +] + +[[package]] +name = "cleo" +version = "0.7.6" +description = "Cleo allows you to create beautiful and testable command-line interfaces." +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ + {file = "cleo-0.7.6-py2.py3-none-any.whl", hash = "sha256:9443d67e5b2da79b32d820ae41758dd6a25618345cb10b9a022a695e26b291b9"}, + {file = "cleo-0.7.6.tar.gz", hash = "sha256:99cf342406f3499cec43270fcfaf93c126c5164092eca201dfef0f623360b409"}, +] + +[package.dependencies] +clikit = ">=0.4.0,<0.5.0" + +[[package]] +name = "clikit" +version = "0.4.3" +description = "CliKit is a group of utilities to build beautiful and testable command line interfaces." +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ + {file = "clikit-0.4.3-py2.py3-none-any.whl", hash = "sha256:71e321b7795a2a6c4888629f43365d52db071737e668ab16861121d7dd3ada09"}, + {file = "clikit-0.4.3.tar.gz", hash = "sha256:6e2d7e115e7c7b35bceb0209109935ab2f9ab50910e9ff2293f7fa0b7abf973e"}, +] + +[package.dependencies] +pastel = ">=0.2.0,<0.3.0" +pylev = ">=1.3,<2.0" + +[[package]] +name = "colorama" +version = "0.4.6" +description = "Cross-platform colored terminal text." +optional = true +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +files = [ + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, +] + +[[package]] +name = "coverage" +version = "6.5.0" +description = "Code coverage measurement for Python" +optional = true +python-versions = ">=3.7" +files = [ + {file = "coverage-6.5.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ef8674b0ee8cc11e2d574e3e2998aea5df5ab242e012286824ea3c6970580e53"}, + {file = "coverage-6.5.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:784f53ebc9f3fd0e2a3f6a78b2be1bd1f5575d7863e10c6e12504f240fd06660"}, + {file = "coverage-6.5.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b4a5be1748d538a710f87542f22c2cad22f80545a847ad91ce45e77417293eb4"}, + {file = "coverage-6.5.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:83516205e254a0cb77d2d7bb3632ee019d93d9f4005de31dca0a8c3667d5bc04"}, + {file = "coverage-6.5.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:af4fffaffc4067232253715065e30c5a7ec6faac36f8fc8d6f64263b15f74db0"}, + {file = "coverage-6.5.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:97117225cdd992a9c2a5515db1f66b59db634f59d0679ca1fa3fe8da32749cae"}, + {file = "coverage-6.5.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:a1170fa54185845505fbfa672f1c1ab175446c887cce8212c44149581cf2d466"}, + {file = "coverage-6.5.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:11b990d520ea75e7ee8dcab5bc908072aaada194a794db9f6d7d5cfd19661e5a"}, + {file = "coverage-6.5.0-cp310-cp310-win32.whl", hash = "sha256:5dbec3b9095749390c09ab7c89d314727f18800060d8d24e87f01fb9cfb40b32"}, + {file = "coverage-6.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:59f53f1dc5b656cafb1badd0feb428c1e7bc19b867479ff72f7a9dd9b479f10e"}, + {file = "coverage-6.5.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4a5375e28c5191ac38cca59b38edd33ef4cc914732c916f2929029b4bfb50795"}, + {file = "coverage-6.5.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c4ed2820d919351f4167e52425e096af41bfabacb1857186c1ea32ff9983ed75"}, + {file = "coverage-6.5.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:33a7da4376d5977fbf0a8ed91c4dffaaa8dbf0ddbf4c8eea500a2486d8bc4d7b"}, + {file = "coverage-6.5.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a8fb6cf131ac4070c9c5a3e21de0f7dc5a0fbe8bc77c9456ced896c12fcdad91"}, + {file = "coverage-6.5.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a6b7d95969b8845250586f269e81e5dfdd8ff828ddeb8567a4a2eaa7313460c4"}, + {file = "coverage-6.5.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:1ef221513e6f68b69ee9e159506d583d31aa3567e0ae84eaad9d6ec1107dddaa"}, + {file = "coverage-6.5.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:cca4435eebea7962a52bdb216dec27215d0df64cf27fc1dd538415f5d2b9da6b"}, + {file = "coverage-6.5.0-cp311-cp311-win32.whl", hash = "sha256:98e8a10b7a314f454d9eff4216a9a94d143a7ee65018dd12442e898ee2310578"}, + {file = "coverage-6.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:bc8ef5e043a2af066fa8cbfc6e708d58017024dc4345a1f9757b329a249f041b"}, + {file = "coverage-6.5.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:4433b90fae13f86fafff0b326453dd42fc9a639a0d9e4eec4d366436d1a41b6d"}, + {file = "coverage-6.5.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f4f05d88d9a80ad3cac6244d36dd89a3c00abc16371769f1340101d3cb899fc3"}, + {file = "coverage-6.5.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:94e2565443291bd778421856bc975d351738963071e9b8839ca1fc08b42d4bef"}, + {file = "coverage-6.5.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:027018943386e7b942fa832372ebc120155fd970837489896099f5cfa2890f79"}, + {file = "coverage-6.5.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:255758a1e3b61db372ec2736c8e2a1fdfaf563977eedbdf131de003ca5779b7d"}, + {file = "coverage-6.5.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:851cf4ff24062c6aec510a454b2584f6e998cada52d4cb58c5e233d07172e50c"}, + {file = "coverage-6.5.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:12adf310e4aafddc58afdb04d686795f33f4d7a6fa67a7a9d4ce7d6ae24d949f"}, + {file = "coverage-6.5.0-cp37-cp37m-win32.whl", hash = "sha256:b5604380f3415ba69de87a289a2b56687faa4fe04dbee0754bfcae433489316b"}, + {file = "coverage-6.5.0-cp37-cp37m-win_amd64.whl", hash = "sha256:4a8dbc1f0fbb2ae3de73eb0bdbb914180c7abfbf258e90b311dcd4f585d44bd2"}, + {file = "coverage-6.5.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d900bb429fdfd7f511f868cedd03a6bbb142f3f9118c09b99ef8dc9bf9643c3c"}, + {file = "coverage-6.5.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2198ea6fc548de52adc826f62cb18554caedfb1d26548c1b7c88d8f7faa8f6ba"}, + {file = "coverage-6.5.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c4459b3de97b75e3bd6b7d4b7f0db13f17f504f3d13e2a7c623786289dd670e"}, + {file = "coverage-6.5.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:20c8ac5386253717e5ccc827caad43ed66fea0efe255727b1053a8154d952398"}, + {file = "coverage-6.5.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b07130585d54fe8dff3d97b93b0e20290de974dc8177c320aeaf23459219c0b"}, + {file = "coverage-6.5.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:dbdb91cd8c048c2b09eb17713b0c12a54fbd587d79adcebad543bc0cd9a3410b"}, + {file = "coverage-6.5.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:de3001a203182842a4630e7b8d1a2c7c07ec1b45d3084a83d5d227a3806f530f"}, + {file = "coverage-6.5.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:e07f4a4a9b41583d6eabec04f8b68076ab3cd44c20bd29332c6572dda36f372e"}, + {file = "coverage-6.5.0-cp38-cp38-win32.whl", hash = "sha256:6d4817234349a80dbf03640cec6109cd90cba068330703fa65ddf56b60223a6d"}, + {file = "coverage-6.5.0-cp38-cp38-win_amd64.whl", hash = "sha256:7ccf362abd726b0410bf8911c31fbf97f09f8f1061f8c1cf03dfc4b6372848f6"}, + {file = "coverage-6.5.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:633713d70ad6bfc49b34ead4060531658dc6dfc9b3eb7d8a716d5873377ab745"}, + {file = "coverage-6.5.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:95203854f974e07af96358c0b261f1048d8e1083f2de9b1c565e1be4a3a48cfc"}, + {file = "coverage-6.5.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b9023e237f4c02ff739581ef35969c3739445fb059b060ca51771e69101efffe"}, + {file = "coverage-6.5.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:265de0fa6778d07de30bcf4d9dc471c3dc4314a23a3c6603d356a3c9abc2dfcf"}, + {file = "coverage-6.5.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f830ed581b45b82451a40faabb89c84e1a998124ee4212d440e9c6cf70083e5"}, + {file = "coverage-6.5.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:7b6be138d61e458e18d8e6ddcddd36dd96215edfe5f1168de0b1b32635839b62"}, + {file = "coverage-6.5.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:42eafe6778551cf006a7c43153af1211c3aaab658d4d66fa5fcc021613d02518"}, + {file = "coverage-6.5.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:723e8130d4ecc8f56e9a611e73b31219595baa3bb252d539206f7bbbab6ffc1f"}, + {file = "coverage-6.5.0-cp39-cp39-win32.whl", hash = "sha256:d9ecf0829c6a62b9b573c7bb6d4dcd6ba8b6f80be9ba4fc7ed50bf4ac9aecd72"}, + {file = "coverage-6.5.0-cp39-cp39-win_amd64.whl", hash = "sha256:fc2af30ed0d5ae0b1abdb4ebdce598eafd5b35397d4d75deb341a614d333d987"}, + {file = "coverage-6.5.0-pp36.pp37.pp38-none-any.whl", hash = "sha256:1431986dac3923c5945271f169f59c45b8802a114c8f548d611f2015133df77a"}, + {file = "coverage-6.5.0.tar.gz", hash = "sha256:f642e90754ee3e06b0e7e51bce3379590e76b7f76b708e1a71ff043f87025c84"}, +] + +[package.dependencies] +tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.11.0a6\" and extra == \"toml\""} + +[package.extras] +toml = ["tomli"] + +[[package]] +name = "coveralls" +version = "3.3.1" +description = "Show coverage stats online via coveralls.io" +optional = true +python-versions = ">= 3.5" +files = [ + {file = "coveralls-3.3.1-py2.py3-none-any.whl", hash = "sha256:f42015f31d386b351d4226389b387ae173207058832fbf5c8ec4b40e27b16026"}, + {file = "coveralls-3.3.1.tar.gz", hash = "sha256:b32a8bb5d2df585207c119d6c01567b81fba690c9c10a753bfe27a335bfc43ea"}, +] + +[package.dependencies] +coverage = ">=4.1,<6.0.dev0 || >6.1,<6.1.1 || >6.1.1,<7.0" +docopt = ">=0.6.1" +requests = ">=1.0.0" + +[package.extras] +yaml = ["PyYAML (>=3.10)"] + +[[package]] +name = "cryptography" +version = "41.0.7" +description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." +optional = false +python-versions = ">=3.7" +files = [ + {file = "cryptography-41.0.7-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:3c78451b78313fa81607fa1b3f1ae0a5ddd8014c38a02d9db0616133987b9cdf"}, + {file = "cryptography-41.0.7-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:928258ba5d6f8ae644e764d0f996d61a8777559f72dfeb2eea7e2fe0ad6e782d"}, + {file = "cryptography-41.0.7-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5a1b41bc97f1ad230a41657d9155113c7521953869ae57ac39ac7f1bb471469a"}, + {file = "cryptography-41.0.7-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:841df4caa01008bad253bce2a6f7b47f86dc9f08df4b433c404def869f590a15"}, + {file = "cryptography-41.0.7-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:5429ec739a29df2e29e15d082f1d9ad683701f0ec7709ca479b3ff2708dae65a"}, + {file = "cryptography-41.0.7-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:43f2552a2378b44869fe8827aa19e69512e3245a219104438692385b0ee119d1"}, + {file = "cryptography-41.0.7-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:af03b32695b24d85a75d40e1ba39ffe7db7ffcb099fe507b39fd41a565f1b157"}, + {file = "cryptography-41.0.7-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:49f0805fc0b2ac8d4882dd52f4a3b935b210935d500b6b805f321addc8177406"}, + {file = "cryptography-41.0.7-cp37-abi3-win32.whl", hash = "sha256:f983596065a18a2183e7f79ab3fd4c475205b839e02cbc0efbbf9666c4b3083d"}, + {file = "cryptography-41.0.7-cp37-abi3-win_amd64.whl", hash = "sha256:90452ba79b8788fa380dfb587cca692976ef4e757b194b093d845e8d99f612f2"}, + {file = "cryptography-41.0.7-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:079b85658ea2f59c4f43b70f8119a52414cdb7be34da5d019a77bf96d473b960"}, + {file = "cryptography-41.0.7-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:b640981bf64a3e978a56167594a0e97db71c89a479da8e175d8bb5be5178c003"}, + {file = "cryptography-41.0.7-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:e3114da6d7f95d2dee7d3f4eec16dacff819740bbab931aff8648cb13c5ff5e7"}, + {file = "cryptography-41.0.7-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:d5ec85080cce7b0513cfd233914eb8b7bbd0633f1d1703aa28d1dd5a72f678ec"}, + {file = "cryptography-41.0.7-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:7a698cb1dac82c35fcf8fe3417a3aaba97de16a01ac914b89a0889d364d2f6be"}, + {file = "cryptography-41.0.7-pp38-pypy38_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:37a138589b12069efb424220bf78eac59ca68b95696fc622b6ccc1c0a197204a"}, + {file = "cryptography-41.0.7-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:68a2dec79deebc5d26d617bfdf6e8aab065a4f34934b22d3b5010df3ba36612c"}, + {file = "cryptography-41.0.7-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:09616eeaef406f99046553b8a40fbf8b1e70795a91885ba4c96a70793de5504a"}, + {file = "cryptography-41.0.7-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:48a0476626da912a44cc078f9893f292f0b3e4c739caf289268168d8f4702a39"}, + {file = "cryptography-41.0.7-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:c7f3201ec47d5207841402594f1d7950879ef890c0c495052fa62f58283fde1a"}, + {file = "cryptography-41.0.7-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:c5ca78485a255e03c32b513f8c2bc39fedb7f5c5f8535545bdc223a03b24f248"}, + {file = "cryptography-41.0.7-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:d6c391c021ab1f7a82da5d8d0b3cee2f4b2c455ec86c8aebbc84837a631ff309"}, + {file = "cryptography-41.0.7.tar.gz", hash = "sha256:13f93ce9bea8016c253b34afc6bd6a75993e5c40672ed5405a9c832f0d4a00bc"}, +] + +[package.dependencies] +cffi = ">=1.12" + +[package.extras] +docs = ["sphinx (>=5.3.0)", "sphinx-rtd-theme (>=1.1.1)"] +docstest = ["pyenchant (>=1.6.11)", "sphinxcontrib-spelling (>=4.0.1)", "twine (>=1.12.0)"] +nox = ["nox"] +pep8test = ["black", "check-sdist", "mypy", "ruff"] +sdist = ["build"] +ssh = ["bcrypt (>=3.1.5)"] +test = ["pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-xdist"] +test-randomorder = ["pytest-randomly"] + +[[package]] +name = "docker" +version = "6.1.3" +description = "A Python library for the Docker Engine API." +optional = false +python-versions = ">=3.7" +files = [ + {file = "docker-6.1.3-py3-none-any.whl", hash = "sha256:aecd2277b8bf8e506e484f6ab7aec39abe0038e29fa4a6d3ba86c3fe01844ed9"}, + {file = "docker-6.1.3.tar.gz", hash = "sha256:aa6d17830045ba5ef0168d5eaa34d37beeb113948c413affe1d5991fc11f9a20"}, +] + +[package.dependencies] +packaging = ">=14.0" +pywin32 = {version = ">=304", markers = "sys_platform == \"win32\""} +requests = ">=2.26.0" +urllib3 = ">=1.26.0" +websocket-client = ">=0.32.0" + +[package.extras] +ssh = ["paramiko (>=2.4.3)"] + +[[package]] +name = "docker-pycreds" +version = "0.4.0" +description = "Python bindings for the docker credentials store API" +optional = false +python-versions = "*" +files = [ + {file = "docker-pycreds-0.4.0.tar.gz", hash = "sha256:6ce3270bcaf404cc4c3e27e4b6c70d3521deae82fb508767870fdbf772d584d4"}, + {file = "docker_pycreds-0.4.0-py2.py3-none-any.whl", hash = "sha256:7266112468627868005106ec19cd0d722702d2b7d5912a28e19b826c3d37af49"}, +] + +[package.dependencies] +six = ">=1.4.0" + +[[package]] +name = "dockerpty" +version = "0.4.1" +description = "Python library to use the pseudo-tty of a docker container" +optional = false +python-versions = "*" +files = [ + {file = "dockerpty-0.4.1.tar.gz", hash = "sha256:69a9d69d573a0daa31bcd1c0774eeed5c15c295fe719c61aca550ed1393156ce"}, +] + +[package.dependencies] +six = ">=1.3.0" + +[[package]] +name = "docopt" +version = "0.6.2" +description = "Pythonic argument parser, that will make you smile" +optional = true +python-versions = "*" +files = [ + {file = "docopt-0.6.2.tar.gz", hash = "sha256:49b3a825280bd66b3aa83585ef59c4a8c82f2c8a522dbe754a8bc8d08c85c491"}, +] + +[[package]] +name = "docutils" +version = "0.19" +description = "Docutils -- Python Documentation Utilities" +optional = true +python-versions = ">=3.7" +files = [ + {file = "docutils-0.19-py3-none-any.whl", hash = "sha256:5e1de4d849fee02c63b040a4a3fd567f4ab104defd8a5511fbbc24a8a017efbc"}, + {file = "docutils-0.19.tar.gz", hash = "sha256:33995a6753c30b7f577febfc2c50411fec6aac7f7ffeb7c4cfe5991072dcf9e6"}, +] + +[[package]] +name = "exceptiongroup" +version = "1.2.0" +description = "Backport of PEP 654 (exception groups)" +optional = true +python-versions = ">=3.7" +files = [ + {file = "exceptiongroup-1.2.0-py3-none-any.whl", hash = "sha256:4bfd3996ac73b41e9b9628b04e079f193850720ea5945fc96a08633c66912f14"}, + {file = "exceptiongroup-1.2.0.tar.gz", hash = "sha256:91f5c769735f051a4290d52edd0858999b57e5876e9f85937691bd4c9fa3ed68"}, +] + +[package.extras] +test = ["pytest (>=6)"] + +[[package]] +name = "html5lib" +version = "1.1" +description = "HTML parser based on the WHATWG HTML specification" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +files = [ + {file = "html5lib-1.1-py2.py3-none-any.whl", hash = "sha256:0d78f8fde1c230e99fe37986a60526d7049ed4bf8a9fadbad5f00e22e58e041d"}, + {file = "html5lib-1.1.tar.gz", hash = "sha256:b2e5b40261e20f354d198eae92afc10d750afb487ed5e50f9c4eaf07c184146f"}, +] + +[package.dependencies] +six = ">=1.9" +webencodings = "*" + +[package.extras] +all = ["chardet (>=2.2)", "genshi", "lxml"] +chardet = ["chardet (>=2.2)"] +genshi = ["genshi"] +lxml = ["lxml"] + +[[package]] +name = "idna" +version = "3.6" +description = "Internationalized Domain Names in Applications (IDNA)" +optional = false +python-versions = ">=3.5" +files = [ + {file = "idna-3.6-py3-none-any.whl", hash = "sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f"}, + {file = "idna-3.6.tar.gz", hash = "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca"}, +] + +[[package]] +name = "imagesize" +version = "1.4.1" +description = "Getting image size from png/jpeg/jpeg2000/gif file" +optional = true +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ + {file = "imagesize-1.4.1-py2.py3-none-any.whl", hash = "sha256:0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b"}, + {file = "imagesize-1.4.1.tar.gz", hash = "sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a"}, +] + +[[package]] +name = "importlib-metadata" +version = "6.7.0" +description = "Read metadata from Python packages" +optional = false +python-versions = ">=3.7" +files = [ + {file = "importlib_metadata-6.7.0-py3-none-any.whl", hash = "sha256:cb52082e659e97afc5dac71e79de97d8681de3aa07ff18578330904a9d18e5b5"}, + {file = "importlib_metadata-6.7.0.tar.gz", hash = "sha256:1aaf550d4f73e5d6783e7acb77aec43d49da8017410afae93822cc9cca98c4d4"}, +] + +[package.dependencies] +zipp = ">=0.5" + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +perf = ["ipython"] +testing = ["flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)", "pytest-ruff"] + +[[package]] +name = "iniconfig" +version = "2.0.0" +description = "brain-dead simple config-ini parsing" +optional = true +python-versions = ">=3.7" +files = [ + {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, + {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, +] + +[[package]] +name = "jeepney" +version = "0.8.0" +description = "Low-level, pure Python DBus protocol wrapper." +optional = false +python-versions = ">=3.7" +files = [ + {file = "jeepney-0.8.0-py3-none-any.whl", hash = "sha256:c0a454ad016ca575060802ee4d590dd912e35c122fa04e70306de3d076cce755"}, + {file = "jeepney-0.8.0.tar.gz", hash = "sha256:5efe48d255973902f6badc3ce55e2aa6c5c3b3bc642059ef3a91247bcfcc5806"}, +] + +[package.extras] +test = ["async-timeout", "pytest", "pytest-asyncio (>=0.17)", "pytest-trio", "testpath", "trio"] +trio = ["async_generator", "trio"] + +[[package]] +name = "jinja2" +version = "3.1.2" +description = "A very fast and expressive template engine." +optional = true +python-versions = ">=3.7" +files = [ + {file = "Jinja2-3.1.2-py3-none-any.whl", hash = "sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61"}, + {file = "Jinja2-3.1.2.tar.gz", hash = "sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852"}, +] + +[package.dependencies] +MarkupSafe = ">=2.0" + +[package.extras] +i18n = ["Babel (>=2.7)"] + +[[package]] +name = "jsonschema" +version = "3.2.0" +description = "An implementation of JSON Schema validation for Python" +optional = false +python-versions = "*" +files = [ + {file = "jsonschema-3.2.0-py2.py3-none-any.whl", hash = "sha256:4e5b3cf8216f577bee9ce139cbe72eca3ea4f292ec60928ff24758ce626cd163"}, + {file = "jsonschema-3.2.0.tar.gz", hash = "sha256:c8a85b28d377cc7737e46e2d9f2b4f44ee3c0e1deac6bf46ddefc7187d30797a"}, +] + +[package.dependencies] +attrs = ">=17.4.0" +pyrsistent = ">=0.14.0" +setuptools = "*" +six = ">=1.11.0" + +[package.extras] +format = ["idna", "jsonpointer (>1.13)", "rfc3987", "strict-rfc3339", "webcolors"] +format-nongpl = ["idna", "jsonpointer (>1.13)", "rfc3339-validator", "rfc3986-validator (>0.1.0)", "webcolors"] + +[[package]] +name = "keyring" +version = "20.0.1" +description = "Store and access your passwords safely." +optional = false +python-versions = ">=3.5" +files = [ + {file = "keyring-20.0.1-py2.py3-none-any.whl", hash = "sha256:c674f032424b4bffc62abeac5523ec49cc84aed07a480c3233e0baf618efc15c"}, + {file = "keyring-20.0.1.tar.gz", hash = "sha256:963bfa7f090269d30bdc5e25589e5fd9dad2cf2a7c6f176a7f2386910e5d0d8d"}, +] + +[package.dependencies] +pywin32-ctypes = {version = "<0.1.0 || >0.1.0,<0.1.1 || >0.1.1", markers = "sys_platform == \"win32\""} +secretstorage = {version = "*", markers = "sys_platform == \"linux\""} + +[package.extras] +docs = ["jaraco.packaging (>=3.2)", "rst.linker (>=1.9)", "sphinx"] +testing = ["pytest (>=3.5,!=3.7.3)", "pytest-black-multipy", "pytest-checkdocs (>=1.2.3)", "pytest-cov", "pytest-flake8"] + +[[package]] +name = "lockfile" +version = "0.12.2" +description = "Platform-independent file locking module" +optional = false +python-versions = "*" +files = [ + {file = "lockfile-0.12.2-py2.py3-none-any.whl", hash = "sha256:6c3cb24f344923d30b2785d5ad75182c8ea7ac1b6171b08657258ec7429d50fa"}, + {file = "lockfile-0.12.2.tar.gz", hash = "sha256:6aed02de03cba24efabcd600b30540140634fc06cfa603822d508d5361e9f799"}, +] + +[[package]] +name = "markdown-it-py" +version = "2.2.0" +description = "Python port of markdown-it. Markdown parsing, done right!" +optional = false +python-versions = ">=3.7" +files = [ + {file = "markdown-it-py-2.2.0.tar.gz", hash = "sha256:7c9a5e412688bc771c67432cbfebcdd686c93ce6484913dccf06cb5a0bea35a1"}, + {file = "markdown_it_py-2.2.0-py3-none-any.whl", hash = "sha256:5a35f8d1870171d9acc47b99612dc146129b631baf04970128b568f190d0cc30"}, +] + +[package.dependencies] +mdurl = ">=0.1,<1.0" + +[package.extras] +benchmarking = ["psutil", "pytest", "pytest-benchmark"] +code-style = ["pre-commit (>=3.0,<4.0)"] +compare = ["commonmark (>=0.9,<1.0)", "markdown (>=3.4,<4.0)", "mistletoe (>=1.0,<2.0)", "mistune (>=2.0,<3.0)", "panflute (>=2.3,<3.0)"] +linkify = ["linkify-it-py (>=1,<3)"] +plugins = ["mdit-py-plugins"] +profiling = ["gprof2dot"] +rtd = ["attrs", "myst-parser", "pyyaml", "sphinx", "sphinx-copybutton", "sphinx-design", "sphinx_book_theme"] +testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] + +[[package]] +name = "markupsafe" +version = "2.1.3" +description = "Safely add untrusted strings to HTML/XML markup." +optional = true +python-versions = ">=3.7" +files = [ + {file = "MarkupSafe-2.1.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cd0f502fe016460680cd20aaa5a76d241d6f35a1c3350c474bac1273803893fa"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e09031c87a1e51556fdcb46e5bd4f59dfb743061cf93c4d6831bf894f125eb57"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:68e78619a61ecf91e76aa3e6e8e33fc4894a2bebe93410754bd28fce0a8a4f9f"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65c1a9bcdadc6c28eecee2c119465aebff8f7a584dd719facdd9e825ec61ab52"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:525808b8019e36eb524b8c68acdd63a37e75714eac50e988180b169d64480a00"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:962f82a3086483f5e5f64dbad880d31038b698494799b097bc59c2edf392fce6"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:aa7bd130efab1c280bed0f45501b7c8795f9fdbeb02e965371bbef3523627779"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c9c804664ebe8f83a211cace637506669e7890fec1b4195b505c214e50dd4eb7"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-win32.whl", hash = "sha256:10bbfe99883db80bdbaff2dcf681dfc6533a614f700da1287707e8a5d78a8431"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-win_amd64.whl", hash = "sha256:1577735524cdad32f9f694208aa75e422adba74f1baee7551620e43a3141f559"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ad9e82fb8f09ade1c3e1b996a6337afac2b8b9e365f926f5a61aacc71adc5b3c"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3c0fae6c3be832a0a0473ac912810b2877c8cb9d76ca48de1ed31e1c68386575"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b076b6226fb84157e3f7c971a47ff3a679d837cf338547532ab866c57930dbee"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bfce63a9e7834b12b87c64d6b155fdd9b3b96191b6bd334bf37db7ff1fe457f2"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:338ae27d6b8745585f87218a3f23f1512dbf52c26c28e322dbe54bcede54ccb9"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e4dd52d80b8c83fdce44e12478ad2e85c64ea965e75d66dbeafb0a3e77308fcc"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:df0be2b576a7abbf737b1575f048c23fb1d769f267ec4358296f31c2479db8f9"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5bbe06f8eeafd38e5d0a4894ffec89378b6c6a625ff57e3028921f8ff59318ac"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-win32.whl", hash = "sha256:dd15ff04ffd7e05ffcb7fe79f1b98041b8ea30ae9234aed2a9168b5797c3effb"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-win_amd64.whl", hash = "sha256:134da1eca9ec0ae528110ccc9e48041e0828d79f24121a1a146161103c76e686"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:f698de3fd0c4e6972b92290a45bd9b1536bffe8c6759c62471efaa8acb4c37bc"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:aa57bd9cf8ae831a362185ee444e15a93ecb2e344c8e52e4d721ea3ab6ef1823"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffcc3f7c66b5f5b7931a5aa68fc9cecc51e685ef90282f4a82f0f5e9b704ad11"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47d4f1c5f80fc62fdd7777d0d40a2e9dda0a05883ab11374334f6c4de38adffd"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1f67c7038d560d92149c060157d623c542173016c4babc0c1913cca0564b9939"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:9aad3c1755095ce347e26488214ef77e0485a3c34a50c5a5e2471dff60b9dd9c"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:14ff806850827afd6b07a5f32bd917fb7f45b046ba40c57abdb636674a8b559c"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8f9293864fe09b8149f0cc42ce56e3f0e54de883a9de90cd427f191c346eb2e1"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-win32.whl", hash = "sha256:715d3562f79d540f251b99ebd6d8baa547118974341db04f5ad06d5ea3eb8007"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-win_amd64.whl", hash = "sha256:1b8dd8c3fd14349433c79fa8abeb573a55fc0fdd769133baac1f5e07abf54aeb"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8e254ae696c88d98da6555f5ace2279cf7cd5b3f52be2b5cf97feafe883b58d2"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb0932dc158471523c9637e807d9bfb93e06a95cbf010f1a38b98623b929ef2b"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9402b03f1a1b4dc4c19845e5c749e3ab82d5078d16a2a4c2cd2df62d57bb0707"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca379055a47383d02a5400cb0d110cef0a776fc644cda797db0c5696cfd7e18e"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:b7ff0f54cb4ff66dd38bebd335a38e2c22c41a8ee45aa608efc890ac3e3931bc"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:c011a4149cfbcf9f03994ec2edffcb8b1dc2d2aede7ca243746df97a5d41ce48"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:56d9f2ecac662ca1611d183feb03a3fa4406469dafe241673d521dd5ae92a155"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-win32.whl", hash = "sha256:8758846a7e80910096950b67071243da3e5a20ed2546e6392603c096778d48e0"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-win_amd64.whl", hash = "sha256:787003c0ddb00500e49a10f2844fac87aa6ce977b90b0feaaf9de23c22508b24"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:2ef12179d3a291be237280175b542c07a36e7f60718296278d8593d21ca937d4"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2c1b19b3aaacc6e57b7e25710ff571c24d6c3613a45e905b1fde04d691b98ee0"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8afafd99945ead6e075b973fefa56379c5b5c53fd8937dad92c662da5d8fd5ee"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c41976a29d078bb235fea9b2ecd3da465df42a562910f9022f1a03107bd02be"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d080e0a5eb2529460b30190fcfcc4199bd7f827663f858a226a81bc27beaa97e"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:69c0f17e9f5a7afdf2cc9fb2d1ce6aabdb3bafb7f38017c0b77862bcec2bbad8"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:504b320cd4b7eff6f968eddf81127112db685e81f7e36e75f9f84f0df46041c3"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:42de32b22b6b804f42c5d98be4f7e5e977ecdd9ee9b660fda1a3edf03b11792d"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-win32.whl", hash = "sha256:ceb01949af7121f9fc39f7d27f91be8546f3fb112c608bc4029aef0bab86a2a5"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-win_amd64.whl", hash = "sha256:1b40069d487e7edb2676d3fbdb2b0829ffa2cd63a2ec26c4938b2d34391b4ecc"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:8023faf4e01efadfa183e863fefde0046de576c6f14659e8782065bcece22198"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6b2b56950d93e41f33b4223ead100ea0fe11f8e6ee5f641eb753ce4b77a7042b"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9dcdfd0eaf283af041973bff14a2e143b8bd64e069f4c383416ecd79a81aab58"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:05fb21170423db021895e1ea1e1f3ab3adb85d1c2333cbc2310f2a26bc77272e"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:282c2cb35b5b673bbcadb33a585408104df04f14b2d9b01d4c345a3b92861c2c"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ab4a0df41e7c16a1392727727e7998a467472d0ad65f3ad5e6e765015df08636"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7ef3cb2ebbf91e330e3bb937efada0edd9003683db6b57bb108c4001f37a02ea"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:0a4e4a1aff6c7ac4cd55792abf96c915634c2b97e3cc1c7129578aa68ebd754e"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-win32.whl", hash = "sha256:fec21693218efe39aa7f8599346e90c705afa52c5b31ae019b2e57e8f6542bb2"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-win_amd64.whl", hash = "sha256:3fd4abcb888d15a94f32b75d8fd18ee162ca0c064f35b11134be77050296d6ba"}, + {file = "MarkupSafe-2.1.3.tar.gz", hash = "sha256:af598ed32d6ae86f1b747b82783958b1a4ab8f617b06fe68795c7f026abbdcad"}, +] + +[[package]] +name = "mdurl" +version = "0.1.2" +description = "Markdown URL utilities" +optional = false +python-versions = ">=3.7" +files = [ + {file = "mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"}, + {file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"}, +] + +[[package]] +name = "msgpack" +version = "1.0.7" +description = "MessagePack serializer" +optional = false +python-versions = ">=3.8" +files = [ + {file = "msgpack-1.0.7-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:04ad6069c86e531682f9e1e71b71c1c3937d6014a7c3e9edd2aa81ad58842862"}, + {file = "msgpack-1.0.7-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:cca1b62fe70d761a282496b96a5e51c44c213e410a964bdffe0928e611368329"}, + {file = "msgpack-1.0.7-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e50ebce52f41370707f1e21a59514e3375e3edd6e1832f5e5235237db933c98b"}, + {file = "msgpack-1.0.7-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4a7b4f35de6a304b5533c238bee86b670b75b03d31b7797929caa7a624b5dda6"}, + {file = "msgpack-1.0.7-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:28efb066cde83c479dfe5a48141a53bc7e5f13f785b92ddde336c716663039ee"}, + {file = "msgpack-1.0.7-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4cb14ce54d9b857be9591ac364cb08dc2d6a5c4318c1182cb1d02274029d590d"}, + {file = "msgpack-1.0.7-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b573a43ef7c368ba4ea06050a957c2a7550f729c31f11dd616d2ac4aba99888d"}, + {file = "msgpack-1.0.7-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:ccf9a39706b604d884d2cb1e27fe973bc55f2890c52f38df742bc1d79ab9f5e1"}, + {file = "msgpack-1.0.7-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:cb70766519500281815dfd7a87d3a178acf7ce95390544b8c90587d76b227681"}, + {file = "msgpack-1.0.7-cp310-cp310-win32.whl", hash = "sha256:b610ff0f24e9f11c9ae653c67ff8cc03c075131401b3e5ef4b82570d1728f8a9"}, + {file = "msgpack-1.0.7-cp310-cp310-win_amd64.whl", hash = "sha256:a40821a89dc373d6427e2b44b572efc36a2778d3f543299e2f24eb1a5de65415"}, + {file = "msgpack-1.0.7-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:576eb384292b139821c41995523654ad82d1916da6a60cff129c715a6223ea84"}, + {file = "msgpack-1.0.7-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:730076207cb816138cf1af7f7237b208340a2c5e749707457d70705715c93b93"}, + {file = "msgpack-1.0.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:85765fdf4b27eb5086f05ac0491090fc76f4f2b28e09d9350c31aac25a5aaff8"}, + {file = "msgpack-1.0.7-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3476fae43db72bd11f29a5147ae2f3cb22e2f1a91d575ef130d2bf49afd21c46"}, + {file = "msgpack-1.0.7-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d4c80667de2e36970ebf74f42d1088cc9ee7ef5f4e8c35eee1b40eafd33ca5b"}, + {file = "msgpack-1.0.7-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5b0bf0effb196ed76b7ad883848143427a73c355ae8e569fa538365064188b8e"}, + {file = "msgpack-1.0.7-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:f9a7c509542db4eceed3dcf21ee5267ab565a83555c9b88a8109dcecc4709002"}, + {file = "msgpack-1.0.7-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:84b0daf226913133f899ea9b30618722d45feffa67e4fe867b0b5ae83a34060c"}, + {file = "msgpack-1.0.7-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:ec79ff6159dffcc30853b2ad612ed572af86c92b5168aa3fc01a67b0fa40665e"}, + {file = "msgpack-1.0.7-cp311-cp311-win32.whl", hash = "sha256:3e7bf4442b310ff154b7bb9d81eb2c016b7d597e364f97d72b1acc3817a0fdc1"}, + {file = "msgpack-1.0.7-cp311-cp311-win_amd64.whl", hash = "sha256:3f0c8c6dfa6605ab8ff0611995ee30d4f9fcff89966cf562733b4008a3d60d82"}, + {file = "msgpack-1.0.7-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:f0936e08e0003f66bfd97e74ee530427707297b0d0361247e9b4f59ab78ddc8b"}, + {file = "msgpack-1.0.7-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:98bbd754a422a0b123c66a4c341de0474cad4a5c10c164ceed6ea090f3563db4"}, + {file = "msgpack-1.0.7-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b291f0ee7961a597cbbcc77709374087fa2a9afe7bdb6a40dbbd9b127e79afee"}, + {file = "msgpack-1.0.7-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ebbbba226f0a108a7366bf4b59bf0f30a12fd5e75100c630267d94d7f0ad20e5"}, + {file = "msgpack-1.0.7-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1e2d69948e4132813b8d1131f29f9101bc2c915f26089a6d632001a5c1349672"}, + {file = "msgpack-1.0.7-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bdf38ba2d393c7911ae989c3bbba510ebbcdf4ecbdbfec36272abe350c454075"}, + {file = "msgpack-1.0.7-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:993584fc821c58d5993521bfdcd31a4adf025c7d745bbd4d12ccfecf695af5ba"}, + {file = "msgpack-1.0.7-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:52700dc63a4676669b341ba33520f4d6e43d3ca58d422e22ba66d1736b0a6e4c"}, + {file = "msgpack-1.0.7-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:e45ae4927759289c30ccba8d9fdce62bb414977ba158286b5ddaf8df2cddb5c5"}, + {file = "msgpack-1.0.7-cp312-cp312-win32.whl", hash = "sha256:27dcd6f46a21c18fa5e5deed92a43d4554e3df8d8ca5a47bf0615d6a5f39dbc9"}, + {file = "msgpack-1.0.7-cp312-cp312-win_amd64.whl", hash = "sha256:7687e22a31e976a0e7fc99c2f4d11ca45eff652a81eb8c8085e9609298916dcf"}, + {file = "msgpack-1.0.7-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5b6ccc0c85916998d788b295765ea0e9cb9aac7e4a8ed71d12e7d8ac31c23c95"}, + {file = "msgpack-1.0.7-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:235a31ec7db685f5c82233bddf9858748b89b8119bf4538d514536c485c15fe0"}, + {file = "msgpack-1.0.7-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:cab3db8bab4b7e635c1c97270d7a4b2a90c070b33cbc00c99ef3f9be03d3e1f7"}, + {file = "msgpack-1.0.7-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0bfdd914e55e0d2c9e1526de210f6fe8ffe9705f2b1dfcc4aecc92a4cb4b533d"}, + {file = "msgpack-1.0.7-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:36e17c4592231a7dbd2ed09027823ab295d2791b3b1efb2aee874b10548b7524"}, + {file = "msgpack-1.0.7-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:38949d30b11ae5f95c3c91917ee7a6b239f5ec276f271f28638dec9156f82cfc"}, + {file = "msgpack-1.0.7-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:ff1d0899f104f3921d94579a5638847f783c9b04f2d5f229392ca77fba5b82fc"}, + {file = "msgpack-1.0.7-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:dc43f1ec66eb8440567186ae2f8c447d91e0372d793dfe8c222aec857b81a8cf"}, + {file = "msgpack-1.0.7-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:dd632777ff3beaaf629f1ab4396caf7ba0bdd075d948a69460d13d44357aca4c"}, + {file = "msgpack-1.0.7-cp38-cp38-win32.whl", hash = "sha256:4e71bc4416de195d6e9b4ee93ad3f2f6b2ce11d042b4d7a7ee00bbe0358bd0c2"}, + {file = "msgpack-1.0.7-cp38-cp38-win_amd64.whl", hash = "sha256:8f5b234f567cf76ee489502ceb7165c2a5cecec081db2b37e35332b537f8157c"}, + {file = "msgpack-1.0.7-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:bfef2bb6ef068827bbd021017a107194956918ab43ce4d6dc945ffa13efbc25f"}, + {file = "msgpack-1.0.7-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:484ae3240666ad34cfa31eea7b8c6cd2f1fdaae21d73ce2974211df099a95d81"}, + {file = "msgpack-1.0.7-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3967e4ad1aa9da62fd53e346ed17d7b2e922cba5ab93bdd46febcac39be636fc"}, + {file = "msgpack-1.0.7-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8dd178c4c80706546702c59529ffc005681bd6dc2ea234c450661b205445a34d"}, + {file = "msgpack-1.0.7-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f6ffbc252eb0d229aeb2f9ad051200668fc3a9aaa8994e49f0cb2ffe2b7867e7"}, + {file = "msgpack-1.0.7-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:822ea70dc4018c7e6223f13affd1c5c30c0f5c12ac1f96cd8e9949acddb48a61"}, + {file = "msgpack-1.0.7-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:384d779f0d6f1b110eae74cb0659d9aa6ff35aaf547b3955abf2ab4c901c4819"}, + {file = "msgpack-1.0.7-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:f64e376cd20d3f030190e8c32e1c64582eba56ac6dc7d5b0b49a9d44021b52fd"}, + {file = "msgpack-1.0.7-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5ed82f5a7af3697b1c4786053736f24a0efd0a1b8a130d4c7bfee4b9ded0f08f"}, + {file = "msgpack-1.0.7-cp39-cp39-win32.whl", hash = "sha256:f26a07a6e877c76a88e3cecac8531908d980d3d5067ff69213653649ec0f60ad"}, + {file = "msgpack-1.0.7-cp39-cp39-win_amd64.whl", hash = "sha256:1dc93e8e4653bdb5910aed79f11e165c85732067614f180f70534f056da97db3"}, + {file = "msgpack-1.0.7.tar.gz", hash = "sha256:572efc93db7a4d27e404501975ca6d2d9775705c2d922390d878fcf768d92c87"}, +] + +[[package]] +name = "numpy" +version = "1.21.1" +description = "NumPy is the fundamental package for array computing with Python." +optional = false +python-versions = ">=3.7" +files = [ + {file = "numpy-1.21.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:38e8648f9449a549a7dfe8d8755a5979b45b3538520d1e735637ef28e8c2dc50"}, + {file = "numpy-1.21.1-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:fd7d7409fa643a91d0a05c7554dd68aa9c9bb16e186f6ccfe40d6e003156e33a"}, + {file = "numpy-1.21.1-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:a75b4498b1e93d8b700282dc8e655b8bd559c0904b3910b144646dbbbc03e062"}, + {file = "numpy-1.21.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1412aa0aec3e00bc23fbb8664d76552b4efde98fb71f60737c83efbac24112f1"}, + {file = "numpy-1.21.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e46ceaff65609b5399163de5893d8f2a82d3c77d5e56d976c8b5fb01faa6b671"}, + {file = "numpy-1.21.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:c6a2324085dd52f96498419ba95b5777e40b6bcbc20088fddb9e8cbb58885e8e"}, + {file = "numpy-1.21.1-cp37-cp37m-win32.whl", hash = "sha256:73101b2a1fef16602696d133db402a7e7586654682244344b8329cdcbbb82172"}, + {file = "numpy-1.21.1-cp37-cp37m-win_amd64.whl", hash = "sha256:7a708a79c9a9d26904d1cca8d383bf869edf6f8e7650d85dbc77b041e8c5a0f8"}, + {file = "numpy-1.21.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:95b995d0c413f5d0428b3f880e8fe1660ff9396dcd1f9eedbc311f37b5652e16"}, + {file = "numpy-1.21.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:635e6bd31c9fb3d475c8f44a089569070d10a9ef18ed13738b03049280281267"}, + {file = "numpy-1.21.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4a3d5fb89bfe21be2ef47c0614b9c9c707b7362386c9a3ff1feae63e0267ccb6"}, + {file = "numpy-1.21.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:8a326af80e86d0e9ce92bcc1e65c8ff88297de4fa14ee936cb2293d414c9ec63"}, + {file = "numpy-1.21.1-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:791492091744b0fe390a6ce85cc1bf5149968ac7d5f0477288f78c89b385d9af"}, + {file = "numpy-1.21.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0318c465786c1f63ac05d7c4dbcecd4d2d7e13f0959b01b534ea1e92202235c5"}, + {file = "numpy-1.21.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9a513bd9c1551894ee3d31369f9b07460ef223694098cf27d399513415855b68"}, + {file = "numpy-1.21.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:91c6f5fc58df1e0a3cc0c3a717bb3308ff850abdaa6d2d802573ee2b11f674a8"}, + {file = "numpy-1.21.1-cp38-cp38-win32.whl", hash = "sha256:978010b68e17150db8765355d1ccdd450f9fc916824e8c4e35ee620590e234cd"}, + {file = "numpy-1.21.1-cp38-cp38-win_amd64.whl", hash = "sha256:9749a40a5b22333467f02fe11edc98f022133ee1bfa8ab99bda5e5437b831214"}, + {file = "numpy-1.21.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:d7a4aeac3b94af92a9373d6e77b37691b86411f9745190d2c351f410ab3a791f"}, + {file = "numpy-1.21.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d9e7912a56108aba9b31df688a4c4f5cb0d9d3787386b87d504762b6754fbb1b"}, + {file = "numpy-1.21.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:25b40b98ebdd272bc3020935427a4530b7d60dfbe1ab9381a39147834e985eac"}, + {file = "numpy-1.21.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:8a92c5aea763d14ba9d6475803fc7904bda7decc2a0a68153f587ad82941fec1"}, + {file = "numpy-1.21.1-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:05a0f648eb28bae4bcb204e6fd14603de2908de982e761a2fc78efe0f19e96e1"}, + {file = "numpy-1.21.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f01f28075a92eede918b965e86e8f0ba7b7797a95aa8d35e1cc8821f5fc3ad6a"}, + {file = "numpy-1.21.1-cp39-cp39-win32.whl", hash = "sha256:88c0b89ad1cc24a5efbb99ff9ab5db0f9a86e9cc50240177a571fbe9c2860ac2"}, + {file = "numpy-1.21.1-cp39-cp39-win_amd64.whl", hash = "sha256:01721eefe70544d548425a07c80be8377096a54118070b8a62476866d5208e33"}, + {file = "numpy-1.21.1-pp37-pypy37_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:2d4d1de6e6fb3d28781c73fbde702ac97f03d79e4ffd6598b880b2d95d62ead4"}, + {file = "numpy-1.21.1.zip", hash = "sha256:dff4af63638afcc57a3dfb9e4b26d434a7a602d225b42d746ea7fe2edf1342fd"}, +] + +[[package]] +name = "packaging" +version = "23.2" +description = "Core utilities for Python packages" +optional = false +python-versions = ">=3.7" +files = [ + {file = "packaging-23.2-py3-none-any.whl", hash = "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7"}, + {file = "packaging-23.2.tar.gz", hash = "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5"}, +] + +[[package]] +name = "pandas" +version = "1.1.5" +description = "Powerful data structures for data analysis, time series, and statistics" +optional = false +python-versions = ">=3.6.1" +files = [ + {file = "pandas-1.1.5-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:bf23a3b54d128b50f4f9d4675b3c1857a688cc6731a32f931837d72effb2698d"}, + {file = "pandas-1.1.5-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:5a780260afc88268a9d3ac3511d8f494fdcf637eece62fb9eb656a63d53eb7ca"}, + {file = "pandas-1.1.5-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:b61080750d19a0122469ab59b087380721d6b72a4e7d962e4d7e63e0c4504814"}, + {file = "pandas-1.1.5-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:0de3ddb414d30798cbf56e642d82cac30a80223ad6fe484d66c0ce01a84d6f2f"}, + {file = "pandas-1.1.5-cp36-cp36m-win32.whl", hash = "sha256:70865f96bb38fec46f7ebd66d4b5cfd0aa6b842073f298d621385ae3898d28b5"}, + {file = "pandas-1.1.5-cp36-cp36m-win_amd64.whl", hash = "sha256:19a2148a1d02791352e9fa637899a78e371a3516ac6da5c4edc718f60cbae648"}, + {file = "pandas-1.1.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:26fa92d3ac743a149a31b21d6f4337b0594b6302ea5575b37af9ca9611e8981a"}, + {file = "pandas-1.1.5-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:c16d59c15d946111d2716856dd5479221c9e4f2f5c7bc2d617f39d870031e086"}, + {file = "pandas-1.1.5-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:3be7a7a0ca71a2640e81d9276f526bca63505850add10206d0da2e8a0a325dae"}, + {file = "pandas-1.1.5-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:573fba5b05bf2c69271a32e52399c8de599e4a15ab7cec47d3b9c904125ab788"}, + {file = "pandas-1.1.5-cp37-cp37m-win32.whl", hash = "sha256:21b5a2b033380adbdd36b3116faaf9a4663e375325831dac1b519a44f9e439bb"}, + {file = "pandas-1.1.5-cp37-cp37m-win_amd64.whl", hash = "sha256:24c7f8d4aee71bfa6401faeba367dd654f696a77151a8a28bc2013f7ced4af98"}, + {file = "pandas-1.1.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2860a97cbb25444ffc0088b457da0a79dc79f9c601238a3e0644312fcc14bf11"}, + {file = "pandas-1.1.5-cp38-cp38-manylinux1_i686.whl", hash = "sha256:5008374ebb990dad9ed48b0f5d0038124c73748f5384cc8c46904dace27082d9"}, + {file = "pandas-1.1.5-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:2c2f7c670ea4e60318e4b7e474d56447cf0c7d83b3c2a5405a0dbb2600b9c48e"}, + {file = "pandas-1.1.5-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:0a643bae4283a37732ddfcecab3f62dd082996021b980f580903f4e8e01b3c5b"}, + {file = "pandas-1.1.5-cp38-cp38-win32.whl", hash = "sha256:5447ea7af4005b0daf695a316a423b96374c9c73ffbd4533209c5ddc369e644b"}, + {file = "pandas-1.1.5-cp38-cp38-win_amd64.whl", hash = "sha256:4c62e94d5d49db116bef1bd5c2486723a292d79409fc9abd51adf9e05329101d"}, + {file = "pandas-1.1.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:731568be71fba1e13cae212c362f3d2ca8932e83cb1b85e3f1b4dd77d019254a"}, + {file = "pandas-1.1.5-cp39-cp39-manylinux1_i686.whl", hash = "sha256:c61c043aafb69329d0f961b19faa30b1dab709dd34c9388143fc55680059e55a"}, + {file = "pandas-1.1.5-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:2b1c6cd28a0dfda75c7b5957363333f01d370936e4c6276b7b8e696dd500582a"}, + {file = "pandas-1.1.5-cp39-cp39-win32.whl", hash = "sha256:c94ff2780a1fd89f190390130d6d36173ca59fcfb3fe0ff596f9a56518191ccb"}, + {file = "pandas-1.1.5-cp39-cp39-win_amd64.whl", hash = "sha256:edda9bacc3843dfbeebaf7a701763e68e741b08fccb889c003b0a52f0ee95782"}, + {file = "pandas-1.1.5.tar.gz", hash = "sha256:f10fc41ee3c75a474d3bdf68d396f10782d013d7f67db99c0efbfd0acb99701b"}, +] + +[package.dependencies] +numpy = ">=1.15.4" +python-dateutil = ">=2.7.3" +pytz = ">=2017.2" + +[package.extras] +test = ["hypothesis (>=3.58)", "pytest (>=4.0.2)", "pytest-xdist"] + +[[package]] +name = "pastel" +version = "0.2.1" +description = "Bring colors to your terminal." +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ + {file = "pastel-0.2.1-py2.py3-none-any.whl", hash = "sha256:4349225fcdf6c2bb34d483e523475de5bb04a5c10ef711263452cb37d7dd4364"}, + {file = "pastel-0.2.1.tar.gz", hash = "sha256:e6581ac04e973cac858828c6202c1e1e81fee1dc7de7683f3e1ffe0bfd8a573d"}, +] + +[[package]] +name = "pexpect" +version = "4.9.0" +description = "Pexpect allows easy control of interactive console applications." +optional = false +python-versions = "*" +files = [ + {file = "pexpect-4.9.0-py2.py3-none-any.whl", hash = "sha256:7236d1e080e4936be2dc3e326cec0af72acf9212a7e1d060210e70a47e253523"}, + {file = "pexpect-4.9.0.tar.gz", hash = "sha256:ee7d41123f3c9911050ea2c2dac107568dc43b2d3b0c7557a33212c398ead30f"}, +] + +[package.dependencies] +ptyprocess = ">=0.5" + +[[package]] +name = "pkginfo" +version = "1.9.6" +description = "Query metadata from sdists / bdists / installed packages." +optional = false +python-versions = ">=3.6" +files = [ + {file = "pkginfo-1.9.6-py3-none-any.whl", hash = "sha256:4b7a555a6d5a22169fcc9cf7bfd78d296b0361adad412a346c1226849af5e546"}, + {file = "pkginfo-1.9.6.tar.gz", hash = "sha256:8fd5896e8718a4372f0ea9cc9d96f6417c9b986e23a4d116dda26b62cc29d046"}, +] + +[package.extras] +testing = ["pytest", "pytest-cov"] + +[[package]] +name = "pluggy" +version = "1.2.0" +description = "plugin and hook calling mechanisms for python" +optional = true +python-versions = ">=3.7" +files = [ + {file = "pluggy-1.2.0-py3-none-any.whl", hash = "sha256:c2fd55a7d7a3863cba1a013e4e2414658b1d07b6bc57b3919e0c63c9abb99849"}, + {file = "pluggy-1.2.0.tar.gz", hash = "sha256:d12f0c4b579b15f5e054301bb226ee85eeeba08ffec228092f8defbaa3a4c4b3"}, +] + +[package.extras] +dev = ["pre-commit", "tox"] +testing = ["pytest", "pytest-benchmark"] + +[[package]] +name = "poetry" +version = "1.0.10" +description = "Python dependency management and packaging made easy." +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ + {file = "poetry-1.0.10-py2.py3-none-any.whl", hash = "sha256:bf5f809a5f08bb4262c636b4bfee8e177fc8524ab817a7708e0901633da808de"}, + {file = "poetry-1.0.10.tar.gz", hash = "sha256:4b1b895d272d9bae22e1796dfe38d6122a75963709d7a909068e68aa6937a6f2"}, +] + +[package.dependencies] +cachecontrol = {version = ">=0.12.4,<0.13.0", extras = ["filecache"]} +cachy = ">=0.3.0,<0.4.0" +cleo = ">=0.7.6,<0.8.0" +clikit = ">=0.4.2,<0.5.0" +html5lib = ">=1.0,<2.0" +jsonschema = ">=3.1,<4.0" +keyring = {version = ">=20.0.1,<21.0.0", markers = "python_version >= \"3.5\" and python_version < \"4.0\""} +pexpect = ">=4.7.0,<5.0.0" +pkginfo = ">=1.4,<2.0" +pyparsing = ">=2.2,<3.0" +pyrsistent = ">=0.14.2,<0.15.0" +requests = ">=2.18,<3.0" +requests-toolbelt = ">=0.8.0,<0.9.0" +shellingham = ">=1.1,<2.0" +tomlkit = ">=0.5.11,<0.6.0" + +[[package]] +name = "ptyprocess" +version = "0.7.0" +description = "Run a subprocess in a pseudo terminal" +optional = false +python-versions = "*" +files = [ + {file = "ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35"}, + {file = "ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220"}, +] + +[[package]] +name = "pycparser" +version = "2.21" +description = "C parser in Python" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ + {file = "pycparser-2.21-py2.py3-none-any.whl", hash = "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9"}, + {file = "pycparser-2.21.tar.gz", hash = "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"}, +] + +[[package]] +name = "pygments" +version = "2.17.2" +description = "Pygments is a syntax highlighting package written in Python." +optional = false +python-versions = ">=3.7" +files = [ + {file = "pygments-2.17.2-py3-none-any.whl", hash = "sha256:b27c2826c47d0f3219f29554824c30c5e8945175d888647acd804ddd04af846c"}, + {file = "pygments-2.17.2.tar.gz", hash = "sha256:da46cec9fd2de5be3a8a784f434e4c4ab670b4ff54d605c4c2717e9d49c4c367"}, +] + +[package.extras] +plugins = ["importlib-metadata"] +windows-terminal = ["colorama (>=0.4.6)"] + +[[package]] +name = "pylev" +version = "1.4.0" +description = "A pure Python Levenshtein implementation that's not freaking GPL'd." +optional = false +python-versions = "*" +files = [ + {file = "pylev-1.4.0-py2.py3-none-any.whl", hash = "sha256:7b2e2aa7b00e05bb3f7650eb506fc89f474f70493271a35c242d9a92188ad3dd"}, + {file = "pylev-1.4.0.tar.gz", hash = "sha256:9e77e941042ad3a4cc305dcdf2b2dec1aec2fbe3dd9015d2698ad02b173006d1"}, +] + +[[package]] +name = "pyparsing" +version = "2.4.7" +description = "Python parsing module" +optional = false +python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" +files = [ + {file = "pyparsing-2.4.7-py2.py3-none-any.whl", hash = "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"}, + {file = "pyparsing-2.4.7.tar.gz", hash = "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1"}, +] + +[[package]] +name = "pyrsistent" +version = "0.14.11" +description = "Persistent/Functional/Immutable data structures" +optional = false +python-versions = "*" +files = [ + {file = "pyrsistent-0.14.11.tar.gz", hash = "sha256:3ca82748918eb65e2d89f222b702277099aca77e34843c5eb9d52451173970e2"}, +] + +[package.dependencies] +six = "*" + +[[package]] +name = "pytest" +version = "7.4.3" +description = "pytest: simple powerful testing with Python" +optional = true +python-versions = ">=3.7" +files = [ + {file = "pytest-7.4.3-py3-none-any.whl", hash = "sha256:0d009c083ea859a71b76adf7c1d502e4bc170b80a8ef002da5806527b9591fac"}, + {file = "pytest-7.4.3.tar.gz", hash = "sha256:d989d136982de4e3b29dabcc838ad581c64e8ed52c11fbe86ddebd9da0818cd5"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "sys_platform == \"win32\""} +exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} +iniconfig = "*" +packaging = "*" +pluggy = ">=0.12,<2.0" +tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""} + +[package.extras] +testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] + +[[package]] +name = "pytest-cov" +version = "4.1.0" +description = "Pytest plugin for measuring coverage." +optional = true +python-versions = ">=3.7" +files = [ + {file = "pytest-cov-4.1.0.tar.gz", hash = "sha256:3904b13dfbfec47f003b8e77fd5b589cd11904a21ddf1ab38a64f204d6a10ef6"}, + {file = "pytest_cov-4.1.0-py3-none-any.whl", hash = "sha256:6ba70b9e97e69fcc3fb45bfeab2d0a138fb65c4d0d6a41ef33983ad114be8c3a"}, +] + +[package.dependencies] +coverage = {version = ">=5.2.1", extras = ["toml"]} +pytest = ">=4.6" + +[package.extras] +testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtualenv"] + +[[package]] +name = "pytest-remotedata" +version = "0.4.1" +description = "Pytest plugin for controlling remote data access." +optional = true +python-versions = ">=3.7" +files = [ + {file = "pytest-remotedata-0.4.1.tar.gz", hash = "sha256:05c08bf638cdd1ed66eb01738a1647c3c714737c3ec3abe009d2c1f793b4bb59"}, + {file = "pytest_remotedata-0.4.1-py3-none-any.whl", hash = "sha256:4e840bd8733091c2a84e52528ee2c2a98aa2d4a26376ba20448f211bccd30a35"}, +] + +[package.dependencies] +packaging = "*" +pytest = ">=4.6" + +[[package]] +name = "pytest-runner" +version = "6.0.1" +description = "Invoke py.test as distutils command with dependency resolution" +optional = true +python-versions = ">=3.7" +files = [ + {file = "pytest-runner-6.0.1.tar.gz", hash = "sha256:70d4739585a7008f37bf4933c013fdb327b8878a5a69fcbb3316c88882f0f49b"}, + {file = "pytest_runner-6.0.1-py3-none-any.whl", hash = "sha256:ea326ed6f6613992746062362efab70212089a4209c08d67177b3df1c52cd9f2"}, +] + +[package.extras] +docs = ["jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx"] +testing = ["pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.0.1)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-virtualenv", "types-setuptools"] + +[[package]] +name = "python-dateutil" +version = "2.8.2" +description = "Extensions to the standard Python datetime module" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +files = [ + {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"}, + {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"}, +] + +[package.dependencies] +six = ">=1.5" + +[[package]] +name = "pytz" +version = "2023.3.post1" +description = "World timezone definitions, modern and historical" +optional = false +python-versions = "*" +files = [ + {file = "pytz-2023.3.post1-py2.py3-none-any.whl", hash = "sha256:ce42d816b81b68506614c11e8937d3aa9e41007ceb50bfdcb0749b921bf646c7"}, + {file = "pytz-2023.3.post1.tar.gz", hash = "sha256:7b4fddbeb94a1eba4b557da24f19fdf9db575192544270a9101d8509f9f43d7b"}, +] + +[[package]] +name = "pywin32" +version = "306" +description = "Python for Window Extensions" +optional = false +python-versions = "*" +files = [ + {file = "pywin32-306-cp310-cp310-win32.whl", hash = "sha256:06d3420a5155ba65f0b72f2699b5bacf3109f36acbe8923765c22938a69dfc8d"}, + {file = "pywin32-306-cp310-cp310-win_amd64.whl", hash = "sha256:84f4471dbca1887ea3803d8848a1616429ac94a4a8d05f4bc9c5dcfd42ca99c8"}, + {file = "pywin32-306-cp311-cp311-win32.whl", hash = "sha256:e65028133d15b64d2ed8f06dd9fbc268352478d4f9289e69c190ecd6818b6407"}, + {file = "pywin32-306-cp311-cp311-win_amd64.whl", hash = "sha256:a7639f51c184c0272e93f244eb24dafca9b1855707d94c192d4a0b4c01e1100e"}, + {file = "pywin32-306-cp311-cp311-win_arm64.whl", hash = "sha256:70dba0c913d19f942a2db25217d9a1b726c278f483a919f1abfed79c9cf64d3a"}, + {file = "pywin32-306-cp312-cp312-win32.whl", hash = "sha256:383229d515657f4e3ed1343da8be101000562bf514591ff383ae940cad65458b"}, + {file = "pywin32-306-cp312-cp312-win_amd64.whl", hash = "sha256:37257794c1ad39ee9be652da0462dc2e394c8159dfd913a8a4e8eb6fd346da0e"}, + {file = "pywin32-306-cp312-cp312-win_arm64.whl", hash = "sha256:5821ec52f6d321aa59e2db7e0a35b997de60c201943557d108af9d4ae1ec7040"}, + {file = "pywin32-306-cp37-cp37m-win32.whl", hash = "sha256:1c73ea9a0d2283d889001998059f5eaaba3b6238f767c9cf2833b13e6a685f65"}, + {file = "pywin32-306-cp37-cp37m-win_amd64.whl", hash = "sha256:72c5f621542d7bdd4fdb716227be0dd3f8565c11b280be6315b06ace35487d36"}, + {file = "pywin32-306-cp38-cp38-win32.whl", hash = "sha256:e4c092e2589b5cf0d365849e73e02c391c1349958c5ac3e9d5ccb9a28e017b3a"}, + {file = "pywin32-306-cp38-cp38-win_amd64.whl", hash = "sha256:e8ac1ae3601bee6ca9f7cb4b5363bf1c0badb935ef243c4733ff9a393b1690c0"}, + {file = "pywin32-306-cp39-cp39-win32.whl", hash = "sha256:e25fd5b485b55ac9c057f67d94bc203f3f6595078d1fb3b458c9c28b7153a802"}, + {file = "pywin32-306-cp39-cp39-win_amd64.whl", hash = "sha256:39b61c15272833b5c329a2989999dcae836b1eed650252ab1b7bfbe1d59f30f4"}, +] + +[[package]] +name = "pywin32-ctypes" +version = "0.2.2" +description = "A (partial) reimplementation of pywin32 using ctypes/cffi" +optional = false +python-versions = ">=3.6" +files = [ + {file = "pywin32-ctypes-0.2.2.tar.gz", hash = "sha256:3426e063bdd5fd4df74a14fa3cf80a0b42845a87e1d1e81f6549f9daec593a60"}, + {file = "pywin32_ctypes-0.2.2-py3-none-any.whl", hash = "sha256:bf490a1a709baf35d688fe0ecf980ed4de11d2b3e37b51e5442587a75d9957e7"}, +] + +[[package]] +name = "pyyaml" +version = "6.0.1" +description = "YAML parser and emitter for Python" +optional = false +python-versions = ">=3.6" +files = [ + {file = "PyYAML-6.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d858aa552c999bc8a8d57426ed01e40bef403cd8ccdd0fc5f6f04a00414cac2a"}, + {file = "PyYAML-6.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f"}, + {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"}, + {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"}, + {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"}, + {file = "PyYAML-6.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290"}, + {file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"}, + {file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"}, + {file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"}, + {file = "PyYAML-6.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f003ed9ad21d6a4713f0a9b5a7a0a79e08dd0f221aff4525a2be4c346ee60aab"}, + {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"}, + {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"}, + {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"}, + {file = "PyYAML-6.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b"}, + {file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"}, + {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, + {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, + {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, + {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, + {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, + {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, + {file = "PyYAML-6.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df"}, + {file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"}, + {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"}, + {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"}, + {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:afd7e57eddb1a54f0f1a974bc4391af8bcce0b444685d936840f125cf046d5bd"}, + {file = "PyYAML-6.0.1-cp36-cp36m-win32.whl", hash = "sha256:fca0e3a251908a499833aa292323f32437106001d436eca0e6e7833256674585"}, + {file = "PyYAML-6.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:f22ac1c3cac4dbc50079e965eba2c1058622631e526bd9afd45fedd49ba781fa"}, + {file = "PyYAML-6.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b1275ad35a5d18c62a7220633c913e1b42d44b46ee12554e5fd39c70a243d6a3"}, + {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:18aeb1bf9a78867dc38b259769503436b7c72f7a1f1f4c93ff9a17de54319b27"}, + {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:596106435fa6ad000c2991a98fa58eeb8656ef2325d7e158344fb33864ed87e3"}, + {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:baa90d3f661d43131ca170712d903e6295d1f7a0f595074f151c0aed377c9b9c"}, + {file = "PyYAML-6.0.1-cp37-cp37m-win32.whl", hash = "sha256:9046c58c4395dff28dd494285c82ba00b546adfc7ef001486fbf0324bc174fba"}, + {file = "PyYAML-6.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:4fb147e7a67ef577a588a0e2c17b6db51dda102c71de36f8549b6816a96e1867"}, + {file = "PyYAML-6.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1d4c7e777c441b20e32f52bd377e0c409713e8bb1386e1099c2415f26e479595"}, + {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"}, + {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"}, + {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"}, + {file = "PyYAML-6.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6"}, + {file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"}, + {file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"}, + {file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"}, + {file = "PyYAML-6.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c8098ddcc2a85b61647b2590f825f3db38891662cfc2fc776415143f599bb859"}, + {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"}, + {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"}, + {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"}, + {file = "PyYAML-6.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5"}, + {file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"}, + {file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"}, + {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, +] + +[[package]] +name = "requests" +version = "2.31.0" +description = "Python HTTP for Humans." +optional = false +python-versions = ">=3.7" +files = [ + {file = "requests-2.31.0-py3-none-any.whl", hash = "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f"}, + {file = "requests-2.31.0.tar.gz", hash = "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1"}, +] + +[package.dependencies] +certifi = ">=2017.4.17" +charset-normalizer = ">=2,<4" +idna = ">=2.5,<4" +urllib3 = ">=1.21.1,<3" + +[package.extras] +socks = ["PySocks (>=1.5.6,!=1.5.7)"] +use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] + +[[package]] +name = "requests-toolbelt" +version = "0.8.0" +description = "A utility belt for advanced users of python-requests" +optional = false +python-versions = "*" +files = [ + {file = "requests-toolbelt-0.8.0.tar.gz", hash = "sha256:f6a531936c6fa4c6cfce1b9c10d5c4f498d16528d2a54a22ca00011205a187b5"}, + {file = "requests_toolbelt-0.8.0-py2.py3-none-any.whl", hash = "sha256:42c9c170abc2cacb78b8ab23ac957945c7716249206f90874651971a4acff237"}, +] + +[package.dependencies] +requests = ">=2.0.1,<3.0.0" + +[[package]] +name = "rich" +version = "13.7.0" +description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" +optional = false +python-versions = ">=3.7.0" +files = [ + {file = "rich-13.7.0-py3-none-any.whl", hash = "sha256:6da14c108c4866ee9520bbffa71f6fe3962e193b7da68720583850cd4548e235"}, + {file = "rich-13.7.0.tar.gz", hash = "sha256:5cb5123b5cf9ee70584244246816e9114227e0b98ad9176eede6ad54bf5403fa"}, +] + +[package.dependencies] +markdown-it-py = ">=2.2.0" +pygments = ">=2.13.0,<3.0.0" +typing-extensions = {version = ">=4.0.0,<5.0", markers = "python_version < \"3.9\""} + +[package.extras] +jupyter = ["ipywidgets (>=7.5.1,<9)"] + +[[package]] +name = "secretstorage" +version = "3.3.3" +description = "Python bindings to FreeDesktop.org Secret Service API" +optional = false +python-versions = ">=3.6" +files = [ + {file = "SecretStorage-3.3.3-py3-none-any.whl", hash = "sha256:f356e6628222568e3af06f2eba8df495efa13b3b63081dafd4f7d9a7b7bc9f99"}, + {file = "SecretStorage-3.3.3.tar.gz", hash = "sha256:2403533ef369eca6d2ba81718576c5e0f564d5cca1b58f73a8b23e7d4eeebd77"}, +] + +[package.dependencies] +cryptography = ">=2.0" +jeepney = ">=0.6" + +[[package]] +name = "semver" +version = "3.0.2" +description = "Python helper for Semantic Versioning (https://semver.org)" +optional = false +python-versions = ">=3.7" +files = [ + {file = "semver-3.0.2-py3-none-any.whl", hash = "sha256:b1ea4686fe70b981f85359eda33199d60c53964284e0cfb4977d243e37cf4bf4"}, + {file = "semver-3.0.2.tar.gz", hash = "sha256:6253adb39c70f6e51afed2fa7152bcd414c411286088fb4b9effb133885ab4cc"}, +] + +[[package]] +name = "setuptools" +version = "69.0.3" +description = "Easily download, build, install, upgrade, and uninstall Python packages" +optional = false +python-versions = ">=3.8" +files = [ + {file = "setuptools-69.0.3-py3-none-any.whl", hash = "sha256:385eb4edd9c9d5c17540511303e39a147ce2fc04bc55289c322b9e5904fe2c05"}, + {file = "setuptools-69.0.3.tar.gz", hash = "sha256:be1af57fc409f93647f2e8e4573a142ed38724b8cdd389706a867bb4efcf1e78"}, +] + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"] +testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] +testing-integration = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "packaging (>=23.1)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] + +[[package]] +name = "shellingham" +version = "1.5.4" +description = "Tool to Detect Surrounding Shell" +optional = false +python-versions = ">=3.7" +files = [ + {file = "shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686"}, + {file = "shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de"}, +] + +[[package]] +name = "six" +version = "1.16.0" +description = "Python 2 and 3 compatibility utilities" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" +files = [ + {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, + {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, +] + +[[package]] +name = "snowballstemmer" +version = "2.2.0" +description = "This package provides 29 stemmers for 28 languages generated from Snowball algorithms." +optional = true +python-versions = "*" +files = [ + {file = "snowballstemmer-2.2.0-py2.py3-none-any.whl", hash = "sha256:c8e1716e83cc398ae16824e5572ae04e0d9fc2c6b985fb0f900f5f0c96ecba1a"}, + {file = "snowballstemmer-2.2.0.tar.gz", hash = "sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1"}, +] + +[[package]] +name = "sphinx" +version = "5.3.0" +description = "Python documentation generator" +optional = true +python-versions = ">=3.6" +files = [ + {file = "Sphinx-5.3.0.tar.gz", hash = "sha256:51026de0a9ff9fc13c05d74913ad66047e104f56a129ff73e174eb5c3ee794b5"}, + {file = "sphinx-5.3.0-py3-none-any.whl", hash = "sha256:060ca5c9f7ba57a08a1219e547b269fadf125ae25b06b9fa7f66768efb652d6d"}, +] + +[package.dependencies] +alabaster = ">=0.7,<0.8" +babel = ">=2.9" +colorama = {version = ">=0.4.5", markers = "sys_platform == \"win32\""} +docutils = ">=0.14,<0.20" +imagesize = ">=1.3" +importlib-metadata = {version = ">=4.8", markers = "python_version < \"3.10\""} +Jinja2 = ">=3.0" +packaging = ">=21.0" +Pygments = ">=2.12" +requests = ">=2.5.0" +snowballstemmer = ">=2.0" +sphinxcontrib-applehelp = "*" +sphinxcontrib-devhelp = "*" +sphinxcontrib-htmlhelp = ">=2.0.0" +sphinxcontrib-jsmath = "*" +sphinxcontrib-qthelp = "*" +sphinxcontrib-serializinghtml = ">=1.1.5" + +[package.extras] +docs = ["sphinxcontrib-websupport"] +lint = ["docutils-stubs", "flake8 (>=3.5.0)", "flake8-bugbear", "flake8-comprehensions", "flake8-simplify", "isort", "mypy (>=0.981)", "sphinx-lint", "types-requests", "types-typed-ast"] +test = ["cython", "html5lib", "pytest (>=4.6)", "typed_ast"] + +[[package]] +name = "sphinxcontrib-applehelp" +version = "1.0.2" +description = "sphinxcontrib-applehelp is a sphinx extension which outputs Apple help books" +optional = true +python-versions = ">=3.5" +files = [ + {file = "sphinxcontrib-applehelp-1.0.2.tar.gz", hash = "sha256:a072735ec80e7675e3f432fcae8610ecf509c5f1869d17e2eecff44389cdbc58"}, + {file = "sphinxcontrib_applehelp-1.0.2-py2.py3-none-any.whl", hash = "sha256:806111e5e962be97c29ec4c1e7fe277bfd19e9652fb1a4392105b43e01af885a"}, +] + +[package.extras] +lint = ["docutils-stubs", "flake8", "mypy"] +test = ["pytest"] + +[[package]] +name = "sphinxcontrib-devhelp" +version = "1.0.2" +description = "sphinxcontrib-devhelp is a sphinx extension which outputs Devhelp document." +optional = true +python-versions = ">=3.5" +files = [ + {file = "sphinxcontrib-devhelp-1.0.2.tar.gz", hash = "sha256:ff7f1afa7b9642e7060379360a67e9c41e8f3121f2ce9164266f61b9f4b338e4"}, + {file = "sphinxcontrib_devhelp-1.0.2-py2.py3-none-any.whl", hash = "sha256:8165223f9a335cc1af7ffe1ed31d2871f325254c0423bc0c4c7cd1c1e4734a2e"}, +] + +[package.extras] +lint = ["docutils-stubs", "flake8", "mypy"] +test = ["pytest"] + +[[package]] +name = "sphinxcontrib-htmlhelp" +version = "2.0.0" +description = "sphinxcontrib-htmlhelp is a sphinx extension which renders HTML help files" +optional = true +python-versions = ">=3.6" +files = [ + {file = "sphinxcontrib-htmlhelp-2.0.0.tar.gz", hash = "sha256:f5f8bb2d0d629f398bf47d0d69c07bc13b65f75a81ad9e2f71a63d4b7a2f6db2"}, + {file = "sphinxcontrib_htmlhelp-2.0.0-py2.py3-none-any.whl", hash = "sha256:d412243dfb797ae3ec2b59eca0e52dac12e75a241bf0e4eb861e450d06c6ed07"}, +] + +[package.extras] +lint = ["docutils-stubs", "flake8", "mypy"] +test = ["html5lib", "pytest"] + +[[package]] +name = "sphinxcontrib-jsmath" +version = "1.0.1" +description = "A sphinx extension which renders display math in HTML via JavaScript" +optional = true +python-versions = ">=3.5" +files = [ + {file = "sphinxcontrib-jsmath-1.0.1.tar.gz", hash = "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8"}, + {file = "sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl", hash = "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178"}, +] + +[package.extras] +test = ["flake8", "mypy", "pytest"] + +[[package]] +name = "sphinxcontrib-qthelp" +version = "1.0.3" +description = "sphinxcontrib-qthelp is a sphinx extension which outputs QtHelp document." +optional = true +python-versions = ">=3.5" +files = [ + {file = "sphinxcontrib-qthelp-1.0.3.tar.gz", hash = "sha256:4c33767ee058b70dba89a6fc5c1892c0d57a54be67ddd3e7875a18d14cba5a72"}, + {file = "sphinxcontrib_qthelp-1.0.3-py2.py3-none-any.whl", hash = "sha256:bd9fc24bcb748a8d51fd4ecaade681350aa63009a347a8c14e637895444dfab6"}, +] + +[package.extras] +lint = ["docutils-stubs", "flake8", "mypy"] +test = ["pytest"] + +[[package]] +name = "sphinxcontrib-serializinghtml" +version = "1.1.5" +description = "sphinxcontrib-serializinghtml is a sphinx extension which outputs \"serialized\" HTML files (json and pickle)." +optional = true +python-versions = ">=3.5" +files = [ + {file = "sphinxcontrib-serializinghtml-1.1.5.tar.gz", hash = "sha256:aa5f6de5dfdf809ef505c4895e51ef5c9eac17d0f287933eb49ec495280b6952"}, + {file = "sphinxcontrib_serializinghtml-1.1.5-py2.py3-none-any.whl", hash = "sha256:352a9a00ae864471d3a7ead8d7d79f5fc0b57e8b3f95e9867eb9eb28999b92fd"}, +] + +[package.extras] +lint = ["docutils-stubs", "flake8", "mypy"] +test = ["pytest"] + +[[package]] +name = "spython" +version = "0.3.13" +description = "Command line python tool for working with singularity." +optional = false +python-versions = "*" +files = [ + {file = "spython-0.3.13-py3-none-any.whl", hash = "sha256:3c16e05261647dc4f5931d641adb179f21b4bd117572c161bdf54fe511cda4d8"}, + {file = "spython-0.3.13.tar.gz", hash = "sha256:931105675f7889705e50859df72f01b8e00b5ad92ab2e5d9ef47563343419e4e"}, +] + +[[package]] +name = "tabulate" +version = "0.9.0" +description = "Pretty-print tabular data" +optional = false +python-versions = ">=3.7" +files = [ + {file = "tabulate-0.9.0-py3-none-any.whl", hash = "sha256:024ca478df22e9340661486f85298cff5f6dcdba14f3813e8830015b9ed1948f"}, + {file = "tabulate-0.9.0.tar.gz", hash = "sha256:0095b12bf5966de529c0feb1fa08671671b3368eec77d7ef7ab114be2c068b3c"}, +] + +[package.extras] +widechars = ["wcwidth"] + +[[package]] +name = "tomli" +version = "2.0.1" +description = "A lil' TOML parser" +optional = true +python-versions = ">=3.7" +files = [ + {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, + {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, +] + +[[package]] +name = "tomlkit" +version = "0.5.11" +description = "Style preserving TOML library" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ + {file = "tomlkit-0.5.11-py2.py3-none-any.whl", hash = "sha256:4e1bd6c9197d984528f9ff0cc9db667c317d8881288db50db20eeeb0f6b0380b"}, + {file = "tomlkit-0.5.11.tar.gz", hash = "sha256:f044eda25647882e5ef22b43a1688fb6ab12af2fc50e8456cdfc751c873101cf"}, +] + +[[package]] +name = "tornado" +version = "6.2" +description = "Tornado is a Python web framework and asynchronous networking library, originally developed at FriendFeed." +optional = false +python-versions = ">= 3.7" +files = [ + {file = "tornado-6.2-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:20f638fd8cc85f3cbae3c732326e96addff0a15e22d80f049e00121651e82e72"}, + {file = "tornado-6.2-cp37-abi3-macosx_10_9_x86_64.whl", hash = "sha256:87dcafae3e884462f90c90ecc200defe5e580a7fbbb4365eda7c7c1eb809ebc9"}, + {file = "tornado-6.2-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ba09ef14ca9893954244fd872798b4ccb2367c165946ce2dd7376aebdde8e3ac"}, + {file = "tornado-6.2-cp37-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b8150f721c101abdef99073bf66d3903e292d851bee51910839831caba341a75"}, + {file = "tornado-6.2-cp37-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d3a2f5999215a3a06a4fc218026cd84c61b8b2b40ac5296a6db1f1451ef04c1e"}, + {file = "tornado-6.2-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:5f8c52d219d4995388119af7ccaa0bcec289535747620116a58d830e7c25d8a8"}, + {file = "tornado-6.2-cp37-abi3-musllinux_1_1_i686.whl", hash = "sha256:6fdfabffd8dfcb6cf887428849d30cf19a3ea34c2c248461e1f7d718ad30b66b"}, + {file = "tornado-6.2-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:1d54d13ab8414ed44de07efecb97d4ef7c39f7438cf5e976ccd356bebb1b5fca"}, + {file = "tornado-6.2-cp37-abi3-win32.whl", hash = "sha256:5c87076709343557ef8032934ce5f637dbb552efa7b21d08e89ae7619ed0eb23"}, + {file = "tornado-6.2-cp37-abi3-win_amd64.whl", hash = "sha256:e5f923aa6a47e133d1cf87d60700889d7eae68988704e20c75fb2d65677a8e4b"}, + {file = "tornado-6.2.tar.gz", hash = "sha256:9b630419bde84ec666bfd7ea0a4cb2a8a651c2d5cccdbdd1972a0c859dfc3c13"}, +] + +[[package]] +name = "typing-extensions" +version = "4.7.1" +description = "Backported and Experimental Type Hints for Python 3.7+" +optional = false +python-versions = ">=3.7" +files = [ + {file = "typing_extensions-4.7.1-py3-none-any.whl", hash = "sha256:440d5dd3af93b060174bf433bccd69b0babc3b15b1a8dca43789fd7f61514b36"}, + {file = "typing_extensions-4.7.1.tar.gz", hash = "sha256:b75ddc264f0ba5615db7ba217daeb99701ad295353c45f9e95963337ceeeffb2"}, +] + +[[package]] +name = "urllib3" +version = "2.0.7" +description = "HTTP library with thread-safe connection pooling, file post, and more." +optional = false +python-versions = ">=3.7" +files = [ + {file = "urllib3-2.0.7-py3-none-any.whl", hash = "sha256:fdb6d215c776278489906c2f8916e6e7d4f5a9b602ccbcfdf7f016fc8da0596e"}, + {file = "urllib3-2.0.7.tar.gz", hash = "sha256:c97dfde1f7bd43a71c8d2a58e369e9b2bf692d1334ea9f9cae55add7d0dd0f84"}, +] + +[package.extras] +brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] +secure = ["certifi", "cryptography (>=1.9)", "idna (>=2.0.0)", "pyopenssl (>=17.1.0)", "urllib3-secure-extra"] +socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] +zstd = ["zstandard (>=0.18.0)"] + +[[package]] +name = "webencodings" +version = "0.5.1" +description = "Character encoding aliases for legacy web content" +optional = false +python-versions = "*" +files = [ + {file = "webencodings-0.5.1-py2.py3-none-any.whl", hash = "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78"}, + {file = "webencodings-0.5.1.tar.gz", hash = "sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923"}, +] + +[[package]] +name = "websocket-client" +version = "1.6.1" +description = "WebSocket client for Python with low level API options" +optional = false +python-versions = ">=3.7" +files = [ + {file = "websocket-client-1.6.1.tar.gz", hash = "sha256:c951af98631d24f8df89ab1019fc365f2227c0892f12fd150e935607c79dd0dd"}, + {file = "websocket_client-1.6.1-py3-none-any.whl", hash = "sha256:f1f9f2ad5291f0225a49efad77abf9e700b6fef553900623060dad6e26503b9d"}, +] + +[package.extras] +docs = ["Sphinx (>=3.4)", "sphinx-rtd-theme (>=0.5)"] +optional = ["python-socks", "wsaccel"] +test = ["websockets"] + +[[package]] +name = "zipp" +version = "3.15.0" +description = "Backport of pathlib-compatible object wrapper for zip files" +optional = false +python-versions = ">=3.7" +files = [ + {file = "zipp-3.15.0-py3-none-any.whl", hash = "sha256:48904fc76a60e542af151aded95726c1a5c34ed43ab4134b597665c86d7ad556"}, + {file = "zipp-3.15.0.tar.gz", hash = "sha256:112929ad649da941c23de50f356a2b5570c954b65150642bccdd66bf194d224b"}, +] + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +testing = ["big-O", "flake8 (<5)", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"] + +[extras] +dev = ["coveralls", "pytest", "pytest-remotedata", "pytest-runner", "sphinx"] +testing = ["alabaster", "imagesize", "pytest", "pytest-cov", "pytest-runner", "sphinx"] + +[metadata] +lock-version = "2.0" +python-versions = ">=3.8" +content-hash = "f1345bd4f365e448f30fbedda8808c2f18161f1bcceda4480d94063495549325" diff --git a/pyproject.toml b/pyproject.toml index 4191f90a..583ab508 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,108 +1,104 @@ [build-system] -requires = [ - "semver", - "setuptools>=61.2", - "setuptools_scm", - "pyyaml", -] -build-backend = "setuptools.build_meta" +requires = ["poetry-core>=1.0.0"] +build-backend = "poetry.core.masonry.api" + +[tool.aliases] +build = "poetry build" +release = "poetry publish" +test = "pytest" + +[tool.devpi.upload] +no-vcs = "1" +formats = "bdist_wheel" -[project] +[tool.poetry] name = "cpac" description = "C-PAC Python Package" -authors = [{name = "C-PAC developers", email = "cpac@cnl.childmind.org"}] -license = {text = "mit"} +authors = ["C-PAC developers "] +license = "MIT" classifiers = [ - "Development Status :: 3 - Alpha", - "Intended Audience :: Science/Research", - "License :: OSI Approved :: MIT License", - "Natural Language :: English", - "Operating System :: OS Independent", - "Programming Language :: Python :: 3", - "Topic :: Scientific/Engineering :: Bio-Informatics", -] -version = "0.6.0" -urls = {Homepage = "https://github.com/FCP-INDI/cpac"} -requires-python = ">=3.7" -dependencies = [ - "docker >= 4.2.1", - "dockerpty", - "docker-pycreds", - 'importlib_metadata; python_version < "3.8"', - "pandas >= 0.23.4", - "spython >= 0.0.81", - "pyyaml", - "rich", - "semver", - "tabulate >= 0.8.6", - "tornado", - "websocket-client", + "Development Status :: 3 - Alpha", + "Intended Audience :: Science/Research", + "License :: OSI Approved :: MIT License", + "Natural Language :: English", + "Operating System :: OS Independent", + "Programming Language :: Python :: 3", + "Topic :: Scientific/Engineering :: Bio-Informatics" ] +documentation = "https://fcp-indi.github.io/docs/latest/user/cpac" +readme = "README.rst" +repository = "https://github.com/FCP-INDI/cpac" +version = "1.8.5" -[project.readme] -file = "README.rst" -content-type = "text/x-rst" +[tool.poetry.dependencies] +python = ">=3.8" +docker = ">=4.2.1" +dockerpty = "*" +docker-pycreds = "*" +importlib_metadata = {version = "*", markers = "python_version < '3.8'"} +pandas = ">=0.23.4" +poetry = "*" +pyyaml = "*" +rich = "*" +semver = "*" +spython = ">=0.0.81" +tabulate = ">=0.8.6" +tornado = "*" +websocket-client = "*" +alabaster = {version = "*", optional = true} +coveralls = {version = "*", optional = true} +imagesize = {version = "*", optional = true} +pytest = {version = "*", optional = true} +pytest-cov = {version = "*", optional = true} +pytest-remotedata = {version = ">=0.3.2", optional = true} +pytest-runner = {version = "*", optional = true} +sphinx = {version = "*", optional = true} -[project.optional-dependencies] -testing = [ - "alabaster", - "imagesize", - "pytest", - "pytest-cov", - "pytest-runne", - "sphinx", -] +[tool.poetry.extras] +dev = ["coveralls", "pytest", "pytest-remotedata", "pytest-runner", "sphinx"] +testing = ["alabaster", "imagesize", "pytest", "pytest-cov", "pytest-runner", "sphinx"] -[project.scripts] +[tool.poetry.scripts] cpac = "cpac.__main__:run" -[tool.setuptools] -zip-safe = false -include-package-data = true -package-dir = {"" = "src"} -platforms = ["any"] - -[tool.setuptools.packages.find] -where = ["src"] -exclude = ["tests"] -namespaces = false - -[tool.distutils.test] -addopts = "--verbose -s" -extras = true - -[tool.distutils.bdist_wheel] -universal = 1 - -[tool.distutils.build_sphinx] -source-dir = "docs" -build-dir = "docs/_build" - [tool.pytest.ini_options] addopts = "-s" # --cov cpac --cov-report term-missing -s # -s # --verbose norecursedirs = [ - "dist", - "build", - ".tox", + "dist", + "build", + ".tox" ] testpaths = ["tests"] -[tool.aliases] -build = "bdist_wheel" -release = "build upload" -test = "pytest" +[tool.ruff] +extend-exclude = ["docs/conf.py"] +extend-select = ["A", "C4", "D", "G", "I", "ICN", "NPY", "PL", "RET", "RSE", "RUF", "Q", "W"] +target-version = "py38" -[tool.devpi.upload] -no-vcs = "1" -formats = "bdist_wheel" +[tool.ruff.lint.isort] +combine-as-imports = true +force-sort-within-sections = true +known-first-party = ["cpac"] +no-lines-before = ["collab", "other-first-party", "local-folder"] +order-by-type = false +section-order = ["future", "standard-library", "third-party", "collab", "other-first-party", "first-party", "local-folder"] + +[tool.ruff.lint.isort.sections] +"collab" = ["nipype", "spython"] +"other-first-party" = [] + +[tool.ruff.lint.pydocstyle] +convention = "numpy" + +[tool.ruff.lint.pylint] +max-args = 10 +max-branches = 50 +max-returns = 12 +max-statements = 100 -[tool.flake8] -exclude = """ -.tox -build -dist -.eggs -docs/conf.py""" +[tool.ruff.lint.pyupgrade] +# Until variants Python ≥ 3.10 +keep-runtime-typing = true diff --git a/requirements-dev.txt b/requirements-dev.txt deleted file mode 100644 index 6196be48..00000000 --- a/requirements-dev.txt +++ /dev/null @@ -1,5 +0,0 @@ -coveralls -pytest -pytest-remotedata >= 0.3.2 -pytest-runner -sphinx \ No newline at end of file diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index 9452b439..00000000 --- a/requirements.txt +++ /dev/null @@ -1,12 +0,0 @@ -docker >= 4.2.1 -dockerpty -docker-pycreds -pandas >= 0.23.4 -pyyaml -rich -semver -setuptools -spython >= 0.0.81 -tabulate >= 0.8.6 -tornado -websocket-client diff --git a/setup.py b/setup.py deleted file mode 100644 index c2f6d125..00000000 --- a/setup.py +++ /dev/null @@ -1,16 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -try: - from importlib.metadata import version -except ModuleNotFoundError: - from importlib_metadata import version -from semver.version import Version -from setuptools import setup - -_MIN_VERSION = '61.2' -_MIN_VERSION = Version(*_MIN_VERSION.split('.')) -assert Version(*version('setuptools').split('.')).compare(_MIN_VERSION), ( - f"Version of setuptools is too old ({_MIN_VERSION})!") - -if __name__ == "__main__": - setup() diff --git a/src/cpac/__init__.py b/src/cpac/__init__.py index d1fc92df..bf8bc36a 100644 --- a/src/cpac/__init__.py +++ b/src/cpac/__init__.py @@ -1,13 +1,11 @@ # -*- coding: utf-8 -*- -try: - from importlib.metadata import distribution, PackageNotFoundError -except ModuleNotFoundError: - from importlib_metadata import distribution, PackageNotFoundError +"""Init file for cpac.""" +from importlib.metadata import distribution, PackageNotFoundError DIST_NAME = __name__ try: __version__ = distribution(DIST_NAME).version except (AttributeError, NameError, PackageNotFoundError): - __version__ = 'unknown' + __version__ = "unknown" finally: del distribution, PackageNotFoundError diff --git a/src/cpac/__main__.py b/src/cpac/__main__.py index 62e166aa..104b5940 100644 --- a/src/cpac/__main__.py +++ b/src/cpac/__main__.py @@ -1,22 +1,22 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- +"""Main module for cpac package.""" import argparse +from itertools import chain import logging import os import sys from docker.errors import DockerException, NotFound -from itertools import chain from cpac import __version__ from cpac.backends import Backends from cpac.helpers import cpac_parse_resources as parse_resources, TODOs _logger = logging.getLogger(__name__) - -# commandline arguments to pass into container after `--`: -clargs = {'group', 'utils'} +_CLARGS: set = {"group", "utils"} +"""commandline arguments to pass into container after `--`""" class ExtendAction(argparse.Action): @@ -26,14 +26,19 @@ def __call__(self, parser, namespace, values, option_string=None): setattr(namespace, self.dest, items) -def address(str): # pragma: no cover - addr, port = str.split(':') +def address(str_addy): + addr, port = str_addy.split(":") port = int(port) return addr, port +def help_call(argument_list: list) -> bool: + """Check if the helpstring is being called.""" + return "--help" in argument_list or "-h" in argument_list + + def _parser(): - '''Generate parser. + """Generate parser. Parameters ---------- @@ -42,211 +47,220 @@ def _parser(): Returns ------- parser : argparse.ArgumentParser - ''' + """ cwd = os.getcwd() parser = argparse.ArgumentParser( - description='cpac: a Python package that simplifies using C-PAC ' - ' containerized images. \n\n' - 'This commandline interface package is designed to ' - 'minimize repetition.\nAs such, nearly all arguments are ' - 'optional.\n\nWhen launching a container, this package ' - 'will try to bind any paths mentioned in \n • the command' - '\n • the data configuration\n\nAn example minimal run ' - 'command:\n\tcpac run /path/to/data /path/for/outputs' - '\n\nAn example run command with optional arguments:\n\t' - 'cpac -B /path/to/data/configs:/configs \\\n\t\t' - '--image fcpindi/c-pac --tag latest \\\n\t\t' - 'run /path/to/data /path/for/outputs \\\n\t\t' - '--data_config_file /configs/data_config.yml \\\n\t\t' - '--save_working_dir\n\n' - 'Each command can take "--help" to provide additonal ' - 'usage information, e.g.,\n\n\tcpac run --help\n\n' - 'Known issues:\n' + - '\n'.join([f'- {todo}' for todo in TODOs.values()]) + - '\n- https://github.com/FCP-INDI/cpac/issues', - conflict_handler='resolve', - formatter_class=argparse.RawTextHelpFormatter + description="cpac: a Python package that simplifies using C-PAC " + " containerized images. \n\n" + "This commandline interface package is designed to " + "minimize repetition.\nAs such, nearly all arguments are " + "optional.\n\nWhen launching a container, this package " + "will try to bind any paths mentioned in \n • the command" + "\n • the data configuration\n\nAn example minimal run " + "command:\n\tcpac run /path/to/data /path/for/outputs" + "\n\nAn example run command with optional arguments:\n\t" + "cpac -B /path/to/data/configs:/configs \\\n\t\t" + "--image fcpindi/c-pac --tag latest \\\n\t\t" + "run /path/to/data /path/for/outputs \\\n\t\t" + "--data_config_file /configs/data_config.yml \\\n\t\t" + "--save_working_dir\n\n" + 'Each command can take "--help" to provide additonal ' + "usage information, e.g.,\n\n\tcpac run --help\n\n" + "Known issues:\n" + + "\n".join([f"- {todo}" for todo in TODOs.values()]) + + "\n- https://github.com/FCP-INDI/cpac/issues", + conflict_handler="resolve", + formatter_class=argparse.RawTextHelpFormatter, ) parser.add_argument( - '--version', - action='version', - version='cpac (convenience wrapper) version {ver}\nFor C-PAC version, ' - 'run `cpac version` with any cpac options (e.g., ' - '`--platform`, `--image`, `--tag`) that you would use ' - 'while running'.format(ver=__version__) + "--version", + action="version", + version="cpac (convenience wrapper) version {ver}\nFor C-PAC version, " + "run `cpac version` with any cpac options (e.g., " + "`--platform`, `--image`, `--tag`) that you would use " + "while running".format(ver=__version__), ) parser.add_argument( - '-o', '--container_option', - dest='container_option', - action='append', - help='parameters and flags to pass through to Docker or Singularity\n' - '\nThis flag can take multiple arguments so cannot ' - 'be\nthe final argument before the command argument (i.e.,\nrun ' - 'or any other command that does not start with - or --)\n', - metavar='OPT' + "-o", + "--container_option", + dest="container_option", + action="append", + help="parameters and flags to pass through to Docker or Singularity\n" + "\nThis flag can take multiple arguments so cannot " + "be\nthe final argument before the command argument (i.e.,\nrun " + "or any other command that does not start with - or --)\n", + metavar="OPT", ) parser.add_argument( - '-B', '--custom_binding', - dest='custom_binding', - action='append', - help='directories to bind with a different path in\nthe container ' - 'than the real path of the directory.\nOne or more pairs in the ' - 'format:\n\treal_path:container_path\n(eg, ' - '/home/C-PAC/run5/outputs:/outputs).\nUse absolute paths for ' - 'both paths.\n\nThis flag can take multiple arguments so cannot ' - 'be\nthe final argument before the command argument (i.e.,\nrun ' - 'or any other command that does not start with - or --)\n' + "-B", + "--custom_binding", + dest="custom_binding", + action="append", + help="directories to bind with a different path in\nthe container " + "than the real path of the directory.\nOne or more pairs in the " + "format:\n\treal_path:container_path\n(eg, " + "/home/C-PAC/run5/outputs:/outputs).\nUse absolute paths for " + "both paths.\n\nThis flag can take multiple arguments so cannot " + "be\nthe final argument before the command argument (i.e.,\nrun " + "or any other command that does not start with - or --)\n", ) parser.add_argument( - '--platform', - choices=['docker', 'singularity'], - help='If neither platform nor image is specified,\ncpac will try ' - 'Docker first, then try\nSingularity if Docker fails.' + "--platform", + choices=["docker", "singularity"], + help="If neither platform nor image is specified,\ncpac will try " + "Docker first, then try\nSingularity if Docker fails.", ) parser.add_argument( - '--image', - help='path to Singularity image file OR name of Docker image (eg, ' - '"fcpindi/c-pac").\nWill attempt to pull from Singularity Hub or ' - 'Docker Hub if not provided.\nIf image is specified but platform ' - 'is not, platform is\nassumed to be Singularity if image is a ' - 'path or \nDocker if image is an image name.' + "--image", + help="path to Singularity image file OR name of Docker image (eg, " + '"fcpindi/c-pac").\nWill attempt to pull from Singularity Hub or ' + "Docker Hub if not provided.\nIf image is specified but platform " + "is not, platform is\nassumed to be Singularity if image is a " + "path or \nDocker if image is an image name.", ) parser.add_argument( - '--tag', - help='tag of the Docker image to use (eg, "latest" or "nightly").' + "--tag", help='tag of the Docker image to use (eg, "latest" or "nightly").' ) parser.add_argument( - '--working_dir', - help='working directory', - default=cwd, - metavar='PATH' + "--working_dir", help="working directory", default=cwd, metavar="PATH" ) parser.add_argument( - '-v', - '--verbose', - dest='loglevel', - help='set loglevel to INFO', - action='store_const', - const=logging.INFO + "-v", + "--verbose", + dest="loglevel", + help="set loglevel to INFO", + action="store_const", + const=logging.INFO, ) parser.add_argument( - '-vv', - '--very-verbose', - dest='loglevel', - help='set loglevel to DEBUG', - action='store_const', - const=logging.DEBUG + "-vv", + "--very-verbose", + dest="loglevel", + help="set loglevel to DEBUG", + action="store_const", + const=logging.DEBUG, ) - subparsers = parser.add_subparsers(dest='command') + subparsers = parser.add_subparsers(dest="command") run_parser = subparsers.add_parser( - 'run', add_help=False, + "run", + add_help=False, help='Run C-PAC. See\n"cpac [--platform {docker,singularity}] ' - '[--image IMAGE] [--tag TAG] run --help"\nfor more ' - 'information.', - formatter_class=argparse.ArgumentDefaultsHelpFormatter + '[--image IMAGE] [--tag TAG] run --help"\nfor more ' + "information.", + formatter_class=argparse.ArgumentDefaultsHelpFormatter, ) utils_parser = subparsers.add_parser( - 'utils', add_help=False, + "utils", + add_help=False, help='Run C-PAC commandline utilities. See\n"cpac [--platform ' - '{docker,singularity}] [--image IMAGE] [--tag TAG] utils ' - '--help"\nfor more information.', - formatter_class=argparse.ArgumentDefaultsHelpFormatter + "{docker,singularity}] [--image IMAGE] [--tag TAG] utils " + '--help"\nfor more information.', + formatter_class=argparse.ArgumentDefaultsHelpFormatter, ) version_parser = subparsers.add_parser( - 'version', add_help=True, - help='Print the version of C-PAC that cpac is using.') + "version", add_help=True, help="Print the version of C-PAC that cpac is using." + ) - help_call = '--help' in sys.argv or '-h' in sys.argv # run_parser.add_argument('--address', action='store', type=address) - if not help_call: + if not help_call(sys.argv): # These positional arguments are required unless we're just getting # the helpstring + run_parser.add_argument("bids_dir") + run_parser.add_argument("output_dir", default=os.path.join(cwd, "outputs")) run_parser.add_argument( - 'bids_dir' - ) - run_parser.add_argument( - 'output_dir', - default=os.path.join(cwd, 'outputs') - ) - run_parser.add_argument( - 'level_of_analysis', - choices=['participant', 'group', 'test_config'] + "level_of_analysis", choices=["participant", "group", "test_config"] ) - run_parser.add_argument( - '--data_config_file', - metavar='PATH' - ) - run_parser.add_argument( - 'extra_args', - nargs=argparse.REMAINDER - ) + run_parser.add_argument("--data_config_file", metavar="PATH") + run_parser.add_argument("extra_args", nargs=argparse.REMAINDER) group_parser = subparsers.add_parser( - 'group', add_help=False, + "group", + add_help=False, help='Run a group level analysis in C-PAC. See\n"cpac [--platform ' - '{docker,singularity}] [--image IMAGE] [--tag TAG] group ' - '--help"\nfor more information.', - formatter_class=argparse.ArgumentDefaultsHelpFormatter + "{docker,singularity}] [--image IMAGE] [--tag TAG] group " + '--help"\nfor more information.', + formatter_class=argparse.ArgumentDefaultsHelpFormatter, ) subparsers.add_parser( - 'pull', add_help=True, - help='Upgrade your local C-PAC version to the latest version\n' - 'by pulling from Docker Hub or other repository.\nUse with ' - '"--image" and/or "--tag" to specify an image\nother than ' - 'the default "fcpindi/c-pac:latest" to pull.', - aliases=['upgrade'], - formatter_class=argparse.ArgumentDefaultsHelpFormatter + "pull", + add_help=True, + help="Upgrade your local C-PAC version to the latest version\n" + "by pulling from Docker Hub or other repository.\nUse with " + '"--image" and/or "--tag" to specify an image\nother than ' + 'the default "fcpindi/c-pac:latest" to pull.', + aliases=["upgrade"], + formatter_class=argparse.ArgumentDefaultsHelpFormatter, ) enter_parser = subparsers.add_parser( - 'enter', add_help=True, help='Enter a new C-PAC container via BASH.', - aliases=['bash', 'shell']) + "enter", + add_help=True, + help="Enter a new C-PAC container via BASH.", + aliases=["bash", "shell"], + ) - parse_resources.set_args(subparsers.add_parser( - 'parse-resources', add_help=True, aliases=['parse_resources'], - help='\n'.join([parse_resources.__doc__.split( - parse_resources.__file__.split('/', maxsplit=-1)[-1], - maxsplit=1)[-1].strip().replace( - r'`cpac_parse_resources`', '"parse-resources"'), - 'See "cpac parse-resources --help" for more information.']))) + parse_resources.set_args( + subparsers.add_parser( + "parse-resources", + add_help=True, + aliases=["parse_resources"], + help="\n".join( + [ + parse_resources.__doc__.split( + parse_resources.__file__.split("/", maxsplit=-1)[-1], maxsplit=1 + )[-1] + .strip() + .replace(r"`cpac_parse_resources`", '"parse-resources"'), + 'See "cpac parse-resources --help" for more information.', + ] + ), + ) + ) crash_parser = subparsers.add_parser( - 'crash', add_help=True, - help='Convert a crash pickle to plain text (C-PAC < 1.8.0).', - formatter_class=argparse.ArgumentDefaultsHelpFormatter + "crash", + add_help=True, + help="Convert a crash pickle to plain text (C-PAC < 1.8.0).", + formatter_class=argparse.ArgumentDefaultsHelpFormatter, ) crash_parser.add_argument( - 'crashfile', - help='path to crashfile, to read a crashfile from C-PAC < 1.8.0.\n' - 'Crashfiles in C-PAC >= 1.8.0 are plain text files.' + "crashfile", + help="path to crashfile, to read a crashfile from C-PAC < 1.8.0.\n" + "Crashfiles in C-PAC >= 1.8.0 are plain text files.", ) - for subparser in [crash_parser, enter_parser, group_parser, run_parser, - utils_parser, version_parser]: - subparser.register('action', 'extend', ExtendAction) + for subparser in [ + crash_parser, + enter_parser, + group_parser, + run_parser, + utils_parser, + version_parser, + ]: + subparser.register("action", "extend", ExtendAction) return parser def parse_args(args): - '''Parse commandline arguments + """Parse commandline arguments. Parameters ---------- @@ -255,13 +269,13 @@ def parse_args(args): Returns ------- parsed : Namespace - ''' + """ parser = _parser() parsed, extras = parser.parse_known_args(args) parsed.extra_args = [ - *(parsed.extra_args if hasattr(parsed, 'extra_args') else []), - *extras + *(parsed.extra_args if hasattr(parsed, "extra_args") else []), + *extras, ] return parsed @@ -269,12 +283,13 @@ def parse_args(args): def setup_logging(loglevel): logformat = "[%(asctime)s] %(levelname)s:%(name)s:%(message)s" - logging.basicConfig(level=loglevel, stream=sys.stdout, - format=logformat, datefmt="%Y-%m-%d %H:%M:%S") + logging.basicConfig( + level=loglevel, stream=sys.stdout, format=logformat, datefmt="%Y-%m-%d %H:%M:%S" + ) def main(args): - '''Connect to C-PAC container and perform specified action + """Connect to C-PAC container and perform specified action. Parameters ---------- @@ -283,24 +298,19 @@ def main(args): Returns ------- None - ''' - if any([ - '--data_config_file' in arg for arg in args.extra_args - ]): + """ + if any("--data_config_file" in arg for arg in args.extra_args): try: args.data_config_file = args.extra_args[ - args.extra_args.index('--data_config_file') + 1 + args.extra_args.index("--data_config_file") + 1 ] except ValueError: try: - args.data_config_file = [ - arg.split( - '=', - 1 - )[1] for arg in args.extra_args if arg.startswith( - '--data_config_file=' - ) - ][0] + args.data_config_file = next( + arg.split("=", 1)[1] + for arg in args.extra_args + if arg.startswith("--data_config_file=") + ) except Exception: # pragma: no cover raise ValueError( f"""Something about {[ @@ -309,78 +319,59 @@ def main(args): ]} is confusing.""" ) else: - args.data_config_file = args.data_config_file if hasattr( - args, - 'data_config_file' - ) else None + args.data_config_file = ( + args.data_config_file if hasattr(args, "data_config_file") else None + ) - args.bids_dir = args.bids_dir if hasattr( - args, - 'bids_dir' - ) else 'bids_dir' + args.bids_dir = args.bids_dir if hasattr(args, "bids_dir") else "bids_dir" setup_logging(args.loglevel) arg_vars = vars(args) - if args.command == 'run': - if any([ - '--help' in arg_vars, - '-h' in arg_vars, - '--help' in args.extra_args, - '-h' in args.extra_args - ]): + if args.command == "run": + if any( + [ + "--help" in arg_vars, + "-h" in arg_vars, + "--help" in args.extra_args, + "-h" in args.extra_args, + ] + ): pwd = os.getcwd() - if arg_vars.get('level_of_analysis') is None: - arg_vars['level_of_analysis'] = 'participant' - for arg in ['output_dir', 'bids_dir']: + if arg_vars.get("level_of_analysis") is None: + arg_vars["level_of_analysis"] = "participant" + for arg in ["output_dir", "bids_dir"]: if arg_vars.get(arg) is None: arg_vars[arg] = pwd - Backends(**arg_vars).run( - flags=args.extra_args, - **arg_vars - ) + Backends(**arg_vars).run(flags=args.extra_args, **arg_vars) - elif args.command in ['enter', 'version']: + elif args.command in ["enter", "version"]: Backends(**arg_vars).run( - run_type=args.command, - flags=args.extra_args, - **arg_vars + run_type=args.command, flags=args.extra_args, **arg_vars ) - elif args.command in ['pull', 'upgrade']: - Backends(**arg_vars).pull( - force=True, - **arg_vars - ) + elif args.command in ["pull", "upgrade"]: + Backends(**arg_vars).pull(force=True, **arg_vars) - elif args.command in clargs: + elif args.command in _CLARGS: # utils doesn't have '-h' flag for help - if args.command == 'utils' and '-h' in arg_vars.get('extra_args', []): - arg_vars['extra_args'] = [ - arg if arg != '-h' else '--help' for arg in arg_vars[ - 'extra_args' - ] + if args.command == "utils" and "-h" in arg_vars.get("extra_args", []): + arg_vars["extra_args"] = [ + arg if arg != "-h" else "--help" for arg in arg_vars["extra_args"] ] - Backends(**arg_vars).clarg( - args.command, - flags=args.extra_args, - **arg_vars - ) + Backends(**arg_vars).clarg(args.command, flags=args.extra_args, **arg_vars) - elif args.command == 'crash': - Backends(**arg_vars).read_crash( - flags=args.extra_args, - **arg_vars - ) + elif args.command == "crash": + Backends(**arg_vars).read_crash(flags=args.extra_args, **arg_vars) - elif args.command == 'parse-resources': + elif args.command == "parse-resources": parse_resources.main(args) def run(): - '''Function to try Docker first and fall back on Singularity if - Docker fails if --platform is not specified. + """ + Try Docker first and fall back on Singularity if Docker fails if --platform is not specified. Parameters ---------- @@ -393,23 +384,25 @@ def run(): Notes ----- Consumes commandline arguments. Run `cpac --help` for usage string. - ''' + """ args = sys.argv[1:] # reorder args command = None command_index = 0 parser = _parser() - if args and ( - args[0] == '--version' or args[0] == '--help' or args[0 == '-h'] - ): + if args and (args[0] == "--version" or args[0] == "--help" or args[0] == "-h"): parse_args(args) # pylint: disable=protected-access - commands = list([cmd for cmd in parser._get_positional_actions( - ) if cmd.dest == 'command'][0].choices) - options = set(chain.from_iterable([ - o.option_strings for o in parser._get_optional_actions()])) + commands = list( + next( + cmd for cmd in parser._get_positional_actions() if cmd.dest == "command" + ).choices + ) + options = set( + chain.from_iterable([o.option_strings for o in parser._get_optional_actions()]) + ) # keep help option with specific command - for option in {'-h', '--help'}: + for option in ("-h", "--help"): options.discard(option) for cmd in commands: if command is None and cmd in args: @@ -426,27 +419,27 @@ def run(): if arg in options: reordered_args.append(args.pop(args.index(arg))) option_value_setting = True - elif any(arg.startswith(f'{option}=') for option in options): + elif any(arg.startswith(f"{option}=") for option in options): reordered_args.append(args.pop(args.index(arg))) option_value_setting = True elif option_value_setting: - if arg.startswith('-'): + if arg.startswith("-"): option_value_setting = False else: reordered_args.append(args.pop(args.index(arg))) - args = reordered_args + [command] + args + args = [*reordered_args, command, *args] # parse args parsed = parse_args(args) if not parsed.platform and "--platform" not in args: if parsed.image and os.path.exists(parsed.image): - parsed.platform = 'singularity' + parsed.platform = "singularity" else: - parsed.platform = 'docker' + parsed.platform = "docker" try: main(parsed) # fall back on Singularity if Docker not found except (DockerException, NotFound): # pragma: no cover - parsed.platform = 'singularity' + parsed.platform = "singularity" main(parsed) else: main(parsed) diff --git a/src/cpac/backends/__init__.py b/src/cpac/backends/__init__.py index 60821047..66563acd 100644 --- a/src/cpac/backends/__init__.py +++ b/src/cpac/backends/__init__.py @@ -1,28 +1,21 @@ -class BackendMapper(object): +from typing import Any, ClassVar, Dict + - parameters = {} +class BackendMapper(object): + parameters: ClassVar[Dict[str, Any]] = {} def __init__(self, *args, **kwargs): self.parameters = kwargs def __call__(self, platform, parent=None): return self._clients[platform.__class__]( - platform=platform, - **self.parameters, - parent=parent + platform=platform, **self.parameters, parent=parent ) def Backends(platform, **kwargs): - """ - Given a string, return a Backend - """ + """Given a string, return a Backend.""" from .docker import Docker from .singularity import Singularity - return( - { - 'docker': Docker, - 'singularity': Singularity - }[platform](**kwargs) - ) + return {"docker": Docker, "singularity": Singularity}[platform](**kwargs) diff --git a/src/cpac/backends/docker.py b/src/cpac/backends/docker.py index 98d2f247..1edf5467 100644 --- a/src/cpac/backends/docker.py +++ b/src/cpac/backends/docker.py @@ -1,8 +1,8 @@ import os import docker -import dockerpty from docker.errors import ImageNotFound +import dockerpty from cpac.backends.platform import Backend, PlatformMeta @@ -11,39 +11,32 @@ class Docker(Backend): def __init__(self, **kwargs): super().__init__(**kwargs) self.container = None - self.platform = PlatformMeta('Docker', '🐳') + self.platform = PlatformMeta("Docker", "🐳") self._print_loading_with_symbol(self.platform.name) self.client = docker.from_env() - self.platform.version = self.client.version().get('Version', 'unknown') + self.platform.version = self.client.version().get("Version", "unknown") try: self.client.ping() except (docker.errors.APIError, ConnectionError): # pragma: no cover raise OSError( - f"Could not connect to {self.platform.name}. " - "Is Docker running?" + f"Could not connect to {self.platform.name}. " "Is Docker running?" ) - image = kwargs['image'] if kwargs.get( - 'image' - ) is not None else 'fcpindi/c-pac' + image = kwargs["image"] if kwargs.get("image") is not None else "fcpindi/c-pac" - tag = kwargs['tag'] if kwargs.get( - 'tag' - ) is not None else 'latest' - self.image = ':'.join([image, tag]) + tag = kwargs["tag"] if kwargs.get("tag") is not None else "latest" + self.image = ":".join([image, tag]) self._collect_config(**kwargs) - self.docker_kwargs = {'init': True} - if isinstance(kwargs.get('container_options'), list): - for opt in kwargs['container_options']: - if '=' in opt or ' ' in opt: - delimiter = min([ - i for i in [ - opt.find('='), opt.find(' ') - ] if i > 0 - ]) - k = opt[:delimiter].lstrip('-').replace('-', '_') - v = opt[delimiter+1:].strip('"').strip("'") + self.docker_kwargs = {"init": True} + if isinstance(kwargs.get("container_options"), list): + for opt in kwargs["container_options"]: + if "=" in opt or " " in opt: + delimiter = min( + [i for i in [opt.find("="), opt.find(" ")] if i > 0] + ) + k = opt[:delimiter].lstrip("-").replace("-", "_") + v = opt[delimiter + 1 :].strip('"').strip("'") if k in self.docker_kwargs: if isinstance(self.docker_kwargs[k], list): self.docker_kwargs[k].append(v) @@ -53,25 +46,28 @@ def __init__(self, **kwargs): self.docker_kwargs[k] = v def _collect_config(self, **kwargs): - if kwargs.get('command') not in {'pull', 'upgrade', None}: + if kwargs.get("command") not in {"pull", "upgrade", None}: if isinstance(self.pipeline_config, str): - container_kwargs = {'image': self.image} + container_kwargs = {"image": self.image} if os.path.exists(self.pipeline_config): - container_kwargs['volumes'] = {self.pipeline_config: { - 'bind': self.pipeline_config, - 'mode': 'ro', - }} + container_kwargs["volumes"] = { + self.pipeline_config: { + "bind": self.pipeline_config, + "mode": "ro", + } + } try: - container = self.client.containers.create( - **container_kwargs) + container = self.client.containers.create(**container_kwargs) except ImageNotFound: # pragma: no cover self.pull(**kwargs) - container = self.client.containers.create( - **container_kwargs) + container = self.client.containers.create(**container_kwargs) stream = container.get_archive(path=self.pipeline_config)[0] - self.config = b''.join([ - l for l in stream # noqa E741 - ]).split(b'\x000000000')[-1].replace(b'\x00', b'').decode() + self.config = ( + b"".join(list(stream)) + .split(b"\x000000000")[-1] + .replace(b"\x00", b"") + .decode() + ) container.remove() else: self.config = self.pipeline_config @@ -79,31 +75,34 @@ def _collect_config(self, **kwargs): self._set_bindings(**kwargs) def pull(self, **kwargs): - image, tag = self.image.split(':') - [print(layer[k]) for layer in self.client.api.pull( - repository=image, - tag=tag, - stream=True, - decode=True - ) for k in layer if k in {'id', 'status', 'progress'}] + image, tag = self.image.split(":") + [ + print(layer[k]) + for layer in self.client.api.pull( + repository=image, tag=tag, stream=True, decode=True + ) + for k in layer + if k in {"id", "status", "progress"} + ] def _read_crash(self, read_crash_command, **kwargs): - return self._execute( - command=read_crash_command, run_type='exec', **kwargs - ) + return self._execute(command=read_crash_command, run_type="exec", **kwargs) def run(self, flags=[], **kwargs): - kwargs['command'] = [i for i in [ - kwargs.get('bids_dir'), - kwargs.get('output_dir'), - kwargs.get('level_of_analysis'), - *flags - ] if (i is not None and len(i))] + kwargs["command"] = [ + i + for i in [ + kwargs.get("bids_dir"), + kwargs.get("output_dir"), + kwargs.get("level_of_analysis"), + *flags, + ] + if (i is not None and len(i)) + ] return self._execute(**kwargs) def clarg(self, clcommand, flags=None, **kwargs): - """ - Runs a commandline command + """Run a commandline command. Parameters ---------- @@ -115,75 +114,76 @@ def clarg(self, clcommand, flags=None, **kwargs): """ if flags is None: flags = [] - kwargs['command'] = [i for i in [ - kwargs.get('bids_dir', kwargs.get('working_dir', '/tmp')), - kwargs.get('output_dir', '/outputs'), - 'cli', - '--', - clcommand, - *flags - ] if (i is not None and len(i))] + kwargs["command"] = [ + i + for i in [ + kwargs.get("bids_dir", kwargs.get("working_dir", "/tmp")), + kwargs.get("output_dir", "/outputs"), + "cli", + "--", + clcommand, + *flags, + ] + if (i is not None and len(i)) + ] self._execute(**kwargs) - def _execute(self, command, run_type='run', **kwargs): + def _execute(self, command, run_type="run", **kwargs): container_return = None try: self.client.images.get(self.image) except docker.errors.ImageNotFound: # pragma: no cover self.pull(**kwargs) - if run_type != 'version': + if run_type != "version": self._load_logging() shared_kwargs = { - 'image': self.image, - 'user': str(self.bindings['uid']), + "image": self.image, + "user": str(self.bindings["uid"]), **self._volumes_to_docker_mounts(), - 'working_dir': kwargs.get('working_dir', os.getcwd()), - **self.docker_kwargs + "working_dir": kwargs.get("working_dir", os.getcwd()), + **self.docker_kwargs, } - if run_type == 'run': + if run_type == "run": self.container = self.client.containers.run( **shared_kwargs, command=command, detach=True, stderr=True, stdout=True, - remove=True + remove=True, ) self._run = DockerRun(self.container) - elif run_type == 'version': + elif run_type == "version": return self.get_version() - elif run_type == 'exec': + elif run_type == "exec": self.container = self.client.containers.create( **shared_kwargs, auto_remove=True, - entrypoint='/bin/bash', - stdin_open=True + entrypoint="/bin/bash", + stdin_open=True, ) self.container.start() container_return = self.container.exec_run( - cmd=command, - stdout=True, - stderr=True, - stream=True + cmd=command, stdout=True, stderr=True, stream=True )[1] - elif run_type == 'enter': + elif run_type == "enter": self.container = self.client.containers.create( **shared_kwargs, auto_remove=True, - entrypoint='/bin/bash', + entrypoint="/bin/bash", stdin_open=True, tty=True, - detach=False + detach=False, ) dockerpty.start(self.client.api, self.container.id) return container_return def get_response(self, command, **kwargs): - """Method to return the response of running a command in the - Docker container. + """ + Return the response of running a command in the Docker container. Parameters ---------- @@ -194,41 +194,39 @@ def get_response(self, command, **kwargs): str """ full_response = [] - for response in self._execute(command, run_type='exec', **kwargs): + for response in self._execute(command, run_type="exec", **kwargs): full_response.append(response.decode()) - return ''.join(full_response) + return "".join(full_response) class DockerRun: def __init__(self, container): # pylint: disable=expression-not-assigned self.container = container - [print( - l.decode('utf-8'), end='' - ) for l in self.container.attach( # noqa E741 - logs=True, - stderr=True, - stdout=True, - stream=True - )] + [ + print(l.decode("utf-8"), end="") + for l in self.container.attach( # noqa E741 + logs=True, stderr=True, stdout=True, stream=True + ) + ] @property def status(self): try: self.container.reload() except Exception: - return 'stopped' + return "stopped" status = self.container.status status_map = { - 'created': 'starting', - 'restarting': 'running', - 'running': 'running', - 'removing': 'running', - 'paused': 'running', - 'exited': 'success', - 'dead': 'failed' + "created": "starting", + "restarting": "running", + "running": "running", + "removing": "running", + "paused": "running", + "exited": "success", + "dead": "failed", } if status in status_map: return status_map[status] - return 'unknown' + return "unknown" diff --git a/src/cpac/backends/platform.py b/src/cpac/backends/platform.py index 53791aab..16e155e7 100644 --- a/src/cpac/backends/platform.py +++ b/src/cpac/backends/platform.py @@ -1,45 +1,47 @@ -"""Base classes for platform-specific implementations""" +"""Base classes for platform-specific implementations.""" import atexit +from collections import namedtuple +from contextlib import redirect_stderr +from io import StringIO import os import pwd import tempfile import textwrap - -from collections import namedtuple -from contextlib import redirect_stderr -from io import StringIO from typing import overload from warnings import warn -import pandas as pd -import yaml - from docker import errors as docker_errors +import pandas as pd from tabulate import tabulate +import yaml +from cpac import __version__ as cpac_version from cpac.helpers import cpac_read_crash, get_extra_arg_value from cpac.helpers.cpac_parse_resources import get_or_create_config from cpac.utils import LocalsToBind, Volume, Volumes -from cpac import __version__ as cpac_version class CpacVersion: - """Class to hold the version of C-PAC running in the container""" + """Class to hold the version of C-PAC running in the container.""" + # pylint: disable=too-few-public-methods def __init__(self, backend): - self.versions = namedtuple('versions', 'cpac CPAC') + self.versions = namedtuple("versions", "cpac CPAC") self.versions.cpac = cpac_version - self.versions.CPAC = backend.get_response('cat /code/version').rstrip() + self.versions.CPAC = backend.get_response("cat /code/version").rstrip() self.platform = backend.platform def __str__(self): - return (f'cpac (convenience wrapper) version {self.versions.cpac}\n' - f'C-PAC version {self.versions.CPAC} running on ' - f'{self.platform.name} version {self.platform.version}') + return ( + f"cpac (convenience wrapper) version {self.versions.cpac}\n" + f"C-PAC version {self.versions.CPAC} running on " + f"{self.platform.name} version {self.platform.version}" + ) class _MockBinding: - """Class to hold a ``.bind`` property with the value ``None``""" + """Class to hold a ``.bind`` property with the value ``None``.""" + def __init__(self): self.bind = None @@ -47,53 +49,58 @@ def __bool__(self): return False def __repr__(self): - return 'cpac.backends.platform._MockBinding()' + return "cpac.backends.platform._MockBinding()" def __str__(self): - return 'None' + return "None" class PlatformMeta: - """Class to hold platform metadata""" + """Class to hold platform metadata.""" + # pylint: disable=too-few-public-methods def __init__(self, name, symbol): self.name = name self.symbol = symbol - self.version = 'unknown' + self.version = "unknown" def __str__(self): - return f'{self.symbol} {self.name}' + return f"{self.symbol} {self.name}" class Backend: def __init__(self, **kwargs): self.pipeline_config = None - if 'extra_args' in kwargs and isinstance(kwargs['extra_args'], list): - pipeline_config = get_extra_arg_value( - kwargs['extra_args'], 'pipeline_file') + if "extra_args" in kwargs and isinstance(kwargs["extra_args"], list): + pipeline_config = get_extra_arg_value(kwargs["extra_args"], "pipeline_file") if pipeline_config is not None: self.pipeline_config = yaml.safe_load(pipeline_config) else: - pipeline_config = get_extra_arg_value( - kwargs['extra_args'], 'preconfig') + pipeline_config = get_extra_arg_value(kwargs["extra_args"], "preconfig") if pipeline_config is not None: - self.pipeline_config = '/'.join([ - '/code/CPAC/resources/configs', - f'pipeline_config_{pipeline_config}.yml' - ]) - self.volumes = Volume('/etc/passwd', mode='ro') - tracking_opt_out = '--tracking_opt-out' - if not(tracking_opt_out in kwargs or - tracking_opt_out in kwargs.get('extra_args', [])): - udir = os.path.expanduser('~') - if udir != '/': + self.pipeline_config = "/".join( + [ + "/code/CPAC/resources/configs", + f"pipeline_config_{pipeline_config}.yml", + ] + ) + self.volumes = Volume("/etc/passwd", mode="ro") + tracking_opt_out = "--tracking_opt-out" + if not ( + tracking_opt_out in kwargs + or tracking_opt_out in kwargs.get("extra_args", []) + ): + udir = os.path.expanduser("~") + if udir != "/": tracking_path = get_or_create_config(udir) self.volumes += Volume(tracking_path) else: - raise EnvironmentError('Unable to create tracking ' - 'configuration. Please run with ' - '--tracking_opt-out and C-PAC >= ' - '1.8.4') + raise EnvironmentError( + "Unable to create tracking " + "configuration. Please run with " + "--tracking_opt-out and C-PAC >= " + "1.8.4" + ) # initilizing these for overriding on load self.bindings = {} self.container = None @@ -101,17 +108,18 @@ def __init__(self, **kwargs): self.platform = None self._run = None self.uid = 0 - self.username = 'root' - self.working_dir = kwargs.get('working_dir', os.getcwd()) + self.username = "root" + self.working_dir = kwargs.get("working_dir", os.getcwd()) atexit.register(self._cleanup) def __del__(self): self._cleanup() def read_crash(self, crashfile, flags=None, **kwargs): - """For C-PAC < 1.8.0, this method is used to decode a - crashfile into plain text. Since C-PAC 1.8.0, - crashfiles are stored as plain text. + """ + Decode a crashfile into plain text. + + For C-PAC < 1.8.0. Since C-PAC 1.8.0, crashfiles are stored as plain text. Parameters ---------- @@ -128,24 +136,26 @@ def read_crash(self, crashfile, flags=None, **kwargs): flags = [] os.chmod(cpac_read_crash.__file__, 0o775) self._set_crashfile_binding(crashfile) - if self.platform.name == 'Singularity': + if self.platform.name == "Singularity": self._load_logging() stderr = StringIO() with redirect_stderr(stderr): - del kwargs['command'] - crash_lines = list(self._read_crash( - f'{cpac_read_crash.__file__} {crashfile}', - **kwargs - )) - crash_message = ''.join([ - l.decode('utf-8') if isinstance( - l, bytes - ) else l for l in ( # noqa: E741 - [line[0] for line in crash_lines] if ( - len(crash_lines) and isinstance(crash_lines[0], tuple) - ) else crash_lines - ) - ]) + del kwargs["command"] + crash_lines = list( + self._read_crash(f"{cpac_read_crash.__file__} {crashfile}", **kwargs) + ) + crash_message = "".join( + [ + crash_line.decode("utf-8") + if isinstance(crash_line, bytes) + else crash_line + for crash_line in ( + [line[0] for line in crash_lines] + if (len(crash_lines) and isinstance(crash_lines[0], tuple)) + else crash_lines + ) + ] + ) crash_message += stderr.getvalue() stderr.read() # clear stderr print(crash_message.strip()) @@ -164,35 +174,33 @@ def _collect_config_binding(self, config, config_key): config_binding = _MockBinding() if isinstance(config, str): if os.path.exists(config): - self._set_bindings(custom_binding=Volume(config, mode='r')) + self._set_bindings(custom_binding=Volume(config, mode="r")) config = self.clarg( clcommand='python -c "from CPAC.utils.configuration; ' - 'import Configuration; ' + "import Configuration; " f'yaml.dump(Configuration({config}).dict())"' ) config = yaml.safe_load(config) if config: - pipeline_setup = config.get('pipeline_setup', {}) - minimal = pipeline_setup.get('FROM', False) + pipeline_setup = config.get("pipeline_setup", {}) + minimal = pipeline_setup.get("FROM", False) if isinstance(pipeline_setup, dict): - config_binding = Volume(pipeline_setup.get(config_key, {}).get( - 'path')) + config_binding = Volume(pipeline_setup.get(config_key, {}).get("path")) else: minimal = True if minimal: warn( - 'This run is using a minimal pipeline configuration. If ' - 'this configuration imports a configuration that ' - 'requires paths to be bound from your real environment ' - 'to your container, you need to bind those paths ' - 'manually with the `-B` flag.', - UserWarning + "This run is using a minimal pipeline configuration. If " + "this configuration imports a configuration that " + "requires paths to be bound from your real environment " + "to your container, you need to bind those paths " + "manually with the `-B` flag.", + UserWarning, ) return config_binding def clarg(self, clcommand, flags=None, **kwargs): - """ - Runs a commandline command + """Run a commandline command. Parameters ---------- @@ -202,17 +210,17 @@ def clarg(self, clcommand, flags=None, **kwargs): kwargs: dict """ - raise NotImplementedError() + raise NotImplementedError def _cleanup(self): - if hasattr(self, 'container') and hasattr(self.container, 'stop'): + if hasattr(self, "container") and hasattr(self.container, "stop"): try: self.container.stop() except (docker_errors.APIError, docker_errors.NotFound): pass def collect_config_bindings(self, config, **kwargs): - """Function to collect bindings for a given configuration. + """Collect bindings for a given configuration. Parameters ---------- @@ -230,25 +238,23 @@ def collect_config_bindings(self, config, **kwargs): Returns ------- + kwargs : dict """ - kwargs['output_dir'] = kwargs.get( - 'output_dir', - os.getcwd() - ) - kwargs['working_dir'] = self.working_dir + kwargs["output_dir"] = kwargs.get("output_dir", os.getcwd()) + kwargs["working_dir"] = self.working_dir config_bindings = Volumes() cwd = os.getcwd() - for c_b in { - ('log_directory', 'log'), - ('working_directory', 'working', 'working_dir'), - ('crash_log_directory', 'log'), - ('output_directory', 'outputs', 'output_dir') - }: + for c_b in ( + ("log_directory", "log"), + ("working_directory", "working", "working_dir"), + ("crash_log_directory", "log"), + ("output_directory", "outputs", "output_dir"), + ): inner_binding = self._collect_config_binding(config, c_b[0]).bind outer_binding = None if inner_binding is not None: - if len(c_b) == 3: + if len(c_b) == 3: # noqa: PLR2004 if kwargs.get(c_b[2]) is not None: outer_binding = kwargs[c_b[2]] else: @@ -256,10 +262,9 @@ def collect_config_bindings(self, config, **kwargs): try: os.makedirs(inner_binding, exist_ok=True) except (PermissionError, OSError): - outer_binding = os.path.join(kwargs.get( - 'output_dir', - os.path.join(cwd, 'outputs') - ), c_b[1]) + outer_binding = os.path.join( + kwargs.get("output_dir", os.path.join(cwd, "outputs")), c_b[1] + ) if outer_binding is not None and inner_binding is not None: config_bindings += Volume(inner_binding) elif outer_binding is not None: @@ -267,12 +272,14 @@ def collect_config_bindings(self, config, **kwargs): else: path = os.path.join(cwd, c_b[1]) config_bindings += Volume(path) - kwargs['config_bindings'] = config_bindings + kwargs["config_bindings"] = config_bindings return kwargs def get_response(self, command, **kwargs): - """Method to return the response of running a command in the - container. Implemented in the subclasses. + """ + Return the response of running a command in the container. + + Implemented in the subclasses. Parameters ---------- @@ -282,10 +289,10 @@ def get_response(self, command, **kwargs): ------- str """ - raise NotImplementedError() + raise NotImplementedError def get_version(self): - """Method to get the version of C-PAC running in container. + """Get the version of C-PAC running in container. Parameters ---------- @@ -300,34 +307,43 @@ def get_version(self): return version def _load_logging(self): - table = pd.DataFrame([(volume.local, volume.bind, volume.mode) for - volume in self.bindings['volumes']]) + table = pd.DataFrame( + [ + (volume.local, volume.bind, volume.mode) + for volume in self.bindings["volumes"] + ] + ) if not table.empty: - table.columns = ['local', self.platform.name, 'mode'] + table.columns = ["local", self.platform.name, "mode"] self._print_loading_with_symbol( - ' '.join([ - self.image, - f'as "{self.username} ({self.uid})"', - 'with these directory bindings:' - ]) + " ".join( + [ + self.image, + f'as "{self.username} ({self.uid})"', + "with these directory bindings:", + ] + ) + ) + print( + textwrap.indent( + tabulate( + table.applymap( + lambda x: ("\n".join(textwrap.wrap(x, 42))) + if isinstance(x, str) + else x + ), + headers="keys", + showindex=False, + ), + " ", + ) ) - print(textwrap.indent( - tabulate(table.applymap( - lambda x: ( - '\n'.join(textwrap.wrap(x, 42)) - ) if isinstance(x, str) else x - ), headers='keys', showindex=False), - ' ' - )) print( - f"Logging messages will refer to the {self.platform.name} " - "paths.\n" + f"Logging messages will refer to the {self.platform.name} " "paths.\n" ) - def _prep_binding(self, volume: Volume, - second_try: bool = False) -> Volume: - """ - Prepares a volume binding for the container. + def _prep_binding(self, volume: Volume, second_try: bool = False) -> Volume: + """Prepare a volume binding for the container. Parameters ---------- @@ -341,42 +357,44 @@ def _prep_binding(self, volume: Volume, ------- Volume """ - volume.local = os.path.abspath( - os.path.expanduser(volume.local) - ) + volume.local = os.path.abspath(os.path.expanduser(volume.local)) if not os.path.exists(volume.local): try: os.makedirs(volume.local, exist_ok=True) except (PermissionError, OSError) as perm: if second_try: raise perm - new_local = os.path.join(self.working_dir, - volume.local.lstrip('/')) - print(f'Could not create {volume.local}. Binding ' - f'{volume.bind} to {new_local} instead.') + new_local = os.path.join(self.working_dir, volume.local.lstrip("/")) + print( + f"Could not create {volume.local}. Binding " + f"{volume.bind} to {new_local} instead." + ) volume.local = new_local return self._prep_binding(volume, second_try=True) return volume - def _print_loading_with_symbol(self, message, prefix='Loading'): + def _print_loading_with_symbol(self, message, prefix="Loading"): if prefix is not None: - print(prefix, end=' ') + print(prefix, end=" ") try: - print(' '.join([self.platform.symbol, message])) + print(" ".join([self.platform.symbol, message])) except UnicodeEncodeError: print(message) @overload def __setattr__(self, name: str, value: Volume) -> None: ... - @overload # noqa: E301 + + @overload def __setattr__(self, name: str, value: list) -> None: ... - @overload # noqa: E301 + + @overload def __setattr__(self, name: str, value: Volumes) -> None: ... - def __setattr__(self, name, value): # noqa: E301 - if name == 'volumes': + + def __setattr__(self, name, value): + if name == "volumes": if isinstance(value, Volumes): self.__dict__[name] = value else: @@ -385,69 +403,63 @@ def __setattr__(self, name, value): # noqa: E301 self.__dict__[name] = value def _set_bindings(self, **kwargs): - tag = kwargs.get('tag', None) + tag = kwargs.get("tag", None) tag = tag if isinstance(tag, str) else None - for kwarg in [ - *kwargs.get('extra_args', []), kwargs.get('crashfile', '') - ]: + for kwarg in [*kwargs.get("extra_args", []), kwargs.get("crashfile", "")]: if os.path.exists(kwarg): - self._bind_volume(Volume(kwarg, mode='r')) - if 'data_config_file' in kwargs and isinstance( - kwargs['data_config_file'], str - ) and os.path.exists(kwargs['data_config_file']): - self._bind_volume(Volume(kwargs['data_config_file'], mode='r')) + self._bind_volume(Volume(kwarg, mode="r")) + if ( + "data_config_file" in kwargs + and isinstance(kwargs["data_config_file"], str) + and os.path.exists(kwargs["data_config_file"]) + ): + self._bind_volume(Volume(kwargs["data_config_file"], mode="r")) locals_from_data_config = LocalsToBind() - locals_from_data_config.from_config_file( - kwargs['data_config_file'] - ) + locals_from_data_config.from_config_file(kwargs["data_config_file"]) for local in locals_from_data_config.locals: - self._bind_volume(Volume(local, mode='r')) - for dir_type in ['working', 'output']: - self._bind_volume(Volume(kwargs[f'{dir_type}_dir'])) - if kwargs.get('custom_binding'): - for d in kwargs['custom_binding']: # pylint: disable=invalid-name - bind_parts = d.split(':') - if len(bind_parts) == 3: + self._bind_volume(Volume(local, mode="r")) + for dir_type in ["working", "output"]: + self._bind_volume(Volume(kwargs[f"{dir_type}_dir"])) + if kwargs.get("custom_binding"): + for d in kwargs["custom_binding"]: # pylint: disable=invalid-name + bind_parts = d.split(":") + if len(bind_parts) == 3: # noqa: PLR2004 self._bind_volume(Volume(*bind_parts)) - elif len(bind_parts) == 2: - self._bind_volume(Volume(*bind_parts, mode='rw')) - elif len(bind_parts) == 1: + elif len(bind_parts) == 2: # noqa: PLR2004 + self._bind_volume(Volume(*bind_parts, mode="rw")) + elif len(bind_parts) == 1: # noqa: PLR2004, RUF100 self._bind_volume(Volume(bind_parts[0])) else: - raise SyntaxError("I don't know what to do with custom " - "binding {}".format(d)) - for d in ['bids_dir', 'output_dir']: # pylint: disable=invalid-name - if d in kwargs and isinstance(kwargs[d], str) and os.path.exists( - kwargs[d] - ): - self._bind_volume(Volume( - kwargs[d], mode='rw' if d == 'output_dir' else 'r')) - if kwargs.get('config_bindings'): - for binding in kwargs['config_bindings']: + raise SyntaxError( + "I don't know what to do with custom " "binding {}".format(d) + ) + for d in ["bids_dir", "output_dir"]: # pylint: disable=invalid-name + if d in kwargs and isinstance(kwargs[d], str) and os.path.exists(kwargs[d]): + self._bind_volume( + Volume(kwargs[d], mode="rw" if d == "output_dir" else "r") + ) + if kwargs.get("config_bindings"): + for binding in kwargs["config_bindings"]: self._bind_volume(binding) self.uid = os.getuid() pwuid = pwd.getpwuid(self.uid) - self.username = getattr(pwuid, 'pw_name', - getattr(pwuid, 'pw_gecos', str(self.uid))) - self.bindings.update({ - 'tag': tag, - 'uid': self.uid, - 'volumes': self.volumes - }) + self.username = getattr( + pwuid, "pw_name", getattr(pwuid, "pw_gecos", str(self.uid)) + ) + self.bindings.update({"tag": tag, "uid": self.uid, "volumes": self.volumes}) def _volumes_to_docker_mounts(self): - return {'volumes': [str(volume) for volume in self.volumes]} + return {"volumes": [str(volume) for volume in self.volumes]} def _set_crashfile_binding(self, crashfile): for ckey in ["/wd/", "/crash/", "/log"]: if ckey in crashfile: - self._bind_volume(Volume( - crashfile.split(ckey)[0], '/outputs', 'rw')) + self._bind_volume(Volume(crashfile.split(ckey)[0], "/outputs", "rw")) with tempfile.TemporaryDirectory() as temp_dir: - self._bind_volume(Volume(temp_dir, '/out', 'rw')) + self._bind_volume(Volume(temp_dir, "/out", "rw")) helper = cpac_read_crash.__file__ - self._bind_volume(Volume(helper, mode='ro')) + self._bind_volume(Volume(helper, mode="ro")) class Result: @@ -462,20 +474,17 @@ def __call__(self): @property def description(self): - return { - 'type': 'object' - } + return {"type": "object"} class FileResult(Result): - def __init__(self, name, value, mime): self.name = name self.value = value self.mime = mime def __call__(self): - with open(self.value, 'rb') as f: + with open(self.value, "rb") as f: while True: piece = f.read(1024) if piece: @@ -485,7 +494,4 @@ def __call__(self): @property def description(self): - return { - 'type': 'file', - 'mime': self.mime - } + return {"type": "file", "mime": self.mime} diff --git a/src/cpac/backends/singularity.py b/src/cpac/backends/singularity.py index ac766ded..b306ffac 100644 --- a/src/cpac/backends/singularity.py +++ b/src/cpac/backends/singularity.py @@ -1,30 +1,32 @@ +from itertools import chain import os -from itertools import chain from spython.image import Image from spython.main import Client from cpac.backends.platform import Backend, PlatformMeta -BINDING_MODES = {'ro': 'ro', 'w': 'rw', 'rw': 'rw'} +BINDING_MODES = {"ro": "ro", "w": "rw", "rw": "rw"} class Singularity(Backend): def __init__(self, **kwargs): super().__init__(**kwargs) self.container = None - self.platform = PlatformMeta('Singularity', 'Ⓢ') - self.platform.version = Client.version().split(' ')[-1] + self.platform = PlatformMeta("Singularity", "Ⓢ") + self.platform.version = Client.version().split(" ")[-1] self._print_loading_with_symbol(self.platform.name) self.pull(**kwargs, force=False) - self.options = list(chain.from_iterable(kwargs[ - "container_options" - ])) if bool(kwargs.get("container_options")) else [] + self.options = ( + list(chain.from_iterable(kwargs["container_options"])) + if bool(kwargs.get("container_options")) + else [] + ) if isinstance(self.pipeline_config, str): self.config = Client.execute( image=self.image, - command=f'cat {self.pipeline_config}', - return_result=False + command=f"cat {self.pipeline_config}", + return_result=False, ) else: self.config = self.pipeline_config @@ -33,67 +35,64 @@ def __init__(self, **kwargs): self._set_bindings(**kwargs) def _bindings_as_option(self): - self.options += ( - ['-B', ','.join([':'.join([ - binding.local, binding.bind, str(binding.mode) - ]) for binding in self.volumes])]) + self.options += [ + "-B", + ",".join( + [ + ":".join([binding.local, binding.bind, str(binding.mode)]) + for binding in self.volumes + ] + ), + ] def _bindings_from_option(self): enter_options = {} - if '-B' in self.options: - enter_options['bind'] = self.options[ - self.options.index('-B') + 1] - self.options.remove(enter_options['bind']) - self.options.remove('-B') - enter_options['singularity_options'] = self.options + if "-B" in self.options: + enter_options["bind"] = self.options[self.options.index("-B") + 1] + self.options.remove(enter_options["bind"]) + self.options.remove("-B") + enter_options["singularity_options"] = self.options return enter_options def _pull(self, img, force, pull_folder): - '''Tries to pull image gracefully''' + """Try to pull image gracefully.""" try: self.image = Client.pull(img, force=force, pull_folder=pull_folder) except ValueError as value_error: - if 'closed file' in str(value_error): + if "closed file" in str(value_error): # pylint: disable=protected-access self.image = Image(Client._get_filename(img)) def pull(self, force=False, **kwargs): - image = kwargs['image'] if kwargs.get( - 'image' - ) is not None else 'fcpindi/c-pac' + image = kwargs["image"] if kwargs.get("image") is not None else "fcpindi/c-pac" tag = kwargs.get("tag") pwd = os.getcwd() if kwargs.get("working_dir") is not None: pwd = kwargs["working_dir"] os.chdir(pwd) image_path = Client._get_filename( # pylint: disable=protected-access - image if tag is None else ':'.join([image, tag])) + image if tag is None else ":".join([image, tag]) + ) if ( - not force and - image and isinstance(image, str) and os.path.exists(image_path) + not force + and image + and isinstance(image, str) + and os.path.exists(image_path) ): self.image = image_path elif tag and isinstance(tag, str): # pragma: no cover - self._pull( - f"docker://{image}:{tag}", - force=force, - pull_folder=pwd - ) + self._pull(f"docker://{image}:{tag}", force=force, pull_folder=pwd) else: # pragma: no cover try: self._pull( - "docker://fcpindi/c-pac:latest", - force=force, - pull_folder=pwd + "docker://fcpindi/c-pac:latest", force=force, pull_folder=pwd ) except Exception as exception: - raise OSError( - "Could not connect to Singularity" - ) from exception + raise OSError("Could not connect to Singularity") from exception def get_response(self, command, **kwargs): - """Method to return the response of running a command in the - Singularity container. + """ + Return the response of running a command in the Singularity container. Parameters ---------- @@ -105,81 +104,89 @@ def get_response(self, command, **kwargs): """ full_response = [] for response in self._try_to_stream( - args={'command': command}, - stream_command='execute', - silent=True, - **kwargs + args={"command": command}, stream_command="execute", silent=True, **kwargs ): full_response.append(response) - return ''.join(full_response) + return "".join(full_response) - def _try_to_stream(self, args, stream_command='run', silent=False, - **kwargs): + def _try_to_stream(self, args, stream_command="run", silent=False, **kwargs): self._bindings_as_option() - if stream_command == 'run': + if stream_command == "run": self.container = Client.run( Client.instance(self.image), args=args, options=self.options, stream=not silent, return_result=True, - **kwargs) + **kwargs, + ) else: enter_options = self._bindings_from_option() - if stream_command == 'execute': + if stream_command == "execute": self.container = Client.execute( self.image, - command=args['command'].split(' '), + command=args["command"].split(" "), options=self.options, stream=not silent, quiet=silent, - **{kwarg: value for kwarg, value in kwargs.items() if - kwarg in ['contain', 'environ', 'nv', 'sudo', - 'return_result', 'writable']}) - elif stream_command == 'enter': - Client.shell( - self.image, - **enter_options, - **kwargs) + **{ + kwarg: value + for kwarg, value in kwargs.items() + if kwarg + in [ + "contain", + "environ", + "nv", + "sudo", + "return_result", + "writable", + ] + }, + ) + elif stream_command == "enter": + Client.shell(self.image, **enter_options, **kwargs) if self.container is not None: for line in self.container: yield line - if hasattr(self.container, 'close') and callable( - self.container.close - ): + if hasattr(self.container, "close") and callable(self.container.close): self.container.close() def _read_crash(self, read_crash_command, **kwargs): return self._try_to_stream( - args={'command': read_crash_command}, - stream_command='execute', - **kwargs + args={"command": read_crash_command}, stream_command="execute", **kwargs ) - def run(self, flags=None, run_type='run', **kwargs): + def run(self, flags=None, run_type="run", **kwargs): # pylint: disable=expression-not-assigned if flags is None: flags = [] self._load_logging() - if run_type == 'run': - [print(o, end='') for o in self._try_to_stream( - args=' '.join([ - kwargs['bids_dir'], - kwargs['output_dir'], - kwargs['level_of_analysis'], - *flags - ]).strip(' ') - )] - elif run_type == 'version': + if run_type == "run": + [ + print(o, end="") + for o in self._try_to_stream( + args=" ".join( + [ + kwargs["bids_dir"], + kwargs["output_dir"], + kwargs["level_of_analysis"], + *flags, + ] + ).strip(" ") + ) + ] + elif run_type == "version": return self.get_version() else: - [print(o, end='') for o in self._try_to_stream( - args=' '.join(flags).strip(' '), - stream_command=run_type)] + for o in self._try_to_stream( + args=" ".join(flags).strip(" "), stream_command=run_type + ): + print(o, end="") + return None def clarg(self, clcommand, flags=None, **kwargs): """ - Runs a commandline command + Run a commandline command. Parameters ---------- @@ -193,11 +200,16 @@ def clarg(self, clcommand, flags=None, **kwargs): if flags is None: flags = [] self._load_logging() - [print(o, end='') for o in self._try_to_stream( - args=' '.join([ - kwargs.get('bids_dir', 'bids_dir'), - kwargs.get('output_dir', 'output_dir'), - f'cli -- {clcommand}', - *flags - ]).strip(' ') - )] + [ + print(o, end="") + for o in self._try_to_stream( + args=" ".join( + [ + kwargs.get("bids_dir", "bids_dir"), + kwargs.get("output_dir", "output_dir"), + f"cli -- {clcommand}", + *flags, + ] + ).strip(" ") + ) + ] diff --git a/src/cpac/helpers/__init__.py b/src/cpac/helpers/__init__.py index dce15153..86c55c03 100644 --- a/src/cpac/helpers/__init__.py +++ b/src/cpac/helpers/__init__.py @@ -1,20 +1,22 @@ -'''Hepler functions for cpac Python package.''' -import re +"""Hepler functions for cpac Python package.""" from itertools import chain +import re -TODOs = {'persisting_containers': 'Some Docker containers unexpectedly ' - 'persist after cpac finishes. To clear ' - 'them, run\n ' - r'1. `docker ps` to list the containers' - '\n For each C-PAC conatainer that ' - 'persists, run\n ' - r'2. `docker attach `' - '\n ' - r'3. `exit`'} +TODOs = { + "persisting_containers": "Some Docker containers unexpectedly " + "persist after cpac finishes. To clear " + "them, run\n " + r"1. `docker ps` to list the containers" + "\n For each C-PAC conatainer that " + "persists, run\n " + r"2. `docker attach `" + "\n " + r"3. `exit`" +} def get_extra_arg_value(extra_args, argument): - '''Function to parse passed-through arguments and get their values + """Parse passed-through arguments and get their values. Parameters ---------- @@ -38,14 +40,15 @@ def get_extra_arg_value(extra_args, argument): ... '--data_config_file=/configs/data_config_regtest.yml', ... '--participant_ndx 3'], 'participant_ndx') '3' - ''' - extra_args = list(chain.from_iterable([ - re.split(r'[=\s]', arg) for arg in extra_args])) + """ + extra_args = list( + chain.from_iterable([re.split(r"[=\s]", arg) for arg in extra_args]) + ) for index, item in enumerate(extra_args): - if item.startswith('-') and item.lstrip('-') == argument: + if item.startswith("-") and item.lstrip("-") == argument: return extra_args[index + 1] return None -__all__ = ['get_extra_arg_value', 'TODOs'] +__all__ = ["get_extra_arg_value", "TODOs"] diff --git a/src/cpac/helpers/cpac_parse_resources.py b/src/cpac/helpers/cpac_parse_resources.py index 4d5bf377..a4e562a5 100755 --- a/src/cpac/helpers/cpac_parse_resources.py +++ b/src/cpac/helpers/cpac_parse_resources.py @@ -1,31 +1,26 @@ #!/usr/bin/env python -r'''cpac_parse_resources.py +r"""cpac_parse_resources.py. When provided with a `callback.log` file, this utility can sort through the memory `runtime` usage, `estimate`, and associated `efficiency`, to identify the `n` tasks with the `highest` or `lowest` of each of these categories. `cpac_parse_resources` is intended to be run outside a C-PAC container. -''' +""" +from argparse import ArgumentParser import configparser +import json import os import uuid +import pandas as pd from rich.console import Console from rich.table import Table -from argparse import ArgumentParser -import pandas as pd -import numpy as np -import json - - -runti = 'runtime_memory_gb' -estim = 'estimated_memory_gb' +runti = "runtime_memory_gb" +estim = "estimated_memory_gb" -field = {'runtime': runti, - 'estimate': estim, - 'efficiency': 'efficiency'} +field = {"runtime": runti, "estimate": estim, "efficiency": "efficiency"} def display(df): @@ -38,14 +33,13 @@ def display(df): table.add_column("Memory Efficiency") for _, d in df.iterrows(): - tmp = list() - tmp += [d['id']] + tmp = [] + tmp += [d["id"]] tmp += [d[runti]] tmp += [d[estim]] - tmp += ["{0:.2f} %".format(100*d[runti] * 1.0 / d[estim])] + tmp += ["{0:.2f} %".format(100 * d[runti] * 1.0 / d[estim])] - tmp = ["{0:.4f}".format(t) if isinstance(t, float) else str(t) - for t in tmp] + tmp = ["{0:.4f}".format(t) if isinstance(t, float) else str(t) for t in tmp] table.add_row(*tmp) del tmp @@ -53,18 +47,17 @@ def display(df): def get_or_create_config(udir): - """Function to create a Google Analytics tracking config file. + """Create a Google Analytics tracking config file. Sourced from https://github.com/FCP-INDI/C-PAC/blob/80424468c7f4e59c102f446b05d4154ec1cd4b2d/CPAC/utils/ga.py#L19-L30 - """ # noqa: E501 # pylint: disable=line-too-long - tracking_path = os.path.join(udir, '.cpac') + """ # pylint: disable=line-too-long + tracking_path = os.path.join(udir, ".cpac") cparser = configparser.ConfigParser() if os.path.exists(tracking_path): cparser.read(tracking_path) - if not cparser.has_section('user'): - cparser.read_dict(dict(user=dict(uid=uuid.uuid1().hex, - track=True))) - with open(tracking_path, 'w+') as fhandle: + if not cparser.has_section("user"): + cparser.read_dict({"user": {"uid": uuid.uuid1().hex, "track": True}}) + with open(tracking_path, "w+") as fhandle: cparser.write(fhandle) return tracking_path @@ -79,9 +72,9 @@ def load_runtime_stats(callback): continue tmp = {} - for k in ['id', runti, estim]: + for k in ["id", runti, estim]: tmp[k] = log[k] - tmp['efficiency'] = tmp[runti] / tmp[estim] * 100 + tmp["efficiency"] = tmp[runti] / tmp[estim] * 100 pruned_logs += [tmp] del tmp @@ -90,7 +83,7 @@ def load_runtime_stats(callback): def main(args): - """Main function to parse and display resource usage + """Parse and display resource usage. Parameters ---------- @@ -101,20 +94,21 @@ def main(args): None """ usage = load_runtime_stats(args.callback) - filtered_usage = query(usage, args.filter_field, args.filter_group, - args.filter_count) + filtered_usage = query( + usage, args.filter_field, args.filter_group, args.filter_count + ) display(filtered_usage) def query(usage, f, g, c): - order = g == 'lowest' + order = g == "lowest" usage.sort_values(by=field[f], ascending=order, inplace=True) usage.reset_index(inplace=True, drop=True) return usage[0:c] def set_args(parser): - """Set up the command line arguments for the script + """Set up the command line arguments for the script. Parameters ---------- @@ -124,18 +118,28 @@ def set_args(parser): ------- parser : argparse.ArgumentParser """ - parser.add_argument("callback", - help="callback.log file found in the 'log' " - "directory of the specified derivatives path") - parser.add_argument("--filter_field", "-f", action="store", - choices=['runtime', 'estimate', 'efficiency'], - default='efficiency') - parser.add_argument("--filter_group", "-g", action="store", - choices=['lowest', 'highest'], default='lowest') - parser.add_argument("--filter_count", "-n", action="store", type=int, - default=10) + parser.add_argument( + "callback", + help="callback.log file found in the 'log' " + "directory of the specified derivatives path", + ) + parser.add_argument( + "--filter_field", + "-f", + action="store", + choices=["runtime", "estimate", "efficiency"], + default="efficiency", + ) + parser.add_argument( + "--filter_group", + "-g", + action="store", + choices=["lowest", "highest"], + default="lowest", + ) + parser.add_argument("--filter_count", "-n", action="store", type=int, default=10) return parser -if __name__ == '__main__': +if __name__ == "__main__": main(set_args(ArgumentParser(__file__, add_help=True)).parse_args()) diff --git a/src/cpac/helpers/cpac_read_crash.py b/src/cpac/helpers/cpac_read_crash.py index 000fb784..67c18ea7 100755 --- a/src/cpac/helpers/cpac_read_crash.py +++ b/src/cpac/helpers/cpac_read_crash.py @@ -1,9 +1,9 @@ #!/usr/bin/env python -'''cpac_read_crash.py +"""cpac_read_crash.py. `cpac_read_crash` is intended to be run inside a C-PAC container. The current run environment does not include the package `nipype`. -''' +""" import os import re @@ -16,22 +16,24 @@ class NoNipype(ModuleNotFoundError): def __init__(self, msg=None, *args, **kwargs): - no_nipype_message = __doc__.lstrip('cpac_read_crash.py').lstrip() - self.msg = '\n'.join([ - msg, no_nipype_message - ]) if msg is not None else no_nipype_message + no_nipype_message = __doc__.lstrip("cpac_read_crash.py").lstrip() + self.msg = ( + "\n".join([msg, no_nipype_message]) + if msg is not None + else no_nipype_message + ) self.args = (self.msg,) def read_crash(path, touch_dir=None): try: - from nipype.utils.filemanip import loadcrash from traits.trait_errors import TraitError + from nipype.utils.filemanip import loadcrash except ModuleNotFoundError: raise NoNipype from None try: - crash_report = '\n'.join(loadcrash(path).get('traceback', [])) + crash_report = "\n".join(loadcrash(path).get("traceback", [])) print(crash_report) except TraitError as e: touched_dir = _touch_trait_error_path(e.args[0]) @@ -46,15 +48,16 @@ def _touch_trait_error_path(crash_message): if match: print(f'\nTouching "{match[0]}" …\n') os.makedirs(os.path.dirname(match[0]), exist_ok=True) - with open(match[0], 'w') as fp: - fp.write('') - return(match) + with open(match[0], "w") as fp: + fp.write("") + return match + return None -if __name__ == '__main__': +if __name__ == "__main__": try: from nipype import __version__ as nipype_version except ModuleNotFoundError: raise NoNipype from None - print(f'Nipype version {nipype_version}') + print(f"Nipype version {nipype_version}") read_crash(argv[1]) diff --git a/src/cpac/utils/__init__.py b/src/cpac/utils/__init__.py index d1f4c39d..12cd2f2b 100644 --- a/src/cpac/utils/__init__.py +++ b/src/cpac/utils/__init__.py @@ -1,5 +1,10 @@ from .checks import check_version_at_least from .utils import LocalsToBind, PermissionMode, Volume, Volumes -__all__ = ['check_version_at_least', 'LocalsToBind', 'PermissionMode', - 'Volume', 'Volumes'] +__all__ = [ + "check_version_at_least", + "LocalsToBind", + "PermissionMode", + "Volume", + "Volumes", +] diff --git a/src/cpac/utils/checks.py b/src/cpac/utils/checks.py index 2d85a018..06db2227 100644 --- a/src/cpac/utils/checks.py +++ b/src/cpac/utils/checks.py @@ -5,7 +5,7 @@ def check_version_at_least(min_version, platform, image=None, tag=None): - """Function to check the in-container C-PAC version + """Check the in-container C-PAC version. Parameters ---------- @@ -24,9 +24,8 @@ def check_version_at_least(min_version, platform, image=None, tag=None): Is the version at least the minimum version? """ if platform is None: - platform = 'docker' - arg_vars = {'platform': platform, 'image': image, 'tag': tag, - 'command': 'version'} + platform = "docker" + arg_vars = {"platform": platform, "image": image, "tag": tag, "command": "version"} return VersionInfo.parse(min_version) <= VersionInfo.parse( - Backends(**arg_vars).run( - run_type='version').versions.CPAC.lstrip('v')) + Backends(**arg_vars).run(run_type="version").versions.CPAC.lstrip("v") + ) diff --git a/src/cpac/utils/utils.py b/src/cpac/utils/utils.py index 4193ef54..cc54f8e3 100644 --- a/src/cpac/utils/utils.py +++ b/src/cpac/utils/utils.py @@ -1,9 +1,8 @@ from __future__ import annotations -import os - from itertools import permutations -from typing import Iterator, overload +import os +from typing import ClassVar, Iterator, Optional, Set, Union from warnings import warn import yaml @@ -13,6 +12,7 @@ class LocalsToBind: """Class to collect local directories to bind to containers.""" + def __init__(self): self.locals = set() @@ -24,27 +24,33 @@ def __str__(self): def from_config_file(self, config_path): """ - Paramter + Add local bindings from a configuration file. + + Paramter. -------- config_path : str path to data config file """ - with open(config_path, 'r') as config_yml: + with open(config_path, "r") as config_yml: config_dict = yaml.safe_load(config_yml) self._add_locals(config_dict) def _add_locals(self, local: str) -> None: """ - Parameter + Add local paths to bindings. + + Parameter. --------- local : any object to search for local paths """ # pylint: disable=expression-not-assigned if isinstance(local, dict): - [self._add_locals(local[k]) for k in local] + for k in local: + self._add_locals(local[k]) elif isinstance(local, (list, tuple)): - [self._add_locals(i) for i in local] + for i in local: + self._add_locals(i) elif isinstance(local, str): if os.path.exists(local): self.locals.add(local) @@ -56,10 +62,11 @@ def _local_common_paths(self): def common_path(paths): x = os.path.commonprefix(list(paths)) - while not x.endswith('/'): + while not x.endswith("/"): x = x[:-2] x - return(x) + return x + for i in list(permutations(self.locals, 3)): c = common_path(i) if len(c) > 1: @@ -67,9 +74,9 @@ def common_path(paths): else: for f in i: stragglers.add(f) - self.locals = new_locals | {s for s in stragglers if not any([ - s.startswith(n) for n in new_locals - ])} + self.locals = new_locals | { + s for s in stragglers if not any(s.startswith(n) for n in new_locals) + } class PermissionMode: @@ -109,13 +116,17 @@ class PermissionMode: >>> PermissionMode('ro') == PermissionMode('r') True """ - defined_modes = {'rw', 'w', 'r', 'ro'} + + defined_modes: ClassVar[Set[str]] = {"rw", "w", "r", "ro"} def __init__(self, fs_str): - self.mode = fs_str.mode if isinstance( - fs_str, - PermissionMode - ) else 'ro' if fs_str == 'r' else fs_str + self.mode = ( + fs_str.mode + if isinstance(fs_str, PermissionMode) + else "ro" + if fs_str == "r" + else fs_str + ) self.defined = self.mode in PermissionMode.defined_modes self._warn_if_undefined() @@ -129,10 +140,10 @@ def __gt__(self, other): for permission in (self, other): if permission._warn_if_undefined(): return NotImplemented - if self.mode == 'rw': - if other.mode in {'w', 'ro'}: + if self.mode == "rw": + if other.mode in {"w", "ro"}: return True - elif self.mode == 'w' and other.mode == 'ro': + elif self.mode == "w" and other.mode == "ro": return True return False @@ -148,10 +159,10 @@ def __lt__(self, other): for permission in (self, other): if permission._warn_if_undefined(): return NotImplemented - if self.mode == 'ro': - if other.mode in {'w', 'rw'}: + if self.mode == "ro": + if other.mode in {"w", "rw"}: return True - elif self.mode == 'ro' and other.mode == 'w': + elif self.mode == "ro" and other.mode == "w": return True return False @@ -165,78 +176,67 @@ def __le__(self, other): def _warn_if_undefined(self): if not self.defined: - warn(f'\'{self.mode}\' is not a fully-configured permission ' - f'level in {DIST_NAME}. Configured permission levels are ' - f'''{", ".join([ + warn( + f"'{self.mode}' is not a fully-configured permission " + f"level in {DIST_NAME}. Configured permission levels are " + f"""{", ".join([ f"'{mode}'" for mode in PermissionMode.defined_modes - ])}''', - UserWarning) + ])}""", + UserWarning, + ) return True return False class Volume: - '''Class to store bind volume information''' - @overload - def __init__(self, local: str, bind: str = None, mode: None = None - ) -> None: - ... - @overload # noqa: E301 - def __init__(self, local: str, bind: str = None, - mode: PermissionMode = None) -> None: - ... - def __init__(self, local, bind=None, mode=None): # noqa: E301 + """Class to store bind volume information.""" + + def __init__( + self, + local: str, + bind: Optional[str] = None, + mode: Optional[Union[str, PermissionMode]] = None, + ) -> None: self.local = local self.bind = bind if bind is not None else local - if self.bind is not None and not self.bind.startswith('/'): + if self.bind is not None and not self.bind.startswith("/"): self.bind = os.path.abspath(self.bind) if isinstance(mode, PermissionMode): self.mode = mode elif mode is not None: self.mode = PermissionMode(mode) else: - self.mode = PermissionMode('rw') + self.mode = PermissionMode("rw") def __repr__(self): return str(self) def __str__(self): - return f'{self.local}:{self.bind}:{self.mode}' + return f"{self.local}:{self.bind}:{self.mode}" class Volumes: - '''Class to store all bind volumes. Prevents duplicate mount points.''' - @overload - def __init__(self, volumes: list = None) -> None: - ... - @overload # noqa: E301 - def __init__(self, volumes: Volume = None) -> None: - ... - def __init__(self, volumes=None): # noqa: E301 + """Class to store all bind volumes. Prevents duplicate mount points.""" + + def __init__(self, volumes: Optional[Union[list, Volume]] = None) -> None: try: if volumes is None: self.volumes = {} elif isinstance(volumes, list): - self.volumes = {volume.local: volume for volume in [ - Volume(volume) for volume in volumes]} + self.volumes = { + volume.local: volume + for volume in [Volume(volume) for volume in volumes] + } elif isinstance(volumes, Volume): self.volumes = {volumes.local: volumes} except AttributeError as attribute_error: - raise TypeError('Volumes must be initialized with a Volume ' - 'object, a list of Volume objects or None' - ) from attribute_error - - @overload - def __add__(self, other: list) -> Volumes: - ... - @overload # noqa: E301 - def __add__(self, other: Volume) -> Volumes: - ... - @overload # noqa: E301 - def __add__(self, other: Volumes) -> Volumes: - ... - def __add__(self, other): # noqa: E301 - '''Add volume + raise TypeError( + "Volumes must be initialized with a Volume " + "object, a list of Volume objects or None" + ) from attribute_error + + def __add__(self, other: Union[list, Volume, Volumes]) -> Volumes: + """Add volume. Parameters ---------- @@ -246,8 +246,8 @@ def __add__(self, other): # noqa: E301 Returns ------- Volumes - ''' - new_volumes = Volumes(self.volumes.copy()) + """ + new_volumes = Volumes([self.volumes.copy()]) if isinstance(other, (list, Volumes)): for volume in other: new_volumes += volume @@ -255,17 +255,8 @@ def __add__(self, other): # noqa: E301 new_volumes.volumes.update({other.bind: other}) return new_volumes - @overload - def __iadd__(self, other: list) -> Volumes: - ... - @overload # noqa: E301 - def __iadd__(self, other: Volume) -> Volumes: - ... - @overload # noqa: E301 - def __iadd__(self, other: Volumes) -> Volumes: - ... - def __iadd__(self, other): # noqa: E301 - '''Add volume in place + def __iadd__(self, other: Union[list, Volume, Volumes]) -> Volumes: + """Add volume in place. Parameters ---------- @@ -275,7 +266,7 @@ def __iadd__(self, other): # noqa: E301 Returns ------- Volumes - ''' + """ if isinstance(other, (list, Volumes)): for volume in other: self += volume @@ -284,7 +275,7 @@ def __iadd__(self, other): # noqa: E301 return self def __isub__(self, bind: str) -> Volumes: - '''Remove volume in place + """Remove volume in place. Parameters ---------- @@ -294,13 +285,13 @@ def __isub__(self, bind: str) -> Volumes: Returns ------- Volumes - ''' + """ if bind in self.volumes: del self.volumes[bind] return self def __iter__(self) -> Iterator[Volume]: - '''Iterator over volumes''' + """Iterate over volumes.""" return iter(self.volumes.values()) def __repr__(self) -> str: @@ -310,7 +301,7 @@ def __str__(self) -> str: return str(list(self.volumes.values())) def __sub__(self, bind: str) -> Volumes: - '''Remove volume + """Remove volume. Parameters ---------- @@ -320,8 +311,8 @@ def __sub__(self, bind: str) -> Volumes: Returns ------- Volumes - ''' - new_volumes = Volumes(self.volumes.copy()) + """ + new_volumes = Volumes([self.volumes.copy()]) if bind in new_volumes.volumes: del new_volumes.volumes[bind] return new_volumes diff --git a/tests/CONSTANTS.py b/tests/CONSTANTS.py index 9018fa7d..252610eb 100644 --- a/tests/CONSTANTS.py +++ b/tests/CONSTANTS.py @@ -1,11 +1,11 @@ -'''Constants for tests''' +"""Constants for tests.""" # pylint: disable=invalid-name -TAGS = [None, 'latest', 'nightly'] +TAGS = [None, "latest", "nightly"] def args_before_after(argv, args): - '''Function to create a mock sys.argv with arguments before - and one with arguments after the command and its arguments. + """ + Create a mock sys.argv with arguments before and one with arguments after the command and its arguments. Parameters ---------- @@ -21,23 +21,22 @@ def args_before_after(argv, args): f'cpac {args} {argv}'.split(' ') after : list f'cpac {argv} {args}'.split(' ') - ''' + """ argv = single_space(argv).strip() args = single_space(args).strip() - if argv.startswith('cpac'): - argv = argv.lstrip('cpac').strip() + if argv.startswith("cpac"): + argv = argv.lstrip("cpac").strip() if args is not None and len(args): - before = f'cpac {args} {argv}'.split(' ') - after = f'cpac {argv} {args}'.split(' ') + before = f"cpac {args} {argv}".split(" ") + after = f"cpac {argv} {args}".split(" ") else: - before = f'cpac {argv}'.split(' ') + before = f"cpac {argv}".split(" ") after = before return before, after -def set_commandline_args(platform, tag, sep=' '): - '''Function to turn pytest commandline options into mock - cpac commandline option strings +def set_commandline_args(platform, tag, sep=" "): + """Turn pytest commandline options into mock cpac commandline option strings. Parameters ---------- @@ -48,17 +47,17 @@ def set_commandline_args(platform, tag, sep=' '): Returns ------- args : string - ''' - args = '' + """ + args = "" if platform is not None: - args += f' --platform{sep}{platform.lower()} ' + args += f" --platform{sep}{platform.lower()} " if tag and tag is not None: - args = args + f' --tag{sep}{tag} ' + args = args + f" --tag{sep}{tag} " return args def single_space(string): - '''Function to remove spaces from a string + """Remove spaces from a string. Parameters ---------- @@ -67,7 +66,7 @@ def single_space(string): Returns ------- string : str - ''' - while ' ' in string: - string = string.replace(' ', ' ') + """ + while " " in string: + string = string.replace(" ", " ") return string diff --git a/tests/conftest.py b/tests/conftest.py index 9a293853..63424ee4 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,11 +1,12 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -'''conftest.py for cpac. +"""conftest.py for cpac. Read more about conftest.py under: https://pytest.org/latest/plugins.html -''' +""" import logging + import pytest # pylint: disable=import-error LOGGER = logging.getLogger() @@ -19,12 +20,13 @@ def ensure_logging_framework_not_altered(): def pytest_addoption(parser): - '''Add command line options for pytest.''' + """Add command line options for pytest.""" + def add_option(option): - '''Factory function to add option and fixture''' - parser.addoption(f'--{option}', action='store', nargs=1, - default=[None]) - for option in ['platform', 'tag']: + """Factory function to add option and fixture.""" # noqa: D401 + parser.addoption(f"--{option}", action="store", nargs=1, default=[None]) + + for option in ["platform", "tag"]: add_option(option) @@ -33,7 +35,7 @@ def pytest_generate_tests(metafunc): # if the argument is specified in the list of test 'fixturenames'. platform = metafunc.config.option.platform tag = metafunc.config.option.tag - if 'platform' in metafunc.fixturenames: - metafunc.parametrize('platform', platform) - if 'tag' in metafunc.fixturenames: - metafunc.parametrize('tag', tag) + if "platform" in metafunc.fixturenames: + metafunc.parametrize("platform", platform) + if "tag" in metafunc.fixturenames: + metafunc.parametrize("tag", tag) diff --git a/tests/test_cpac.py b/tests/test_cpac.py index 76264732..5d1ab92a 100644 --- a/tests/test_cpac.py +++ b/tests/test_cpac.py @@ -1,57 +1,54 @@ """Tests for top-level cpac cli.""" -import sys from contextlib import redirect_stdout -from io import StringIO, TextIOWrapper, BytesIO +from io import BytesIO, StringIO, TextIOWrapper +import sys from unittest import mock import pytest -from cpac.backends import Backends from cpac.__main__ import run +from cpac.backends import Backends from .CONSTANTS import set_commandline_args def test_loading_message(platform, tag): - """Test loading message""" + """Test loading message.""" if platform is not None: redirect_out = StringIO() with redirect_stdout(redirect_out): loaded = Backends(platform, tag=tag) - with_symbol = ' '.join([ - 'Loading', - loaded.platform.symbol, - loaded.platform.name - ]) + with_symbol = " ".join( + ["Loading", loaded.platform.symbol, loaded.platform.name] + ) assert with_symbol in redirect_out.getvalue() redirect_out = TextIOWrapper( - BytesIO(), encoding='latin-1', errors='strict', write_through=True) + BytesIO(), encoding="latin-1", errors="strict", write_through=True + ) with redirect_stdout(redirect_out): loaded = Backends(platform, tag=tag) - without_symbol = ' '.join([ - 'Loading', - loaded.platform.name - ]) + without_symbol = " ".join(["Loading", loaded.platform.name]) # pylint: disable=no-member assert without_symbol in redirect_out.buffer.getvalue().decode() -@pytest.mark.parametrize('argsep', [' ', '=']) +@pytest.mark.parametrize("argsep", [" ", "="]) def test_pull(argsep, capsys, platform, tag): - """Test pull command""" + """Test pull command.""" + def run_test(argv): argv = [arg for arg in argv if arg] - with mock.patch.object(sys, 'argv', argv): + with mock.patch.object(sys, "argv", argv): run() captured = capsys.readouterr() - checkstring = f':{tag}' if tag is not None else ':latest' + checkstring = f":{tag}" if tag is not None else ":latest" outstring = captured.out + captured.err - assert checkstring in outstring or 'cached' in outstring + assert checkstring in outstring or "cached" in outstring args = set_commandline_args(platform, tag, argsep) # test args before command - run_test(f'cpac {args} pull'.split(' ')) + run_test(f"cpac {args} pull".split(" ")) # test args after command - run_test(f'cpac pull {args}'.split(' ')) + run_test(f"cpac pull {args}".split(" ")) diff --git a/tests/test_cpac_crash.py b/tests/test_cpac_crash.py index cd3b41a8..4d840846 100644 --- a/tests/test_cpac_crash.py +++ b/tests/test_cpac_crash.py @@ -1,6 +1,5 @@ import os import sys - from unittest import mock import pytest @@ -9,17 +8,19 @@ from .CONSTANTS import set_commandline_args -@pytest.mark.parametrize('argsep', [' ', '=']) +@pytest.mark.parametrize("argsep", [" ", "="]) def test_cpac_crash(argsep, capsys, platform, tag): args = set_commandline_args(platform, tag, argsep) - crashfile = os.path.join( - os.path.dirname(__file__), 'test_data', 'test_pickle.pklz' - ) - argv = ['cpac', 'crash', crashfile] - argv = [arg for arg in ' '.join([ - w for w in ['cpac', args, 'crash', crashfile] if len(w) - ]).split(' ') if arg] - with mock.patch.object(sys, 'argv', argv): + crashfile = os.path.join(os.path.dirname(__file__), "test_data", "test_pickle.pklz") + argv = ["cpac", "crash", crashfile] + argv = [ + arg + for arg in " ".join( + [w for w in ["cpac", args, "crash", crashfile] if len(w)] + ).split(" ") + if arg + ] + with mock.patch.object(sys, "argv", argv): run() captured = capsys.readouterr() assert "MemoryError" in captured.out or "MemoryError" in captured.err diff --git a/tests/test_cpac_group.py b/tests/test_cpac_group.py index 86c80fcd..ccd624d5 100644 --- a/tests/test_cpac_group.py +++ b/tests/test_cpac_group.py @@ -1,5 +1,4 @@ import sys - from unittest import mock import pytest @@ -8,18 +7,18 @@ from .CONSTANTS import args_before_after, set_commandline_args -@pytest.mark.parametrize('argsep', [' ', '=']) +@pytest.mark.parametrize("argsep", [" ", "="]) def test_utils_help(argsep, capsys, platform, tag): def run_test(argv, platform): argv = [arg for arg in argv if arg] - with mock.patch.object(sys, 'argv', argv): + with mock.patch.object(sys, "argv", argv): run() captured = capsys.readouterr() if platform is not None: assert platform.title() in captured.out - assert 'COMMAND' in captured.out + assert "COMMAND" in captured.out - argv = 'group --help' + argv = "group --help" args = set_commandline_args(platform, tag, argsep) if len(args): before, after = args_before_after(argv, args) @@ -29,4 +28,4 @@ def run_test(argv, platform): run_test(after, platform) else: # test without --platform and --tag args - run_test(f'cpac {argv}'.split(' '), platform) + run_test(f"cpac {argv}".split(" "), platform) diff --git a/tests/test_cpac_run.py b/tests/test_cpac_run.py index ba8165fd..5fcc84b4 100644 --- a/tests/test_cpac_run.py +++ b/tests/test_cpac_run.py @@ -1,8 +1,8 @@ +"""Test actually running C-PAC with cpac.""" +from datetime import date, timedelta import os -import sys - -from datetime import date from pathlib import Path +import sys from unittest import mock import pytest @@ -11,23 +11,23 @@ from cpac.utils import check_version_at_least from .CONSTANTS import args_before_after, set_commandline_args -MINIMAL_CONFIG = os.path.join( - os.path.dirname(__file__), 'test_data', 'minimal.min.yml' -) +MINIMAL_CONFIG = os.path.join(os.path.dirname(__file__), "test_data", "minimal.min.yml") -@pytest.mark.parametrize('helpflag', ['--help', '-h']) -@pytest.mark.parametrize('argsep', [' ', '=']) +@pytest.mark.parametrize("helpflag", ["--help", "-h"]) +@pytest.mark.parametrize("argsep", [" ", "="]) def test_run_help(argsep, capsys, helpflag, platform, tag): + """Test 'help' run command.""" + def run_test(argv): argv = [arg for arg in argv if arg] - with mock.patch.object(sys, 'argv', argv): + with mock.patch.object(sys, "argv", argv): run() captured = capsys.readouterr() - assert 'participant' in captured.out + captured.err + assert "participant" in captured.out + captured.err args = set_commandline_args(platform, tag, argsep) - argv = f'run {helpflag}' + argv = f"run {helpflag}" if len(args): before, after = args_before_after(argv, args) # test with args before command @@ -36,34 +36,48 @@ def run_test(argv): run_test(after) else: # test without --platform and --tag args - run_test(f'cpac {argv}'.split(' ')) + run_test(f"cpac {argv}".split(" ")) -@pytest.mark.parametrize('argsep', [' ', '=']) -@pytest.mark.parametrize('pipeline_file', [None, MINIMAL_CONFIG]) +@pytest.mark.parametrize("argsep", [" ", "="]) +@pytest.mark.parametrize("pipeline_file", [None, MINIMAL_CONFIG]) def test_run_test_config(argsep, pipeline_file, tmp_path, platform, tag): - """Test 'test_config' run command""" + """Test 'test_config' run command.""" + def run_test(argv, wd): # pylint: disable=invalid-name os.chdir(wd) argv = [arg for arg in argv if arg] - with mock.patch.object(sys, 'argv', argv): + with mock.patch.object(sys, "argv", argv): run() possibilities = _where_to_find_runlogs(wd) + today = date.today() + datestamps = [ + _date.isoformat() + for _date in [ + today, + today - timedelta(days=1), + today + timedelta(days=1), + ] + ] assert any( - date.today().isoformat() in fp for fp in - possibilities), ( - f'wd: {wd}\n' - f'expected log not found in {possibilities}\n') + datestamp in fp for fp in possibilities for datestamp in datestamps + ), ( + f"wd: {wd}\n" + f"expected log ({datestamps[0]} ± 1 day) not found in {possibilities}\n" + ) + wd = tmp_path # pylint: disable=invalid-name args = set_commandline_args(platform, tag, argsep) - pipeline = '' if pipeline_file is None else ' '.join([ - ' --pipeline_file', pipeline_file]) + pipeline = ( + "" if pipeline_file is None else " ".join([" --pipeline_file", pipeline_file]) + ) argv = ( - 'run ' - f's3://fcp-indi/data/Projects/ABIDE/RawDataBIDS/NYU {wd} ' - f'test_config --participant_ndx=2{pipeline}') - if check_version_at_least('1.8.4', platform): - argv += ' --tracking_opt-out' + "run " + f"s3://fcp-indi/data/Projects/ABIDE/RawDataBIDS/NYU {wd} " + f"test_config --participant_ndx=2{pipeline}" + ) + if check_version_at_least("1.8.4", platform): + argv += " --tracking_opt-out" if args: before, after = args_before_after(argv, args) # test with args before command @@ -72,12 +86,12 @@ def run_test(argv, wd): # pylint: disable=invalid-name run_test(after, wd) else: # test without --platform and --tag args - run_test(f'cpac {argv}'.split(' '), wd) + run_test(f"cpac {argv}".split(" "), wd) def _where_to_find_runlogs(_wd) -> list: - """The location of run logs changed in 1.8.5. - This function will list all the files in both the old and new locations. + """ + List all the files in both the old and new locations of run logs changed in 1.8.5. Parameters ---------- diff --git a/tests/test_cpac_utils.py b/tests/test_cpac_utils.py index efd7dedb..9a8ed1d9 100644 --- a/tests/test_cpac_utils.py +++ b/tests/test_cpac_utils.py @@ -1,6 +1,5 @@ import os import sys - from unittest import mock import pytest @@ -9,20 +8,20 @@ from .CONSTANTS import args_before_after, set_commandline_args -@pytest.mark.parametrize('argsep', [' ', '=']) -@pytest.mark.parametrize('helpflag', ['--help', '-h']) +@pytest.mark.parametrize("argsep", [" ", "="]) +@pytest.mark.parametrize("helpflag", ["--help", "-h"]) def test_utils_help(argsep, capsys, helpflag, platform, tag): def run_test(argv, platform): argv = [arg for arg in argv if arg] - with mock.patch.object(sys, 'argv', argv): + with mock.patch.object(sys, "argv", argv): run() captured = capsys.readouterr() if platform is not None: assert platform.title() in captured.out - assert 'COMMAND' in captured.out + assert "COMMAND" in captured.out args = set_commandline_args(platform, tag, argsep) - argv = f'utils {helpflag}' + argv = f"utils {helpflag}" if len(args): before, after = args_before_after(argv, args) # test with args before command @@ -31,23 +30,23 @@ def run_test(argv, platform): run_test(after, platform) else: # test without --platform and --tag args - run_test(f'cpac {argv}'.split(' '), platform) + run_test(f"cpac {argv}".split(" "), platform) -@pytest.mark.parametrize('argsep', [' ', '=']) +@pytest.mark.parametrize("argsep", [" ", "="]) def test_utils_new_settings_template(argsep, tmp_path, platform, tag): - """Test 'utils data_config new_settings_template' command""" + """Test 'utils data_config new_settings_template' command.""" wd = tmp_path # pylint: disable=invalid-name def run_test(argv): argv = [arg for arg in argv if arg] - with mock.patch.object(sys, 'argv', argv): + with mock.patch.object(sys, "argv", argv): run() - template_path = os.path.join(wd, 'data_settings.yml') + template_path = os.path.join(wd, "data_settings.yml") assert os.path.exists(template_path) args = set_commandline_args(platform, tag, argsep) - argv = f'--working_dir {wd} utils data_config new_settings_template' + argv = f"--working_dir {wd} utils data_config new_settings_template" if args: before, after = args_before_after(argv, args) # test with args before command @@ -56,4 +55,4 @@ def run_test(argv): run_test(after) else: # test without --platform and --tag args - run_test(f'cpac {argv}'.split(' ')) + run_test(f"cpac {argv}".split(" ")) diff --git a/tox.ini b/tox.ini deleted file mode 100644 index 0ffb7b0c..00000000 --- a/tox.ini +++ /dev/null @@ -1,26 +0,0 @@ -# Tox configuration file -# Read more under https://tox.readthedocs.org/ -# THIS SCRIPT IS SUPPOSED TO BE AN EXAMPLE. MODIFY IT ACCORDING TO YOUR NEEDS! - -[tox] -minversion = 2.4 -envlist = py36,flake8 -skip_missing_interpreters = True - -[testenv] -# uncomment to omit testing package builds & installs for faster runs -# usedevelop = True -commands = - py.test {posargs} -extras = testing -deps = - # DEPRECATION WARNING: - # The automatic creation of a `requirements.txt` file is deprecated. - # See `Dependency Management` in the docs for other options. - -r{toxinidir}/requirements.txt - -[testenv:flake8] -skip_install = true -changedir = {toxinidir} -deps = flake8 -commands = flake8 setup.py cpac tests