From e4110fca28847009dc375244da6d16b0ea525a65 Mon Sep 17 00:00:00 2001 From: Delilah Wu Date: Mon, 22 Apr 2024 16:53:55 +1000 Subject: [PATCH 01/15] feat: Codespaces pip ADO auth via custom keyring backend --- .../.gitignore | 222 ++++ .../README.md | 43 + .../noxfile.py | 31 + .../pdm.lock | 993 ++++++++++++++++++ .../pyproject.toml | 62 ++ .../__init__.py | 2 + .../artifacts_helper_wrapper.py | 114 ++ .../keyring_backend.py | 65 ++ .../tests/__init__.py | 0 .../tests/test_artifacts_helper_wrapper.py | 108 ++ .../tests/test_backend.py | 145 +++ 11 files changed, 1785 insertions(+) create mode 100644 src/artifacts-helper/codespaces_artifacts_helper_keyring/.gitignore create mode 100644 src/artifacts-helper/codespaces_artifacts_helper_keyring/README.md create mode 100644 src/artifacts-helper/codespaces_artifacts_helper_keyring/noxfile.py create mode 100644 src/artifacts-helper/codespaces_artifacts_helper_keyring/pdm.lock create mode 100644 src/artifacts-helper/codespaces_artifacts_helper_keyring/pyproject.toml create mode 100644 src/artifacts-helper/codespaces_artifacts_helper_keyring/src/codespaces_artifacts_helper_keyring/__init__.py create mode 100644 src/artifacts-helper/codespaces_artifacts_helper_keyring/src/codespaces_artifacts_helper_keyring/artifacts_helper_wrapper.py create mode 100644 src/artifacts-helper/codespaces_artifacts_helper_keyring/src/codespaces_artifacts_helper_keyring/keyring_backend.py create mode 100644 src/artifacts-helper/codespaces_artifacts_helper_keyring/tests/__init__.py create mode 100644 src/artifacts-helper/codespaces_artifacts_helper_keyring/tests/test_artifacts_helper_wrapper.py create mode 100644 src/artifacts-helper/codespaces_artifacts_helper_keyring/tests/test_backend.py diff --git a/src/artifacts-helper/codespaces_artifacts_helper_keyring/.gitignore b/src/artifacts-helper/codespaces_artifacts_helper_keyring/.gitignore new file mode 100644 index 0000000..62a0ffe --- /dev/null +++ b/src/artifacts-helper/codespaces_artifacts_helper_keyring/.gitignore @@ -0,0 +1,222 @@ +### Python ### +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +#pdm.lock +# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it +# in version control. +# https://pdm.fming.dev/#use-with-ide +.pdm.toml + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +#.idea/ + +### Python Patch ### +# Poetry local configuration file - https://python-poetry.org/docs/configuration/#local-configuration +poetry.toml + +# ruff +.ruff_cache/ + +# LSP config files +pyrightconfig.json + +# PDM +.pdm-python + +### macOS ### +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +### macOS Patch ### +# iCloud generated files +*.icloud + +### Windows ### +# Windows thumbnail cache files +Thumbs.db +Thumbs.db:encryptable +ehthumbs.db +ehthumbs_vista.db + +# Dump file +*.stackdump + +# Folder config file +[Dd]esktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ diff --git a/src/artifacts-helper/codespaces_artifacts_helper_keyring/README.md b/src/artifacts-helper/codespaces_artifacts_helper_keyring/README.md new file mode 100644 index 0000000..09e201c --- /dev/null +++ b/src/artifacts-helper/codespaces_artifacts_helper_keyring/README.md @@ -0,0 +1,43 @@ +# codespaces artifacts helper keyring + +wow what a long package name. i'm open to suggestions for renaming it. + +## what is this? + +The codespaces artifacts helper keyring is a package that provides a keyring implementation for the codespaces artifacts helper at https://github.com/microsoft/ado-codespaces-auth. When the keyring package and this keyring are both installed, pip will automatically use this keyring to store and retrieve credentials when accessing ADO package feeds. + +## build instructins + +This package uses `pyproject.toml`, and `pdm` for building. To build the package, run the following commands: + +```sh +cd src/artifacts-helper/codespaces_artifacts_helper_keyring + +# PDM is used to manage the project +$ pip install 'pdm>=2.14' + +# Install dependencies and build the package +$ pdm build + +# Install package + deps with pip +$ pip install dist/codespaces_artifacts_helper_keyring-*.whl +``` + +## contributing + +```sh +# Lint +$ pdm run check + +# Format +$ pdm run fmt + +# Type check +$ pdm nox -s mypy + +# Test on current python version +$ pdm run test + +# Test on all supported python versions +$ pdm run nox +``` diff --git a/src/artifacts-helper/codespaces_artifacts_helper_keyring/noxfile.py b/src/artifacts-helper/codespaces_artifacts_helper_keyring/noxfile.py new file mode 100644 index 0000000..56d90b6 --- /dev/null +++ b/src/artifacts-helper/codespaces_artifacts_helper_keyring/noxfile.py @@ -0,0 +1,31 @@ +import os + +import nox + +os.environ.update({"PDM_IGNORE_SAVED_PYTHON": "1"}) + +PYTHON_VERSIONS = ["3.8", "3.9", "3.10", "3.11", "3.12"] +LOCATIONS = "src", "tests", "noxfile.py" + + +@nox.session(py=PYTHON_VERSIONS) +@nox.parametrize("keyring", ["20", "25.1"]) +@nox.parametrize("pyjwt", ["2.0.0", "2.8"]) +def tests(session, keyring, pyjwt): + session.run_always("pdm", "install", "-G", "test", external=True) + session.install(f"keyring=={keyring}") + session.install(f"pyjwt=={pyjwt}") + session.run("pdm", "test", *session.posargs, external=True) + + +@nox.session +def lint(session): + session.run_always("pdm", "install", "-G", "lint", external=True) + session.run("pdm", "check", external=True) + + +@nox.session(py=PYTHON_VERSIONS) +def mypy(session): + session.run_always("pdm", "install", external=True) + args = session.posargs or LOCATIONS + session.run("pdm", "run", "mypy", *args, external=True) diff --git a/src/artifacts-helper/codespaces_artifacts_helper_keyring/pdm.lock b/src/artifacts-helper/codespaces_artifacts_helper_keyring/pdm.lock new file mode 100644 index 0000000..ff7450f --- /dev/null +++ b/src/artifacts-helper/codespaces_artifacts_helper_keyring/pdm.lock @@ -0,0 +1,993 @@ +# This file is @generated by PDM. +# It is not intended for manual editing. + +[metadata] +groups = ["default", "test", "lint", "dev", "stubs"] +strategy = ["cross_platform", "inherit_metadata"] +lock_version = "4.4.1" +content_hash = "sha256:3484757b3b80a58839cc0a968d925c995ae6f9e694bbe75a2c210f11ce6ece04" + +[[package]] +name = "argcomplete" +version = "3.3.0" +requires_python = ">=3.8" +summary = "Bash tab completion for argparse" +groups = ["dev"] +files = [ + {file = "argcomplete-3.3.0-py3-none-any.whl", hash = "sha256:c168c3723482c031df3c207d4ba8fa702717ccb9fc0bfe4117166c1f537b4a54"}, + {file = "argcomplete-3.3.0.tar.gz", hash = "sha256:fd03ff4a5b9e6580569d34b273f741e85cd9e072f3feeeee3eba4891c70eda62"}, +] + +[[package]] +name = "backports-tarfile" +version = "1.1.0" +requires_python = ">=3.8" +summary = "Backport of CPython tarfile module" +groups = ["default"] +marker = "python_version < \"3.12\"" +files = [ + {file = "backports.tarfile-1.1.0-py3-none-any.whl", hash = "sha256:b2f4df351db942d094db94588bbf2c6938697a5f190f44c934acc697da56008b"}, + {file = "backports_tarfile-1.1.0.tar.gz", hash = "sha256:91d59138ea401ee2a95e8b839c1e2f51f3e9ca76bdba8b6a29f8d773564686a8"}, +] + +[[package]] +name = "certifi" +version = "2024.2.2" +requires_python = ">=3.6" +summary = "Python package for providing Mozilla's CA Bundle." +groups = ["default"] +files = [ + {file = "certifi-2024.2.2-py3-none-any.whl", hash = "sha256:dc383c07b76109f368f6106eee2b593b04a011ea4d55f652c6ca24a754d1cdd1"}, + {file = "certifi-2024.2.2.tar.gz", hash = "sha256:0569859f95fc761b18b45ef421b1290a0f65f147e92a1e5eb3e635f9a5e4e66f"}, +] + +[[package]] +name = "cffi" +version = "1.16.0" +requires_python = ">=3.8" +summary = "Foreign Function Interface for Python calling C code." +groups = ["default"] +marker = "platform_python_implementation != \"PyPy\" and sys_platform == \"linux\"" +dependencies = [ + "pycparser", +] +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]] +name = "cfgv" +version = "3.4.0" +requires_python = ">=3.8" +summary = "Validate configuration and produce human readable error messages." +groups = ["lint"] +files = [ + {file = "cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9"}, + {file = "cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560"}, +] + +[[package]] +name = "charset-normalizer" +version = "3.3.2" +requires_python = ">=3.7.0" +summary = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." +groups = ["default"] +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-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 = "colorama" +version = "0.4.6" +requires_python = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +summary = "Cross-platform colored terminal text." +groups = ["dev", "test"] +marker = "sys_platform == \"win32\"" +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 = "colorlog" +version = "6.8.2" +requires_python = ">=3.6" +summary = "Add colours to the output of Python's logging module." +groups = ["dev"] +dependencies = [ + "colorama; sys_platform == \"win32\"", +] +files = [ + {file = "colorlog-6.8.2-py3-none-any.whl", hash = "sha256:4dcbb62368e2800cb3c5abd348da7e53f6c362dda502ec27c560b2e58a66bd33"}, + {file = "colorlog-6.8.2.tar.gz", hash = "sha256:3e3e079a41feb5a1b64f978b5ea4f46040a94f11f0e8bbb8261e3dbbeca64d44"}, +] + +[[package]] +name = "coverage" +version = "7.4.4" +requires_python = ">=3.8" +summary = "Code coverage measurement for Python" +groups = ["test"] +files = [ + {file = "coverage-7.4.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e0be5efd5127542ef31f165de269f77560d6cdef525fffa446de6f7e9186cfb2"}, + {file = "coverage-7.4.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ccd341521be3d1b3daeb41960ae94a5e87abe2f46f17224ba5d6f2b8398016cf"}, + {file = "coverage-7.4.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:09fa497a8ab37784fbb20ab699c246053ac294d13fc7eb40ec007a5043ec91f8"}, + {file = "coverage-7.4.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b1a93009cb80730c9bca5d6d4665494b725b6e8e157c1cb7f2db5b4b122ea562"}, + {file = "coverage-7.4.4-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:690db6517f09336559dc0b5f55342df62370a48f5469fabf502db2c6d1cffcd2"}, + {file = "coverage-7.4.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:09c3255458533cb76ef55da8cc49ffab9e33f083739c8bd4f58e79fecfe288f7"}, + {file = "coverage-7.4.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:8ce1415194b4a6bd0cdcc3a1dfbf58b63f910dcb7330fe15bdff542c56949f87"}, + {file = "coverage-7.4.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b91cbc4b195444e7e258ba27ac33769c41b94967919f10037e6355e998af255c"}, + {file = "coverage-7.4.4-cp310-cp310-win32.whl", hash = "sha256:598825b51b81c808cb6f078dcb972f96af96b078faa47af7dfcdf282835baa8d"}, + {file = "coverage-7.4.4-cp310-cp310-win_amd64.whl", hash = "sha256:09ef9199ed6653989ebbcaacc9b62b514bb63ea2f90256e71fea3ed74bd8ff6f"}, + {file = "coverage-7.4.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0f9f50e7ef2a71e2fae92774c99170eb8304e3fdf9c8c3c7ae9bab3e7229c5cf"}, + {file = "coverage-7.4.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:623512f8ba53c422fcfb2ce68362c97945095b864cda94a92edbaf5994201083"}, + {file = "coverage-7.4.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0513b9508b93da4e1716744ef6ebc507aff016ba115ffe8ecff744d1322a7b63"}, + {file = "coverage-7.4.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40209e141059b9370a2657c9b15607815359ab3ef9918f0196b6fccce8d3230f"}, + {file = "coverage-7.4.4-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8a2b2b78c78293782fd3767d53e6474582f62443d0504b1554370bde86cc8227"}, + {file = "coverage-7.4.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:73bfb9c09951125d06ee473bed216e2c3742f530fc5acc1383883125de76d9cd"}, + {file = "coverage-7.4.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:1f384c3cc76aeedce208643697fb3e8437604b512255de6d18dae3f27655a384"}, + {file = "coverage-7.4.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:54eb8d1bf7cacfbf2a3186019bcf01d11c666bd495ed18717162f7eb1e9dd00b"}, + {file = "coverage-7.4.4-cp311-cp311-win32.whl", hash = "sha256:cac99918c7bba15302a2d81f0312c08054a3359eaa1929c7e4b26ebe41e9b286"}, + {file = "coverage-7.4.4-cp311-cp311-win_amd64.whl", hash = "sha256:b14706df8b2de49869ae03a5ccbc211f4041750cd4a66f698df89d44f4bd30ec"}, + {file = "coverage-7.4.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:201bef2eea65e0e9c56343115ba3814e896afe6d36ffd37bab783261db430f76"}, + {file = "coverage-7.4.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:41c9c5f3de16b903b610d09650e5e27adbfa7f500302718c9ffd1c12cf9d6818"}, + {file = "coverage-7.4.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d898fe162d26929b5960e4e138651f7427048e72c853607f2b200909794ed978"}, + {file = "coverage-7.4.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3ea79bb50e805cd6ac058dfa3b5c8f6c040cb87fe83de10845857f5535d1db70"}, + {file = "coverage-7.4.4-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce4b94265ca988c3f8e479e741693d143026632672e3ff924f25fab50518dd51"}, + {file = "coverage-7.4.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:00838a35b882694afda09f85e469c96367daa3f3f2b097d846a7216993d37f4c"}, + {file = "coverage-7.4.4-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:fdfafb32984684eb03c2d83e1e51f64f0906b11e64482df3c5db936ce3839d48"}, + {file = "coverage-7.4.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:69eb372f7e2ece89f14751fbcbe470295d73ed41ecd37ca36ed2eb47512a6ab9"}, + {file = "coverage-7.4.4-cp312-cp312-win32.whl", hash = "sha256:137eb07173141545e07403cca94ab625cc1cc6bc4c1e97b6e3846270e7e1fea0"}, + {file = "coverage-7.4.4-cp312-cp312-win_amd64.whl", hash = "sha256:d71eec7d83298f1af3326ce0ff1d0ea83c7cb98f72b577097f9083b20bdaf05e"}, + {file = "coverage-7.4.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d5ae728ff3b5401cc320d792866987e7e7e880e6ebd24433b70a33b643bb0384"}, + {file = "coverage-7.4.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:cc4f1358cb0c78edef3ed237ef2c86056206bb8d9140e73b6b89fbcfcbdd40e1"}, + {file = "coverage-7.4.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8130a2aa2acb8788e0b56938786c33c7c98562697bf9f4c7d6e8e5e3a0501e4a"}, + {file = "coverage-7.4.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cf271892d13e43bc2b51e6908ec9a6a5094a4df1d8af0bfc360088ee6c684409"}, + {file = "coverage-7.4.4-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a4cdc86d54b5da0df6d3d3a2f0b710949286094c3a6700c21e9015932b81447e"}, + {file = "coverage-7.4.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:ae71e7ddb7a413dd60052e90528f2f65270aad4b509563af6d03d53e979feafd"}, + {file = "coverage-7.4.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:38dd60d7bf242c4ed5b38e094baf6401faa114fc09e9e6632374388a404f98e7"}, + {file = "coverage-7.4.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:aa5b1c1bfc28384f1f53b69a023d789f72b2e0ab1b3787aae16992a7ca21056c"}, + {file = "coverage-7.4.4-cp38-cp38-win32.whl", hash = "sha256:dfa8fe35a0bb90382837b238fff375de15f0dcdb9ae68ff85f7a63649c98527e"}, + {file = "coverage-7.4.4-cp38-cp38-win_amd64.whl", hash = "sha256:b2991665420a803495e0b90a79233c1433d6ed77ef282e8e152a324bbbc5e0c8"}, + {file = "coverage-7.4.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3b799445b9f7ee8bf299cfaed6f5b226c0037b74886a4e11515e569b36fe310d"}, + {file = "coverage-7.4.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b4d33f418f46362995f1e9d4f3a35a1b6322cb959c31d88ae56b0298e1c22357"}, + {file = "coverage-7.4.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aadacf9a2f407a4688d700e4ebab33a7e2e408f2ca04dbf4aef17585389eff3e"}, + {file = "coverage-7.4.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7c95949560050d04d46b919301826525597f07b33beba6187d04fa64d47ac82e"}, + {file = "coverage-7.4.4-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ff7687ca3d7028d8a5f0ebae95a6e4827c5616b31a4ee1192bdfde697db110d4"}, + {file = "coverage-7.4.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:5fc1de20b2d4a061b3df27ab9b7c7111e9a710f10dc2b84d33a4ab25065994ec"}, + {file = "coverage-7.4.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:c74880fc64d4958159fbd537a091d2a585448a8f8508bf248d72112723974cbd"}, + {file = "coverage-7.4.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:742a76a12aa45b44d236815d282b03cfb1de3b4323f3e4ec933acfae08e54ade"}, + {file = "coverage-7.4.4-cp39-cp39-win32.whl", hash = "sha256:d89d7b2974cae412400e88f35d86af72208e1ede1a541954af5d944a8ba46c57"}, + {file = "coverage-7.4.4-cp39-cp39-win_amd64.whl", hash = "sha256:9ca28a302acb19b6af89e90f33ee3e1906961f94b54ea37de6737b7ca9d8827c"}, + {file = "coverage-7.4.4-pp38.pp39.pp310-none-any.whl", hash = "sha256:b2c5edc4ac10a7ef6605a966c58929ec6c1bd0917fb8c15cb3363f65aa40e677"}, + {file = "coverage-7.4.4.tar.gz", hash = "sha256:c901df83d097649e257e803be22592aedfd5182f07b3cc87d640bbb9afd50f49"}, +] + +[[package]] +name = "coverage" +version = "7.4.4" +extras = ["toml"] +requires_python = ">=3.8" +summary = "Code coverage measurement for Python" +groups = ["test"] +dependencies = [ + "coverage==7.4.4", + "tomli; python_full_version <= \"3.11.0a6\"", +] +files = [ + {file = "coverage-7.4.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e0be5efd5127542ef31f165de269f77560d6cdef525fffa446de6f7e9186cfb2"}, + {file = "coverage-7.4.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ccd341521be3d1b3daeb41960ae94a5e87abe2f46f17224ba5d6f2b8398016cf"}, + {file = "coverage-7.4.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:09fa497a8ab37784fbb20ab699c246053ac294d13fc7eb40ec007a5043ec91f8"}, + {file = "coverage-7.4.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b1a93009cb80730c9bca5d6d4665494b725b6e8e157c1cb7f2db5b4b122ea562"}, + {file = "coverage-7.4.4-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:690db6517f09336559dc0b5f55342df62370a48f5469fabf502db2c6d1cffcd2"}, + {file = "coverage-7.4.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:09c3255458533cb76ef55da8cc49ffab9e33f083739c8bd4f58e79fecfe288f7"}, + {file = "coverage-7.4.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:8ce1415194b4a6bd0cdcc3a1dfbf58b63f910dcb7330fe15bdff542c56949f87"}, + {file = "coverage-7.4.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b91cbc4b195444e7e258ba27ac33769c41b94967919f10037e6355e998af255c"}, + {file = "coverage-7.4.4-cp310-cp310-win32.whl", hash = "sha256:598825b51b81c808cb6f078dcb972f96af96b078faa47af7dfcdf282835baa8d"}, + {file = "coverage-7.4.4-cp310-cp310-win_amd64.whl", hash = "sha256:09ef9199ed6653989ebbcaacc9b62b514bb63ea2f90256e71fea3ed74bd8ff6f"}, + {file = "coverage-7.4.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0f9f50e7ef2a71e2fae92774c99170eb8304e3fdf9c8c3c7ae9bab3e7229c5cf"}, + {file = "coverage-7.4.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:623512f8ba53c422fcfb2ce68362c97945095b864cda94a92edbaf5994201083"}, + {file = "coverage-7.4.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0513b9508b93da4e1716744ef6ebc507aff016ba115ffe8ecff744d1322a7b63"}, + {file = "coverage-7.4.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40209e141059b9370a2657c9b15607815359ab3ef9918f0196b6fccce8d3230f"}, + {file = "coverage-7.4.4-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8a2b2b78c78293782fd3767d53e6474582f62443d0504b1554370bde86cc8227"}, + {file = "coverage-7.4.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:73bfb9c09951125d06ee473bed216e2c3742f530fc5acc1383883125de76d9cd"}, + {file = "coverage-7.4.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:1f384c3cc76aeedce208643697fb3e8437604b512255de6d18dae3f27655a384"}, + {file = "coverage-7.4.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:54eb8d1bf7cacfbf2a3186019bcf01d11c666bd495ed18717162f7eb1e9dd00b"}, + {file = "coverage-7.4.4-cp311-cp311-win32.whl", hash = "sha256:cac99918c7bba15302a2d81f0312c08054a3359eaa1929c7e4b26ebe41e9b286"}, + {file = "coverage-7.4.4-cp311-cp311-win_amd64.whl", hash = "sha256:b14706df8b2de49869ae03a5ccbc211f4041750cd4a66f698df89d44f4bd30ec"}, + {file = "coverage-7.4.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:201bef2eea65e0e9c56343115ba3814e896afe6d36ffd37bab783261db430f76"}, + {file = "coverage-7.4.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:41c9c5f3de16b903b610d09650e5e27adbfa7f500302718c9ffd1c12cf9d6818"}, + {file = "coverage-7.4.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d898fe162d26929b5960e4e138651f7427048e72c853607f2b200909794ed978"}, + {file = "coverage-7.4.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3ea79bb50e805cd6ac058dfa3b5c8f6c040cb87fe83de10845857f5535d1db70"}, + {file = "coverage-7.4.4-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce4b94265ca988c3f8e479e741693d143026632672e3ff924f25fab50518dd51"}, + {file = "coverage-7.4.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:00838a35b882694afda09f85e469c96367daa3f3f2b097d846a7216993d37f4c"}, + {file = "coverage-7.4.4-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:fdfafb32984684eb03c2d83e1e51f64f0906b11e64482df3c5db936ce3839d48"}, + {file = "coverage-7.4.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:69eb372f7e2ece89f14751fbcbe470295d73ed41ecd37ca36ed2eb47512a6ab9"}, + {file = "coverage-7.4.4-cp312-cp312-win32.whl", hash = "sha256:137eb07173141545e07403cca94ab625cc1cc6bc4c1e97b6e3846270e7e1fea0"}, + {file = "coverage-7.4.4-cp312-cp312-win_amd64.whl", hash = "sha256:d71eec7d83298f1af3326ce0ff1d0ea83c7cb98f72b577097f9083b20bdaf05e"}, + {file = "coverage-7.4.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d5ae728ff3b5401cc320d792866987e7e7e880e6ebd24433b70a33b643bb0384"}, + {file = "coverage-7.4.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:cc4f1358cb0c78edef3ed237ef2c86056206bb8d9140e73b6b89fbcfcbdd40e1"}, + {file = "coverage-7.4.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8130a2aa2acb8788e0b56938786c33c7c98562697bf9f4c7d6e8e5e3a0501e4a"}, + {file = "coverage-7.4.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cf271892d13e43bc2b51e6908ec9a6a5094a4df1d8af0bfc360088ee6c684409"}, + {file = "coverage-7.4.4-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a4cdc86d54b5da0df6d3d3a2f0b710949286094c3a6700c21e9015932b81447e"}, + {file = "coverage-7.4.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:ae71e7ddb7a413dd60052e90528f2f65270aad4b509563af6d03d53e979feafd"}, + {file = "coverage-7.4.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:38dd60d7bf242c4ed5b38e094baf6401faa114fc09e9e6632374388a404f98e7"}, + {file = "coverage-7.4.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:aa5b1c1bfc28384f1f53b69a023d789f72b2e0ab1b3787aae16992a7ca21056c"}, + {file = "coverage-7.4.4-cp38-cp38-win32.whl", hash = "sha256:dfa8fe35a0bb90382837b238fff375de15f0dcdb9ae68ff85f7a63649c98527e"}, + {file = "coverage-7.4.4-cp38-cp38-win_amd64.whl", hash = "sha256:b2991665420a803495e0b90a79233c1433d6ed77ef282e8e152a324bbbc5e0c8"}, + {file = "coverage-7.4.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3b799445b9f7ee8bf299cfaed6f5b226c0037b74886a4e11515e569b36fe310d"}, + {file = "coverage-7.4.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b4d33f418f46362995f1e9d4f3a35a1b6322cb959c31d88ae56b0298e1c22357"}, + {file = "coverage-7.4.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aadacf9a2f407a4688d700e4ebab33a7e2e408f2ca04dbf4aef17585389eff3e"}, + {file = "coverage-7.4.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7c95949560050d04d46b919301826525597f07b33beba6187d04fa64d47ac82e"}, + {file = "coverage-7.4.4-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ff7687ca3d7028d8a5f0ebae95a6e4827c5616b31a4ee1192bdfde697db110d4"}, + {file = "coverage-7.4.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:5fc1de20b2d4a061b3df27ab9b7c7111e9a710f10dc2b84d33a4ab25065994ec"}, + {file = "coverage-7.4.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:c74880fc64d4958159fbd537a091d2a585448a8f8508bf248d72112723974cbd"}, + {file = "coverage-7.4.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:742a76a12aa45b44d236815d282b03cfb1de3b4323f3e4ec933acfae08e54ade"}, + {file = "coverage-7.4.4-cp39-cp39-win32.whl", hash = "sha256:d89d7b2974cae412400e88f35d86af72208e1ede1a541954af5d944a8ba46c57"}, + {file = "coverage-7.4.4-cp39-cp39-win_amd64.whl", hash = "sha256:9ca28a302acb19b6af89e90f33ee3e1906961f94b54ea37de6737b7ca9d8827c"}, + {file = "coverage-7.4.4-pp38.pp39.pp310-none-any.whl", hash = "sha256:b2c5edc4ac10a7ef6605a966c58929ec6c1bd0917fb8c15cb3363f65aa40e677"}, + {file = "coverage-7.4.4.tar.gz", hash = "sha256:c901df83d097649e257e803be22592aedfd5182f07b3cc87d640bbb9afd50f49"}, +] + +[[package]] +name = "cryptography" +version = "42.0.5" +requires_python = ">=3.7" +summary = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." +groups = ["default"] +marker = "sys_platform == \"linux\"" +dependencies = [ + "cffi>=1.12; platform_python_implementation != \"PyPy\"", +] +files = [ + {file = "cryptography-42.0.5-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:a30596bae9403a342c978fb47d9b0ee277699fa53bbafad14706af51fe543d16"}, + {file = "cryptography-42.0.5-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:b7ffe927ee6531c78f81aa17e684e2ff617daeba7f189f911065b2ea2d526dec"}, + {file = "cryptography-42.0.5-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2424ff4c4ac7f6b8177b53c17ed5d8fa74ae5955656867f5a8affaca36a27abb"}, + {file = "cryptography-42.0.5-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:329906dcc7b20ff3cad13c069a78124ed8247adcac44b10bea1130e36caae0b4"}, + {file = "cryptography-42.0.5-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:b03c2ae5d2f0fc05f9a2c0c997e1bc18c8229f392234e8a0194f202169ccd278"}, + {file = "cryptography-42.0.5-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:f8837fe1d6ac4a8052a9a8ddab256bc006242696f03368a4009be7ee3075cdb7"}, + {file = "cryptography-42.0.5-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:0270572b8bd2c833c3981724b8ee9747b3ec96f699a9665470018594301439ee"}, + {file = "cryptography-42.0.5-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:b8cac287fafc4ad485b8a9b67d0ee80c66bf3574f655d3b97ef2e1082360faf1"}, + {file = "cryptography-42.0.5-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:16a48c23a62a2f4a285699dba2e4ff2d1cff3115b9df052cdd976a18856d8e3d"}, + {file = "cryptography-42.0.5-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:2bce03af1ce5a5567ab89bd90d11e7bbdff56b8af3acbbec1faded8f44cb06da"}, + {file = "cryptography-42.0.5-cp37-abi3-win32.whl", hash = "sha256:b6cd2203306b63e41acdf39aa93b86fb566049aeb6dc489b70e34bcd07adca74"}, + {file = "cryptography-42.0.5-cp37-abi3-win_amd64.whl", hash = "sha256:98d8dc6d012b82287f2c3d26ce1d2dd130ec200c8679b6213b3c73c08b2b7940"}, + {file = "cryptography-42.0.5-cp39-abi3-macosx_10_12_universal2.whl", hash = "sha256:5e6275c09d2badf57aea3afa80d975444f4be8d3bc58f7f80d2a484c6f9485c8"}, + {file = "cryptography-42.0.5-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e4985a790f921508f36f81831817cbc03b102d643b5fcb81cd33df3fa291a1a1"}, + {file = "cryptography-42.0.5-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7cde5f38e614f55e28d831754e8a3bacf9ace5d1566235e39d91b35502d6936e"}, + {file = "cryptography-42.0.5-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:7367d7b2eca6513681127ebad53b2582911d1736dc2ffc19f2c3ae49997496bc"}, + {file = "cryptography-42.0.5-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:cd2030f6650c089aeb304cf093f3244d34745ce0cfcc39f20c6fbfe030102e2a"}, + {file = "cryptography-42.0.5-cp39-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:a2913c5375154b6ef2e91c10b5720ea6e21007412f6437504ffea2109b5a33d7"}, + {file = "cryptography-42.0.5-cp39-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:c41fb5e6a5fe9ebcd58ca3abfeb51dffb5d83d6775405305bfa8715b76521922"}, + {file = "cryptography-42.0.5-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:3eaafe47ec0d0ffcc9349e1708be2aaea4c6dd4978d76bf6eb0cb2c13636c6fc"}, + {file = "cryptography-42.0.5-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:1b95b98b0d2af784078fa69f637135e3c317091b615cd0905f8b8a087e86fa30"}, + {file = "cryptography-42.0.5-cp39-abi3-win32.whl", hash = "sha256:1f71c10d1e88467126f0efd484bd44bca5e14c664ec2ede64c32f20875c0d413"}, + {file = "cryptography-42.0.5-cp39-abi3-win_amd64.whl", hash = "sha256:a011a644f6d7d03736214d38832e030d8268bcff4a41f728e6030325fea3e400"}, + {file = "cryptography-42.0.5-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:9481ffe3cf013b71b2428b905c4f7a9a4f76ec03065b05ff499bb5682a8d9ad8"}, + {file = "cryptography-42.0.5-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:ba334e6e4b1d92442b75ddacc615c5476d4ad55cc29b15d590cc6b86efa487e2"}, + {file = "cryptography-42.0.5-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:ba3e4a42397c25b7ff88cdec6e2a16c2be18720f317506ee25210f6d31925f9c"}, + {file = "cryptography-42.0.5-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:111a0d8553afcf8eb02a4fea6ca4f59d48ddb34497aa8706a6cf536f1a5ec576"}, + {file = "cryptography-42.0.5-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:cd65d75953847815962c84a4654a84850b2bb4aed3f26fadcc1c13892e1e29f6"}, + {file = "cryptography-42.0.5-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:e807b3188f9eb0eaa7bbb579b462c5ace579f1cedb28107ce8b48a9f7ad3679e"}, + {file = "cryptography-42.0.5-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:f12764b8fffc7a123f641d7d049d382b73f96a34117e0b637b80643169cec8ac"}, + {file = "cryptography-42.0.5-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:37dd623507659e08be98eec89323469e8c7b4c1407c85112634ae3dbdb926fdd"}, + {file = "cryptography-42.0.5.tar.gz", hash = "sha256:6fe07eec95dfd477eb9530aef5bead34fec819b3aaf6c5bd6d20565da607bfe1"}, +] + +[[package]] +name = "distlib" +version = "0.3.8" +summary = "Distribution utilities" +groups = ["dev", "lint"] +files = [ + {file = "distlib-0.3.8-py2.py3-none-any.whl", hash = "sha256:034db59a0b96f8ca18035f36290806a9a6e6bd9d1ff91e45a7f172eb17e51784"}, + {file = "distlib-0.3.8.tar.gz", hash = "sha256:1530ea13e350031b6312d8580ddb6b27a104275a31106523b8f123787f494f64"}, +] + +[[package]] +name = "exceptiongroup" +version = "1.2.1" +requires_python = ">=3.7" +summary = "Backport of PEP 654 (exception groups)" +groups = ["test"] +marker = "python_version < \"3.11\"" +files = [ + {file = "exceptiongroup-1.2.1-py3-none-any.whl", hash = "sha256:5258b9ed329c5bbdd31a309f53cbfb0b155341807f6ff7606a1e801a891b29ad"}, + {file = "exceptiongroup-1.2.1.tar.gz", hash = "sha256:a4785e48b045528f5bfe627b6ad554ff32def154f42372786903b7abcfe1aa16"}, +] + +[[package]] +name = "filelock" +version = "3.13.4" +requires_python = ">=3.8" +summary = "A platform independent file lock." +groups = ["dev", "lint"] +files = [ + {file = "filelock-3.13.4-py3-none-any.whl", hash = "sha256:404e5e9253aa60ad457cae1be07c0f0ca90a63931200a47d9b6a6af84fd7b45f"}, + {file = "filelock-3.13.4.tar.gz", hash = "sha256:d13f466618bfde72bd2c18255e269f72542c6e70e7bac83a0232d6b1cc5c8cf4"}, +] + +[[package]] +name = "identify" +version = "2.5.35" +requires_python = ">=3.8" +summary = "File identification library for Python" +groups = ["lint"] +files = [ + {file = "identify-2.5.35-py2.py3-none-any.whl", hash = "sha256:c4de0081837b211594f8e877a6b4fad7ca32bbfc1a9307fdd61c28bfe923f13e"}, + {file = "identify-2.5.35.tar.gz", hash = "sha256:10a7ca245cfcd756a554a7288159f72ff105ad233c7c4b9c6f0f4d108f5f6791"}, +] + +[[package]] +name = "idna" +version = "3.7" +requires_python = ">=3.5" +summary = "Internationalized Domain Names in Applications (IDNA)" +groups = ["default"] +files = [ + {file = "idna-3.7-py3-none-any.whl", hash = "sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0"}, + {file = "idna-3.7.tar.gz", hash = "sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc"}, +] + +[[package]] +name = "importlib-metadata" +version = "7.1.0" +requires_python = ">=3.8" +summary = "Read metadata from Python packages" +groups = ["default"] +marker = "python_version < \"3.12\"" +dependencies = [ + "zipp>=0.5", +] +files = [ + {file = "importlib_metadata-7.1.0-py3-none-any.whl", hash = "sha256:30962b96c0c223483ed6cc7280e7f0199feb01a0e40cfae4d4450fc6fab1f570"}, + {file = "importlib_metadata-7.1.0.tar.gz", hash = "sha256:b78938b926ee8d5f020fc4772d487045805a55ddbad2ecf21c6d60938dc7fcd2"}, +] + +[[package]] +name = "importlib-resources" +version = "6.4.0" +requires_python = ">=3.8" +summary = "Read resources from Python packages" +groups = ["default"] +marker = "python_version < \"3.9\"" +dependencies = [ + "zipp>=3.1.0; python_version < \"3.10\"", +] +files = [ + {file = "importlib_resources-6.4.0-py3-none-any.whl", hash = "sha256:50d10f043df931902d4194ea07ec57960f66a80449ff867bfe782b4c486ba78c"}, + {file = "importlib_resources-6.4.0.tar.gz", hash = "sha256:cdb2b453b8046ca4e3798eb1d84f3cce1446a0e8e7b5ef4efb600f19fc398145"}, +] + +[[package]] +name = "iniconfig" +version = "2.0.0" +requires_python = ">=3.7" +summary = "brain-dead simple config-ini parsing" +groups = ["test"] +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 = "jaraco-classes" +version = "3.4.0" +requires_python = ">=3.8" +summary = "Utility functions for Python class constructs" +groups = ["default"] +dependencies = [ + "more-itertools", +] +files = [ + {file = "jaraco.classes-3.4.0-py3-none-any.whl", hash = "sha256:f662826b6bed8cace05e7ff873ce0f9283b5c924470fe664fff1c2f00f581790"}, + {file = "jaraco.classes-3.4.0.tar.gz", hash = "sha256:47a024b51d0239c0dd8c8540c6c7f484be3b8fcf0b2d85c13825780d3b3f3acd"}, +] + +[[package]] +name = "jaraco-context" +version = "5.3.0" +requires_python = ">=3.8" +summary = "Useful decorators and context managers" +groups = ["default"] +dependencies = [ + "backports-tarfile; python_version < \"3.12\"", +] +files = [ + {file = "jaraco.context-5.3.0-py3-none-any.whl", hash = "sha256:3e16388f7da43d384a1a7cd3452e72e14732ac9fe459678773a3608a812bf266"}, + {file = "jaraco.context-5.3.0.tar.gz", hash = "sha256:c2f67165ce1f9be20f32f650f25d8edfc1646a8aeee48ae06fb35f90763576d2"}, +] + +[[package]] +name = "jaraco-functools" +version = "4.0.1" +requires_python = ">=3.8" +summary = "Functools like those found in stdlib" +groups = ["default"] +dependencies = [ + "more-itertools", +] +files = [ + {file = "jaraco.functools-4.0.1-py3-none-any.whl", hash = "sha256:3b24ccb921d6b593bdceb56ce14799204f473976e2a9d4b15b04d0f2c2326664"}, + {file = "jaraco_functools-4.0.1.tar.gz", hash = "sha256:d33fa765374c0611b52f8b3a795f8900869aa88c84769d4d1746cd68fb28c3e8"}, +] + +[[package]] +name = "jeepney" +version = "0.8.0" +requires_python = ">=3.7" +summary = "Low-level, pure Python DBus protocol wrapper." +groups = ["default"] +marker = "sys_platform == \"linux\"" +files = [ + {file = "jeepney-0.8.0-py3-none-any.whl", hash = "sha256:c0a454ad016ca575060802ee4d590dd912e35c122fa04e70306de3d076cce755"}, + {file = "jeepney-0.8.0.tar.gz", hash = "sha256:5efe48d255973902f6badc3ce55e2aa6c5c3b3bc642059ef3a91247bcfcc5806"}, +] + +[[package]] +name = "keyring" +version = "25.1.0" +requires_python = ">=3.8" +summary = "Store and access your passwords safely." +groups = ["default"] +dependencies = [ + "SecretStorage>=3.2; sys_platform == \"linux\"", + "importlib-metadata>=4.11.4; python_version < \"3.12\"", + "importlib-resources; python_version < \"3.9\"", + "jaraco-classes", + "jaraco-context", + "jaraco-functools", + "jeepney>=0.4.2; sys_platform == \"linux\"", + "pywin32-ctypes>=0.2.0; sys_platform == \"win32\"", +] +files = [ + {file = "keyring-25.1.0-py3-none-any.whl", hash = "sha256:26fc12e6a329d61d24aa47b22a7c5c3f35753df7d8f2860973cf94f4e1fb3427"}, + {file = "keyring-25.1.0.tar.gz", hash = "sha256:7230ea690525133f6ad536a9b5def74a4bd52642abe594761028fc044d7c7893"}, +] + +[[package]] +name = "more-itertools" +version = "10.2.0" +requires_python = ">=3.8" +summary = "More routines for operating on iterables, beyond itertools" +groups = ["default"] +files = [ + {file = "more-itertools-10.2.0.tar.gz", hash = "sha256:8fccb480c43d3e99a00087634c06dd02b0d50fbf088b380de5a41a015ec239e1"}, + {file = "more_itertools-10.2.0-py3-none-any.whl", hash = "sha256:686b06abe565edfab151cb8fd385a05651e1fdf8f0a14191e4439283421f8684"}, +] + +[[package]] +name = "mypy" +version = "1.9.0" +requires_python = ">=3.8" +summary = "Optional static typing for Python" +groups = ["lint"] +dependencies = [ + "mypy-extensions>=1.0.0", + "tomli>=1.1.0; python_version < \"3.11\"", + "typing-extensions>=4.1.0", +] +files = [ + {file = "mypy-1.9.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f8a67616990062232ee4c3952f41c779afac41405806042a8126fe96e098419f"}, + {file = "mypy-1.9.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d357423fa57a489e8c47b7c85dfb96698caba13d66e086b412298a1a0ea3b0ed"}, + {file = "mypy-1.9.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:49c87c15aed320de9b438ae7b00c1ac91cd393c1b854c2ce538e2a72d55df150"}, + {file = "mypy-1.9.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:48533cdd345c3c2e5ef48ba3b0d3880b257b423e7995dada04248725c6f77374"}, + {file = "mypy-1.9.0-cp310-cp310-win_amd64.whl", hash = "sha256:4d3dbd346cfec7cb98e6cbb6e0f3c23618af826316188d587d1c1bc34f0ede03"}, + {file = "mypy-1.9.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:653265f9a2784db65bfca694d1edd23093ce49740b2244cde583aeb134c008f3"}, + {file = "mypy-1.9.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3a3c007ff3ee90f69cf0a15cbcdf0995749569b86b6d2f327af01fd1b8aee9dc"}, + {file = "mypy-1.9.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2418488264eb41f69cc64a69a745fad4a8f86649af4b1041a4c64ee61fc61129"}, + {file = "mypy-1.9.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:68edad3dc7d70f2f17ae4c6c1b9471a56138ca22722487eebacfd1eb5321d612"}, + {file = "mypy-1.9.0-cp311-cp311-win_amd64.whl", hash = "sha256:85ca5fcc24f0b4aeedc1d02f93707bccc04733f21d41c88334c5482219b1ccb3"}, + {file = "mypy-1.9.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:aceb1db093b04db5cd390821464504111b8ec3e351eb85afd1433490163d60cd"}, + {file = "mypy-1.9.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0235391f1c6f6ce487b23b9dbd1327b4ec33bb93934aa986efe8a9563d9349e6"}, + {file = "mypy-1.9.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d4d5ddc13421ba3e2e082a6c2d74c2ddb3979c39b582dacd53dd5d9431237185"}, + {file = "mypy-1.9.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:190da1ee69b427d7efa8aa0d5e5ccd67a4fb04038c380237a0d96829cb157913"}, + {file = "mypy-1.9.0-cp312-cp312-win_amd64.whl", hash = "sha256:fe28657de3bfec596bbeef01cb219833ad9d38dd5393fc649f4b366840baefe6"}, + {file = "mypy-1.9.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:e54396d70be04b34f31d2edf3362c1edd023246c82f1730bbf8768c28db5361b"}, + {file = "mypy-1.9.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:5e6061f44f2313b94f920e91b204ec600982961e07a17e0f6cd83371cb23f5c2"}, + {file = "mypy-1.9.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:81a10926e5473c5fc3da8abb04119a1f5811a236dc3a38d92015cb1e6ba4cb9e"}, + {file = "mypy-1.9.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b685154e22e4e9199fc95f298661deea28aaede5ae16ccc8cbb1045e716b3e04"}, + {file = "mypy-1.9.0-cp38-cp38-win_amd64.whl", hash = "sha256:5d741d3fc7c4da608764073089e5f58ef6352bedc223ff58f2f038c2c4698a89"}, + {file = "mypy-1.9.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:587ce887f75dd9700252a3abbc9c97bbe165a4a630597845c61279cf32dfbf02"}, + {file = "mypy-1.9.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f88566144752999351725ac623471661c9d1cd8caa0134ff98cceeea181789f4"}, + {file = "mypy-1.9.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:61758fabd58ce4b0720ae1e2fea5cfd4431591d6d590b197775329264f86311d"}, + {file = "mypy-1.9.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:e49499be624dead83927e70c756970a0bc8240e9f769389cdf5714b0784ca6bf"}, + {file = "mypy-1.9.0-cp39-cp39-win_amd64.whl", hash = "sha256:571741dc4194b4f82d344b15e8837e8c5fcc462d66d076748142327626a1b6e9"}, + {file = "mypy-1.9.0-py3-none-any.whl", hash = "sha256:a260627a570559181a9ea5de61ac6297aa5af202f06fd7ab093ce74e7181e43e"}, + {file = "mypy-1.9.0.tar.gz", hash = "sha256:3cc5da0127e6a478cddd906068496a97a7618a21ce9b54bde5bf7e539c7af974"}, +] + +[[package]] +name = "mypy-extensions" +version = "1.0.0" +requires_python = ">=3.5" +summary = "Type system extensions for programs checked with the mypy type checker." +groups = ["lint"] +files = [ + {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, + {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, +] + +[[package]] +name = "nodeenv" +version = "1.8.0" +requires_python = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*" +summary = "Node.js virtual environment builder" +groups = ["lint"] +dependencies = [ + "setuptools", +] +files = [ + {file = "nodeenv-1.8.0-py2.py3-none-any.whl", hash = "sha256:df865724bb3c3adc86b3876fa209771517b0cfe596beff01a92700e0e8be4cec"}, + {file = "nodeenv-1.8.0.tar.gz", hash = "sha256:d51e0c37e64fbf47d017feac3145cdbb58836d7eee8c6f6d3b6880c5456227d2"}, +] + +[[package]] +name = "nox" +version = "2024.4.15" +requires_python = ">=3.7" +summary = "Flexible test automation." +groups = ["dev"] +dependencies = [ + "argcomplete<4.0,>=1.9.4", + "colorlog<7.0.0,>=2.6.1", + "packaging>=20.9", + "tomli>=1; python_version < \"3.11\"", + "virtualenv>=20.14.1", +] +files = [ + {file = "nox-2024.4.15-py3-none-any.whl", hash = "sha256:6492236efa15a460ecb98e7b67562a28b70da006ab0be164e8821177577c0565"}, + {file = "nox-2024.4.15.tar.gz", hash = "sha256:ecf6700199cdfa9e5ea0a41ff5e6ef4641d09508eda6edb89d9987864115817f"}, +] + +[[package]] +name = "packaging" +version = "24.0" +requires_python = ">=3.7" +summary = "Core utilities for Python packages" +groups = ["dev", "test"] +files = [ + {file = "packaging-24.0-py3-none-any.whl", hash = "sha256:2ddfb553fdf02fb784c234c7ba6ccc288296ceabec964ad2eae3777778130bc5"}, + {file = "packaging-24.0.tar.gz", hash = "sha256:eb82c5e3e56209074766e6885bb04b8c38a0c015d0a30036ebe7ece34c9989e9"}, +] + +[[package]] +name = "pip" +version = "24.0" +requires_python = ">=3.7" +summary = "The PyPA recommended tool for installing Python packages." +groups = ["dev"] +files = [ + {file = "pip-24.0-py3-none-any.whl", hash = "sha256:ba0d021a166865d2265246961bec0152ff124de910c5cc39f1156ce3fa7c69dc"}, + {file = "pip-24.0.tar.gz", hash = "sha256:ea9bd1a847e8c5774a5777bb398c19e80bcd4e2aa16a4b301b718fe6f593aba2"}, +] + +[[package]] +name = "platformdirs" +version = "4.2.0" +requires_python = ">=3.8" +summary = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +groups = ["dev", "lint"] +files = [ + {file = "platformdirs-4.2.0-py3-none-any.whl", hash = "sha256:0614df2a2f37e1a662acbd8e2b25b92ccf8632929bc6d43467e17fe89c75e068"}, + {file = "platformdirs-4.2.0.tar.gz", hash = "sha256:ef0cc731df711022c174543cb70a9b5bd22e5a9337c8624ef2c2ceb8ddad8768"}, +] + +[[package]] +name = "pluggy" +version = "1.4.0" +requires_python = ">=3.8" +summary = "plugin and hook calling mechanisms for python" +groups = ["test"] +files = [ + {file = "pluggy-1.4.0-py3-none-any.whl", hash = "sha256:7db9f7b503d67d1c5b95f59773ebb58a8c1c288129a88665838012cfb07b8981"}, + {file = "pluggy-1.4.0.tar.gz", hash = "sha256:8c85c2876142a764e5b7548e7d9a0e0ddb46f5185161049a79b7e974454223be"}, +] + +[[package]] +name = "pre-commit" +version = "3.5.0" +requires_python = ">=3.8" +summary = "A framework for managing and maintaining multi-language pre-commit hooks." +groups = ["lint"] +dependencies = [ + "cfgv>=2.0.0", + "identify>=1.0.0", + "nodeenv>=0.11.1", + "pyyaml>=5.1", + "virtualenv>=20.10.0", +] +files = [ + {file = "pre_commit-3.5.0-py2.py3-none-any.whl", hash = "sha256:841dc9aef25daba9a0238cd27984041fa0467b4199fc4852e27950664919f660"}, + {file = "pre_commit-3.5.0.tar.gz", hash = "sha256:5804465c675b659b0862f07907f96295d490822a450c4c40e747d0b1c6ebcb32"}, +] + +[[package]] +name = "pycparser" +version = "2.22" +requires_python = ">=3.8" +summary = "C parser in Python" +groups = ["default"] +marker = "platform_python_implementation != \"PyPy\" and sys_platform == \"linux\"" +files = [ + {file = "pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc"}, + {file = "pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6"}, +] + +[[package]] +name = "pyjwt" +version = "2.8.0" +requires_python = ">=3.7" +summary = "JSON Web Token implementation in Python" +groups = ["default"] +files = [ + {file = "PyJWT-2.8.0-py3-none-any.whl", hash = "sha256:59127c392cc44c2da5bb3192169a91f429924e17aff6534d70fdc02ab3e04320"}, + {file = "PyJWT-2.8.0.tar.gz", hash = "sha256:57e28d156e3d5c10088e0c68abb90bfac3df82b40a71bd0daa20c65ccd5c23de"}, +] + +[[package]] +name = "pytest" +version = "8.1.1" +requires_python = ">=3.8" +summary = "pytest: simple powerful testing with Python" +groups = ["test"] +dependencies = [ + "colorama; sys_platform == \"win32\"", + "exceptiongroup>=1.0.0rc8; python_version < \"3.11\"", + "iniconfig", + "packaging", + "pluggy<2.0,>=1.4", + "tomli>=1; python_version < \"3.11\"", +] +files = [ + {file = "pytest-8.1.1-py3-none-any.whl", hash = "sha256:2a8386cfc11fa9d2c50ee7b2a57e7d898ef90470a7a34c4b949ff59662bb78b7"}, + {file = "pytest-8.1.1.tar.gz", hash = "sha256:ac978141a75948948817d360297b7aae0fcb9d6ff6bc9ec6d514b85d5a65c044"}, +] + +[[package]] +name = "pytest-cov" +version = "5.0.0" +requires_python = ">=3.8" +summary = "Pytest plugin for measuring coverage." +groups = ["test"] +dependencies = [ + "coverage[toml]>=5.2.1", + "pytest>=4.6", +] +files = [ + {file = "pytest-cov-5.0.0.tar.gz", hash = "sha256:5837b58e9f6ebd335b0f8060eecce69b662415b16dc503883a02f45dfeb14857"}, + {file = "pytest_cov-5.0.0-py3-none-any.whl", hash = "sha256:4f0764a1219df53214206bf1feea4633c3b558a2925c8b59f144f682861ce652"}, +] + +[[package]] +name = "pywin32-ctypes" +version = "0.2.2" +requires_python = ">=3.6" +summary = "A (partial) reimplementation of pywin32 using ctypes/cffi" +groups = ["default"] +marker = "sys_platform == \"win32\"" +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" +requires_python = ">=3.6" +summary = "YAML parser and emitter for Python" +groups = ["lint"] +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_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef"}, + {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-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" +requires_python = ">=3.7" +summary = "Python HTTP for Humans." +groups = ["default"] +dependencies = [ + "certifi>=2017.4.17", + "charset-normalizer<4,>=2", + "idna<4,>=2.5", + "urllib3<3,>=1.21.1", +] +files = [ + {file = "requests-2.31.0-py3-none-any.whl", hash = "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f"}, + {file = "requests-2.31.0.tar.gz", hash = "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1"}, +] + +[[package]] +name = "ruff" +version = "0.4.0" +requires_python = ">=3.7" +summary = "An extremely fast Python linter and code formatter, written in Rust." +groups = ["lint"] +files = [ + {file = "ruff-0.4.0-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:70b8c620cf2212744eabd6d69c4f839f2be0d8880d27beaeb0adb6aa0b316aa8"}, + {file = "ruff-0.4.0-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:cfa3e3ff53be05a8c5570c1585ea1e089f6b399ca99fcb78598d4a8234f248db"}, + {file = "ruff-0.4.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5616cca501d1d16b932b7e607d7e1fd1b8c8c51d6ee484b7940fc1adc5bea541"}, + {file = "ruff-0.4.0-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:46eff08dd480b5d9b540846159fe134d70e3c45a3c913c600047cbf7f0e4e308"}, + {file = "ruff-0.4.0-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2d546f511431fff2b17adcf7110f3b2c2c0c8d33b0e10e5fd27fd340bc617efc"}, + {file = "ruff-0.4.0-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:c7b6b6b38e216036284c5779b6aa14acbf5664e3b5872533219cf93daf40ddfb"}, + {file = "ruff-0.4.0-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5e1cf8b064bb2a6b4922af7274fe2dffcb552d96ba716b2fbe5e2c970ed7de18"}, + {file = "ruff-0.4.0-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9911c9046b94253e1fa844c0192bb764b86866a881502dee324686474d498c17"}, + {file = "ruff-0.4.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4ca7a971c8f1a0b6f5ff4a819c0d1c2619536530bbd5a289af725d8b2ef1013d"}, + {file = "ruff-0.4.0-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:752e0f77f421141dd470a0b1bed4fd8f763aebabe32c80ed3580f740ef4ba807"}, + {file = "ruff-0.4.0-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:84f2a5dd8f33964d826c5377e094f7ce11e55e432cd42d3bf64efe4384224a03"}, + {file = "ruff-0.4.0-py3-none-musllinux_1_2_i686.whl", hash = "sha256:0b20e7db4a672495320a8a18149b7febf4e4f97509a4657367144569ce0915fd"}, + {file = "ruff-0.4.0-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:0b0eddd339e24dc4f7719b1cde4967f6b6bc0ad948cc183711ba8910f14aeafe"}, + {file = "ruff-0.4.0-py3-none-win32.whl", hash = "sha256:e70befd488271a2c28c80bd427f73d8855dd222fc549fa1e9967d287c5cfe781"}, + {file = "ruff-0.4.0-py3-none-win_amd64.whl", hash = "sha256:8584b9361900997ccf8d7aaa4dc4ab43e258a853ca7189d98ac929dc9ee50875"}, + {file = "ruff-0.4.0-py3-none-win_arm64.whl", hash = "sha256:fea4ec813c965e40af29ee627a1579ee1d827d77e81d54b85bdd7b42d1540cdd"}, + {file = "ruff-0.4.0.tar.gz", hash = "sha256:7457308d9ebf00d6a1c9a26aa755e477787a636c90b823f91cd7d4bea9e89260"}, +] + +[[package]] +name = "secretstorage" +version = "3.3.3" +requires_python = ">=3.6" +summary = "Python bindings to FreeDesktop.org Secret Service API" +groups = ["default"] +marker = "sys_platform == \"linux\"" +dependencies = [ + "cryptography>=2.0", + "jeepney>=0.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]] +name = "setuptools" +version = "69.5.1" +requires_python = ">=3.8" +summary = "Easily download, build, install, upgrade, and uninstall Python packages" +groups = ["lint"] +files = [ + {file = "setuptools-69.5.1-py3-none-any.whl", hash = "sha256:c636ac361bc47580504644275c9ad802c50415c7522212252c033bd15f301f32"}, + {file = "setuptools-69.5.1.tar.gz", hash = "sha256:6c1fccdac05a97e598fb0ae3bbed5904ccb317337a51139dcd51453611bbb987"}, +] + +[[package]] +name = "tomli" +version = "2.0.1" +requires_python = ">=3.7" +summary = "A lil' TOML parser" +groups = ["dev", "lint", "test"] +marker = "python_version < \"3.11\"" +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 = "types-requests" +version = "2.31.0.20240406" +requires_python = ">=3.8" +summary = "Typing stubs for requests" +groups = ["stubs"] +dependencies = [ + "urllib3>=2", +] +files = [ + {file = "types-requests-2.31.0.20240406.tar.gz", hash = "sha256:4428df33c5503945c74b3f42e82b181e86ec7b724620419a2966e2de604ce1a1"}, + {file = "types_requests-2.31.0.20240406-py3-none-any.whl", hash = "sha256:6216cdac377c6b9a040ac1c0404f7284bd13199c0e1bb235f4324627e8898cf5"}, +] + +[[package]] +name = "typing-extensions" +version = "4.11.0" +requires_python = ">=3.8" +summary = "Backported and Experimental Type Hints for Python 3.8+" +groups = ["lint"] +files = [ + {file = "typing_extensions-4.11.0-py3-none-any.whl", hash = "sha256:c1f94d72897edaf4ce775bb7558d5b79d8126906a14ea5ed1635921406c0387a"}, + {file = "typing_extensions-4.11.0.tar.gz", hash = "sha256:83f085bd5ca59c80295fc2a82ab5dac679cbe02b9f33f7d83af68e241bea51b0"}, +] + +[[package]] +name = "urllib3" +version = "2.2.1" +requires_python = ">=3.8" +summary = "HTTP library with thread-safe connection pooling, file post, and more." +groups = ["default", "stubs"] +files = [ + {file = "urllib3-2.2.1-py3-none-any.whl", hash = "sha256:450b20ec296a467077128bff42b73080516e71b56ff59a60a02bef2232c4fa9d"}, + {file = "urllib3-2.2.1.tar.gz", hash = "sha256:d0570876c61ab9e520d776c38acbbb5b05a776d3f9ff98a5c8fd5162a444cf19"}, +] + +[[package]] +name = "virtualenv" +version = "20.25.3" +requires_python = ">=3.7" +summary = "Virtual Python Environment builder" +groups = ["dev", "lint"] +dependencies = [ + "distlib<1,>=0.3.7", + "filelock<4,>=3.12.2", + "platformdirs<5,>=3.9.1", +] +files = [ + {file = "virtualenv-20.25.3-py3-none-any.whl", hash = "sha256:8aac4332f2ea6ef519c648d0bc48a5b1d324994753519919bddbb1aff25a104e"}, + {file = "virtualenv-20.25.3.tar.gz", hash = "sha256:7bb554bbdfeaacc3349fa614ea5bff6ac300fc7c335e9facf3a3bcfc703f45be"}, +] + +[[package]] +name = "zipp" +version = "3.18.1" +requires_python = ">=3.8" +summary = "Backport of pathlib-compatible object wrapper for zip files" +groups = ["default"] +marker = "python_version < \"3.12\"" +files = [ + {file = "zipp-3.18.1-py3-none-any.whl", hash = "sha256:206f5a15f2af3dbaee80769fb7dc6f249695e940acca08dfb2a4769fe61e538b"}, + {file = "zipp-3.18.1.tar.gz", hash = "sha256:2884ed22e7d8961de1c9a05142eb69a247f120291bc0206a00a7642f09b5b715"}, +] diff --git a/src/artifacts-helper/codespaces_artifacts_helper_keyring/pyproject.toml b/src/artifacts-helper/codespaces_artifacts_helper_keyring/pyproject.toml new file mode 100644 index 0000000..c7ec4d1 --- /dev/null +++ b/src/artifacts-helper/codespaces_artifacts_helper_keyring/pyproject.toml @@ -0,0 +1,62 @@ +[project] +name = "codespaces-artifacts-helper-keyring" +version = "0.1.0" +description = "Keyring backend to retrieve credentials for Azure Artifacts on Codespaces using https://github.com/microsoft/ado-codespaces-auth" +authors = [{ name = "Delilah Wu", email = "delilahwu@microsoft.com" }] +classifiers = [ + "Development Status :: 3 - Alpha", + "Intended Audience :: Developers", + "License :: OSI Approved :: MIT License", + "Programming Language :: Python :: 3 :: Only", + "Typing :: Typed", +] +dependencies = [ + "jaraco-classes>=3.0.0", + "keyring>=20.0.0", + "PyJWT>=2.0.0", + "requests>=2.20.0", +] +requires-python = ">=3.8" +readme = "README.md" +license = { text = "MIT" } + +[project.entry-points."keyring.backends"] +CodespacesArtifactsHelperKeyringBackend = "codespaces_artifacts_helper_keyring" + +[build-system] +requires = ["setuptools>=61", "wheel"] +build-backend = "setuptools.build_meta" + +[tool.setuptools.packages.find] +where = ["src"] + +[tool.setuptools.package-data] +"codespaces_artifacts_helper_keyring" = ["py.typed"] + +[tool.pdm] +distribution = true + +[tool.pdm.dev-dependencies] +lint = ["mypy>=1.9.0", "ruff>=0.4.0", "pre-commit>=3.5.0"] +dev = ["pip>=24.0", "nox>=2024.4.15"] +test = ["pytest>=8.1.1", "pytest-cov>=5.0.0"] +stubs = ["types-requests>=2.31.0.20240406"] + +[tool.pdm.scripts] +check = "ruff check ." +fmt = "ruff format ." +test = "pytest -v tests" + +[tool.ruff] +line-length = 88 +indent-width = 4 + +[tool.ruff.lint] +extend-select = ["E4", "E7", "E9", "F", "B", "I", "W", "Q"] + +[tool.ruff.lint.pydocstyle] +convention = "google" + +[tool.ruff.lint.per-file-ignores] +"__init__.py" = ["F401"] +"**/{tests,docs,tools}/*" = ["E402"] diff --git a/src/artifacts-helper/codespaces_artifacts_helper_keyring/src/codespaces_artifacts_helper_keyring/__init__.py b/src/artifacts-helper/codespaces_artifacts_helper_keyring/src/codespaces_artifacts_helper_keyring/__init__.py new file mode 100644 index 0000000..a8d7576 --- /dev/null +++ b/src/artifacts-helper/codespaces_artifacts_helper_keyring/src/codespaces_artifacts_helper_keyring/__init__.py @@ -0,0 +1,2 @@ +from .artifacts_helper_wrapper import CredentialProvider +from .keyring_backend import CodespacesArtifactsHelperKeyringBackend diff --git a/src/artifacts-helper/codespaces_artifacts_helper_keyring/src/codespaces_artifacts_helper_keyring/artifacts_helper_wrapper.py b/src/artifacts-helper/codespaces_artifacts_helper_keyring/src/codespaces_artifacts_helper_keyring/artifacts_helper_wrapper.py new file mode 100644 index 0000000..5cf4b7e --- /dev/null +++ b/src/artifacts-helper/codespaces_artifacts_helper_keyring/src/codespaces_artifacts_helper_keyring/artifacts_helper_wrapper.py @@ -0,0 +1,114 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See LICENSE in the project root for license information. +# -------------------------------------------------------------------------------------------- + +from __future__ import absolute_import + +import os +import shutil +import subprocess +from dataclasses import dataclass +from pathlib import Path +from typing import Optional, Union + +import jwt +import requests + + +@dataclass +class Credentials: + username: str + password: str + + +class CredentialProviderError(RuntimeError): + pass + + +class CredentialProvider: + DEFAULT_AUTH_HELPER_PATH = "~/ado-auth-helper" + + def __init__( + self, + auth_helper_path: Union[os.PathLike, str] = DEFAULT_AUTH_HELPER_PATH, + timeout: int = 30, + ): + self.auth_tool_path = self.resolve_auth_helper_path(auth_helper_path) + self.timeout = timeout + + @staticmethod + def resolve_auth_helper_path( + auth_helper_path: Union[os.PathLike, str], ) -> Optional[str]: + return shutil.which(str(Path(auth_helper_path).expanduser()), + mode=os.X_OK) + + @classmethod + def auth_helper_installed( + cls, auth_helper_path: Union[os.PathLike, str]) -> bool: + return cls.resolve_auth_helper_path(auth_helper_path) is not None + + def get_credentials(self, url) -> Optional[Credentials]: + # Public feed short circuit: return nothing if not getting credentials for the upload endpoint + # (which always requires auth) and the endpoint is public (can authenticate without credentials). + if not self._is_upload_endpoint(url) and self._can_authenticate( + url, None): + return None + + jwt_str = self._get_jwt_from_helper() + if not jwt_str: + return None + + return self._get_credentials_from_jwt(jwt_str) + + @staticmethod + def _is_upload_endpoint(url) -> bool: + url = url[:-1] if url[-1] == "/" else url + return url.endswith("pypi/upload") + + def _can_authenticate(self, url, auth) -> bool: + response = requests.get(url, auth=auth, timeout=self.timeout) + return response.status_code < 500 and response.status_code not in (401, + 403) + + def _get_jwt_from_helper(self) -> str: + if self.auth_tool_path is None: + raise CredentialProviderError( + "Failed to get credentials: No authentication tool found") + + try: + p = subprocess.run( + [self.auth_tool_path, "get-access-token"], + capture_output=True, + encoding="utf-8", + check=True, + timeout=self.timeout, + ) + stdout = p.stdout + if stdout: + return stdout.strip() + else: + raise CredentialProviderError( + f"Failed to get credentials: No output from subprocess {self.auth_tool_path}" + ) + + except subprocess.CalledProcessError as e: + raise CredentialProviderError( + f"Failed to get credentials: Process {self.auth_tool_path} exited with code {e.returncode}. Error: {e.stderr}" + ) from e + except subprocess.TimeoutExpired as e: + raise CredentialProviderError( + f"Failed to get credentials: Process {self.auth_tool_path} timed out after {self.timeout} seconds" + ) from e + + def _get_credentials_from_jwt(self, jwt_str: str) -> Credentials: + try: + decoded = jwt.decode(jwt_str, + verify=False, + options={"verify_signature": False}) + return Credentials( + username=decoded.get("unique_name", decoded.get("upn", None)), + password=jwt_str, + ) + except jwt.PyJWTError as e: + raise CredentialProviderError(f"Failed to decode JWT: {e}") from e diff --git a/src/artifacts-helper/codespaces_artifacts_helper_keyring/src/codespaces_artifacts_helper_keyring/keyring_backend.py b/src/artifacts-helper/codespaces_artifacts_helper_keyring/src/codespaces_artifacts_helper_keyring/keyring_backend.py new file mode 100644 index 0000000..90f1f2e --- /dev/null +++ b/src/artifacts-helper/codespaces_artifacts_helper_keyring/src/codespaces_artifacts_helper_keyring/keyring_backend.py @@ -0,0 +1,65 @@ +import warnings +from typing import Optional, Type +from urllib.parse import urlsplit + +from jaraco.classes import properties +from keyring.backend import KeyringBackend +from keyring.credentials import Credential, SimpleCredential + +from .artifacts_helper_wrapper import CredentialProvider + + +class CodespacesArtifactsHelperKeyringBackend(KeyringBackend): + SUPPORTED_NETLOC = ( + "pkgs.dev.azure.com", + "pkgs.visualstudio.com", + "pkgs.codedev.ms", + "pkgs.vsts.me", + ) + + _PROVIDER: Type[CredentialProvider] = CredentialProvider + AUTH_HELPER_PATH = _PROVIDER.DEFAULT_AUTH_HELPER_PATH + + @properties.classproperty + @classmethod + def priority(cls) -> float: + if not cls._PROVIDER.auth_helper_installed(cls.AUTH_HELPER_PATH): + raise RuntimeError( + f"Auth helper not found at {cls.AUTH_HELPER_PATH}. " + "Install https://github.com/microsoft/ado-codespaces-auth" + ) + return 10.0 + + def get_credential( + self, service: str, username: Optional[str] + ) -> Optional[Credential]: + if not self._is_supported_netloc(service): + return None + + provider = self._PROVIDER(auth_helper_path=self.AUTH_HELPER_PATH) + creds = provider.get_credentials(service) + if creds is None: + return None + return SimpleCredential(creds.username or username, creds.password) + + def _is_supported_netloc(self, service) -> bool: + try: + parsed = urlsplit(service, scheme="https") + except Exception as exc: + warnings.warn(str(exc), stacklevel=2) + return False + + netloc = parsed.netloc.rpartition("@")[-1] + return netloc is not None and netloc.endswith(self.SUPPORTED_NETLOC) + + def get_password(self, service, username): + creds = self.get_credential(service, None) + if creds and username == creds.username: + return creds.password + return None + + def set_password(self, service, username, password): + raise NotImplementedError() + + def delete_password(self, service, username): + raise NotImplementedError() diff --git a/src/artifacts-helper/codespaces_artifacts_helper_keyring/tests/__init__.py b/src/artifacts-helper/codespaces_artifacts_helper_keyring/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/artifacts-helper/codespaces_artifacts_helper_keyring/tests/test_artifacts_helper_wrapper.py b/src/artifacts-helper/codespaces_artifacts_helper_keyring/tests/test_artifacts_helper_wrapper.py new file mode 100644 index 0000000..6a9e04b --- /dev/null +++ b/src/artifacts-helper/codespaces_artifacts_helper_keyring/tests/test_artifacts_helper_wrapper.py @@ -0,0 +1,108 @@ +import os +import stat +import unittest +from pathlib import Path +from typing import Optional, Union + +import pytest +from codespaces_artifacts_helper_keyring import ( + CredentialProvider, +) +from codespaces_artifacts_helper_keyring.artifacts_helper_wrapper import ( + CredentialProviderError, +) + + +class TestArtifactsHelperWrapper(unittest.TestCase): + SUPPORTED_HOST = "https://pkgs.dev.azure.com/" + TEST_JWT = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1cG4iOiJ1cG5AY29udG9zby5jb20iLCJ1bmlxdWVfbmFtZSI6Im5hbWVAY29udG9zby5jb20ifQ.srKYrr5B0i29XERHsvE6mqZpLBzyyrX-gUKe9OHZODw" + TEST_JWT_USERNAME = "name@contoso.com" + + def __init__(self, *args, **kwargs) -> None: + super().__init__(*args, **kwargs) + self.tmp_dir: Optional[os.PathLike] = None + self.script_name = "mock_artifacts_helper.py" + + @pytest.fixture(autouse=True) + def init_tmp_dir(self, tmp_path): + self.tmp_dir = Path(tmp_path) + + @property + def script_path(self): + return self.tmp_dir / self.script_name + + def write_script(self, content: str, shebang: str = "#!/usr/bin/env python3"): + with open(self.script_path, "w", encoding="utf-8") as f: + f.write(shebang + "\n") + f.write(content) + set_path_executable(self.script_path) + + def test_auth_helper_installed_invalid_path(self): + assert not CredentialProvider.resolve_auth_helper_path( + self.tmp_dir / "nonexistent" + ) + assert not CredentialProvider.auth_helper_installed( + self.tmp_dir / "nonexistent" + ) + + def test_auth_helper_installed_and_not_executable(self): + self.write_script("pass") + set_path_not_executable(self.script_path) + assert not CredentialProvider.resolve_auth_helper_path(self.script_path) + assert not CredentialProvider.auth_helper_installed(self.script_path) + + def test_auth_helper_installed_and_executable(self): + self.write_script("pass") + assert CredentialProvider.resolve_auth_helper_path(self.script_path) is not None + assert CredentialProvider.auth_helper_installed(self.script_path) + + def test_get_jwt_from_helper(self): + raw_jwt_value = "raw_jwt_here._-azAZ09" + self.write_script(f"print('{raw_jwt_value}')") + provider = CredentialProvider(self.script_path) + assert provider._get_jwt_from_helper() == raw_jwt_value.strip() + + def test_get_jwt_from_helper_not_installed(self): + provider = CredentialProvider() + with pytest.raises( + CredentialProviderError, match="No authentication tool found" + ): + provider._get_jwt_from_helper() + + def test_get_credentials_from_jwt(self): + provider = CredentialProvider() + creds = provider._get_credentials_from_jwt(self.TEST_JWT) + assert creds.username == self.TEST_JWT_USERNAME + assert creds.password == self.TEST_JWT + + def test_get_credentials(self): + self.write_script(f"print('{self.TEST_JWT}')") + provider = CredentialProvider(self.script_path) + creds = provider.get_credentials(self.SUPPORTED_HOST) + assert creds.username == self.TEST_JWT_USERNAME + assert creds.password == self.TEST_JWT + + def test_get_credentials_invalid_jwt(self): + self.write_script("print('invalid jwt')") + provider = CredentialProvider(self.script_path) + with pytest.raises(CredentialProviderError, match="Failed to decode JWT:"): + provider.get_credentials(self.SUPPORTED_HOST) + + def test_get_crendentials_helper_non_zero_exit(self): + self.write_script("exit(1)") + provider = CredentialProvider(self.script_path) + with pytest.raises( + CredentialProviderError, + match=f"Process .*{self.script_name}.* exited with code 1", + ): + provider.get_credentials(self.SUPPORTED_HOST) + + +def set_path_executable(path: Union[os.PathLike, str]): + p = Path(path) + p.chmod(p.stat().st_mode | stat.S_IEXEC) + + +def set_path_not_executable(path: Union[os.PathLike, str]): + p = Path(path) + p.chmod(p.stat().st_mode & ~stat.S_IEXEC) diff --git a/src/artifacts-helper/codespaces_artifacts_helper_keyring/tests/test_backend.py b/src/artifacts-helper/codespaces_artifacts_helper_keyring/tests/test_backend.py new file mode 100644 index 0000000..dde4abf --- /dev/null +++ b/src/artifacts-helper/codespaces_artifacts_helper_keyring/tests/test_backend.py @@ -0,0 +1,145 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +import keyring +import keyring.backend +import keyring.backends.chainer +import keyring.errors +import pytest +from codespaces_artifacts_helper_keyring import ( + CodespacesArtifactsHelperKeyringBackend, + CredentialProvider, +) +from keyring.credentials import SimpleCredential + +# Shouldn't be accessed by tests, but needs to be able +# to get past the quick check. +SUPPORTED_HOST = "https://pkgs.dev.azure.com/" + + +class FakeProvider(CredentialProvider): + + def get_credentials(self, service): + return SimpleCredential("user" + service[-4:], "pass" + service[-4:]) + + @staticmethod + def auth_helper_installed(auth_tool_path): + return True + + +class PasswordsBackend(keyring.backend.KeyringBackend): + priority = 10.0 # type: ignore + + def __init__(self): + self.passwords = {} + + def get_password(self, system, username): + return self.passwords.get((system, username)) + + def set_password(self, system, username, password): + self.passwords[system, username] = password + + def delete_password(self, system, username): + try: + del self.passwords[system, username] + except LookupError as e: + raise keyring.errors.PasswordDeleteError(username) from e + + +class MockGetResponse: + status_code = 200 + + +@pytest.fixture +def only_backend(): + previous = keyring.get_keyring() + backend = CodespacesArtifactsHelperKeyringBackend() + keyring.set_keyring(backend) + yield backend + keyring.set_keyring(previous) + + +@pytest.fixture +def passwords(monkeypatch): + passwords_backend = PasswordsBackend() + + def mock_get_all_keyring(): + return [CodespacesArtifactsHelperKeyringBackend(), passwords_backend] + + monkeypatch.setattr(keyring.backend, "get_all_keyring", + mock_get_all_keyring) + + chainer_backend = keyring.backends.chainer.ChainerBackend() + + previous = keyring.get_keyring() + keyring.set_keyring(chainer_backend) + yield passwords_backend.passwords + keyring.set_keyring(previous) + + +@pytest.fixture +def fake_provider(monkeypatch): + monkeypatch.setattr(CodespacesArtifactsHelperKeyringBackend, "_PROVIDER", + FakeProvider) + + +def test_get_credential_unsupported_host(only_backend): + assert keyring.get_credential("https://example.com", None) is None + + +def test_get_credential(only_backend, fake_provider): + creds = keyring.get_credential(SUPPORTED_HOST + "1234", None) + assert creds.username == "user1234" + assert creds.password == "pass1234" + + +def test_set_password_raises(only_backend): + with pytest.raises(NotImplementedError): + keyring.set_password("SYSTEM", "USERNAME", "PASSWORD") + + +def test_set_password_fallback(passwords, fake_provider): + # Ensure we are getting good credentials + assert keyring.get_credential(SUPPORTED_HOST + "1234", + None).password == "pass1234" + + assert keyring.get_password("SYSTEM", "USERNAME") is None + keyring.set_password("SYSTEM", "USERNAME", "PASSWORD") + assert passwords["SYSTEM", "USERNAME"] == "PASSWORD" + assert keyring.get_password("SYSTEM", "USERNAME") == "PASSWORD" + assert keyring.get_credential("SYSTEM", "USERNAME").username == "USERNAME" + assert keyring.get_credential("SYSTEM", "USERNAME").password == "PASSWORD" + + # Ensure we are getting good credentials + assert keyring.get_credential(SUPPORTED_HOST + "1234", + None).password == "pass1234" + + +def test_delete_password_raises(only_backend): + with pytest.raises(NotImplementedError): + keyring.delete_password("SYSTEM", "USERNAME") + + +def test_delete_password_fallback(passwords, fake_provider): + # Ensure we are getting good credentials + assert keyring.get_credential(SUPPORTED_HOST + "1234", + None).password == "pass1234" + + passwords["SYSTEM", "USERNAME"] = "PASSWORD" + keyring.delete_password("SYSTEM", "USERNAME") + assert keyring.get_password("SYSTEM", "USERNAME") is None + assert not passwords + with pytest.raises(keyring.errors.PasswordDeleteError): + keyring.delete_password("SYSTEM", "USERNAME") + + +def test_cannot_delete_password(passwords, fake_provider): + # Ensure we are getting good credentials + creds = keyring.get_credential(SUPPORTED_HOST + "1234", None) + assert creds.username == "user1234" + assert creds.password == "pass1234" + + with pytest.raises(keyring.errors.PasswordDeleteError): + keyring.delete_password(SUPPORTED_HOST + "1234", creds.username) From d6db7e51ea40f21a14b450a11c035c5ddd222706 Mon Sep 17 00:00:00 2001 From: Delilah Wu Date: Tue, 23 Apr 2024 09:54:18 +1000 Subject: [PATCH 02/15] refactor: Rename CredentialProvider to ArtifactsHelperCredentialProvider Also renames the test files to correspond to the exercised source file. --- .../__init__.py | 2 +- ...> artifacts_helper_credential_provider.py} | 40 ++++++++--------- .../keyring_backend.py | 6 ++- ...t_artifacts_helper_credential_provider.py} | 45 +++++++++++-------- ...est_backend.py => test_keyring_backend.py} | 22 ++++----- 5 files changed, 61 insertions(+), 54 deletions(-) rename src/artifacts-helper/codespaces_artifacts_helper_keyring/src/codespaces_artifacts_helper_keyring/{artifacts_helper_wrapper.py => artifacts_helper_credential_provider.py} (79%) rename src/artifacts-helper/codespaces_artifacts_helper_keyring/tests/{test_artifacts_helper_wrapper.py => test_artifacts_helper_credential_provider.py} (67%) rename src/artifacts-helper/codespaces_artifacts_helper_keyring/tests/{test_backend.py => test_keyring_backend.py} (86%) diff --git a/src/artifacts-helper/codespaces_artifacts_helper_keyring/src/codespaces_artifacts_helper_keyring/__init__.py b/src/artifacts-helper/codespaces_artifacts_helper_keyring/src/codespaces_artifacts_helper_keyring/__init__.py index a8d7576..00e812d 100644 --- a/src/artifacts-helper/codespaces_artifacts_helper_keyring/src/codespaces_artifacts_helper_keyring/__init__.py +++ b/src/artifacts-helper/codespaces_artifacts_helper_keyring/src/codespaces_artifacts_helper_keyring/__init__.py @@ -1,2 +1,2 @@ -from .artifacts_helper_wrapper import CredentialProvider +from .artifacts_helper_credential_provider import ArtifactsHelperCredentialProvider from .keyring_backend import CodespacesArtifactsHelperKeyringBackend diff --git a/src/artifacts-helper/codespaces_artifacts_helper_keyring/src/codespaces_artifacts_helper_keyring/artifacts_helper_wrapper.py b/src/artifacts-helper/codespaces_artifacts_helper_keyring/src/codespaces_artifacts_helper_keyring/artifacts_helper_credential_provider.py similarity index 79% rename from src/artifacts-helper/codespaces_artifacts_helper_keyring/src/codespaces_artifacts_helper_keyring/artifacts_helper_wrapper.py rename to src/artifacts-helper/codespaces_artifacts_helper_keyring/src/codespaces_artifacts_helper_keyring/artifacts_helper_credential_provider.py index 5cf4b7e..066125c 100644 --- a/src/artifacts-helper/codespaces_artifacts_helper_keyring/src/codespaces_artifacts_helper_keyring/artifacts_helper_wrapper.py +++ b/src/artifacts-helper/codespaces_artifacts_helper_keyring/src/codespaces_artifacts_helper_keyring/artifacts_helper_credential_provider.py @@ -22,11 +22,11 @@ class Credentials: password: str -class CredentialProviderError(RuntimeError): +class ArtifactsHelperCredentialProviderError(RuntimeError): pass -class CredentialProvider: +class ArtifactsHelperCredentialProvider: DEFAULT_AUTH_HELPER_PATH = "~/ado-auth-helper" def __init__( @@ -39,20 +39,18 @@ def __init__( @staticmethod def resolve_auth_helper_path( - auth_helper_path: Union[os.PathLike, str], ) -> Optional[str]: - return shutil.which(str(Path(auth_helper_path).expanduser()), - mode=os.X_OK) + auth_helper_path: Union[os.PathLike, str], + ) -> Optional[str]: + return shutil.which(str(Path(auth_helper_path).expanduser()), mode=os.X_OK) @classmethod - def auth_helper_installed( - cls, auth_helper_path: Union[os.PathLike, str]) -> bool: + def auth_helper_installed(cls, auth_helper_path: Union[os.PathLike, str]) -> bool: return cls.resolve_auth_helper_path(auth_helper_path) is not None def get_credentials(self, url) -> Optional[Credentials]: # Public feed short circuit: return nothing if not getting credentials for the upload endpoint # (which always requires auth) and the endpoint is public (can authenticate without credentials). - if not self._is_upload_endpoint(url) and self._can_authenticate( - url, None): + if not self._is_upload_endpoint(url) and self._can_authenticate(url, None): return None jwt_str = self._get_jwt_from_helper() @@ -68,13 +66,13 @@ def _is_upload_endpoint(url) -> bool: def _can_authenticate(self, url, auth) -> bool: response = requests.get(url, auth=auth, timeout=self.timeout) - return response.status_code < 500 and response.status_code not in (401, - 403) + return response.status_code < 500 and response.status_code not in (401, 403) def _get_jwt_from_helper(self) -> str: if self.auth_tool_path is None: - raise CredentialProviderError( - "Failed to get credentials: No authentication tool found") + raise ArtifactsHelperCredentialProviderError( + "Failed to get credentials: No authentication tool found" + ) try: p = subprocess.run( @@ -88,27 +86,29 @@ def _get_jwt_from_helper(self) -> str: if stdout: return stdout.strip() else: - raise CredentialProviderError( + raise ArtifactsHelperCredentialProviderError( f"Failed to get credentials: No output from subprocess {self.auth_tool_path}" ) except subprocess.CalledProcessError as e: - raise CredentialProviderError( + raise ArtifactsHelperCredentialProviderError( f"Failed to get credentials: Process {self.auth_tool_path} exited with code {e.returncode}. Error: {e.stderr}" ) from e except subprocess.TimeoutExpired as e: - raise CredentialProviderError( + raise ArtifactsHelperCredentialProviderError( f"Failed to get credentials: Process {self.auth_tool_path} timed out after {self.timeout} seconds" ) from e def _get_credentials_from_jwt(self, jwt_str: str) -> Credentials: try: - decoded = jwt.decode(jwt_str, - verify=False, - options={"verify_signature": False}) + decoded = jwt.decode( + jwt_str, verify=False, options={"verify_signature": False} + ) return Credentials( username=decoded.get("unique_name", decoded.get("upn", None)), password=jwt_str, ) except jwt.PyJWTError as e: - raise CredentialProviderError(f"Failed to decode JWT: {e}") from e + raise ArtifactsHelperCredentialProviderError( + f"Failed to decode JWT: {e}" + ) from e diff --git a/src/artifacts-helper/codespaces_artifacts_helper_keyring/src/codespaces_artifacts_helper_keyring/keyring_backend.py b/src/artifacts-helper/codespaces_artifacts_helper_keyring/src/codespaces_artifacts_helper_keyring/keyring_backend.py index 90f1f2e..76dc73e 100644 --- a/src/artifacts-helper/codespaces_artifacts_helper_keyring/src/codespaces_artifacts_helper_keyring/keyring_backend.py +++ b/src/artifacts-helper/codespaces_artifacts_helper_keyring/src/codespaces_artifacts_helper_keyring/keyring_backend.py @@ -6,7 +6,7 @@ from keyring.backend import KeyringBackend from keyring.credentials import Credential, SimpleCredential -from .artifacts_helper_wrapper import CredentialProvider +from .artifacts_helper_credential_provider import ArtifactsHelperCredentialProvider class CodespacesArtifactsHelperKeyringBackend(KeyringBackend): @@ -17,7 +17,9 @@ class CodespacesArtifactsHelperKeyringBackend(KeyringBackend): "pkgs.vsts.me", ) - _PROVIDER: Type[CredentialProvider] = CredentialProvider + _PROVIDER: Type[ArtifactsHelperCredentialProvider] = ( + ArtifactsHelperCredentialProvider + ) AUTH_HELPER_PATH = _PROVIDER.DEFAULT_AUTH_HELPER_PATH @properties.classproperty diff --git a/src/artifacts-helper/codespaces_artifacts_helper_keyring/tests/test_artifacts_helper_wrapper.py b/src/artifacts-helper/codespaces_artifacts_helper_keyring/tests/test_artifacts_helper_credential_provider.py similarity index 67% rename from src/artifacts-helper/codespaces_artifacts_helper_keyring/tests/test_artifacts_helper_wrapper.py rename to src/artifacts-helper/codespaces_artifacts_helper_keyring/tests/test_artifacts_helper_credential_provider.py index 6a9e04b..1bd5f52 100644 --- a/src/artifacts-helper/codespaces_artifacts_helper_keyring/tests/test_artifacts_helper_wrapper.py +++ b/src/artifacts-helper/codespaces_artifacts_helper_keyring/tests/test_artifacts_helper_credential_provider.py @@ -6,10 +6,10 @@ import pytest from codespaces_artifacts_helper_keyring import ( - CredentialProvider, + ArtifactsHelperCredentialProvider, ) -from codespaces_artifacts_helper_keyring.artifacts_helper_wrapper import ( - CredentialProviderError, +from codespaces_artifacts_helper_keyring.artifacts_helper_credential_provider import ( + ArtifactsHelperCredentialProviderError, ) @@ -38,61 +38,70 @@ def write_script(self, content: str, shebang: str = "#!/usr/bin/env python3"): set_path_executable(self.script_path) def test_auth_helper_installed_invalid_path(self): - assert not CredentialProvider.resolve_auth_helper_path( + assert not ArtifactsHelperCredentialProvider.resolve_auth_helper_path( self.tmp_dir / "nonexistent" ) - assert not CredentialProvider.auth_helper_installed( + assert not ArtifactsHelperCredentialProvider.auth_helper_installed( self.tmp_dir / "nonexistent" ) def test_auth_helper_installed_and_not_executable(self): self.write_script("pass") set_path_not_executable(self.script_path) - assert not CredentialProvider.resolve_auth_helper_path(self.script_path) - assert not CredentialProvider.auth_helper_installed(self.script_path) + assert not ArtifactsHelperCredentialProvider.resolve_auth_helper_path( + self.script_path + ) + assert not ArtifactsHelperCredentialProvider.auth_helper_installed( + self.script_path + ) def test_auth_helper_installed_and_executable(self): self.write_script("pass") - assert CredentialProvider.resolve_auth_helper_path(self.script_path) is not None - assert CredentialProvider.auth_helper_installed(self.script_path) + assert ( + ArtifactsHelperCredentialProvider.resolve_auth_helper_path(self.script_path) + is not None + ) + assert ArtifactsHelperCredentialProvider.auth_helper_installed(self.script_path) def test_get_jwt_from_helper(self): raw_jwt_value = "raw_jwt_here._-azAZ09" self.write_script(f"print('{raw_jwt_value}')") - provider = CredentialProvider(self.script_path) + provider = ArtifactsHelperCredentialProvider(self.script_path) assert provider._get_jwt_from_helper() == raw_jwt_value.strip() def test_get_jwt_from_helper_not_installed(self): - provider = CredentialProvider() + provider = ArtifactsHelperCredentialProvider() with pytest.raises( - CredentialProviderError, match="No authentication tool found" + ArtifactsHelperCredentialProviderError, match="No authentication tool found" ): provider._get_jwt_from_helper() def test_get_credentials_from_jwt(self): - provider = CredentialProvider() + provider = ArtifactsHelperCredentialProvider() creds = provider._get_credentials_from_jwt(self.TEST_JWT) assert creds.username == self.TEST_JWT_USERNAME assert creds.password == self.TEST_JWT def test_get_credentials(self): self.write_script(f"print('{self.TEST_JWT}')") - provider = CredentialProvider(self.script_path) + provider = ArtifactsHelperCredentialProvider(self.script_path) creds = provider.get_credentials(self.SUPPORTED_HOST) assert creds.username == self.TEST_JWT_USERNAME assert creds.password == self.TEST_JWT def test_get_credentials_invalid_jwt(self): self.write_script("print('invalid jwt')") - provider = CredentialProvider(self.script_path) - with pytest.raises(CredentialProviderError, match="Failed to decode JWT:"): + provider = ArtifactsHelperCredentialProvider(self.script_path) + with pytest.raises( + ArtifactsHelperCredentialProviderError, match="Failed to decode JWT:" + ): provider.get_credentials(self.SUPPORTED_HOST) def test_get_crendentials_helper_non_zero_exit(self): self.write_script("exit(1)") - provider = CredentialProvider(self.script_path) + provider = ArtifactsHelperCredentialProvider(self.script_path) with pytest.raises( - CredentialProviderError, + ArtifactsHelperCredentialProviderError, match=f"Process .*{self.script_name}.* exited with code 1", ): provider.get_credentials(self.SUPPORTED_HOST) diff --git a/src/artifacts-helper/codespaces_artifacts_helper_keyring/tests/test_backend.py b/src/artifacts-helper/codespaces_artifacts_helper_keyring/tests/test_keyring_backend.py similarity index 86% rename from src/artifacts-helper/codespaces_artifacts_helper_keyring/tests/test_backend.py rename to src/artifacts-helper/codespaces_artifacts_helper_keyring/tests/test_keyring_backend.py index dde4abf..b34fd07 100644 --- a/src/artifacts-helper/codespaces_artifacts_helper_keyring/tests/test_backend.py +++ b/src/artifacts-helper/codespaces_artifacts_helper_keyring/tests/test_keyring_backend.py @@ -9,8 +9,8 @@ import keyring.errors import pytest from codespaces_artifacts_helper_keyring import ( + ArtifactsHelperCredentialProvider, CodespacesArtifactsHelperKeyringBackend, - CredentialProvider, ) from keyring.credentials import SimpleCredential @@ -19,8 +19,7 @@ SUPPORTED_HOST = "https://pkgs.dev.azure.com/" -class FakeProvider(CredentialProvider): - +class FakeProvider(ArtifactsHelperCredentialProvider): def get_credentials(self, service): return SimpleCredential("user" + service[-4:], "pass" + service[-4:]) @@ -68,8 +67,7 @@ def passwords(monkeypatch): def mock_get_all_keyring(): return [CodespacesArtifactsHelperKeyringBackend(), passwords_backend] - monkeypatch.setattr(keyring.backend, "get_all_keyring", - mock_get_all_keyring) + monkeypatch.setattr(keyring.backend, "get_all_keyring", mock_get_all_keyring) chainer_backend = keyring.backends.chainer.ChainerBackend() @@ -81,8 +79,9 @@ def mock_get_all_keyring(): @pytest.fixture def fake_provider(monkeypatch): - monkeypatch.setattr(CodespacesArtifactsHelperKeyringBackend, "_PROVIDER", - FakeProvider) + monkeypatch.setattr( + CodespacesArtifactsHelperKeyringBackend, "_PROVIDER", FakeProvider + ) def test_get_credential_unsupported_host(only_backend): @@ -102,8 +101,7 @@ def test_set_password_raises(only_backend): def test_set_password_fallback(passwords, fake_provider): # Ensure we are getting good credentials - assert keyring.get_credential(SUPPORTED_HOST + "1234", - None).password == "pass1234" + assert keyring.get_credential(SUPPORTED_HOST + "1234", None).password == "pass1234" assert keyring.get_password("SYSTEM", "USERNAME") is None keyring.set_password("SYSTEM", "USERNAME", "PASSWORD") @@ -113,8 +111,7 @@ def test_set_password_fallback(passwords, fake_provider): assert keyring.get_credential("SYSTEM", "USERNAME").password == "PASSWORD" # Ensure we are getting good credentials - assert keyring.get_credential(SUPPORTED_HOST + "1234", - None).password == "pass1234" + assert keyring.get_credential(SUPPORTED_HOST + "1234", None).password == "pass1234" def test_delete_password_raises(only_backend): @@ -124,8 +121,7 @@ def test_delete_password_raises(only_backend): def test_delete_password_fallback(passwords, fake_provider): # Ensure we are getting good credentials - assert keyring.get_credential(SUPPORTED_HOST + "1234", - None).password == "pass1234" + assert keyring.get_credential(SUPPORTED_HOST + "1234", None).password == "pass1234" passwords["SYSTEM", "USERNAME"] = "PASSWORD" keyring.delete_password("SYSTEM", "USERNAME") From 4cada009a487133219692fea1ebb4e60f37427d7 Mon Sep 17 00:00:00 2001 From: Delilah Wu Date: Tue, 23 Apr 2024 10:40:02 +1000 Subject: [PATCH 03/15] chore: Adjust linting rules Add rules to detect lines exceeding max length, missing docstrings, etc. See diff and ruff rules for more details. --- .../codespaces_artifacts_helper_keyring/pyproject.toml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/artifacts-helper/codespaces_artifacts_helper_keyring/pyproject.toml b/src/artifacts-helper/codespaces_artifacts_helper_keyring/pyproject.toml index c7ec4d1..c686224 100644 --- a/src/artifacts-helper/codespaces_artifacts_helper_keyring/pyproject.toml +++ b/src/artifacts-helper/codespaces_artifacts_helper_keyring/pyproject.toml @@ -52,11 +52,12 @@ line-length = 88 indent-width = 4 [tool.ruff.lint] -extend-select = ["E4", "E7", "E9", "F", "B", "I", "W", "Q"] +extend-select = ["B", "C4", "D", "E", "F", "I", "W", "Q"] [tool.ruff.lint.pydocstyle] convention = "google" [tool.ruff.lint.per-file-ignores] "__init__.py" = ["F401"] -"**/{tests,docs,tools}/*" = ["E402"] +"noxfile.py" = ["D1"] +"**/{tests,docs,tools}/*" = ["D1", "E402"] From 7d562d59a88a3e553c91a6c854fed05f39817368 Mon Sep 17 00:00:00 2001 From: Delilah Wu Date: Tue, 23 Apr 2024 10:53:55 +1000 Subject: [PATCH 04/15] docs: Add documentation and lint --- .../pyproject.toml | 1 + .../__init__.py | 2 + .../artifacts_helper_credential_provider.py | 64 +++++++++++++++---- .../keyring_backend.py | 4 ++ ...st_artifacts_helper_credential_provider.py | 6 +- .../tests/test_keyring_backend.py | 5 -- 6 files changed, 62 insertions(+), 20 deletions(-) diff --git a/src/artifacts-helper/codespaces_artifacts_helper_keyring/pyproject.toml b/src/artifacts-helper/codespaces_artifacts_helper_keyring/pyproject.toml index c686224..fa5aae6 100644 --- a/src/artifacts-helper/codespaces_artifacts_helper_keyring/pyproject.toml +++ b/src/artifacts-helper/codespaces_artifacts_helper_keyring/pyproject.toml @@ -53,6 +53,7 @@ indent-width = 4 [tool.ruff.lint] extend-select = ["B", "C4", "D", "E", "F", "I", "W", "Q"] +ignore = ["D102"] [tool.ruff.lint.pydocstyle] convention = "google" diff --git a/src/artifacts-helper/codespaces_artifacts_helper_keyring/src/codespaces_artifacts_helper_keyring/__init__.py b/src/artifacts-helper/codespaces_artifacts_helper_keyring/src/codespaces_artifacts_helper_keyring/__init__.py index 00e812d..85e2650 100644 --- a/src/artifacts-helper/codespaces_artifacts_helper_keyring/src/codespaces_artifacts_helper_keyring/__init__.py +++ b/src/artifacts-helper/codespaces_artifacts_helper_keyring/src/codespaces_artifacts_helper_keyring/__init__.py @@ -1,2 +1,4 @@ +"""Provides a keyring backend for the Codespaces Artifacts Helper.""" + from .artifacts_helper_credential_provider import ArtifactsHelperCredentialProvider from .keyring_backend import CodespacesArtifactsHelperKeyringBackend diff --git a/src/artifacts-helper/codespaces_artifacts_helper_keyring/src/codespaces_artifacts_helper_keyring/artifacts_helper_credential_provider.py b/src/artifacts-helper/codespaces_artifacts_helper_keyring/src/codespaces_artifacts_helper_keyring/artifacts_helper_credential_provider.py index 066125c..8b3f4b5 100644 --- a/src/artifacts-helper/codespaces_artifacts_helper_keyring/src/codespaces_artifacts_helper_keyring/artifacts_helper_credential_provider.py +++ b/src/artifacts-helper/codespaces_artifacts_helper_keyring/src/codespaces_artifacts_helper_keyring/artifacts_helper_credential_provider.py @@ -1,7 +1,4 @@ -# -------------------------------------------------------------------------------------------- -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. See LICENSE in the project root for license information. -# -------------------------------------------------------------------------------------------- +"""Wrapper to interface with the artifacts authentication helper.""" from __future__ import absolute_import @@ -18,15 +15,31 @@ @dataclass class Credentials: + """A set of credentials, consisting of a username and password.""" + username: str password: str class ArtifactsHelperCredentialProviderError(RuntimeError): - pass + """Generic error for ArtifactsHelperCredentialProvider.""" class ArtifactsHelperCredentialProvider: + """A wrapper retrieve credentials from the artifacts authentication helper. + + The authentication helper should be installed from + https://github.com/microsoft/ado-codespaces-auth. + + Attributes: + DEFAULT_AUTH_HELPER_PATH: The default path to the authentication helper + executable. + + Raises: + ArtifactsHelperCredentialProviderError: When the credentials could not be + retrieved. + """ + DEFAULT_AUTH_HELPER_PATH = "~/ado-auth-helper" def __init__( @@ -34,6 +47,16 @@ def __init__( auth_helper_path: Union[os.PathLike, str] = DEFAULT_AUTH_HELPER_PATH, timeout: int = 30, ): + """Initialise the provider. + + Args: + auth_helper_path: The path to the authentication helper executable, or the + name of the executable if it is in the PATH. Defaults to + DEFAULT_AUTH_HELPER_PATH. + + timeout: The timeout in seconds for calling the authentication helper and + any HTTP requests made to test credentials. Defaults to 30. + """ self.auth_tool_path = self.resolve_auth_helper_path(auth_helper_path) self.timeout = timeout @@ -41,15 +64,31 @@ def __init__( def resolve_auth_helper_path( auth_helper_path: Union[os.PathLike, str], ) -> Optional[str]: + """Resolve the path to the authentication helper executable. + + Returns: + The path to the authentication helper executable, or `None` if it is not + executable or not found. + """ return shutil.which(str(Path(auth_helper_path).expanduser()), mode=os.X_OK) @classmethod def auth_helper_installed(cls, auth_helper_path: Union[os.PathLike, str]) -> bool: + """Check whether the authentication helper is installed and executable.""" return cls.resolve_auth_helper_path(auth_helper_path) is not None - def get_credentials(self, url) -> Optional[Credentials]: - # Public feed short circuit: return nothing if not getting credentials for the upload endpoint - # (which always requires auth) and the endpoint is public (can authenticate without credentials). + def get_credentials(self, url: str) -> Optional[Credentials]: + """Get credentials for the given URL. + + Args: + url: The URL to retrieve credentials for. + + Returns: + The credentials for the URL, or `None` if no credentials could be retrieved. + """ + # Public feed short circuit: return nothing if not getting credentials for the + # upload endpoint (which always requires auth) and the endpoint is public (can + # authenticate without credentials). if not self._is_upload_endpoint(url) and self._can_authenticate(url, None): return None @@ -87,16 +126,19 @@ def _get_jwt_from_helper(self) -> str: return stdout.strip() else: raise ArtifactsHelperCredentialProviderError( - f"Failed to get credentials: No output from subprocess {self.auth_tool_path}" + "Failed to get credentials: " + f"No output from subprocess {self.auth_tool_path}" ) except subprocess.CalledProcessError as e: raise ArtifactsHelperCredentialProviderError( - f"Failed to get credentials: Process {self.auth_tool_path} exited with code {e.returncode}. Error: {e.stderr}" + f"Failed to get credentials: Process {self.auth_tool_path} exited with " + f"code {e.returncode}. Error: {e.stderr}" ) from e except subprocess.TimeoutExpired as e: raise ArtifactsHelperCredentialProviderError( - f"Failed to get credentials: Process {self.auth_tool_path} timed out after {self.timeout} seconds" + f"Failed to get credentials: Process {self.auth_tool_path} timed out " + f"after {self.timeout} seconds" ) from e def _get_credentials_from_jwt(self, jwt_str: str) -> Credentials: diff --git a/src/artifacts-helper/codespaces_artifacts_helper_keyring/src/codespaces_artifacts_helper_keyring/keyring_backend.py b/src/artifacts-helper/codespaces_artifacts_helper_keyring/src/codespaces_artifacts_helper_keyring/keyring_backend.py index 76dc73e..b7bdc0a 100644 --- a/src/artifacts-helper/codespaces_artifacts_helper_keyring/src/codespaces_artifacts_helper_keyring/keyring_backend.py +++ b/src/artifacts-helper/codespaces_artifacts_helper_keyring/src/codespaces_artifacts_helper_keyring/keyring_backend.py @@ -1,3 +1,5 @@ +"""Keyring backend for Codespaces Artifacts Helper.""" + import warnings from typing import Optional, Type from urllib.parse import urlsplit @@ -10,6 +12,8 @@ class CodespacesArtifactsHelperKeyringBackend(KeyringBackend): + """A keyring backend for Azure Artifacts using the ADO Codespaces Auth Helper.""" + SUPPORTED_NETLOC = ( "pkgs.dev.azure.com", "pkgs.visualstudio.com", diff --git a/src/artifacts-helper/codespaces_artifacts_helper_keyring/tests/test_artifacts_helper_credential_provider.py b/src/artifacts-helper/codespaces_artifacts_helper_keyring/tests/test_artifacts_helper_credential_provider.py index 1bd5f52..2e73adc 100644 --- a/src/artifacts-helper/codespaces_artifacts_helper_keyring/tests/test_artifacts_helper_credential_provider.py +++ b/src/artifacts-helper/codespaces_artifacts_helper_keyring/tests/test_artifacts_helper_credential_provider.py @@ -5,9 +5,7 @@ from typing import Optional, Union import pytest -from codespaces_artifacts_helper_keyring import ( - ArtifactsHelperCredentialProvider, -) +from codespaces_artifacts_helper_keyring import ArtifactsHelperCredentialProvider from codespaces_artifacts_helper_keyring.artifacts_helper_credential_provider import ( ArtifactsHelperCredentialProviderError, ) @@ -15,7 +13,7 @@ class TestArtifactsHelperWrapper(unittest.TestCase): SUPPORTED_HOST = "https://pkgs.dev.azure.com/" - TEST_JWT = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1cG4iOiJ1cG5AY29udG9zby5jb20iLCJ1bmlxdWVfbmFtZSI6Im5hbWVAY29udG9zby5jb20ifQ.srKYrr5B0i29XERHsvE6mqZpLBzyyrX-gUKe9OHZODw" + TEST_JWT = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1cG4iOiJ1cG5AY29udG9zby5jb20iLCJ1bmlxdWVfbmFtZSI6Im5hbWVAY29udG9zby5jb20ifQ.srKYrr5B0i29XERHsvE6mqZpLBzyyrX-gUKe9OHZODw" # noqa: E501 TEST_JWT_USERNAME = "name@contoso.com" def __init__(self, *args, **kwargs) -> None: diff --git a/src/artifacts-helper/codespaces_artifacts_helper_keyring/tests/test_keyring_backend.py b/src/artifacts-helper/codespaces_artifacts_helper_keyring/tests/test_keyring_backend.py index b34fd07..6a2f488 100644 --- a/src/artifacts-helper/codespaces_artifacts_helper_keyring/tests/test_keyring_backend.py +++ b/src/artifacts-helper/codespaces_artifacts_helper_keyring/tests/test_keyring_backend.py @@ -1,8 +1,3 @@ -# -------------------------------------------------------------------------------------------- -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. See License.txt in the project root for license information. -# -------------------------------------------------------------------------------------------- - import keyring import keyring.backend import keyring.backends.chainer From ca169f9a0d42f07789aaef864e9c958d86603e49 Mon Sep 17 00:00:00 2001 From: Delilah Wu Date: Tue, 23 Apr 2024 11:13:43 +1000 Subject: [PATCH 05/15] feat: Remove JWT parsing and adjust tests --- .../artifacts_helper_credential_provider.py | 39 +++---------------- .../keyring_backend.py | 8 ++-- ...st_artifacts_helper_credential_provider.py | 38 +++++++----------- .../tests/test_keyring_backend.py | 17 +++++--- 4 files changed, 35 insertions(+), 67 deletions(-) diff --git a/src/artifacts-helper/codespaces_artifacts_helper_keyring/src/codespaces_artifacts_helper_keyring/artifacts_helper_credential_provider.py b/src/artifacts-helper/codespaces_artifacts_helper_keyring/src/codespaces_artifacts_helper_keyring/artifacts_helper_credential_provider.py index 8b3f4b5..5d8996e 100644 --- a/src/artifacts-helper/codespaces_artifacts_helper_keyring/src/codespaces_artifacts_helper_keyring/artifacts_helper_credential_provider.py +++ b/src/artifacts-helper/codespaces_artifacts_helper_keyring/src/codespaces_artifacts_helper_keyring/artifacts_helper_credential_provider.py @@ -5,22 +5,12 @@ import os import shutil import subprocess -from dataclasses import dataclass from pathlib import Path from typing import Optional, Union -import jwt import requests -@dataclass -class Credentials: - """A set of credentials, consisting of a username and password.""" - - username: str - password: str - - class ArtifactsHelperCredentialProviderError(RuntimeError): """Generic error for ArtifactsHelperCredentialProvider.""" @@ -77,14 +67,14 @@ def auth_helper_installed(cls, auth_helper_path: Union[os.PathLike, str]) -> boo """Check whether the authentication helper is installed and executable.""" return cls.resolve_auth_helper_path(auth_helper_path) is not None - def get_credentials(self, url: str) -> Optional[Credentials]: - """Get credentials for the given URL. + def get_token(self, url: str) -> Optional[str]: + """Get an access token for the given URL. Args: url: The URL to retrieve credentials for. Returns: - The credentials for the URL, or `None` if no credentials could be retrieved. + The token for the URL, or `None` if no credentials could be retrieved. """ # Public feed short circuit: return nothing if not getting credentials for the # upload endpoint (which always requires auth) and the endpoint is public (can @@ -92,11 +82,8 @@ def get_credentials(self, url: str) -> Optional[Credentials]: if not self._is_upload_endpoint(url) and self._can_authenticate(url, None): return None - jwt_str = self._get_jwt_from_helper() - if not jwt_str: - return None - - return self._get_credentials_from_jwt(jwt_str) + token = self._get_token_from_helper() + return token if token else None @staticmethod def _is_upload_endpoint(url) -> bool: @@ -107,7 +94,7 @@ def _can_authenticate(self, url, auth) -> bool: response = requests.get(url, auth=auth, timeout=self.timeout) return response.status_code < 500 and response.status_code not in (401, 403) - def _get_jwt_from_helper(self) -> str: + def _get_token_from_helper(self) -> str: if self.auth_tool_path is None: raise ArtifactsHelperCredentialProviderError( "Failed to get credentials: No authentication tool found" @@ -140,17 +127,3 @@ def _get_jwt_from_helper(self) -> str: f"Failed to get credentials: Process {self.auth_tool_path} timed out " f"after {self.timeout} seconds" ) from e - - def _get_credentials_from_jwt(self, jwt_str: str) -> Credentials: - try: - decoded = jwt.decode( - jwt_str, verify=False, options={"verify_signature": False} - ) - return Credentials( - username=decoded.get("unique_name", decoded.get("upn", None)), - password=jwt_str, - ) - except jwt.PyJWTError as e: - raise ArtifactsHelperCredentialProviderError( - f"Failed to decode JWT: {e}" - ) from e diff --git a/src/artifacts-helper/codespaces_artifacts_helper_keyring/src/codespaces_artifacts_helper_keyring/keyring_backend.py b/src/artifacts-helper/codespaces_artifacts_helper_keyring/src/codespaces_artifacts_helper_keyring/keyring_backend.py index b7bdc0a..0115f66 100644 --- a/src/artifacts-helper/codespaces_artifacts_helper_keyring/src/codespaces_artifacts_helper_keyring/keyring_backend.py +++ b/src/artifacts-helper/codespaces_artifacts_helper_keyring/src/codespaces_artifacts_helper_keyring/keyring_backend.py @@ -21,6 +21,8 @@ class CodespacesArtifactsHelperKeyringBackend(KeyringBackend): "pkgs.vsts.me", ) + DEFAULT_USERNAME = "codespaces" + _PROVIDER: Type[ArtifactsHelperCredentialProvider] = ( ArtifactsHelperCredentialProvider ) @@ -43,10 +45,10 @@ def get_credential( return None provider = self._PROVIDER(auth_helper_path=self.AUTH_HELPER_PATH) - creds = provider.get_credentials(service) - if creds is None: + token = provider.get_token(service) + if not token: return None - return SimpleCredential(creds.username or username, creds.password) + return SimpleCredential(username or self.DEFAULT_USERNAME, token) def _is_supported_netloc(self, service) -> bool: try: diff --git a/src/artifacts-helper/codespaces_artifacts_helper_keyring/tests/test_artifacts_helper_credential_provider.py b/src/artifacts-helper/codespaces_artifacts_helper_keyring/tests/test_artifacts_helper_credential_provider.py index 2e73adc..4d85181 100644 --- a/src/artifacts-helper/codespaces_artifacts_helper_keyring/tests/test_artifacts_helper_credential_provider.py +++ b/src/artifacts-helper/codespaces_artifacts_helper_keyring/tests/test_artifacts_helper_credential_provider.py @@ -14,7 +14,6 @@ class TestArtifactsHelperWrapper(unittest.TestCase): SUPPORTED_HOST = "https://pkgs.dev.azure.com/" TEST_JWT = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1cG4iOiJ1cG5AY29udG9zby5jb20iLCJ1bmlxdWVfbmFtZSI6Im5hbWVAY29udG9zby5jb20ifQ.srKYrr5B0i29XERHsvE6mqZpLBzyyrX-gUKe9OHZODw" # noqa: E501 - TEST_JWT_USERNAME = "name@contoso.com" def __init__(self, *args, **kwargs) -> None: super().__init__(*args, **kwargs) @@ -61,48 +60,37 @@ def test_auth_helper_installed_and_executable(self): ) assert ArtifactsHelperCredentialProvider.auth_helper_installed(self.script_path) - def test_get_jwt_from_helper(self): - raw_jwt_value = "raw_jwt_here._-azAZ09" - self.write_script(f"print('{raw_jwt_value}')") + def test_get_token_from_helper(self): + raw_token_value = "raw_token_here._-azAZ09" + self.write_script(f"print('{raw_token_value}')") provider = ArtifactsHelperCredentialProvider(self.script_path) - assert provider._get_jwt_from_helper() == raw_jwt_value.strip() + assert provider._get_token_from_helper() == raw_token_value.strip() - def test_get_jwt_from_helper_not_installed(self): + def test_get_token_from_helper_not_installed(self): provider = ArtifactsHelperCredentialProvider() with pytest.raises( ArtifactsHelperCredentialProviderError, match="No authentication tool found" ): - provider._get_jwt_from_helper() + provider._get_token_from_helper() - def test_get_credentials_from_jwt(self): - provider = ArtifactsHelperCredentialProvider() - creds = provider._get_credentials_from_jwt(self.TEST_JWT) - assert creds.username == self.TEST_JWT_USERNAME - assert creds.password == self.TEST_JWT - - def test_get_credentials(self): + def test_get_token(self): self.write_script(f"print('{self.TEST_JWT}')") provider = ArtifactsHelperCredentialProvider(self.script_path) - creds = provider.get_credentials(self.SUPPORTED_HOST) - assert creds.username == self.TEST_JWT_USERNAME - assert creds.password == self.TEST_JWT + assert provider.get_token(self.SUPPORTED_HOST) == self.TEST_JWT - def test_get_credentials_invalid_jwt(self): - self.write_script("print('invalid jwt')") + def test_get_token_invalid_token(self): + self.write_script(r"print('\n\n\n')") provider = ArtifactsHelperCredentialProvider(self.script_path) - with pytest.raises( - ArtifactsHelperCredentialProviderError, match="Failed to decode JWT:" - ): - provider.get_credentials(self.SUPPORTED_HOST) + assert provider.get_token(self.SUPPORTED_HOST) is None - def test_get_crendentials_helper_non_zero_exit(self): + def test_get_token_helper_non_zero_exit(self): self.write_script("exit(1)") provider = ArtifactsHelperCredentialProvider(self.script_path) with pytest.raises( ArtifactsHelperCredentialProviderError, match=f"Process .*{self.script_name}.* exited with code 1", ): - provider.get_credentials(self.SUPPORTED_HOST) + provider.get_token(self.SUPPORTED_HOST) def set_path_executable(path: Union[os.PathLike, str]): diff --git a/src/artifacts-helper/codespaces_artifacts_helper_keyring/tests/test_keyring_backend.py b/src/artifacts-helper/codespaces_artifacts_helper_keyring/tests/test_keyring_backend.py index 6a2f488..c8aceaf 100644 --- a/src/artifacts-helper/codespaces_artifacts_helper_keyring/tests/test_keyring_backend.py +++ b/src/artifacts-helper/codespaces_artifacts_helper_keyring/tests/test_keyring_backend.py @@ -7,7 +7,6 @@ ArtifactsHelperCredentialProvider, CodespacesArtifactsHelperKeyringBackend, ) -from keyring.credentials import SimpleCredential # Shouldn't be accessed by tests, but needs to be able # to get past the quick check. @@ -15,8 +14,8 @@ class FakeProvider(ArtifactsHelperCredentialProvider): - def get_credentials(self, service): - return SimpleCredential("user" + service[-4:], "pass" + service[-4:]) + def get_token(self, service): + return "pass" + service[-4:] @staticmethod def auth_helper_installed(auth_tool_path): @@ -83,9 +82,15 @@ def test_get_credential_unsupported_host(only_backend): assert keyring.get_credential("https://example.com", None) is None -def test_get_credential(only_backend, fake_provider): +def test_get_credential_default_username(only_backend, fake_provider): + creds = keyring.get_credential(SUPPORTED_HOST + "1234", "user12345678") + assert creds.username == "user12345678" + assert creds.password == "pass1234" + + +def test_get_credential_with_username(only_backend, fake_provider): creds = keyring.get_credential(SUPPORTED_HOST + "1234", None) - assert creds.username == "user1234" + assert creds.username == "codespaces" assert creds.password == "pass1234" @@ -128,7 +133,7 @@ def test_delete_password_fallback(passwords, fake_provider): def test_cannot_delete_password(passwords, fake_provider): # Ensure we are getting good credentials - creds = keyring.get_credential(SUPPORTED_HOST + "1234", None) + creds = keyring.get_credential(SUPPORTED_HOST + "1234", "user1234") assert creds.username == "user1234" assert creds.password == "pass1234" From 7f9df1adc5014335593fdd7a6e1e425ce596575e Mon Sep 17 00:00:00 2001 From: Delilah Wu Date: Tue, 23 Apr 2024 11:16:43 +1000 Subject: [PATCH 06/15] feat: Remove PyJWT dependency --- .../codespaces_artifacts_helper_keyring/noxfile.py | 4 +--- .../codespaces_artifacts_helper_keyring/pdm.lock | 13 +------------ .../pyproject.toml | 1 - 3 files changed, 2 insertions(+), 16 deletions(-) diff --git a/src/artifacts-helper/codespaces_artifacts_helper_keyring/noxfile.py b/src/artifacts-helper/codespaces_artifacts_helper_keyring/noxfile.py index 56d90b6..1c75e88 100644 --- a/src/artifacts-helper/codespaces_artifacts_helper_keyring/noxfile.py +++ b/src/artifacts-helper/codespaces_artifacts_helper_keyring/noxfile.py @@ -10,11 +10,9 @@ @nox.session(py=PYTHON_VERSIONS) @nox.parametrize("keyring", ["20", "25.1"]) -@nox.parametrize("pyjwt", ["2.0.0", "2.8"]) -def tests(session, keyring, pyjwt): +def tests(session, keyring): session.run_always("pdm", "install", "-G", "test", external=True) session.install(f"keyring=={keyring}") - session.install(f"pyjwt=={pyjwt}") session.run("pdm", "test", *session.posargs, external=True) diff --git a/src/artifacts-helper/codespaces_artifacts_helper_keyring/pdm.lock b/src/artifacts-helper/codespaces_artifacts_helper_keyring/pdm.lock index ff7450f..a348ef2 100644 --- a/src/artifacts-helper/codespaces_artifacts_helper_keyring/pdm.lock +++ b/src/artifacts-helper/codespaces_artifacts_helper_keyring/pdm.lock @@ -5,7 +5,7 @@ groups = ["default", "test", "lint", "dev", "stubs"] strategy = ["cross_platform", "inherit_metadata"] lock_version = "4.4.1" -content_hash = "sha256:3484757b3b80a58839cc0a968d925c995ae6f9e694bbe75a2c210f11ce6ece04" +content_hash = "sha256:737f2a6b1def7f62ffffc133bd7dfaffe36e9d80b193a98064f80df7faa567cc" [[package]] name = "argcomplete" @@ -741,17 +741,6 @@ files = [ {file = "pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6"}, ] -[[package]] -name = "pyjwt" -version = "2.8.0" -requires_python = ">=3.7" -summary = "JSON Web Token implementation in Python" -groups = ["default"] -files = [ - {file = "PyJWT-2.8.0-py3-none-any.whl", hash = "sha256:59127c392cc44c2da5bb3192169a91f429924e17aff6534d70fdc02ab3e04320"}, - {file = "PyJWT-2.8.0.tar.gz", hash = "sha256:57e28d156e3d5c10088e0c68abb90bfac3df82b40a71bd0daa20c65ccd5c23de"}, -] - [[package]] name = "pytest" version = "8.1.1" diff --git a/src/artifacts-helper/codespaces_artifacts_helper_keyring/pyproject.toml b/src/artifacts-helper/codespaces_artifacts_helper_keyring/pyproject.toml index fa5aae6..97aa31b 100644 --- a/src/artifacts-helper/codespaces_artifacts_helper_keyring/pyproject.toml +++ b/src/artifacts-helper/codespaces_artifacts_helper_keyring/pyproject.toml @@ -13,7 +13,6 @@ classifiers = [ dependencies = [ "jaraco-classes>=3.0.0", "keyring>=20.0.0", - "PyJWT>=2.0.0", "requests>=2.20.0", ] requires-python = ">=3.8" From 911faf4504e0a21c8f6ec07ce64656b25a4c5d1b Mon Sep 17 00:00:00 2001 From: Delilah Wu Date: Fri, 26 Apr 2024 10:38:05 +1000 Subject: [PATCH 07/15] refactor: Change package version to dynamic and fetch from `__init__.py` --- .../codespaces_artifacts_helper_keyring/pyproject.toml | 5 ++++- .../src/codespaces_artifacts_helper_keyring/__init__.py | 2 ++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/artifacts-helper/codespaces_artifacts_helper_keyring/pyproject.toml b/src/artifacts-helper/codespaces_artifacts_helper_keyring/pyproject.toml index 97aa31b..08ede9e 100644 --- a/src/artifacts-helper/codespaces_artifacts_helper_keyring/pyproject.toml +++ b/src/artifacts-helper/codespaces_artifacts_helper_keyring/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "codespaces-artifacts-helper-keyring" -version = "0.1.0" +dynamic = ["version"] description = "Keyring backend to retrieve credentials for Azure Artifacts on Codespaces using https://github.com/microsoft/ado-codespaces-auth" authors = [{ name = "Delilah Wu", email = "delilahwu@microsoft.com" }] classifiers = [ @@ -32,6 +32,9 @@ where = ["src"] [tool.setuptools.package-data] "codespaces_artifacts_helper_keyring" = ["py.typed"] +[tool.setuptools.dynamic] +version = {attr = "codespaces_artifacts_helper_keyring.__version__"} + [tool.pdm] distribution = true diff --git a/src/artifacts-helper/codespaces_artifacts_helper_keyring/src/codespaces_artifacts_helper_keyring/__init__.py b/src/artifacts-helper/codespaces_artifacts_helper_keyring/src/codespaces_artifacts_helper_keyring/__init__.py index 85e2650..e087ece 100644 --- a/src/artifacts-helper/codespaces_artifacts_helper_keyring/src/codespaces_artifacts_helper_keyring/__init__.py +++ b/src/artifacts-helper/codespaces_artifacts_helper_keyring/src/codespaces_artifacts_helper_keyring/__init__.py @@ -2,3 +2,5 @@ from .artifacts_helper_credential_provider import ArtifactsHelperCredentialProvider from .keyring_backend import CodespacesArtifactsHelperKeyringBackend + +__version__ = "0.1.0" From b657f57863d5f4a6a6b86a89b4ada9f80ca31232 Mon Sep 17 00:00:00 2001 From: Delilah Wu Date: Mon, 29 Apr 2024 11:02:33 +1000 Subject: [PATCH 08/15] feat: Add CI for linting, formatting, unit tests, and type checking This also modifies the scripts used to run the linting and formatting tasks. --- .github/workflows/test-keyring.yaml | 59 +++++++++ .../noxfile.py | 62 +++++++-- .../pdm.lock | 123 +----------------- .../pyproject.toml | 18 ++- 4 files changed, 126 insertions(+), 136 deletions(-) create mode 100644 .github/workflows/test-keyring.yaml diff --git a/.github/workflows/test-keyring.yaml b/.github/workflows/test-keyring.yaml new file mode 100644 index 0000000..17e2549 --- /dev/null +++ b/.github/workflows/test-keyring.yaml @@ -0,0 +1,59 @@ +name: Artifacts Helper Keyring Tests + +on: + push: + paths: + - ".github/workflows/test-keyring.yaml" + - "src/artifacts-helper/codespaces_artifacts_helper_keyring/**.py" + + pull_request: + branches: + - main + paths: + - ".github/workflows/test-keyring.yaml" + - "src/artifacts-helper/codespaces_artifacts_helper_keyring/**.py" + +defaults: + run: + working-directory: src/artifacts-helper/codespaces_artifacts_helper_keyring + +jobs: + generate-jobs: + name: Generate jobs + runs-on: ubuntu-latest + outputs: + session: ${{ steps.set-matrix.outputs.session }} + steps: + - uses: actions/checkout@v4 + + - uses: wntrblm/nox@main + with: + python-versions: "3.12" + + - id: set-matrix + shell: bash + run: echo session=$(nox --json -l --tags ci | jq -c '[.[].session]') | tee --append $GITHUB_OUTPUT + + checks: + name: Session ${{ matrix.session }} + needs: [generate-jobs] + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + session: ${{ fromJson(needs.generate-jobs.outputs.session) }} + steps: + - uses: actions/checkout@v4 + + - uses: wntrblm/nox@main + with: + python-versions: "3.8, 3.9, 3.10, 3.11, 3.12, pypy-3.9, pypy-3.10" + + - name: Setup PDM + uses: pdm-project/setup-pdm@v4 + with: + version: "2.15.1" + python-version-file: "src/artifacts-helper/codespaces_artifacts_helper_keyring/pyproject.toml" + + - name: Run ${{ matrix.session }} + run: pdm run nox --error-on-missing-interpreters --error-on-external-run -s "${{ matrix.session }}" diff --git a/src/artifacts-helper/codespaces_artifacts_helper_keyring/noxfile.py b/src/artifacts-helper/codespaces_artifacts_helper_keyring/noxfile.py index 1c75e88..3d5b524 100644 --- a/src/artifacts-helper/codespaces_artifacts_helper_keyring/noxfile.py +++ b/src/artifacts-helper/codespaces_artifacts_helper_keyring/noxfile.py @@ -1,29 +1,63 @@ import os +from typing import List import nox os.environ.update({"PDM_IGNORE_SAVED_PYTHON": "1"}) -PYTHON_VERSIONS = ["3.8", "3.9", "3.10", "3.11", "3.12"] -LOCATIONS = "src", "tests", "noxfile.py" +DEFAULT_PYTHON_VERSION = "3.11" +PYTHON_VERSIONS: List[str] = ["3.8", "3.9", "3.10", "3.11", "3.12"] - -@nox.session(py=PYTHON_VERSIONS) -@nox.parametrize("keyring", ["20", "25.1"]) -def tests(session, keyring): - session.run_always("pdm", "install", "-G", "test", external=True) - session.install(f"keyring=={keyring}") - session.run("pdm", "test", *session.posargs, external=True) +DEFAULT_TEST_LOCATION = "tests" +LOCATIONS = ["src", DEFAULT_TEST_LOCATION, "noxfile.py"] -@nox.session +@nox.session(py=DEFAULT_PYTHON_VERSION, tags=["ci"]) def lint(session): - session.run_always("pdm", "install", "-G", "lint", external=True) - session.run("pdm", "check", external=True) + """Run the linter. + + Returns a failure if the linter finds any issues. + """ + session.run_always("pdm", "install", "-dG", "lint", external=True) + session.run("ruff", "check", *LOCATIONS, *session.posargs) + + +@nox.session(py=DEFAULT_PYTHON_VERSION, tags=["ci"]) +def format_check(session): + """Run the formatter and fail if issues are found.""" + session.notify("format", posargs=["--check", *LOCATIONS]) + + +@nox.session(py=DEFAULT_PYTHON_VERSION) +def format(session): + """Run the formatter and fix issues.""" + session.run_always("pdm", "install", "-dG", "lint", external=True) + args = session.posargs or LOCATIONS + session.run("ruff", "format", *args) + + +@nox.session(tags=["ci"]) +@nox.parametrize( + "python,keyring", + [ + (python, keyring) + for python in PYTHON_VERSIONS + for keyring in ("20", "25.1") + # exclude keyring 20 because it is incompatible with python 3.12 + if (python, keyring) != ("3.12", "20") + ], +) +def tests(session, keyring): + """Run the test suite.""" + session.run_always("pdm", "install", "-dG", "test", external=True) + session.install(f"keyring=={keyring}") + args = session.posargs or [DEFAULT_TEST_LOCATION] + session.run("pytest", "-v", *args) -@nox.session(py=PYTHON_VERSIONS) +@nox.session(py=PYTHON_VERSIONS, tags=["ci"]) def mypy(session): + """Run the type checker.""" session.run_always("pdm", "install", external=True) args = session.posargs or LOCATIONS - session.run("pdm", "run", "mypy", *args, external=True) + session.run("mypy", *args) diff --git a/src/artifacts-helper/codespaces_artifacts_helper_keyring/pdm.lock b/src/artifacts-helper/codespaces_artifacts_helper_keyring/pdm.lock index a348ef2..5734fba 100644 --- a/src/artifacts-helper/codespaces_artifacts_helper_keyring/pdm.lock +++ b/src/artifacts-helper/codespaces_artifacts_helper_keyring/pdm.lock @@ -5,7 +5,7 @@ groups = ["default", "test", "lint", "dev", "stubs"] strategy = ["cross_platform", "inherit_metadata"] lock_version = "4.4.1" -content_hash = "sha256:737f2a6b1def7f62ffffc133bd7dfaffe36e9d80b193a98064f80df7faa567cc" +content_hash = "sha256:925556be1eaa3969c7fccc45fb0e45024b2b5664417c6cbe76b050cb0b2d74da" [[package]] name = "argcomplete" @@ -106,17 +106,6 @@ files = [ {file = "cffi-1.16.0.tar.gz", hash = "sha256:bcb3ef43e58665bbda2fb198698fcae6776483e0c4a631aa5647806c25e02cc0"}, ] -[[package]] -name = "cfgv" -version = "3.4.0" -requires_python = ">=3.8" -summary = "Validate configuration and produce human readable error messages." -groups = ["lint"] -files = [ - {file = "cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9"}, - {file = "cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560"}, -] - [[package]] name = "charset-normalizer" version = "3.3.2" @@ -405,7 +394,7 @@ files = [ name = "distlib" version = "0.3.8" summary = "Distribution utilities" -groups = ["dev", "lint"] +groups = ["dev"] files = [ {file = "distlib-0.3.8-py2.py3-none-any.whl", hash = "sha256:034db59a0b96f8ca18035f36290806a9a6e6bd9d1ff91e45a7f172eb17e51784"}, {file = "distlib-0.3.8.tar.gz", hash = "sha256:1530ea13e350031b6312d8580ddb6b27a104275a31106523b8f123787f494f64"}, @@ -428,23 +417,12 @@ name = "filelock" version = "3.13.4" requires_python = ">=3.8" summary = "A platform independent file lock." -groups = ["dev", "lint"] +groups = ["dev"] files = [ {file = "filelock-3.13.4-py3-none-any.whl", hash = "sha256:404e5e9253aa60ad457cae1be07c0f0ca90a63931200a47d9b6a6af84fd7b45f"}, {file = "filelock-3.13.4.tar.gz", hash = "sha256:d13f466618bfde72bd2c18255e269f72542c6e70e7bac83a0232d6b1cc5c8cf4"}, ] -[[package]] -name = "identify" -version = "2.5.35" -requires_python = ">=3.8" -summary = "File identification library for Python" -groups = ["lint"] -files = [ - {file = "identify-2.5.35-py2.py3-none-any.whl", hash = "sha256:c4de0081837b211594f8e877a6b4fad7ca32bbfc1a9307fdd61c28bfe923f13e"}, - {file = "identify-2.5.35.tar.gz", hash = "sha256:10a7ca245cfcd756a554a7288159f72ff105ad233c7c4b9c6f0f4d108f5f6791"}, -] - [[package]] name = "idna" version = "3.7" @@ -635,20 +613,6 @@ files = [ {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, ] -[[package]] -name = "nodeenv" -version = "1.8.0" -requires_python = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*" -summary = "Node.js virtual environment builder" -groups = ["lint"] -dependencies = [ - "setuptools", -] -files = [ - {file = "nodeenv-1.8.0-py2.py3-none-any.whl", hash = "sha256:df865724bb3c3adc86b3876fa209771517b0cfe596beff01a92700e0e8be4cec"}, - {file = "nodeenv-1.8.0.tar.gz", hash = "sha256:d51e0c37e64fbf47d017feac3145cdbb58836d7eee8c6f6d3b6880c5456227d2"}, -] - [[package]] name = "nox" version = "2024.4.15" @@ -694,7 +658,7 @@ name = "platformdirs" version = "4.2.0" requires_python = ">=3.8" summary = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." -groups = ["dev", "lint"] +groups = ["dev"] files = [ {file = "platformdirs-4.2.0-py3-none-any.whl", hash = "sha256:0614df2a2f37e1a662acbd8e2b25b92ccf8632929bc6d43467e17fe89c75e068"}, {file = "platformdirs-4.2.0.tar.gz", hash = "sha256:ef0cc731df711022c174543cb70a9b5bd22e5a9337c8624ef2c2ceb8ddad8768"}, @@ -711,24 +675,6 @@ files = [ {file = "pluggy-1.4.0.tar.gz", hash = "sha256:8c85c2876142a764e5b7548e7d9a0e0ddb46f5185161049a79b7e974454223be"}, ] -[[package]] -name = "pre-commit" -version = "3.5.0" -requires_python = ">=3.8" -summary = "A framework for managing and maintaining multi-language pre-commit hooks." -groups = ["lint"] -dependencies = [ - "cfgv>=2.0.0", - "identify>=1.0.0", - "nodeenv>=0.11.1", - "pyyaml>=5.1", - "virtualenv>=20.10.0", -] -files = [ - {file = "pre_commit-3.5.0-py2.py3-none-any.whl", hash = "sha256:841dc9aef25daba9a0238cd27984041fa0467b4199fc4852e27950664919f660"}, - {file = "pre_commit-3.5.0.tar.gz", hash = "sha256:5804465c675b659b0862f07907f96295d490822a450c4c40e747d0b1c6ebcb32"}, -] - [[package]] name = "pycparser" version = "2.22" @@ -787,54 +733,6 @@ files = [ {file = "pywin32_ctypes-0.2.2-py3-none-any.whl", hash = "sha256:bf490a1a709baf35d688fe0ecf980ed4de11d2b3e37b51e5442587a75d9957e7"}, ] -[[package]] -name = "pyyaml" -version = "6.0.1" -requires_python = ">=3.6" -summary = "YAML parser and emitter for Python" -groups = ["lint"] -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_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef"}, - {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-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" @@ -894,17 +792,6 @@ files = [ {file = "SecretStorage-3.3.3.tar.gz", hash = "sha256:2403533ef369eca6d2ba81718576c5e0f564d5cca1b58f73a8b23e7d4eeebd77"}, ] -[[package]] -name = "setuptools" -version = "69.5.1" -requires_python = ">=3.8" -summary = "Easily download, build, install, upgrade, and uninstall Python packages" -groups = ["lint"] -files = [ - {file = "setuptools-69.5.1-py3-none-any.whl", hash = "sha256:c636ac361bc47580504644275c9ad802c50415c7522212252c033bd15f301f32"}, - {file = "setuptools-69.5.1.tar.gz", hash = "sha256:6c1fccdac05a97e598fb0ae3bbed5904ccb317337a51139dcd51453611bbb987"}, -] - [[package]] name = "tomli" version = "2.0.1" @@ -958,7 +845,7 @@ name = "virtualenv" version = "20.25.3" requires_python = ">=3.7" summary = "Virtual Python Environment builder" -groups = ["dev", "lint"] +groups = ["dev"] dependencies = [ "distlib<1,>=0.3.7", "filelock<4,>=3.12.2", diff --git a/src/artifacts-helper/codespaces_artifacts_helper_keyring/pyproject.toml b/src/artifacts-helper/codespaces_artifacts_helper_keyring/pyproject.toml index 08ede9e..7714e26 100644 --- a/src/artifacts-helper/codespaces_artifacts_helper_keyring/pyproject.toml +++ b/src/artifacts-helper/codespaces_artifacts_helper_keyring/pyproject.toml @@ -39,15 +39,25 @@ version = {attr = "codespaces_artifacts_helper_keyring.__version__"} distribution = true [tool.pdm.dev-dependencies] -lint = ["mypy>=1.9.0", "ruff>=0.4.0", "pre-commit>=3.5.0"] +lint = [ + "mypy>=1.9.0", + "ruff>=0.4.0", +] dev = ["pip>=24.0", "nox>=2024.4.15"] test = ["pytest>=8.1.1", "pytest-cov>=5.0.0"] stubs = ["types-requests>=2.31.0.20240406"] [tool.pdm.scripts] -check = "ruff check ." -fmt = "ruff format ." -test = "pytest -v tests" +lint-fix = "pdm lint --fix" +lint = "nox --error-on-external-run -R -s lint -- {args}" + +format-check = "pdm format --check" +format = "nox --error-on-external-run -R -s format -- {args}" + +mypy = "nox --error-on-external-run -R -s mypy -- {args}" +tests = "nox --error-on-external-run -R -s tests -- {args}" + +release = "nox --error-on-external-run -R -s release" [tool.ruff] line-length = 88 From 67b08fa203c06ac4b26e9c8e4b24821f6382dd30 Mon Sep 17 00:00:00 2001 From: Delilah Wu Date: Mon, 29 Apr 2024 11:28:27 +1000 Subject: [PATCH 09/15] docs: Update readme --- .../README.md | 100 +++++++++++++++--- 1 file changed, 83 insertions(+), 17 deletions(-) diff --git a/src/artifacts-helper/codespaces_artifacts_helper_keyring/README.md b/src/artifacts-helper/codespaces_artifacts_helper_keyring/README.md index 09e201c..8f5cb8a 100644 --- a/src/artifacts-helper/codespaces_artifacts_helper_keyring/README.md +++ b/src/artifacts-helper/codespaces_artifacts_helper_keyring/README.md @@ -1,17 +1,17 @@ -# codespaces artifacts helper keyring +# codespaces_artifacts_helper_keyring -wow what a long package name. i'm open to suggestions for renaming it. +The `codespaces_artifacts_helper_keyring` package provides [keyring](https://pypi.org/project/keyring) authentication for consuming Python packages from Azure Artifacts feeds using the [Codespaces Artifacts Helper](https://github.com/microsoft/codespace-features/tree/main/src/artifacts-helper) and its underlying authentication tool, [ado-codespaces-auth](https://github.com/microsoft/ado-codespaces-auth). -## what is this? +This package is an extension to [keyring](https://pypi.org/project/keyring), which will automatically find and use it once installed. Both [pip](https://pypi.org/project/pip) and [twine](https://pypi.org/project/twine) will use keyring to find credentials. -The codespaces artifacts helper keyring is a package that provides a keyring implementation for the codespaces artifacts helper at https://github.com/microsoft/ado-codespaces-auth. When the keyring package and this keyring are both installed, pip will automatically use this keyring to store and retrieve credentials when accessing ADO package feeds. +## Installation -## build instructins +### From Source -This package uses `pyproject.toml`, and `pdm` for building. To build the package, run the following commands: +To install this package from source: ```sh -cd src/artifacts-helper/codespaces_artifacts_helper_keyring +$ cd src/artifacts-helper/codespaces_artifacts_helper_keyring # PDM is used to manage the project $ pip install 'pdm>=2.14' @@ -19,25 +19,91 @@ $ pip install 'pdm>=2.14' # Install dependencies and build the package $ pdm build -# Install package + deps with pip +# Install package and dependencies with pip $ pip install dist/codespaces_artifacts_helper_keyring-*.whl ``` -## contributing +### From GitHub Releases + +TODO: Write instructions + +## Usage + +### Requirements + +To use `codespaces_artifacts_helper_keyring` to set up authentication between `pip` and Azure Artifacts, the following requirements must be met: + +- pip version **19.2** or higher +- python version **3.8** or higher +- running inside a Codespace with [Codespaces Artifacts Helper](https://github.com/microsoft/codespace-features/tree/main/src/artifacts-helper) and the `param` option set to `true`. This will automatically install the `codespaces_artifacts_helper_keyring` package for you. + ```json + { + "features": { + "ghcr.io/microsoft/codespace-features/artifacts-helper:1": { + // TODO: Add parameter for installing package + "TODO": true + } + } + } + ``` + +### Inner Workings + +The `codespaces_artifacts_helper_keyring` will detect if the package index has a domain that matches Azure Artifacts, e.g. `pkgs.dev.azure.com`. If it does, it will use the `ado-codespaces-auth` tool at `~/ado-auth-helper` to fetch an access token. This token will be used to authenticate with the Azure Artifacts feed. + +### Installing Packages from an Azure Artifacts Feed + +Once the codespace is ready, to consume a package, use the following `pip` command, replacing **** and **** with your own, and **** with the package you want to install: + +``` +pip install --index-url https://pkgs.dev.azure.com//_packaging//pypi/simple +``` + +## Contributing + +We use [PDM](https://pdm-project.org/) to manage the project and its dependencies. To get started, install PDM: ```sh -# Lint -$ pdm run check +$ pip install 'pdm>=2.14' +``` -# Format -$ pdm run fmt +Then, install the project dependencies: + +```sh +$ pdm install +``` + +### Scripts + +A set of scripts are in `pyproject.toml` to help with common tasks. These can be run using `pdm