From 709db12dcb6a7dff51d54145a92615fa830d838b Mon Sep 17 00:00:00 2001 From: verena <9377970+vpchung@users.noreply.github.com> Date: Tue, 25 Nov 2025 12:05:05 -0800 Subject: [PATCH 01/11] update dependency versions --- poetry.lock | 403 +++++++++++++++++++++++++++++++++---------------- pyproject.toml | 21 ++- 2 files changed, 281 insertions(+), 143 deletions(-) diff --git a/poetry.lock b/poetry.lock index c32cc7b..4251d05 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 2.2.1 and should not be changed by hand. [[package]] name = "anyio" @@ -6,6 +6,7 @@ version = "4.3.0" description = "High level compatibility layer for multiple asynchronous event loop implementations" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "anyio-4.3.0-py3-none-any.whl", hash = "sha256:048e05d0f6caeed70d731f3db756d35dcc1f35747c8c403364a8332c630441b8"}, {file = "anyio-4.3.0.tar.gz", hash = "sha256:f75253795a87df48568485fd18cdd2a3fa5c4f7c5be8e5e36637733fce06fed6"}, @@ -19,7 +20,7 @@ typing-extensions = {version = ">=4.1", markers = "python_version < \"3.11\""} [package.extras] doc = ["Sphinx (>=7)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme"] -test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.17)"] +test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.17) ; platform_python_implementation == \"CPython\" and platform_system != \"Windows\""] trio = ["trio (>=0.23)"] [[package]] @@ -28,6 +29,7 @@ version = "2.0.4" description = "Simple LRU cache for asyncio" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "async-lru-2.0.4.tar.gz", hash = "sha256:b8a59a5df60805ff63220b2a0c5b5393da5521b113cd5465a44eb037d81a5627"}, {file = "async_lru-2.0.4-py3-none-any.whl", hash = "sha256:ff02944ce3c288c5be660c42dbcca0742b32c3b279d6dceda655190240b99224"}, @@ -42,6 +44,7 @@ version = "1.0.1" description = "Like atexit, but for asyncio" optional = false python-versions = ">=3.6" +groups = ["main"] files = [ {file = "asyncio-atexit-1.0.1.tar.gz", hash = "sha256:1d0c71544b8ee2c484d322844ee72c0875dde6f250c0ed5b6993592ab9f7d436"}, {file = "asyncio_atexit-1.0.1-py3-none-any.whl", hash = "sha256:d93d5f7d5633a534abd521ce2896ed0fbe8de170bb1e65ec871d1c20eac9d376"}, @@ -56,6 +59,7 @@ version = "2.15.0" description = "Internationalization utilities" optional = false python-versions = ">=3.8" +groups = ["docs"] files = [ {file = "Babel-2.15.0-py3-none-any.whl", hash = "sha256:08706bdad8d0a3413266ab61bd6c34d0c28d6e1e7badf40a2cebe67644e2e1fb"}, {file = "babel-2.15.0.tar.gz", hash = "sha256:8daf0e265d05768bc6c7a314cf1321e9a123afc328cc635c18622a2f30a04413"}, @@ -64,23 +68,13 @@ files = [ [package.extras] dev = ["freezegun (>=1.0,<2.0)", "pytest (>=6.0)", "pytest-cov"] -[[package]] -name = "backoff" -version = "2.2.1" -description = "Function decoration for backoff and retry" -optional = false -python-versions = ">=3.7,<4.0" -files = [ - {file = "backoff-2.2.1-py3-none-any.whl", hash = "sha256:63579f9a0628e06278f7e47b7d7d5b6ce20dc65c5e96a6f3ca99a6adca0396e8"}, - {file = "backoff-2.2.1.tar.gz", hash = "sha256:03f829f5bb1923180821643f8753b0502c3b682293992485b0eef2807afa5cba"}, -] - [[package]] name = "certifi" version = "2024.2.2" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.6" +groups = ["main", "docs"] files = [ {file = "certifi-2024.2.2-py3-none-any.whl", hash = "sha256:dc383c07b76109f368f6106eee2b593b04a011ea4d55f652c6ca24a754d1cdd1"}, {file = "certifi-2024.2.2.tar.gz", hash = "sha256:0569859f95fc761b18b45ef421b1290a0f65f147e92a1e5eb3e635f9a5e4e66f"}, @@ -92,6 +86,7 @@ version = "3.3.2" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." optional = false python-versions = ">=3.7.0" +groups = ["main", "docs"] 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"}, @@ -191,6 +186,7 @@ version = "8.1.7" description = "Composable command line interface toolkit" optional = false python-versions = ">=3.7" +groups = ["main", "docs"] files = [ {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"}, {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"}, @@ -199,27 +195,18 @@ files = [ [package.dependencies] colorama = {version = "*", markers = "platform_system == \"Windows\""} -[[package]] -name = "cloudpickle" -version = "3.0.0" -description = "Pickler class to extend the standard pickle.Pickler functionality" -optional = false -python-versions = ">=3.8" -files = [ - {file = "cloudpickle-3.0.0-py3-none-any.whl", hash = "sha256:246ee7d0c295602a036e86369c77fecda4ab17b506496730f2f576d9016fd9c7"}, - {file = "cloudpickle-3.0.0.tar.gz", hash = "sha256:996d9a482c6fb4f33c1a35335cf8afd065d2a56e973270364840712d9131a882"}, -] - [[package]] name = "colorama" version = "0.4.6" description = "Cross-platform colored terminal text." optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +groups = ["main", "docs", "test"] files = [ {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] +markers = {test = "sys_platform == \"win32\""} [[package]] name = "deprecated" @@ -227,6 +214,7 @@ version = "1.2.14" description = "Python @deprecated decorator to deprecate old python classes, functions or methods." optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +groups = ["main"] files = [ {file = "Deprecated-1.2.14-py2.py3-none-any.whl", hash = "sha256:6fac8b097794a90302bdbb17b9b815e732d3c4720583ff1b198499d78470466c"}, {file = "Deprecated-1.2.14.tar.gz", hash = "sha256:e5323eb936458dccc2582dc6f9c322c852a775a27065ff2b0c4970b9d53d01b3"}, @@ -244,6 +232,8 @@ version = "1.2.1" description = "Backport of PEP 654 (exception groups)" optional = false python-versions = ">=3.7" +groups = ["main", "test"] +markers = "python_version == \"3.10\"" files = [ {file = "exceptiongroup-1.2.1-py3-none-any.whl", hash = "sha256:5258b9ed329c5bbdd31a309f53cbfb0b155341807f6ff7606a1e801a891b29ad"}, {file = "exceptiongroup-1.2.1.tar.gz", hash = "sha256:a4785e48b045528f5bfe627b6ad554ff32def154f42372786903b7abcfe1aa16"}, @@ -258,6 +248,7 @@ version = "2.1.0" description = "Copy your docs directly to the gh-pages branch." optional = false python-versions = "*" +groups = ["docs"] files = [ {file = "ghp-import-2.1.0.tar.gz", hash = "sha256:9c535c4c61193c2df8871222567d7fd7e5014d835f97dc7b7439069e2413d343"}, {file = "ghp_import-2.1.0-py3-none-any.whl", hash = "sha256:8337dd7b50877f163d4c0289bc1f1c7f127550241988d568c1db512c4324a619"}, @@ -271,20 +262,21 @@ dev = ["flake8", "markdown", "twine", "wheel"] [[package]] name = "googleapis-common-protos" -version = "1.63.0" +version = "1.72.0" description = "Common protobufs used in Google APIs" optional = false python-versions = ">=3.7" +groups = ["main"] files = [ - {file = "googleapis-common-protos-1.63.0.tar.gz", hash = "sha256:17ad01b11d5f1d0171c06d3ba5c04c54474e883b66b949722b4938ee2694ef4e"}, - {file = "googleapis_common_protos-1.63.0-py2.py3-none-any.whl", hash = "sha256:ae45f75702f7c08b541f750854a678bd8f534a1a6bace6afe975f1d0a82d6632"}, + {file = "googleapis_common_protos-1.72.0-py3-none-any.whl", hash = "sha256:4299c5a82d5ae1a9702ada957347726b167f9f8d1fc352477702a1e851ff4038"}, + {file = "googleapis_common_protos-1.72.0.tar.gz", hash = "sha256:e55a601c1b32b52d7a3e65f43563e2aa61bcd737998ee672ac9b951cd49319f5"}, ] [package.dependencies] -protobuf = ">=3.19.5,<3.20.0 || >3.20.0,<3.20.1 || >3.20.1,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<5.0.0.dev0" +protobuf = ">=3.20.2,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<7.0.0" [package.extras] -grpc = ["grpcio (>=1.44.0,<2.0.0.dev0)"] +grpc = ["grpcio (>=1.44.0,<2.0.0)"] [[package]] name = "griffe" @@ -292,6 +284,7 @@ version = "0.45.0" description = "Signatures for entire Python programs. Extract the structure, the frame, the skeleton of your project, to generate API documentation or find breaking changes in your API." optional = false python-versions = ">=3.8" +groups = ["docs"] files = [ {file = "griffe-0.45.0-py3-none-any.whl", hash = "sha256:90fe5c90e1b0ca7dd6fee78f9009f4e01b37dbc9ab484a9b2c1578915db1e571"}, {file = "griffe-0.45.0.tar.gz", hash = "sha256:85cb2868d026ea51c89bdd589ad3ccc94abc5bd8d5d948e3d4450778a2a05b4a"}, @@ -302,35 +295,37 @@ colorama = ">=0.4" [[package]] name = "h11" -version = "0.14.0" +version = "0.16.0" description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" +groups = ["main"] files = [ - {file = "h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"}, - {file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"}, + {file = "h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86"}, + {file = "h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1"}, ] [[package]] name = "httpcore" -version = "1.0.5" +version = "1.0.9" description = "A minimal low-level HTTP client." optional = false python-versions = ">=3.8" +groups = ["main"] files = [ - {file = "httpcore-1.0.5-py3-none-any.whl", hash = "sha256:421f18bac248b25d310f3cacd198d55b8e6125c107797b609ff9b7a6ba7991b5"}, - {file = "httpcore-1.0.5.tar.gz", hash = "sha256:34a38e2f9291467ee3b44e89dd52615370e152954ba21721378a87b2960f7a61"}, + {file = "httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55"}, + {file = "httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8"}, ] [package.dependencies] certifi = "*" -h11 = ">=0.13,<0.15" +h11 = ">=0.16" [package.extras] asyncio = ["anyio (>=4.0,<5.0)"] http2 = ["h2 (>=3,<5)"] socks = ["socksio (==1.*)"] -trio = ["trio (>=0.22.0,<0.26.0)"] +trio = ["trio (>=0.22.0,<1.0)"] [[package]] name = "httpx" @@ -338,6 +333,7 @@ version = "0.27.0" description = "The next generation HTTP client." optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "httpx-0.27.0-py3-none-any.whl", hash = "sha256:71d5465162c13681bff01ad59b2cc68dd838ea1f10e51574bac27103f00c91a5"}, {file = "httpx-0.27.0.tar.gz", hash = "sha256:a0cb88a46f32dc874e04ee956e4c2764aba2aa228f650b06788ba6bda2962ab5"}, @@ -351,7 +347,7 @@ idna = "*" sniffio = "*" [package.extras] -brotli = ["brotli", "brotlicffi"] +brotli = ["brotli ; platform_python_implementation == \"CPython\"", "brotlicffi ; platform_python_implementation != \"CPython\""] cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<14)"] http2 = ["h2 (>=3,<5)"] socks = ["socksio (==1.*)"] @@ -362,6 +358,7 @@ version = "3.7" description = "Internationalized Domain Names in Applications (IDNA)" optional = false python-versions = ">=3.5" +groups = ["main", "docs"] files = [ {file = "idna-3.7-py3-none-any.whl", hash = "sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0"}, {file = "idna-3.7.tar.gz", hash = "sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc"}, @@ -373,6 +370,7 @@ version = "6.11.0" description = "Read metadata from Python packages" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "importlib_metadata-6.11.0-py3-none-any.whl", hash = "sha256:f0afba6205ad8f8947c7d338b5342d5db2afbfd82f9cbef7879a9539cc12eb9b"}, {file = "importlib_metadata-6.11.0.tar.gz", hash = "sha256:1231cf92d825c9e03cfc4da076a16de6422c863558229ea0b22b675657463443"}, @@ -384,7 +382,7 @@ zipp = ">=0.5" [package.extras] docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-lint"] perf = ["ipython"] -testing = ["flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)", "pytest-ruff"] +testing = ["flufl.flake8", "importlib-resources (>=1.3) ; python_version < \"3.9\"", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7) ; platform_python_implementation != \"PyPy\"", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1) ; platform_python_implementation != \"PyPy\"", "pytest-perf (>=0.9.2)", "pytest-ruff"] [[package]] name = "iniconfig" @@ -392,6 +390,7 @@ version = "2.0.0" description = "brain-dead simple config-ini parsing" optional = false python-versions = ">=3.7" +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"}, @@ -403,6 +402,7 @@ version = "3.1.4" description = "A very fast and expressive template engine." optional = false python-versions = ">=3.7" +groups = ["docs"] files = [ {file = "jinja2-3.1.4-py3-none-any.whl", hash = "sha256:bc5dd2abb727a5319567b7a813e6a2e7318c39f4f487cfe6c89c6f9c7d25197d"}, {file = "jinja2-3.1.4.tar.gz", hash = "sha256:4a3aee7acbbe7303aede8e9648d13b8bf88a429282aa6122a993f0ac800cb369"}, @@ -414,34 +414,18 @@ MarkupSafe = ">=2.0" [package.extras] i18n = ["Babel (>=2.7)"] -[[package]] -name = "loky" -version = "3.0.0" -description = "A robust implementation of concurrent.futures.ProcessPoolExecutor" -optional = false -python-versions = "*" -files = [ - {file = "loky-3.0.0-py2.py3-none-any.whl", hash = "sha256:1d5a4d778c7ff09c919aa3fbf2d879a2c7ac936a545c615af40e080a1c902b82"}, - {file = "loky-3.0.0.tar.gz", hash = "sha256:fd8750b24b283a579bafaf0631d114aa4487c682aef6fce01fa3635336297fdf"}, -] - -[package.dependencies] -cloudpickle = "*" - [[package]] name = "markdown" version = "3.6" description = "Python implementation of John Gruber's Markdown." optional = false python-versions = ">=3.8" +groups = ["docs"] files = [ {file = "Markdown-3.6-py3-none-any.whl", hash = "sha256:48f276f4d8cfb8ce6527c8f79e2ee29708508bf4d40aa410fbc3b4ee832c850f"}, {file = "Markdown-3.6.tar.gz", hash = "sha256:ed4f41f6daecbeeb96e576ce414c41d2d876daa9a16cb35fa8ed8c2ddfad0224"}, ] -[package.dependencies] -importlib-metadata = {version = ">=4.4", markers = "python_version < \"3.10\""} - [package.extras] docs = ["mdx-gh-links (>=0.2)", "mkdocs (>=1.5)", "mkdocs-gen-files", "mkdocs-literate-nav", "mkdocs-nature (>=0.6)", "mkdocs-section-index", "mkdocstrings[python]"] testing = ["coverage", "pyyaml"] @@ -452,6 +436,7 @@ version = "3.0.0" description = "Python port of markdown-it. Markdown parsing, done right!" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb"}, {file = "markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1"}, @@ -476,6 +461,7 @@ version = "2.1.5" description = "Safely add untrusted strings to HTML/XML markup." optional = false python-versions = ">=3.7" +groups = ["docs"] files = [ {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a17a92de5231666cfbe003f0e4b9b3a7ae3afb1ec2845aadc2bacc93ff85febc"}, {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:72b6be590cc35924b02c78ef34b467da4ba07e4e0f0454a2c5907f473fc50ce5"}, @@ -545,6 +531,7 @@ version = "0.1.2" description = "Markdown URL utilities" optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"}, {file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"}, @@ -556,6 +543,7 @@ version = "1.3.4" description = "A deep merge function for 🐍." optional = false python-versions = ">=3.6" +groups = ["docs"] files = [ {file = "mergedeep-1.3.4-py3-none-any.whl", hash = "sha256:70775750742b25c0d8f36c55aed03d24c3384d17c951b3175d898bd778ef0307"}, {file = "mergedeep-1.3.4.tar.gz", hash = "sha256:0096d52e9dad9939c3d975a774666af186eda617e6ca84df4c94dec30004f2a8"}, @@ -567,6 +555,7 @@ version = "1.6.0" description = "Project documentation with Markdown." optional = false python-versions = ">=3.8" +groups = ["docs"] files = [ {file = "mkdocs-1.6.0-py3-none-any.whl", hash = "sha256:1eb5cb7676b7d89323e62b56235010216319217d4af5ddc543a91beb8d125ea7"}, {file = "mkdocs-1.6.0.tar.gz", hash = "sha256:a73f735824ef83a4f3bcb7a231dcab23f5a838f88b7efc54a0eef5fbdbc3c512"}, @@ -576,7 +565,6 @@ files = [ click = ">=7.0" colorama = {version = ">=0.4", markers = "platform_system == \"Windows\""} ghp-import = ">=1.0" -importlib-metadata = {version = ">=4.4", markers = "python_version < \"3.10\""} jinja2 = ">=2.11.1" markdown = ">=3.3.6" markupsafe = ">=2.0.1" @@ -590,7 +578,7 @@ watchdog = ">=2.0" [package.extras] i18n = ["babel (>=2.9.0)"] -min-versions = ["babel (==2.9.0)", "click (==7.0)", "colorama (==0.4)", "ghp-import (==1.0)", "importlib-metadata (==4.4)", "jinja2 (==2.11.1)", "markdown (==3.3.6)", "markupsafe (==2.0.1)", "mergedeep (==1.3.4)", "mkdocs-get-deps (==0.2.0)", "packaging (==20.5)", "pathspec (==0.11.1)", "pyyaml (==5.1)", "pyyaml-env-tag (==0.1)", "watchdog (==2.0)"] +min-versions = ["babel (==2.9.0)", "click (==7.0)", "colorama (==0.4) ; platform_system == \"Windows\"", "ghp-import (==1.0)", "importlib-metadata (==4.4) ; python_version < \"3.10\"", "jinja2 (==2.11.1)", "markdown (==3.3.6)", "markupsafe (==2.0.1)", "mergedeep (==1.3.4)", "mkdocs-get-deps (==0.2.0)", "packaging (==20.5)", "pathspec (==0.11.1)", "pyyaml (==5.1)", "pyyaml-env-tag (==0.1)", "watchdog (==2.0)"] [[package]] name = "mkdocs-autorefs" @@ -598,6 +586,7 @@ version = "1.0.1" description = "Automatically link across pages in MkDocs." optional = false python-versions = ">=3.8" +groups = ["docs"] files = [ {file = "mkdocs_autorefs-1.0.1-py3-none-any.whl", hash = "sha256:aacdfae1ab197780fb7a2dac92ad8a3d8f7ca8049a9cbe56a4218cd52e8da570"}, {file = "mkdocs_autorefs-1.0.1.tar.gz", hash = "sha256:f684edf847eced40b570b57846b15f0bf57fb93ac2c510450775dcf16accb971"}, @@ -614,13 +603,13 @@ version = "0.2.0" description = "MkDocs extension that lists all dependencies according to a mkdocs.yml file" optional = false python-versions = ">=3.8" +groups = ["docs"] files = [ {file = "mkdocs_get_deps-0.2.0-py3-none-any.whl", hash = "sha256:2bf11d0b133e77a0dd036abeeb06dec8775e46efa526dc70667d8863eefc6134"}, {file = "mkdocs_get_deps-0.2.0.tar.gz", hash = "sha256:162b3d129c7fad9b19abfdcb9c1458a651628e4b1dea628ac68790fb3061c60c"}, ] [package.dependencies] -importlib-metadata = {version = ">=4.3", markers = "python_version < \"3.10\""} mergedeep = ">=1.3.4" platformdirs = ">=2.2.0" pyyaml = ">=5.1" @@ -631,6 +620,7 @@ version = "9.5.23" description = "Documentation that simply works" optional = false python-versions = ">=3.8" +groups = ["docs"] files = [ {file = "mkdocs_material-9.5.23-py3-none-any.whl", hash = "sha256:ffd08a5beaef3cd135aceb58ded8b98bbbbf2b70e5b656f6a14a63c917d9b001"}, {file = "mkdocs_material-9.5.23.tar.gz", hash = "sha256:4627fc3f15de2cba2bde9debc2fd59b9888ef494beabfe67eb352e23d14bf288"}, @@ -660,6 +650,7 @@ version = "1.3.1" description = "Extension pack for Python Markdown and MkDocs Material." optional = false python-versions = ">=3.8" +groups = ["docs"] files = [ {file = "mkdocs_material_extensions-1.3.1-py3-none-any.whl", hash = "sha256:adff8b62700b25cb77b53358dad940f3ef973dd6db797907c49e3c2ef3ab4e31"}, {file = "mkdocs_material_extensions-1.3.1.tar.gz", hash = "sha256:10c9511cea88f568257f960358a467d12b970e1f7b2c0e5fb2bb48cab1928443"}, @@ -671,6 +662,7 @@ version = "0.24.3" description = "Automatic documentation from sources, for MkDocs." optional = false python-versions = ">=3.8" +groups = ["docs"] files = [ {file = "mkdocstrings-0.24.3-py3-none-any.whl", hash = "sha256:5c9cf2a32958cd161d5428699b79c8b0988856b0d4a8c5baf8395fc1bf4087c3"}, {file = "mkdocstrings-0.24.3.tar.gz", hash = "sha256:f327b234eb8d2551a306735436e157d0a22d45f79963c60a8b585d5f7a94c1d2"}, @@ -678,7 +670,6 @@ files = [ [package.dependencies] click = ">=7.0" -importlib-metadata = {version = ">=4.6", markers = "python_version < \"3.10\""} Jinja2 = ">=2.11.1" Markdown = ">=3.3" MarkupSafe = ">=1.1" @@ -686,7 +677,6 @@ mkdocs = ">=1.4" mkdocs-autorefs = ">=0.3.1" platformdirs = ">=2.2.0" pymdown-extensions = ">=6.3" -typing-extensions = {version = ">=4.1", markers = "python_version < \"3.10\""} [package.extras] crystal = ["mkdocstrings-crystal (>=0.3.4)"] @@ -699,6 +689,7 @@ version = "1.10.0" description = "A Python handler for mkdocstrings." optional = false python-versions = ">=3.8" +groups = ["docs"] files = [ {file = "mkdocstrings_python-1.10.0-py3-none-any.whl", hash = "sha256:ba833fbd9d178a4b9d5cb2553a4df06e51dc1f51e41559a4d2398c16a6f69ecc"}, {file = "mkdocstrings_python-1.10.0.tar.gz", hash = "sha256:71678fac657d4d2bb301eed4e4d2d91499c095fd1f8a90fa76422a87a5693828"}, @@ -714,6 +705,7 @@ version = "1.6.0" description = "Patch asyncio to allow nested event loops" optional = false python-versions = ">=3.5" +groups = ["main"] files = [ {file = "nest_asyncio-1.6.0-py3-none-any.whl", hash = "sha256:87af6efd6b5e897c81050477ef65c62e2b2f35d51703cae01aff2905b1852e1c"}, {file = "nest_asyncio-1.6.0.tar.gz", hash = "sha256:6f172d5449aca15afd6c646851f4e31e02c598d553a667e38cafa997cfec55fe"}, @@ -721,97 +713,210 @@ files = [ [[package]] name = "opentelemetry-api" -version = "1.21.0" +version = "1.38.0" description = "OpenTelemetry Python API" optional = false -python-versions = ">=3.7" +python-versions = ">=3.9" +groups = ["main"] files = [ - {file = "opentelemetry_api-1.21.0-py3-none-any.whl", hash = "sha256:4bb86b28627b7e41098f0e93280fe4892a1abed1b79a19aec6f928f39b17dffb"}, - {file = "opentelemetry_api-1.21.0.tar.gz", hash = "sha256:d6185fd5043e000075d921822fd2d26b953eba8ca21b1e2fa360dd46a7686316"}, + {file = "opentelemetry_api-1.38.0-py3-none-any.whl", hash = "sha256:2891b0197f47124454ab9f0cf58f3be33faca394457ac3e09daba13ff50aa582"}, + {file = "opentelemetry_api-1.38.0.tar.gz", hash = "sha256:f4c193b5e8acb0912b06ac5b16321908dd0843d75049c091487322284a3eea12"}, ] [package.dependencies] -deprecated = ">=1.2.6" -importlib-metadata = ">=6.0,<7.0" +importlib-metadata = ">=6.0,<8.8.0" +typing-extensions = ">=4.5.0" [[package]] name = "opentelemetry-exporter-otlp-proto-common" -version = "1.21.0" +version = "1.38.0" description = "OpenTelemetry Protobuf encoding" optional = false -python-versions = ">=3.7" +python-versions = ">=3.9" +groups = ["main"] files = [ - {file = "opentelemetry_exporter_otlp_proto_common-1.21.0-py3-none-any.whl", hash = "sha256:97b1022b38270ec65d11fbfa348e0cd49d12006485c2321ea3b1b7037d42b6ec"}, - {file = "opentelemetry_exporter_otlp_proto_common-1.21.0.tar.gz", hash = "sha256:61db274d8a68d636fb2ec2a0f281922949361cdd8236e25ff5539edf942b3226"}, + {file = "opentelemetry_exporter_otlp_proto_common-1.38.0-py3-none-any.whl", hash = "sha256:03cb76ab213300fe4f4c62b7d8f17d97fcfd21b89f0b5ce38ea156327ddda74a"}, + {file = "opentelemetry_exporter_otlp_proto_common-1.38.0.tar.gz", hash = "sha256:e333278afab4695aa8114eeb7bf4e44e65c6607d54968271a249c180b2cb605c"}, ] [package.dependencies] -backoff = {version = ">=1.10.0,<3.0.0", markers = "python_version >= \"3.7\""} -opentelemetry-proto = "1.21.0" +opentelemetry-proto = "1.38.0" [[package]] name = "opentelemetry-exporter-otlp-proto-http" -version = "1.21.0" +version = "1.38.0" description = "OpenTelemetry Collector Protobuf over HTTP Exporter" optional = false -python-versions = ">=3.7" +python-versions = ">=3.9" +groups = ["main"] files = [ - {file = "opentelemetry_exporter_otlp_proto_http-1.21.0-py3-none-any.whl", hash = "sha256:56837773de6fb2714c01fc4895caebe876f6397bbc4d16afddf89e1299a55ee2"}, - {file = "opentelemetry_exporter_otlp_proto_http-1.21.0.tar.gz", hash = "sha256:19d60afa4ae8597f7ef61ad75c8b6c6b7ef8cb73a33fb4aed4dbc86d5c8d3301"}, + {file = "opentelemetry_exporter_otlp_proto_http-1.38.0-py3-none-any.whl", hash = "sha256:84b937305edfc563f08ec69b9cb2298be8188371217e867c1854d77198d0825b"}, + {file = "opentelemetry_exporter_otlp_proto_http-1.38.0.tar.gz", hash = "sha256:f16bd44baf15cbe07633c5112ffc68229d0edbeac7b37610be0b2def4e21e90b"}, ] [package.dependencies] -backoff = {version = ">=1.10.0,<3.0.0", markers = "python_version >= \"3.7\""} -deprecated = ">=1.2.6" googleapis-common-protos = ">=1.52,<2.0" opentelemetry-api = ">=1.15,<2.0" -opentelemetry-exporter-otlp-proto-common = "1.21.0" -opentelemetry-proto = "1.21.0" -opentelemetry-sdk = ">=1.21.0,<1.22.0" +opentelemetry-exporter-otlp-proto-common = "1.38.0" +opentelemetry-proto = "1.38.0" +opentelemetry-sdk = ">=1.38.0,<1.39.0" requests = ">=2.7,<3.0" +typing-extensions = ">=4.5.0" + +[[package]] +name = "opentelemetry-instrumentation" +version = "0.59b0" +description = "Instrumentation Tools & Auto Instrumentation for OpenTelemetry Python" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "opentelemetry_instrumentation-0.59b0-py3-none-any.whl", hash = "sha256:44082cc8fe56b0186e87ee8f7c17c327c4c2ce93bdbe86496e600985d74368ee"}, + {file = "opentelemetry_instrumentation-0.59b0.tar.gz", hash = "sha256:6010f0faaacdaf7c4dff8aac84e226d23437b331dcda7e70367f6d73a7db1adc"}, +] + +[package.dependencies] +opentelemetry-api = ">=1.4,<2.0" +opentelemetry-semantic-conventions = "0.59b0" +packaging = ">=18.0" +wrapt = ">=1.0.0,<2.0.0" + +[[package]] +name = "opentelemetry-instrumentation-httpx" +version = "0.59b0" +description = "OpenTelemetry HTTPX Instrumentation" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "opentelemetry_instrumentation_httpx-0.59b0-py3-none-any.whl", hash = "sha256:7dc9f66aef4ca3904d877f459a70c78eafd06131dc64d713b9b1b5a7d0a48f05"}, + {file = "opentelemetry_instrumentation_httpx-0.59b0.tar.gz", hash = "sha256:a1cb9b89d9f05a82701cc9ab9cfa3db54fd76932489449778b350bc1b9f0e872"}, +] + +[package.dependencies] +opentelemetry-api = ">=1.12,<2.0" +opentelemetry-instrumentation = "0.59b0" +opentelemetry-semantic-conventions = "0.59b0" +opentelemetry-util-http = "0.59b0" +wrapt = ">=1.0.0,<2.0.0" [package.extras] -test = ["responses (==0.22.0)"] +instruments = ["httpx (>=0.18.0)"] + +[[package]] +name = "opentelemetry-instrumentation-requests" +version = "0.59b0" +description = "OpenTelemetry requests instrumentation" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "opentelemetry_instrumentation_requests-0.59b0-py3-none-any.whl", hash = "sha256:d43121532877e31a46c48649279cec2504ee1e0ceb3c87b80fe5ccd7eafc14c1"}, + {file = "opentelemetry_instrumentation_requests-0.59b0.tar.gz", hash = "sha256:9af2ffe3317f03074d7f865919139e89170b6763a0251b68c25e8e64e04b3400"}, +] + +[package.dependencies] +opentelemetry-api = ">=1.12,<2.0" +opentelemetry-instrumentation = "0.59b0" +opentelemetry-semantic-conventions = "0.59b0" +opentelemetry-util-http = "0.59b0" + +[package.extras] +instruments = ["requests (>=2.0,<3.0)"] + +[[package]] +name = "opentelemetry-instrumentation-threading" +version = "0.59b0" +description = "Thread context propagation support for OpenTelemetry" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "opentelemetry_instrumentation_threading-0.59b0-py3-none-any.whl", hash = "sha256:76da2fc01fe1dccebff6581080cff9e42ac7b27cc61eb563f3c4435c727e8eca"}, + {file = "opentelemetry_instrumentation_threading-0.59b0.tar.gz", hash = "sha256:ce5658730b697dcbc0e0d6d13643a69fd8aeb1b32fa8db3bade8ce114c7975f3"}, +] + +[package.dependencies] +opentelemetry-api = ">=1.12,<2.0" +opentelemetry-instrumentation = "0.59b0" +wrapt = ">=1.0.0,<2.0.0" + +[[package]] +name = "opentelemetry-instrumentation-urllib" +version = "0.59b0" +description = "OpenTelemetry urllib instrumentation" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "opentelemetry_instrumentation_urllib-0.59b0-py3-none-any.whl", hash = "sha256:ed2bd1a02e4334c13c13033681ff8cf10d5dfcd5b0e6d7514f94a00e7f7bd671"}, + {file = "opentelemetry_instrumentation_urllib-0.59b0.tar.gz", hash = "sha256:1e2bb3427ce13854453777d8dccf3b0144640b03846f00fc302bdb6e1f2f8c7a"}, +] + +[package.dependencies] +opentelemetry-api = ">=1.12,<2.0" +opentelemetry-instrumentation = "0.59b0" +opentelemetry-semantic-conventions = "0.59b0" +opentelemetry-util-http = "0.59b0" [[package]] name = "opentelemetry-proto" -version = "1.21.0" +version = "1.38.0" description = "OpenTelemetry Python Proto" optional = false -python-versions = ">=3.7" +python-versions = ">=3.9" +groups = ["main"] files = [ - {file = "opentelemetry_proto-1.21.0-py3-none-any.whl", hash = "sha256:32fc4248e83eebd80994e13963e683f25f3b443226336bb12b5b6d53638f50ba"}, - {file = "opentelemetry_proto-1.21.0.tar.gz", hash = "sha256:7d5172c29ed1b525b5ecf4ebe758c7138a9224441b3cfe683d0a237c33b1941f"}, + {file = "opentelemetry_proto-1.38.0-py3-none-any.whl", hash = "sha256:b6ebe54d3217c42e45462e2a1ae28c3e2bf2ec5a5645236a490f55f45f1a0a18"}, + {file = "opentelemetry_proto-1.38.0.tar.gz", hash = "sha256:88b161e89d9d372ce723da289b7da74c3a8354a8e5359992be813942969ed468"}, ] [package.dependencies] -protobuf = ">=3.19,<5.0" +protobuf = ">=5.0,<7.0" [[package]] name = "opentelemetry-sdk" -version = "1.21.0" +version = "1.38.0" description = "OpenTelemetry Python SDK" optional = false -python-versions = ">=3.7" +python-versions = ">=3.9" +groups = ["main"] files = [ - {file = "opentelemetry_sdk-1.21.0-py3-none-any.whl", hash = "sha256:9fe633243a8c655fedace3a0b89ccdfc654c0290ea2d8e839bd5db3131186f73"}, - {file = "opentelemetry_sdk-1.21.0.tar.gz", hash = "sha256:3ec8cd3020328d6bc5c9991ccaf9ae820ccb6395a5648d9a95d3ec88275b8879"}, + {file = "opentelemetry_sdk-1.38.0-py3-none-any.whl", hash = "sha256:1c66af6564ecc1553d72d811a01df063ff097cdc82ce188da9951f93b8d10f6b"}, + {file = "opentelemetry_sdk-1.38.0.tar.gz", hash = "sha256:93df5d4d871ed09cb4272305be4d996236eedb232253e3ab864c8620f051cebe"}, ] [package.dependencies] -opentelemetry-api = "1.21.0" -opentelemetry-semantic-conventions = "0.42b0" -typing-extensions = ">=3.7.4" +opentelemetry-api = "1.38.0" +opentelemetry-semantic-conventions = "0.59b0" +typing-extensions = ">=4.5.0" [[package]] name = "opentelemetry-semantic-conventions" -version = "0.42b0" +version = "0.59b0" description = "OpenTelemetry Semantic Conventions" optional = false -python-versions = ">=3.7" +python-versions = ">=3.9" +groups = ["main"] files = [ - {file = "opentelemetry_semantic_conventions-0.42b0-py3-none-any.whl", hash = "sha256:5cd719cbfec448af658860796c5d0fcea2fdf0945a2bed2363f42cb1ee39f526"}, - {file = "opentelemetry_semantic_conventions-0.42b0.tar.gz", hash = "sha256:44ae67a0a3252a05072877857e5cc1242c98d4cf12870159f1a94bec800d38ec"}, + {file = "opentelemetry_semantic_conventions-0.59b0-py3-none-any.whl", hash = "sha256:35d3b8833ef97d614136e253c1da9342b4c3c083bbaf29ce31d572a1c3825eed"}, + {file = "opentelemetry_semantic_conventions-0.59b0.tar.gz", hash = "sha256:7a6db3f30d70202d5bf9fa4b69bc866ca6a30437287de6c510fb594878aed6b0"}, +] + +[package.dependencies] +opentelemetry-api = "1.38.0" +typing-extensions = ">=4.5.0" + +[[package]] +name = "opentelemetry-util-http" +version = "0.59b0" +description = "Web util for OpenTelemetry" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "opentelemetry_util_http-0.59b0-py3-none-any.whl", hash = "sha256:6d036a07563bce87bf521839c0671b507a02a0d39d7ea61b88efa14c6e25355d"}, + {file = "opentelemetry_util_http-0.59b0.tar.gz", hash = "sha256:ae66ee91be31938d832f3b4bc4eb8a911f6eddd38969c4a871b1230db2a0a560"}, ] [[package]] @@ -820,6 +925,7 @@ version = "24.0" description = "Core utilities for Python packages" optional = false python-versions = ">=3.7" +groups = ["main", "docs", "test"] files = [ {file = "packaging-24.0-py3-none-any.whl", hash = "sha256:2ddfb553fdf02fb784c234c7ba6ccc288296ceabec964ad2eae3777778130bc5"}, {file = "packaging-24.0.tar.gz", hash = "sha256:eb82c5e3e56209074766e6885bb04b8c38a0c015d0a30036ebe7ece34c9989e9"}, @@ -831,6 +937,7 @@ version = "0.5.6" description = "Divides large result sets into pages for easier browsing" optional = false python-versions = "*" +groups = ["docs"] files = [ {file = "paginate-0.5.6.tar.gz", hash = "sha256:5e6007b6a9398177a7e1648d04fdd9f8c9766a1a945bceac82f1929e8c78af2d"}, ] @@ -841,6 +948,7 @@ version = "0.12.1" description = "Utility library for gitignore style pattern matching of file paths." optional = false python-versions = ">=3.8" +groups = ["docs"] files = [ {file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"}, {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"}, @@ -852,6 +960,7 @@ version = "4.2.2" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." optional = false python-versions = ">=3.8" +groups = ["docs"] files = [ {file = "platformdirs-4.2.2-py3-none-any.whl", hash = "sha256:2d7a1657e36a80ea911db832a8a6ece5ee53d8de21edd5cc5879af6530b1bfee"}, {file = "platformdirs-4.2.2.tar.gz", hash = "sha256:38b7b51f512eed9e84a22788b4bce1de17c0adb134d6becb09836e37d8654cd3"}, @@ -868,6 +977,7 @@ version = "1.5.0" description = "plugin and hook calling mechanisms for python" optional = false python-versions = ">=3.8" +groups = ["test"] files = [ {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, @@ -879,22 +989,22 @@ testing = ["pytest", "pytest-benchmark"] [[package]] name = "protobuf" -version = "4.25.3" +version = "6.33.1" description = "" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" +groups = ["main"] files = [ - {file = "protobuf-4.25.3-cp310-abi3-win32.whl", hash = "sha256:d4198877797a83cbfe9bffa3803602bbe1625dc30d8a097365dbc762e5790faa"}, - {file = "protobuf-4.25.3-cp310-abi3-win_amd64.whl", hash = "sha256:209ba4cc916bab46f64e56b85b090607a676f66b473e6b762e6f1d9d591eb2e8"}, - {file = "protobuf-4.25.3-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:f1279ab38ecbfae7e456a108c5c0681e4956d5b1090027c1de0f934dfdb4b35c"}, - {file = "protobuf-4.25.3-cp37-abi3-manylinux2014_aarch64.whl", hash = "sha256:e7cb0ae90dd83727f0c0718634ed56837bfeeee29a5f82a7514c03ee1364c019"}, - {file = "protobuf-4.25.3-cp37-abi3-manylinux2014_x86_64.whl", hash = "sha256:7c8daa26095f82482307bc717364e7c13f4f1c99659be82890dcfc215194554d"}, - {file = "protobuf-4.25.3-cp38-cp38-win32.whl", hash = "sha256:f4f118245c4a087776e0a8408be33cf09f6c547442c00395fbfb116fac2f8ac2"}, - {file = "protobuf-4.25.3-cp38-cp38-win_amd64.whl", hash = "sha256:c053062984e61144385022e53678fbded7aea14ebb3e0305ae3592fb219ccfa4"}, - {file = "protobuf-4.25.3-cp39-cp39-win32.whl", hash = "sha256:19b270aeaa0099f16d3ca02628546b8baefe2955bbe23224aaf856134eccf1e4"}, - {file = "protobuf-4.25.3-cp39-cp39-win_amd64.whl", hash = "sha256:e3c97a1555fd6388f857770ff8b9703083de6bf1f9274a002a332d65fbb56c8c"}, - {file = "protobuf-4.25.3-py3-none-any.whl", hash = "sha256:f0700d54bcf45424477e46a9f0944155b46fb0639d69728739c0e47bab83f2b9"}, - {file = "protobuf-4.25.3.tar.gz", hash = "sha256:25b5d0b42fd000320bd7830b349e3b696435f3b329810427a6bcce6a5492cc5c"}, + {file = "protobuf-6.33.1-cp310-abi3-win32.whl", hash = "sha256:f8d3fdbc966aaab1d05046d0240dd94d40f2a8c62856d41eaa141ff64a79de6b"}, + {file = "protobuf-6.33.1-cp310-abi3-win_amd64.whl", hash = "sha256:923aa6d27a92bf44394f6abf7ea0500f38769d4b07f4be41cb52bd8b1123b9ed"}, + {file = "protobuf-6.33.1-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:fe34575f2bdde76ac429ec7b570235bf0c788883e70aee90068e9981806f2490"}, + {file = "protobuf-6.33.1-cp39-abi3-manylinux2014_aarch64.whl", hash = "sha256:f8adba2e44cde2d7618996b3fc02341f03f5bc3f2748be72dc7b063319276178"}, + {file = "protobuf-6.33.1-cp39-abi3-manylinux2014_s390x.whl", hash = "sha256:0f4cf01222c0d959c2b399142deb526de420be8236f22c71356e2a544e153c53"}, + {file = "protobuf-6.33.1-cp39-abi3-manylinux2014_x86_64.whl", hash = "sha256:8fd7d5e0eb08cd5b87fd3df49bc193f5cfd778701f47e11d127d0afc6c39f1d1"}, + {file = "protobuf-6.33.1-cp39-cp39-win32.whl", hash = "sha256:023af8449482fa884d88b4563d85e83accab54138ae098924a985bcbb734a213"}, + {file = "protobuf-6.33.1-cp39-cp39-win_amd64.whl", hash = "sha256:df051de4fd7e5e4371334e234c62ba43763f15ab605579e04c7008c05735cd82"}, + {file = "protobuf-6.33.1-py3-none-any.whl", hash = "sha256:d595a9fd694fdeb061a62fbe10eb039cc1e444df81ec9bb70c7fc59ebcb1eafa"}, + {file = "protobuf-6.33.1.tar.gz", hash = "sha256:97f65757e8d09870de6fd973aeddb92f85435607235d20b2dfed93405d00c85b"}, ] [[package]] @@ -903,6 +1013,7 @@ version = "5.9.8" description = "Cross-platform lib for process and system monitoring in Python." optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" +groups = ["main"] files = [ {file = "psutil-5.9.8-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:26bd09967ae00920df88e0352a91cff1a78f8d69b3ecabbfe733610c0af486c8"}, {file = "psutil-5.9.8-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:05806de88103b25903dff19bb6692bd2e714ccf9e668d050d144012055cbca73"}, @@ -923,7 +1034,7 @@ files = [ ] [package.extras] -test = ["enum34", "ipaddress", "mock", "pywin32", "wmi"] +test = ["enum34 ; python_version <= \"3.4\"", "ipaddress ; python_version < \"3.0\"", "mock ; python_version < \"3.0\"", "pywin32 ; sys_platform == \"win32\"", "wmi ; sys_platform == \"win32\""] [[package]] name = "pygments" @@ -931,6 +1042,7 @@ version = "2.18.0" description = "Pygments is a syntax highlighting package written in Python." optional = false python-versions = ">=3.8" +groups = ["main", "docs"] files = [ {file = "pygments-2.18.0-py3-none-any.whl", hash = "sha256:b8e6aca0523f3ab76fee51799c488e38782ac06eafcf95e7ba832985c8e7b13a"}, {file = "pygments-2.18.0.tar.gz", hash = "sha256:786ff802f32e91311bff3889f6e9a86e81505fe99f2735bb6d60ae0c5004f199"}, @@ -945,6 +1057,7 @@ version = "10.8.1" description = "Extension pack for Python Markdown." optional = false python-versions = ">=3.8" +groups = ["docs"] files = [ {file = "pymdown_extensions-10.8.1-py3-none-any.whl", hash = "sha256:f938326115884f48c6059c67377c46cf631c733ef3629b6eed1349989d1b30cb"}, {file = "pymdown_extensions-10.8.1.tar.gz", hash = "sha256:3ab1db5c9e21728dabf75192d71471f8e50f216627e9a1fa9535ecb0231b9940"}, @@ -963,6 +1076,7 @@ version = "7.4.4" description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.7" +groups = ["test"] files = [ {file = "pytest-7.4.4-py3-none-any.whl", hash = "sha256:b090cdf5ed60bf4c45261be03239c2c1c22df034fbffe691abe93cd80cea01d8"}, {file = "pytest-7.4.4.tar.gz", hash = "sha256:2cf0005922c6ace4a3e2ec8b4080eb0d9753fdc93107415332f50ce9e7994280"}, @@ -985,6 +1099,7 @@ version = "2.9.0.post0" description = "Extensions to the standard Python datetime module" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +groups = ["docs"] files = [ {file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"}, {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"}, @@ -999,6 +1114,7 @@ version = "6.0.1" description = "YAML parser and emitter for Python" optional = false python-versions = ">=3.6" +groups = ["docs"] 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"}, @@ -1018,6 +1134,7 @@ files = [ {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"}, @@ -1058,6 +1175,7 @@ version = "0.1" description = "A custom YAML tag for referencing environment variables in YAML files. " optional = false python-versions = ">=3.6" +groups = ["docs"] files = [ {file = "pyyaml_env_tag-0.1-py3-none-any.whl", hash = "sha256:af31106dec8a4d68c60207c1886031cbf839b68aa7abccdb19868200532c2069"}, {file = "pyyaml_env_tag-0.1.tar.gz", hash = "sha256:70092675bda14fdec33b31ba77e7543de9ddc88f2e5b99160396572d11525bdb"}, @@ -1072,6 +1190,7 @@ version = "2024.5.15" description = "Alternative regular expression module, to replace re." optional = false python-versions = ">=3.8" +groups = ["docs"] files = [ {file = "regex-2024.5.15-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a81e3cfbae20378d75185171587cbf756015ccb14840702944f014e0d93ea09f"}, {file = "regex-2024.5.15-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7b59138b219ffa8979013be7bc85bb60c6f7b7575df3d56dc1e403a438c7a3f6"}, @@ -1160,6 +1279,7 @@ version = "2.31.0" description = "Python HTTP for Humans." optional = false python-versions = ">=3.7" +groups = ["main", "docs"] files = [ {file = "requests-2.31.0-py3-none-any.whl", hash = "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f"}, {file = "requests-2.31.0.tar.gz", hash = "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1"}, @@ -1181,6 +1301,7 @@ version = "13.7.1" description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" optional = false python-versions = ">=3.7.0" +groups = ["main"] files = [ {file = "rich-13.7.1-py3-none-any.whl", hash = "sha256:4edbae314f59eb482f54e9e30bf00d33350aaa94f4bfcd4e9e3110e64d0d7222"}, {file = "rich-13.7.1.tar.gz", hash = "sha256:9be308cb1fe2f1f57d67ce99e95af38a1e2bc71ad9813b0e247cf7ffbcc3a432"}, @@ -1199,6 +1320,7 @@ version = "1.5.4" description = "Tool to Detect Surrounding Shell" optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686"}, {file = "shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de"}, @@ -1210,6 +1332,7 @@ version = "1.16.0" description = "Python 2 and 3 compatibility utilities" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" +groups = ["docs"] files = [ {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, @@ -1221,6 +1344,7 @@ version = "1.3.1" description = "Sniff out which async library your code is running under" optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2"}, {file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"}, @@ -1228,25 +1352,30 @@ files = [ [[package]] name = "synapseclient" -version = "4.2.0" +version = "4.10.0" description = "A client for Synapse, a collaborative, open-source research platform that allows teams to share data, track analyses, and collaborate." optional = false -python-versions = ">=3.8" +python-versions = "<3.14,>=3.9" +groups = ["main"] files = [ - {file = "synapseclient-4.2.0-py3-none-any.whl", hash = "sha256:ab5bc9c2bf5b90f271f1a9478eff7e9fca3e573578401ac706383ddb984d7a13"}, - {file = "synapseclient-4.2.0.tar.gz", hash = "sha256:89222661125de1795b1a096cf8c58b8115c19d6b0fa5846ed2a41cdb394ef773"}, + {file = "synapseclient-4.10.0-py3-none-any.whl", hash = "sha256:ce1e588258132d923dcbb84cc35491b5952e182e4c40bd90f97b8734c2603c25"}, + {file = "synapseclient-4.10.0.tar.gz", hash = "sha256:667db3cad5afd541604b0a4b53fdad6a6cab15ccc00956c365356b59777aba00"}, ] [package.dependencies] async-lru = ">=2.0.4,<2.1.0" asyncio-atexit = ">=1.0.1,<1.1.0" deprecated = ">=1.2.4,<2.0" -httpx = ">=0.27.0,<0.28.0" -loky = ">=3.0.0,<3.1.0" +httpcore = ">=1.0.9" +httpx = ">=0.27.0" nest-asyncio = ">=1.6.0,<1.7.0" -opentelemetry-api = ">=1.21.0,<1.22.0" -opentelemetry-exporter-otlp-proto-http = ">=1.21.0,<1.22.0" -opentelemetry-sdk = ">=1.21.0,<1.22.0" +opentelemetry-api = ">=1.21.0" +opentelemetry-exporter-otlp-proto-http = ">=1.21.0" +opentelemetry-instrumentation-httpx = ">=0.48b0" +opentelemetry-instrumentation-requests = ">=0.48b0" +opentelemetry-instrumentation-threading = ">=0.48b0" +opentelemetry-instrumentation-urllib = ">=0.48b0" +opentelemetry-sdk = ">=1.21.0" psutil = ">=5.9.8,<5.10.0" requests = ">=2.22.0,<3.0" tqdm = ">=4.66.2,<5.0" @@ -1254,11 +1383,11 @@ urllib3 = ">=1.26.18,<2" [package.extras] boto3 = ["boto3 (>=1.7.0,<2.0)"] -dev = ["black", "flake8 (>=3.7.0,<4.0)", "func-timeout (>=4.3,<5.0)", "pre-commit", "pytest (>=6.0.0,<7.0)", "pytest-asyncio (>=0.19,<1.0)", "pytest-cov (>=4.1.0,<4.2.0)", "pytest-mock (>=3.0,<4.0)", "pytest-rerunfailures (>=12.0,<13.0)", "pytest-socket (>=0.6.0,<0.7.0)", "pytest-xdist[psutil] (>=2.2,<3.0.0)"] -docs = ["markdown-include (>=0.8.1,<0.9.0)", "mkdocs (>=1.5.3)", "mkdocs-material (>=9.4.14)", "mkdocs-open-in-new-tab (>=1.0.3,<1.1.0)", "mkdocstrings (>=0.24.0)", "mkdocstrings-python (>=1.7.5)", "termynal (>=0.11.1)"] +dev = ["black", "flake8 (>=3.7.0,<4.0)", "func-timeout (>=4.3,<5.0)", "pandas (>=1.5,<3.0)", "pre-commit", "pytest (>=8.2.0,<8.3.0)", "pytest-asyncio (>=1.2.0,<2.0)", "pytest-cov (>=4.1.0,<4.2.0)", "pytest-mock (>=3.0,<4.0)", "pytest-rerunfailures (>=12.0,<13.0)", "pytest-socket (>=0.6.0,<0.7.0)", "pytest-xdist[psutil] (>=2.2,<3.0.0)"] +docs = ["markdown-include (>=0.8.1,<0.9.0)", "mkdocs (>=1.5.3)", "mkdocs-material (>=9.4.14)", "mkdocs-open-in-new-tab (>=1.0.3,<1.1.0)", "mkdocstrings (>=0.24.0)", "mkdocstrings-python (>=1.8.0)", "termynal (>=0.11.1)"] pandas = ["pandas (>=1.5,<3.0)"] -pysftp = ["pysftp (>=0.2.8,<0.3)"] -tests = ["flake8 (>=3.7.0,<4.0)", "func-timeout (>=4.3,<5.0)", "pytest (>=6.0.0,<7.0)", "pytest-asyncio (>=0.19,<1.0)", "pytest-cov (>=4.1.0,<4.2.0)", "pytest-mock (>=3.0,<4.0)", "pytest-rerunfailures (>=12.0,<13.0)", "pytest-socket (>=0.6.0,<0.7.0)", "pytest-xdist[psutil] (>=2.2,<3.0.0)"] +pysftp = ["paramiko (<4.0.0)", "pysftp (>=0.2.8,<0.3)"] +tests = ["flake8 (>=3.7.0,<4.0)", "func-timeout (>=4.3,<5.0)", "pandas (>=1.5,<3.0)", "pytest (>=8.2.0,<8.3.0)", "pytest-asyncio (>=1.2.0,<2.0)", "pytest-cov (>=4.1.0,<4.2.0)", "pytest-mock (>=3.0,<4.0)", "pytest-rerunfailures (>=12.0,<13.0)", "pytest-socket (>=0.6.0,<0.7.0)", "pytest-xdist[psutil] (>=2.2,<3.0.0)"] [[package]] name = "termynal" @@ -1266,6 +1395,7 @@ version = "0.11.1" description = "A lightweight and modern animated terminal window" optional = false python-versions = ">=3.8.1,<4.0.0" +groups = ["docs"] files = [ {file = "termynal-0.11.1-py3-none-any.whl", hash = "sha256:553fc58b126a0482b71a442263d33a9b47fb69868a7fdd6cc36dc9f006aab7a2"}, {file = "termynal-0.11.1.tar.gz", hash = "sha256:ce70d264a649d26365bf72b8cfc5aa2bf903adafebc1027347343779a8695ff0"}, @@ -1283,6 +1413,8 @@ version = "2.0.1" description = "A lil' TOML parser" optional = false python-versions = ">=3.7" +groups = ["test"] +markers = "python_version == \"3.10\"" files = [ {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, @@ -1294,6 +1426,7 @@ version = "4.66.4" description = "Fast, Extensible Progress Meter" optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "tqdm-4.66.4-py3-none-any.whl", hash = "sha256:b75ca56b413b030bc3f00af51fd2c1a1a5eac6a0c1cca83cbb37a5c52abce644"}, {file = "tqdm-4.66.4.tar.gz", hash = "sha256:e4d936c9de8727928f3be6079590e97d9abfe8d39a590be678eb5919ffc186bb"}, @@ -1314,6 +1447,7 @@ version = "0.9.4" description = "Typer, build great CLIs. Easy to code. Based on Python type hints." optional = false python-versions = ">=3.6" +groups = ["main"] files = [ {file = "typer-0.9.4-py3-none-any.whl", hash = "sha256:aa6c4a4e2329d868b80ecbaf16f807f2b54e192209d7ac9dd42691d63f7a54eb"}, {file = "typer-0.9.4.tar.gz", hash = "sha256:f714c2d90afae3a7929fcd72a3abb08df305e1ff61719381384211c4070af57f"}, @@ -1338,6 +1472,7 @@ version = "4.11.0" description = "Backported and Experimental Type Hints for Python 3.8+" optional = false python-versions = ">=3.8" +groups = ["main"] 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"}, @@ -1349,14 +1484,15 @@ version = "1.26.18" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" +groups = ["main", "docs"] files = [ {file = "urllib3-1.26.18-py2.py3-none-any.whl", hash = "sha256:34b97092d7e0a3a8cf7cd10e386f401b3737364026c45e622aa02903dffe0f07"}, {file = "urllib3-1.26.18.tar.gz", hash = "sha256:f8ecc1bba5667413457c529ab955bf8c67b45db799d159066261719e328580a0"}, ] [package.extras] -brotli = ["brotli (==1.0.9)", "brotli (>=1.0.9)", "brotlicffi (>=0.8.0)", "brotlipy (>=0.6.0)"] -secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress", "pyOpenSSL (>=0.14)", "urllib3-secure-extra"] +brotli = ["brotli (==1.0.9) ; os_name != \"nt\" and python_version < \"3\" and platform_python_implementation == \"CPython\"", "brotli (>=1.0.9) ; python_version >= \"3\" and platform_python_implementation == \"CPython\"", "brotlicffi (>=0.8.0) ; (os_name != \"nt\" or python_version >= \"3\") and platform_python_implementation != \"CPython\"", "brotlipy (>=0.6.0) ; os_name == \"nt\" and python_version < \"3\""] +secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress ; python_version == \"2.7\"", "pyOpenSSL (>=0.14)", "urllib3-secure-extra"] socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] [[package]] @@ -1365,6 +1501,7 @@ version = "4.0.0" description = "Filesystem events monitoring" optional = false python-versions = ">=3.8" +groups = ["docs"] files = [ {file = "watchdog-4.0.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:39cb34b1f1afbf23e9562501673e7146777efe95da24fab5707b88f7fb11649b"}, {file = "watchdog-4.0.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c522392acc5e962bcac3b22b9592493ffd06d1fc5d755954e6be9f4990de932b"}, @@ -1406,6 +1543,7 @@ version = "1.16.0" description = "Module for decorators, wrappers and monkey patching." optional = false python-versions = ">=3.6" +groups = ["main"] files = [ {file = "wrapt-1.16.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ffa565331890b90056c01db69c0fe634a776f8019c143a5ae265f9c6bc4bd6d4"}, {file = "wrapt-1.16.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e4fdb9275308292e880dcbeb12546df7f3e0f96c6b41197e0cf37d2826359020"}, @@ -1485,6 +1623,7 @@ version = "3.18.2" description = "Backport of pathlib-compatible object wrapper for zip files" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "zipp-3.18.2-py3-none-any.whl", hash = "sha256:dce197b859eb796242b0622af1b8beb0a722d52aa2f57133ead08edd5bf5374e"}, {file = "zipp-3.18.2.tar.gz", hash = "sha256:6278d9ddbcfb1f1089a88fde84481528b07b0e10474e09dcfe53dad4069fa059"}, @@ -1495,6 +1634,6 @@ docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.link testing = ["big-O", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more-itertools", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-ignore-flaky", "pytest-mypy", "pytest-ruff (>=0.2.1)"] [metadata] -lock-version = "2.0" -python-versions = "^3.9" -content-hash = "173cc8bb0b09db652af4c46e683c3bafdf365328c34eccc6b0089909d92b0e7f" +lock-version = "2.1" +python-versions = ">=3.10,<3.14" +content-hash = "b6337569ee7288887df90fbd50b7f3b375c3f0a7671a643945707a3e793c9448" diff --git a/pyproject.toml b/pyproject.toml index 4b86505..499e0ef 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "cnb-tools" -version = "0.3.2" +version = "0.3.3" description = "Convenience tools/functions for challenges and benchmarking on Synapse.org" license = "Apache-2.0" authors = ["Sage CNB Team "] @@ -11,14 +11,13 @@ readme = "README.md" repository = "https://github.com/Sage-Bionetworks-Challenges/cnb-tools" documentation = "https://sage-bionetworks-challenges.github.io/cnb-tools" classifiers = [ - "Development Status :: 3 - Alpha", + "Development Status :: 4 - Beta", "Intended Audience :: Developers", "Intended Audience :: Science/Research", "License :: OSI Approved :: Apache Software License", "Natural Language :: English", "Programming Language :: Python", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", @@ -28,9 +27,9 @@ classifiers = [ ] [tool.poetry.dependencies] -python = "^3.9" +python = ">=3.10,<3.14" typer = {extras = ["all"], version = "^0.9.0"} -synapseclient = "^4.2.0" +synapseclient = "^4.7.0" [tool.poetry.scripts] cnb-tools = "cnb_tools.main_cli:app" @@ -39,14 +38,14 @@ cnb-tools = "cnb_tools.main_cli:app" "Bug Tracker" = "https://github.com/Sage-Bionetworks-Challenges/cnb-tools/issues" [tool.poetry.group.test.dependencies] -pytest = "^7.4.3" +pytest = ">=7.4.3" [tool.poetry.group.docs.dependencies] -mkdocs = "^1.5.3" -mkdocs-material = "^9.4.14" -mkdocstrings = "^0.24.0" -mkdocstrings-python = "^1.7.5" -termynal = "^0.11.1" +mkdocs = ">=1.5.3" +mkdocs-material = ">=9.4.14" +mkdocstrings = ">=0.24.0" +mkdocstrings-python = ">=1.7.5" +termynal = ">=0.11.1" [build-system] requires = ["poetry-core"] From 937db9c5ae439c31f80c86dbe00100376d3bf3da Mon Sep 17 00:00:00 2001 From: verena <9377970+vpchung@users.noreply.github.com> Date: Tue, 25 Nov 2025 12:05:31 -0800 Subject: [PATCH 02/11] update readmes to reflect version changes --- README.md | 29 ++++++++++++++--------------- docs/index.md | 26 +++++++++++++------------- 2 files changed, 27 insertions(+), 28 deletions(-) diff --git a/README.md b/README.md index 42d7c6e..c1ac2f1 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ PyPI version - Supported Python versions + Supported Python versions License @@ -33,23 +33,23 @@ but not limited to, [DREAM Challenges]. ## Requirements -- [Python 3.9+] +- [Python 3.10+] - [Synapse account] To fully utilize **cnb-tools**, you must have a Synapse account and -provide your credentials to the tool. To do so, create a `.synapseConfig` -file in your home directory and enter the following: +provide your credentials to the tool. First, generate a Synapse +[Personal Access Token (PAT)] with all token permissions enabled. -```yaml -[authentication] -authtoken= -``` +Next, run the following command, then follow the prompts to enter +your Synapse username and PAT: -Generate a new Synapse [Personal Access Token (PAT)] with all token -permissions enabled, then copy-paste it into `authtoken`. Save the file. +```console +synapse config +``` -For security, we recommend updating its permissions so that other -users on your machine do not have read access to your credentials, e.g. +This will create a `.synapseConfig` file in your home directory. For +security, we recommend updating its permissions so that other users +on your machine do not have read access to your credentials, e.g. ```console chmod 600 ~/.synapseConfig @@ -58,10 +58,9 @@ chmod 600 ~/.synapseConfig ## Installation For best practice, use a Python environment to install **cnb-tools** -rather than directly into your base env. In our docs, we will be +rather than directly into your base env. In our docs, we will be using [miniconda], but you can use [miniforge], [venv], [pyenv], etc. - ```console # Create a new env and activate it conda create -n cnb-tools python=3.12 -y @@ -114,7 +113,7 @@ docker run --rm \ [https://sage-bionetworks-challenges.github.io/cnb-tools]: https://sage-bionetworks-challenges.github.io/cnb-tools [https://github.com/Sage-Bionetworks-Challenges/cnb-tools]: https://github.com/Sage-Bionetworks-Challenges/cnb-tools [DREAM Challenges]: https://dreamchallenges.org/ -[Python 3.9+]: https://www.python.org/downloads/ +[Python 3.10+]: https://www.python.org/downloads/ [Synapse account]: https://www.synapse.org/#!LoginPlace:0 [Personal Access Token (PAT)]: https://www.synapse.org/#!PersonalAccessTokens: [miniconda]: https://docs.conda.io/projects/miniconda/en/latest/miniconda-install.html diff --git a/docs/index.md b/docs/index.md index 5992611..5bcd313 100644 --- a/docs/index.md +++ b/docs/index.md @@ -9,7 +9,7 @@ PyPI version - Supported Python versions + Supported Python versions License @@ -29,23 +29,23 @@ but not limited to, [DREAM Challenges]. ## Requirements -- [Python 3.9+] +- [Python 3.10+] - [Synapse account] To fully utilize **cnb-tools**, you must have a Synapse account and -provide your credentials to the tool. To do so, create a `.synapseConfig` -file in your home directory and enter the following: +provide your credentials to the tool. First, generate a Synapse +[Personal Access Token (PAT)] with all token permissions enabled. -```yaml -[authentication] -authtoken= -``` +Next, run the following command, then follow the prompts to enter +your Synapse username and PAT: -Generate a new Synapse [Personal Access Token (PAT)] with all token -permissions enabled, then copy-paste it into `authtoken`. Save the file. +```console +synapse config +``` -For security, we recommend updating its permissions so that other -users on your machine do not have read access to your credentials, e.g. +This will create a `.synapseConfig` file in your home directory. For +security, we recommend updating its permissions so that other users +on your machine do not have read access to your credentials, e.g. ```console @@ -120,7 +120,7 @@ $ docker run --rm \ [https://sage-bionetworks-challenges.github.io/cnb-tools]: https://sage-bionetworks-challenges.github.io/cnb-tools [https://github.com/Sage-Bionetworks-Challenges/cnb-tools]: https://github.com/Sage-Bionetworks-Challenges/cnb-tools [DREAM Challenges]: https://dreamchallenges.org/ -[Python 3.9+]: https://www.python.org/downloads/ +[Python 3.10+]: https://www.python.org/downloads/ [Synapse account]: https://www.synapse.org/#!LoginPlace:0 [Personal Access Token (PAT)]: https://www.synapse.org/#!PersonalAccessTokens: [miniconda]: https://docs.conda.io/projects/miniconda/en/latest/miniconda-install.html From da746bf1effdabd912845e6ae2439e56888beaf5 Mon Sep 17 00:00:00 2001 From: verena <9377970+vpchung@users.noreply.github.com> Date: Tue, 25 Nov 2025 14:20:49 -0800 Subject: [PATCH 03/11] convert base class into a module --- cnb_tools/classes/base.py | 53 --------------------------------------- cnb_tools/modules/base.py | 28 +++++++++++++++++++++ 2 files changed, 28 insertions(+), 53 deletions(-) delete mode 100644 cnb_tools/classes/base.py create mode 100644 cnb_tools/modules/base.py diff --git a/cnb_tools/classes/base.py b/cnb_tools/classes/base.py deleted file mode 100644 index 0d1723a..0000000 --- a/cnb_tools/classes/base.py +++ /dev/null @@ -1,53 +0,0 @@ -"""General class representing a Synapse entity.""" - -import synapseclient -from synapseclient.core.exceptions import SynapseNoCredentialsError - - -class SynapseBase: - def __init__(self, uid=None): - self._syn = self._check_login() - self._uid = uid - - @property - def syn(self) -> str: - """Synapse object with authentication.""" - return self._syn - - @syn.setter - def syn(self, _: str) -> None: - raise SynapseInternalError("syn object is read-only") - - @property - def uid(self) -> str: - """Synapse entity's unique ID.""" - return self._uid - - @uid.setter - def uid(self, value: str) -> None: - self._uid = value - - # pylint: disable=unsupported-binary-operation - @staticmethod - def _check_login() -> synapseclient.Synapse | None: - try: - return synapseclient.login(silent=True) - except SynapseNoCredentialsError as err: - raise SynapseLoginError( - f"⛔ {err}\n\n" - "Steps on how to provide your Synapse credentials to " - "cnb-tools are available here: " - "https://sage-bionetworks-challenges.github.io/cnb-tools/#requirements" - ) from err - - -class SynapseInternalError(Exception): - pass - - -class SynapseLoginError(SystemExit): - pass - - -class UnknownSynapseID(SystemExit): - pass diff --git a/cnb_tools/modules/base.py b/cnb_tools/modules/base.py new file mode 100644 index 0000000..7a5ff2e --- /dev/null +++ b/cnb_tools/modules/base.py @@ -0,0 +1,28 @@ +"""Base module for cnb-tools, including Synapse client management and custom exceptions.""" + +import synapseclient +from synapseclient.core.exceptions import SynapseNoCredentialsError + + +def get_synapse_client(): + try: + return synapseclient.login(silent=True) + except SynapseNoCredentialsError as err: + raise SynapseLoginError( + f"⛔ {err}\n\n" + "Steps on how to provide your Synapse credentials to " + "cnb-tools are available here: " + "https://sage-bionetworks-challenges.github.io/cnb-tools/#requirements" + ) from err + + +class SynapseInternalError(Exception): + pass + + +class SynapseLoginError(SystemExit): + pass + + +class UnknownSynapseID(SystemExit): + pass From 97b2d730593ae80f71d4084d4f61278e2ee5f5ff Mon Sep 17 00:00:00 2001 From: verena <9377970+vpchung@users.noreply.github.com> Date: Mon, 1 Dec 2025 10:53:55 -0800 Subject: [PATCH 04/11] convert annotation class into a module --- cnb_tools/classes/annotation.py | 73 ------------------ cnb_tools/modules/annotation.py | 128 ++++++++++++++++++++++++++++++++ 2 files changed, 128 insertions(+), 73 deletions(-) delete mode 100644 cnb_tools/classes/annotation.py create mode 100644 cnb_tools/modules/annotation.py diff --git a/cnb_tools/classes/annotation.py b/cnb_tools/classes/annotation.py deleted file mode 100644 index 7e914d3..0000000 --- a/cnb_tools/classes/annotation.py +++ /dev/null @@ -1,73 +0,0 @@ -"""Class representing annotations of a challenge submission.""" - -import json - -from synapseclient import SubmissionStatus -from synapseclient.core.exceptions import SynapseHTTPError -from synapseclient.core.retry import with_retry - -from cnb_tools.classes.base import SynapseBase, UnknownSynapseID - - -class SubmissionAnnotation(SynapseBase): - def __init__(self, sub_id: int): - super().__init__(sub_id) - try: - self._curr_annotations = self.syn.getSubmissionStatus(sub_id) - except SynapseHTTPError as err: - raise UnknownSynapseID( - f"⛔ {err.response.json().get('reason')}. " - "Check the ID and try again." - ) from err - - @property - def curr_annotations(self) -> SubmissionStatus: - """Submission's current list of annotations.""" - return self._curr_annotations - - @curr_annotations.setter - def curr_annotations(self, value: SubmissionStatus): - self._curr_annotations = value - - def __str__(self) -> str: - to_print = f" Status: {self.curr_annotations.status}\n" - to_print += "Annotations:\n" - to_print += json.dumps(self.curr_annotations.submissionAnnotations, indent=2) - return to_print - - # pylint: disable=unsupported-binary-operation - def update( - self, - new_annots: dict[str, str | int | float | bool], - verbose: bool - ) -> None: - self.curr_annotations.submissionAnnotations.update(new_annots) - self.curr_annotations = self.syn.store(self.curr_annotations) - print(f"Submission ID {self.uid} annotations updated.") - - if verbose: - print("Annotations:") - print(json.dumps(self.curr_annotations.submissionAnnotations, indent=2)) - - def update_with_file(self, annots_file: str, verbose: bool) -> None: - with open(annots_file, encoding="utf-8") as f: - new_annots = json.load(f) - - # Filter annotations with null and empty-list values - new_annots = { - key: value - for key, value in new_annots.items() - if value not in [None, []] - } - with_retry( - lambda: self.update(new_annots, verbose), - wait=3, - retries=10, - retry_status_codes=[412, 429, 500, 502, 503, 504], - verbose=True, - ) - - def update_status(self, new_status: str) -> None: - self.curr_annotations.status = new_status - self.syn.store(self.curr_annotations) - print(f"Updated submission ID {self.uid} to status: {new_status}") diff --git a/cnb_tools/modules/annotation.py b/cnb_tools/modules/annotation.py new file mode 100644 index 0000000..53ffefd --- /dev/null +++ b/cnb_tools/modules/annotation.py @@ -0,0 +1,128 @@ +"""Module for managing the annotations of challenge submissions. + +This module provides utility functions that extend synapseclient for managing +submission annotations in Synapse challenges. +""" + +import json + +from synapseclient import SubmissionStatus +from synapseclient.core.exceptions import SynapseHTTPError +from synapseclient.core.retry import with_retry + +from cnb_tools.modules.base import get_synapse_client, UnknownSynapseID + + +def get_submission_status(submission_id: int) -> SubmissionStatus: + """Get the submission status object containing annotations. + + Args: + submission_id: ID of the submission + + Returns: + SubmissionStatus object with current annotations + + Raises: + UnknownSynapseID: If the submission ID is invalid + """ + syn = get_synapse_client() + try: + return syn.getSubmissionStatus(submission_id) + except SynapseHTTPError as err: + raise UnknownSynapseID( + f"⛔ {err.response.json().get('reason')}. " "Check the ID and try again." + ) from err + + +def format_annotations(submission_status: SubmissionStatus) -> str: + """Format submission annotations for display. + + Args: + submission_status: SubmissionStatus object + + Returns: + Formatted string representation of status and annotations + """ + output = f" Status: {submission_status.status}\n" + output += "Annotations:\n" + output += json.dumps(submission_status.submissionAnnotations, indent=2) + return output + + +def update_annotations( + submission_id: int, + new_annotations: dict[str, str | int | float | bool], + verbose: bool = False, +) -> SubmissionStatus: + """Update submission annotations. + + Args: + submission_id: ID of the submission + new_annotations: Dictionary of annotations to add/update + verbose: If True, print updated annotations + + Returns: + Updated SubmissionStatus object + """ + syn = get_synapse_client() + status = get_submission_status(submission_id) + status.submissionAnnotations.update(new_annotations) + status = syn.store(status) + + print(f"Submission ID {submission_id} annotations updated.") + + if verbose: + print("Annotations:") + print(json.dumps(status.submissionAnnotations, indent=2)) + + return status + + +def update_annotations_from_file( + submission_id: int, annots_file: str, verbose: bool = False +) -> SubmissionStatus: + """Update submission annotations from a JSON file. + + Args: + submission_id: ID of the submission + annots_file: Path to JSON file containing annotations + verbose: If True, print updated annotations + + Returns: + Updated SubmissionStatus object + """ + with open(annots_file, encoding="utf-8") as f: + new_annotations = json.load(f) + + # Filter annotations with null and empty-list values + new_annotations = { + key: value for key, value in new_annotations.items() if value not in [None, []] + } + + return with_retry( + lambda: update_annotations(submission_id, new_annotations, verbose), + wait=3, + retries=10, + retry_status_codes=[412, 429, 500, 502, 503, 504], + verbose=True, + ) + + +def update_submission_status(submission_id: int, new_status: str) -> SubmissionStatus: + """Update submission status. + + Args: + submission_id: ID of the submission + new_status: New status value (e.g., 'ACCEPTED', 'REJECTED', 'SCORED') + + Returns: + Updated SubmissionStatus object + """ + syn = get_synapse_client() + status = get_submission_status(submission_id) + status.status = new_status + status = syn.store(status) + + print(f"Updated submission ID {submission_id} to status: {new_status}") + + return status From 14418ecb7d7b2fcea77b35a45391b9156ce074fc Mon Sep 17 00:00:00 2001 From: verena <9377970+vpchung@users.noreply.github.com> Date: Mon, 1 Dec 2025 10:54:08 -0800 Subject: [PATCH 05/11] convert participant class into a module --- cnb_tools/classes/participant.py | 36 -------------------- cnb_tools/modules/participant.py | 57 ++++++++++++++++++++++++++++++++ 2 files changed, 57 insertions(+), 36 deletions(-) delete mode 100644 cnb_tools/classes/participant.py create mode 100644 cnb_tools/modules/participant.py diff --git a/cnb_tools/classes/participant.py b/cnb_tools/classes/participant.py deleted file mode 100644 index b2ebc22..0000000 --- a/cnb_tools/classes/participant.py +++ /dev/null @@ -1,36 +0,0 @@ -"""Class representing a submission team or individual participant.""" - -import sys - -import typer -from synapseclient import Team -from synapseclient.core.exceptions import SynapseHTTPError - -from cnb_tools.classes.base import SynapseBase - - -class Participant(SynapseBase): - def __str__(self) -> str: - try: - return self.syn.getTeam(self.uid).get("name") - except SynapseHTTPError: - return self.syn.getUserProfile(self.uid).get("userName") - - # pylint: disable=unsupported-binary-operation - def create_team( - self, - name: str, - description: str | None = None, - can_public_join: bool = False - ) -> Team: - try: - team = self.syn.getTeam(name) - use_team = typer.confirm(f"Team '{name}' already exists. Use this team?") - if not use_team: - sys.exit("OK. Try again with a new challenge name.") - except ValueError: - team = Team( - name=name, description=description, canPublicJoin=can_public_join - ) - # team = self.syn.store(team) - return team diff --git a/cnb_tools/modules/participant.py b/cnb_tools/modules/participant.py new file mode 100644 index 0000000..df26593 --- /dev/null +++ b/cnb_tools/modules/participant.py @@ -0,0 +1,57 @@ +"""Module for managing submission teams or individual participants. + +This module provides utility functions that extend synapseclient for managing +teams and participants in Synapse challenges. +""" + +import sys + +import typer +from synapseclient import Team +from synapseclient.core.exceptions import SynapseHTTPError + +from cnb_tools.modules.base import get_synapse_client + + +def get_participant_name(participant_id: int) -> str: + """Get the name of a participant (team or user). + + Args: + participant_id: Team ID or User ID + + Returns: + Team name or username + """ + syn = get_synapse_client() + try: + return syn.getTeam(participant_id).get("name") + except SynapseHTTPError: + return syn.getUserProfile(participant_id).get("userName") + + +def create_team( + name: str, description: str | None = None, can_public_join: bool = False +) -> Team: + """Create a new team or get an existing team by name. + + Args: + name: Team name + description: Team description (optional) + can_public_join: Whether the team can be joined publicly + + Returns: + Team object (existing or new) + + Raises: + SystemExit: If user chooses not to use an existing team + """ + syn = get_synapse_client() + try: + team = syn.getTeam(name) + use_team = typer.confirm(f"Team '{name}' already exists. Use this team?") + if not use_team: + sys.exit("OK. Try again with a new challenge name.") + except ValueError: + team = Team(name=name, description=description, canPublicJoin=can_public_join) + # team = syn.store(team) + return team From d2604685901bd8a79e04ab3a5af8ec6dc0050ef7 Mon Sep 17 00:00:00 2001 From: verena <9377970+vpchung@users.noreply.github.com> Date: Mon, 1 Dec 2025 10:54:21 -0800 Subject: [PATCH 06/11] convert queue class into a module --- cnb_tools/classes/queue.py | 34 ---------------------- cnb_tools/modules/queue.py | 59 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+), 34 deletions(-) delete mode 100644 cnb_tools/classes/queue.py create mode 100644 cnb_tools/modules/queue.py diff --git a/cnb_tools/classes/queue.py b/cnb_tools/classes/queue.py deleted file mode 100644 index 70a802e..0000000 --- a/cnb_tools/classes/queue.py +++ /dev/null @@ -1,34 +0,0 @@ -"""Class representing a challenge submission evaluation queue.""" - -from synapseclient import Evaluation -from synapseclient.core.exceptions import SynapseHTTPError - -from cnb_tools.classes.base import SynapseBase, UnknownSynapseID - - -class Queue(SynapseBase): - def __init__(self, uid): - super().__init__(uid) - try: - self._queue = self.syn.getEvaluation(uid) - except SynapseHTTPError as err: - raise UnknownSynapseID( - f"⛔ {err.response.json().get('reason')}. " - "Check the ID and try again." - ) from err - - @property - def queue(self) -> Evaluation: - """Synapse evaluation queue.""" - return self._queue - - @queue.setter - def queue(self, value: Evaluation) -> None: - self._queue = value - - def __str__(self) -> str: - return self.queue.name - - def get_challenge_name(self) -> str: - parent_id = self.queue.contentSource - return self.syn.get(parent_id).name diff --git a/cnb_tools/modules/queue.py b/cnb_tools/modules/queue.py new file mode 100644 index 0000000..637b0e9 --- /dev/null +++ b/cnb_tools/modules/queue.py @@ -0,0 +1,59 @@ +"""Module for managing the challenge submission evaluation queues. + +This module provides utility functions that extend synapseclient for managing +evaluation queues in Synapse challenges. +""" + +from synapseclient import Evaluation +from synapseclient.core.exceptions import SynapseHTTPError + +from cnb_tools.modules.base import get_synapse_client, UnknownSynapseID + + +def get_evaluation(evaluation_id: int) -> Evaluation: + """Get an evaluation queue by ID. + + Args: + evaluation_id: Evaluation queue ID + + Returns: + Evaluation object + + Raises: + UnknownSynapseID: If the evaluation ID is invalid + """ + syn = get_synapse_client() + try: + return syn.getEvaluation(evaluation_id) + except SynapseHTTPError as err: + raise UnknownSynapseID( + f"⛔ {err.response.json().get('reason')}. " "Check the ID and try again." + ) from err + + +def get_evaluation_name(evaluation_id: int) -> str: + """Get the name of an evaluation queue. + + Args: + evaluation_id: Evaluation queue ID + + Returns: + Evaluation queue name + """ + evaluation = get_evaluation(evaluation_id) + return evaluation.name + + +def get_challenge_name_from_evaluation(evaluation_id: int) -> str: + """Get the challenge name for an evaluation queue. + + Args: + evaluation_id: Evaluation queue ID + + Returns: + Challenge name + """ + syn = get_synapse_client() + evaluation = get_evaluation(evaluation_id) + parent_id = evaluation.contentSource + return syn.get(parent_id).name From c1cba65343917bb79c82565a5fbc72e74813ccdb Mon Sep 17 00:00:00 2001 From: verena <9377970+vpchung@users.noreply.github.com> Date: Mon, 1 Dec 2025 10:55:49 -0800 Subject: [PATCH 07/11] convert submission class into a module --- cnb_tools/classes/submission.py | 61 ------------- cnb_tools/commands/submission_cli.py | 24 ++--- cnb_tools/modules/submission.py | 131 +++++++++++++++++++++++++++ 3 files changed, 139 insertions(+), 77 deletions(-) delete mode 100644 cnb_tools/classes/submission.py create mode 100644 cnb_tools/modules/submission.py diff --git a/cnb_tools/classes/submission.py b/cnb_tools/classes/submission.py deleted file mode 100644 index 2347274..0000000 --- a/cnb_tools/classes/submission.py +++ /dev/null @@ -1,61 +0,0 @@ -"""Class representing a challenge submission.""" - -from pathlib import Path -from synapseclient.core.exceptions import SynapseHTTPError - -from cnb_tools.classes.annotation import SubmissionAnnotation -from cnb_tools.classes.base import SynapseBase, UnknownSynapseID -from cnb_tools.classes.participant import Participant -from cnb_tools.classes.queue import Queue - - -class Submission(SynapseBase): - def __init__(self, sub_id: int): - super().__init__(sub_id) - try: - self._submission = self.syn.getSubmission(sub_id, downloadFile=False) - except SynapseHTTPError as err: - raise UnknownSynapseID( - f"⛔ {err.response.json().get('reason')}. " - "Check the ID and try again." - ) from err - - @property - def submission(self): - """Synapse submission.""" - return self._submission - - @submission.setter - def submission(self, value) -> None: - self._sumission = value - - def delete(self) -> None: - self.syn.delete(self.submission) - print(f"Submission deleted: {self.uid}") - - def download(self, dest) -> None: - if "dockerDigest" in self.submission: - print( - f"Submission ID {self.uid} is a Docker image 🐳 To " - "'download', run the following:\n\n" - f"docker pull {self.submission.dockerRepositoryName}" - f"@{self.submission.dockerDigest}\n\n" - "If you receive an error, try logging in first with: " - "docker login docker.synapse.org" - ) - else: - self.syn.getSubmission(self.uid, downloadLocation=dest) - location = Path.cwd() if str(dest) == "." else dest - print(f"Submission ID {self.uid} downloaded to: {location}") - - def info(self, verbose) -> None: - challenge = Queue(self.submission.evaluationId).get_challenge_name() - submitter_id = self.submission.get("teamId") or self.submission.get("userId") - submitter = Participant(submitter_id) - print(f" ID: {self.uid}") - print(f" Challenge: {challenge}") - print(f" Date: {self.submission.createdOn[:10]}") - print(f" Submitter: {submitter}") - if verbose: - annotations = SubmissionAnnotation(self.uid) - print(annotations) diff --git a/cnb_tools/commands/submission_cli.py b/cnb_tools/commands/submission_cli.py index 79df49f..5dacd17 100644 --- a/cnb_tools/commands/submission_cli.py +++ b/cnb_tools/commands/submission_cli.py @@ -13,9 +13,8 @@ from typing_extensions import Annotated import typer -from cnb_tools.classes.base import UnknownSynapseID -from cnb_tools.classes.annotation import SubmissionAnnotation -from cnb_tools.classes.submission import Submission +from cnb_tools.modules.base import UnknownSynapseID +from cnb_tools.modules import annotation, submission class Status(str, Enum): @@ -49,8 +48,7 @@ def annotate( ] = False, ): """Annotate one or more submission(s) with a JSON file.""" - submission_annots = SubmissionAnnotation(submission_id) - submission_annots.update_with_file(json_file, verbose) + annotation.update_annotations_from_file(submission_id, str(json_file), verbose) @app.command() @@ -70,8 +68,7 @@ def change_status( """Update one or more submission statuses.""" for submission_id in submission_ids: try: - submission_annots = SubmissionAnnotation(submission_id) - submission_annots.update_status(new_status) + annotation.update_submission_status(submission_id, new_status.value) except UnknownSynapseID as err: if skip_errors: print(f"Unknown submission ID: {submission_id} - skipping...") @@ -110,8 +107,7 @@ def delete( if force: for submission_id in submission_ids: try: - submission = Submission(submission_id) - submission.delete() + submission.delete_submission(submission_id) except UnknownSynapseID as err: if skip_errors: print(f"Unknown submission ID: {submission_id} - skipping...") @@ -134,8 +130,7 @@ def download( ] = ".", ): """Get a submission (file/Docker image)""" - submission = Submission(submission_id) - submission.download(dest) + submission.download_submission(submission_id, str(dest)) @app.command() @@ -154,8 +149,7 @@ def info( ] = False, ): """Get information about a submission""" - submission = Submission(submission_id) - submission.info(verbose) + submission.print_submission_info(submission_id, verbose) @app.command() @@ -173,7 +167,5 @@ def reset( ): """Reset one or more submission to RECEIVED.""" change_status( - submission_ids=submission_ids, - new_status="RECEIVED", - skip_errors=skip_errors + submission_ids=submission_ids, new_status="RECEIVED", skip_errors=skip_errors ) diff --git a/cnb_tools/modules/submission.py b/cnb_tools/modules/submission.py new file mode 100644 index 0000000..0efed20 --- /dev/null +++ b/cnb_tools/modules/submission.py @@ -0,0 +1,131 @@ +"""Module for managing challenge submissions. + +This module provides utility functions that extend synapseclient for managing +submissions in Synapse challenges. +""" + +from pathlib import Path +from synapseclient import Submission as SynapseSubmission +from synapseclient.core.exceptions import SynapseHTTPError + +from cnb_tools.modules.base import get_synapse_client, UnknownSynapseID +from cnb_tools.modules import annotation + + +def get_submission(submission_id: int, download_file: bool = False) -> SynapseSubmission: + """Get a submission by ID. + + Args: + submission_id: ID of the submission + download_file: If True, download the submission file + + Returns: + Synapse Submission object + + Raises: + UnknownSynapseID: If the submission ID is invalid + """ + syn = get_synapse_client() + try: + return syn.getSubmission(submission_id, downloadFile=download_file) + except SynapseHTTPError as err: + raise UnknownSynapseID( + f"⛔ {err.response.json().get('reason')}. " + "Check the ID and try again." + ) from err + + +def delete_submission(submission_id: int) -> None: + """Delete a submission. + + Args: + submission_id: ID of the submission to delete + """ + syn = get_synapse_client() + submission = get_submission(submission_id) + syn.delete(submission) + print(f"Submission deleted: {submission_id}") + + +def download_submission(submission_id: int, dest: str = ".") -> None: + """Download a submission file or display Docker pull command. + + Args: + submission_id: ID of the submission + dest: Destination directory for download (default: current directory) + """ + syn = get_synapse_client() + submission = get_submission(submission_id) + + if "dockerDigest" in submission: + print( + f"Submission ID {submission_id} is a Docker image 🐳 To " + "'download', run the following:\n\n" + f"docker pull {submission.dockerRepositoryName}" + f"@{submission.dockerDigest}\n\n" + "If you receive an error, try logging in first with: " + "docker login docker.synapse.org" + ) + else: + syn.getSubmission(submission_id, downloadLocation=dest) + location = Path.cwd() if str(dest) == "." else dest + print(f"Submission ID {submission_id} downloaded to: {location}") + + +def get_submitter_name(submitter_id: int) -> str: + """Get the name of a submitter (team or user). + + Args: + submitter_id: Team ID or User ID + + Returns: + Team name or username + """ + syn = get_synapse_client() + try: + return syn.getTeam(submitter_id).get("name") + except SynapseHTTPError: + return syn.getUserProfile(submitter_id).get("userName") + + +def get_challenge_name(evaluation_id: int) -> str: + """Get the challenge name for an evaluation queue. + + Args: + evaluation_id: Evaluation queue ID + + Returns: + Challenge name + """ + syn = get_synapse_client() + try: + evaluation = syn.getEvaluation(evaluation_id) + parent_id = evaluation.contentSource + return syn.get(parent_id).name + except SynapseHTTPError as err: + raise UnknownSynapseID( + f"⛔ {err.response.json().get('reason')}. " + "Check the ID and try again." + ) from err + + +def print_submission_info(submission_id: int, verbose: bool = False) -> None: + """Print information about a submission. + + Args: + submission_id: ID of the submission + verbose: If True, also print submission annotations + """ + submission = get_submission(submission_id) + challenge = get_challenge_name(submission.evaluationId) + submitter_id = submission.get("teamId") or submission.get("userId") + submitter = get_submitter_name(submitter_id) + + print(f" ID: {submission_id}") + print(f" Challenge: {challenge}") + print(f" Date: {submission.createdOn[:10]}") + print(f" Submitter: {submitter}") + + if verbose: + status = annotation.get_submission_status(submission_id) + print(annotation.format_annotations(status)) From 11a858b74749b9b770e8432228058245ab36f5c5 Mon Sep 17 00:00:00 2001 From: verena <9377970+vpchung@users.noreply.github.com> Date: Mon, 1 Dec 2025 10:56:06 -0800 Subject: [PATCH 08/11] lint with black --- cnb_tools/main_cli.py | 1 + cnb_tools/validation_toolkit.py | 10 ++-------- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/cnb_tools/main_cli.py b/cnb_tools/main_cli.py index 06fc7ae..17bb07c 100644 --- a/cnb_tools/main_cli.py +++ b/cnb_tools/main_cli.py @@ -5,6 +5,7 @@ $ cnb-tools --help $ cnb-tools --version """ + from typing import Optional from typing_extensions import Annotated import typer diff --git a/cnb_tools/validation_toolkit.py b/cnb_tools/validation_toolkit.py index 207d730..0e5af66 100644 --- a/cnb_tools/validation_toolkit.py +++ b/cnb_tools/validation_toolkit.py @@ -1,5 +1,3 @@ -from typing import Union - from pandas import Series @@ -100,9 +98,7 @@ def check_nan_values(pred_col: Series) -> str: return "" -def check_binary_values( - pred_col: Series, label1: int = 0, label2: int = 1 -) -> str: +def check_binary_values(pred_col: Series, label1: int = 0, label2: int = 1) -> str: """Check that values are binary (default: 0 or 1). Tip: Example Use Case @@ -123,9 +119,7 @@ def check_binary_values( def check_values_range( - pred_col: Series, - min_val: Union[int, float] = 0, - max_val: Union[int, float] = 1 + pred_col: Series, min_val: int | float = 0, max_val: int | float = 1 ) -> str: """Check that values are between min and max values, inclusive. From 18aab0a893109d88f45dee137bffbdbdb6b6350b Mon Sep 17 00:00:00 2001 From: verena <9377970+vpchung@users.noreply.github.com> Date: Mon, 1 Dec 2025 10:56:35 -0800 Subject: [PATCH 09/11] update release notes --- docs/changelog/release-notes.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/docs/changelog/release-notes.md b/docs/changelog/release-notes.md index 23fb3bb..354dc13 100644 --- a/docs/changelog/release-notes.md +++ b/docs/changelog/release-notes.md @@ -7,7 +7,14 @@ - Add How-To tutorial on how the Validation Toolkit can be used ### Bug fixes -- Remove custom classes, as to prevent future confusion with synapseclient's classes +- Refactor all modules to remove custom classes ([#24](https://github.com/Sage-Bionetworks-Challenges/cnb-tools/issues/24)) + - Convert `annotation.py`, `submission.py`, `participant.py`, and `queue.py` to functional utilities + - All modules now act as extensions to `synapseclient` rather than wrapping it with custom classes + - Synapse authentication is now handled automatically within each function via `get_synapse_client()` + - Simplifies API usage and prevents confusion with synapseclient's native classes + +### Internal +- Drop support for Python 3.9 (reached [end of life](https://devguide.python.org/versions/)) ## 0.3.2 From 5729df372fa77ea4faea71f3f1e09270281d375b Mon Sep 17 00:00:00 2001 From: verena <9377970+vpchung@users.noreply.github.com> Date: Mon, 1 Dec 2025 11:59:00 -0800 Subject: [PATCH 10/11] add unit tests --- .github/workflows/test.yml | 40 +++++++ docs/changelog/release-notes.md | 1 + tests/__init__.py | 0 tests/conftest.py | 107 +++++++++++++++++ tests/test_annotation.py | 137 +++++++++++++++++++++ tests/test_participant.py | 91 ++++++++++++++ tests/test_queue.py | 73 ++++++++++++ tests/test_submission.py | 196 +++++++++++++++++++++++++++++++ tests/test_validation_toolkit.py | 125 ++++++++++++++++++++ 9 files changed, 770 insertions(+) create mode 100644 .github/workflows/test.yml delete mode 100644 tests/__init__.py create mode 100644 tests/conftest.py create mode 100644 tests/test_annotation.py create mode 100644 tests/test_participant.py create mode 100644 tests/test_queue.py create mode 100644 tests/test_submission.py create mode 100644 tests/test_validation_toolkit.py diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..5ec24b0 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,40 @@ +name: Test + +on: + push: + branches: [ main, develop, fix-*, feat-* ] + pull_request: + branches: [ main ] + +jobs: + test: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + python-version: ['3.10', '3.11', '3.12'] + + steps: + - uses: actions/checkout@v4 + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -e . + pip install pytest pytest-cov pandas + + - name: Run unit tests + run: | + python -m pytest tests/ -v --tb=short --cov=cnb_tools --cov-report=term-missing + + - name: Test CLI basics + run: | + cnb-tools + cnb-tools --help + cnb-tools submission --help + diff --git a/docs/changelog/release-notes.md b/docs/changelog/release-notes.md index 354dc13..14ba41f 100644 --- a/docs/changelog/release-notes.md +++ b/docs/changelog/release-notes.md @@ -15,6 +15,7 @@ ### Internal - Drop support for Python 3.9 (reached [end of life](https://devguide.python.org/versions/)) +- Add unit tests ## 0.3.2 diff --git a/tests/__init__.py b/tests/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000..480bfd8 --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,107 @@ +"""Shared pytest fixtures for cnb-tools tests.""" + +import os +import sys +from unittest.mock import MagicMock + +import pandas as pd +import pytest +from synapseclient import Evaluation, SubmissionStatus + + +def pytest_configure(config): + """Allow test scripts to import scripts from parent folder.""" + src_path = os.path.abspath(os.path.join(os.path.dirname(__file__), "..")) + sys.path.insert(0, src_path) + + +@pytest.fixture +def mock_syn(): + """Fixture for mocked Synapse client.""" + return MagicMock() + + +@pytest.fixture +def mock_submission_status(): + """Fixture for mocked SubmissionStatus.""" + status = MagicMock(spec=SubmissionStatus) + status.status = "SCORED" + status.submissionAnnotations = MagicMock() + status.submissionAnnotations.__getitem__ = lambda self, key: { + "score": 0.95, + "passed": True, + }.get(key) + return status + + +@pytest.fixture +def mock_submission_file(): + """Fixture for mocked file submission.""" + sub = MagicMock() + sub.id = "12345" + sub.evaluationId = "98765" + sub.createdOn = "2025-11-26T10:30:00.000Z" + sub.get.side_effect = lambda key: {"teamId": 111, "userId": 222}.get(key) + return sub + + +@pytest.fixture +def mock_submission_docker(): + """Fixture for mocked Docker submission.""" + sub = MagicMock() + sub.id = "12345" + sub.evaluationId = "98765" + sub.dockerRepositoryName = "docker.synapse.org/syn12345/model" + sub.dockerDigest = "sha256:abc123" + sub.__contains__ = lambda self, key: key == "dockerDigest" + return sub + + +@pytest.fixture +def mock_evaluation(): + """Fixture for mocked Evaluation.""" + eval_obj = MagicMock(spec=Evaluation) + eval_obj.id = "98765" + eval_obj.name = "Test Queue" + eval_obj.contentSource = "syn12345" + return eval_obj + + +@pytest.fixture +def truth_ids(): + """Fixture for truth IDs.""" + return pd.Series(["id1", "id2", "id3"], name="ids") + + +@pytest.fixture +def pred_ids_valid(): + """Fixture for valid prediction IDs.""" + return pd.Series(["id1", "id2", "id3"], name="ids") + + +@pytest.fixture +def pred_ids_invalid(): + """Fixture for invalid prediction IDs.""" + return pd.Series(["id1", "id1", "id4"], name="ids") + + +@pytest.fixture +def pred_values_valid(): + """Fixture for valid prediction values.""" + return pd.DataFrame( + { + "predictions": [0, 1, 1], + "probabilities": [0.25, 0.6, 0.83], + } + ) + + +@pytest.fixture +def pred_values_invalid(): + """Fixture for invalid prediction values.""" + return pd.DataFrame( + { + "predictions": [0, 1, None], + "probabilities": [-0.5, 1.0, 1.5], + } + ) diff --git a/tests/test_annotation.py b/tests/test_annotation.py new file mode 100644 index 0000000..769241a --- /dev/null +++ b/tests/test_annotation.py @@ -0,0 +1,137 @@ +"""Unit tests for cnb_tools.modules.annotation""" + +from unittest.mock import MagicMock, Mock, mock_open, patch + +import pytest +from synapseclient.core.exceptions import SynapseHTTPError + +from cnb_tools.modules import annotation +from cnb_tools.modules.base import UnknownSynapseID + + +class TestGetSubmissionStatus: + """Tests for get_submission_status function""" + + @patch("cnb_tools.modules.annotation.get_synapse_client") + def test_get_submission_status_success( + self, mock_get_client, mock_syn, mock_submission_status + ): + """Test successfully getting submission status""" + mock_get_client.return_value = mock_syn + mock_syn.getSubmissionStatus.return_value = mock_submission_status + + result = annotation.get_submission_status(12345) + + mock_syn.getSubmissionStatus.assert_called_once_with(12345) + assert result == mock_submission_status + + @patch("cnb_tools.modules.annotation.get_synapse_client") + def test_get_submission_status_invalid_id(self, mock_get_client, mock_syn): + """Test error handling for invalid submission ID""" + mock_get_client.return_value = mock_syn + mock_response = Mock() + mock_response.json.return_value = {"reason": "Submission not found"} + mock_syn.getSubmissionStatus.side_effect = SynapseHTTPError( + response=mock_response + ) + + with pytest.raises(UnknownSynapseID) as exc_info: + annotation.get_submission_status(99999) + assert "Submission not found" in str(exc_info.value) + + +class TestUpdateAnnotations: + """Tests for update_annotations function""" + + @patch("cnb_tools.modules.annotation.get_synapse_client") + @patch("cnb_tools.modules.annotation.get_submission_status") + def test_update_annotations_success( + self, mock_get_status, mock_get_client, mock_syn, mock_submission_status, capsys + ): + """Test successfully updating annotations""" + mock_get_client.return_value = mock_syn + mock_get_status.return_value = mock_submission_status + mock_syn.store.return_value = mock_submission_status + + # Mock the submissionAnnotations attribute as a dictionary + mock_submission_status.submissionAnnotations = MagicMock() + + new_annots = {"new_field": "value"} + result = annotation.update_annotations(12345, new_annots, verbose=False) + + mock_submission_status.submissionAnnotations.update.assert_called_once_with( + new_annots + ) + mock_syn.store.assert_called_once_with(mock_submission_status) + + captured = capsys.readouterr() + assert "Submission ID 12345 annotations updated" in captured.out + assert result == mock_submission_status + + @patch("cnb_tools.modules.annotation.get_synapse_client") + @patch("cnb_tools.modules.annotation.get_submission_status") + def test_update_annotations_verbose( + self, mock_get_status, mock_get_client, mock_syn, mock_submission_status, capsys + ): + """Test updating annotations with verbose output""" + mock_get_client.return_value = mock_syn + mock_get_status.return_value = mock_submission_status + mock_syn.store.return_value = mock_submission_status + + # Mock submissionAnnotations as a dict for JSON serialization + mock_submission_status.submissionAnnotations = {"score": 0.95, "passed": True} + + annotation.update_annotations(12345, {"test": "value"}, verbose=True) + + captured = capsys.readouterr() + assert "Annotations:" in captured.out + assert "score" in captured.out + + +class TestUpdateAnnotationsFromFile: + """Tests for update_annotations_from_file function""" + + @patch("cnb_tools.modules.annotation.with_retry") + @patch( + "builtins.open", + new_callable=mock_open, + read_data='{"score": 0.85, "status": "pass", "null_field": null, "empty_list": []}', + ) + def test_update_annotations_from_file(self, mock_file, mock_retry): + """Test updating annotations from JSON file""" + mock_retry.return_value = MagicMock() + + annotation.update_annotations_from_file(12345, "test.json", verbose=False) + + mock_file.assert_called_once_with("test.json", encoding="utf-8") + + # Verify with_retry was called + assert mock_retry.called + + # Verify null and empty list values were filtered + call_args = mock_retry.call_args + assert call_args[1]["wait"] == 3 + assert call_args[1]["retries"] == 10 + + +class TestUpdateSubmissionStatus: + """Tests for update_submission_status function""" + + @patch("cnb_tools.modules.annotation.get_synapse_client") + @patch("cnb_tools.modules.annotation.get_submission_status") + def test_update_submission_status( + self, mock_get_status, mock_get_client, mock_syn, mock_submission_status, capsys + ): + """Test updating submission status""" + mock_get_client.return_value = mock_syn + mock_get_status.return_value = mock_submission_status + mock_syn.store.return_value = mock_submission_status + + result = annotation.update_submission_status(12345, "ACCEPTED") + + assert mock_submission_status.status == "ACCEPTED" + mock_syn.store.assert_called_once_with(mock_submission_status) + + captured = capsys.readouterr() + assert "Updated submission ID 12345 to status: ACCEPTED" in captured.out + assert result == mock_submission_status diff --git a/tests/test_participant.py b/tests/test_participant.py new file mode 100644 index 0000000..181ee39 --- /dev/null +++ b/tests/test_participant.py @@ -0,0 +1,91 @@ +"""Unit tests for cnb_tools.modules.participant""" + +from unittest.mock import MagicMock, patch + +import pytest +from synapseclient import Team +from synapseclient.core.exceptions import SynapseHTTPError + +from cnb_tools.modules import participant + + +class TestGetParticipantName: + """Tests for get_participant_name function""" + + @patch("cnb_tools.modules.participant.get_synapse_client") + def test_get_participant_name_team(self, mock_get_client, mock_syn): + """Test getting team name""" + mock_get_client.return_value = mock_syn + mock_syn.getTeam.return_value = {"name": "Dream Team"} + + result = participant.get_participant_name(12345) + + assert result == "Dream Team" + mock_syn.getTeam.assert_called_once_with(12345) + + @patch("cnb_tools.modules.participant.get_synapse_client") + def test_get_participant_name_user(self, mock_get_client, mock_syn): + """Test getting username when team lookup fails""" + mock_get_client.return_value = mock_syn + mock_syn.getTeam.side_effect = SynapseHTTPError(response=MagicMock()) + mock_syn.getUserProfile.return_value = {"userName": "john_doe"} + + result = participant.get_participant_name(67890) + + assert result == "john_doe" + mock_syn.getUserProfile.assert_called_once_with(67890) + + +class TestCreateTeam: + """Tests for create_team function""" + + @patch("cnb_tools.modules.participant.typer.confirm") + @patch("cnb_tools.modules.participant.get_synapse_client") + def test_create_team_new(self, mock_get_client, mock_confirm, mock_syn): + """Test creating a new team""" + mock_get_client.return_value = mock_syn + mock_syn.getTeam.side_effect = ValueError("Team not found") + + result = participant.create_team( + name="New Team", description="Test description", can_public_join=True + ) + + assert isinstance(result, Team) + assert result.name == "New Team" + assert result.description == "Test description" + assert result.canPublicJoin is True + + @patch("cnb_tools.modules.participant.typer.confirm") + @patch("cnb_tools.modules.participant.get_synapse_client") + def test_create_team_existing_confirmed( + self, mock_get_client, mock_confirm, mock_syn + ): + """Test using an existing team when user confirms""" + mock_get_client.return_value = mock_syn + existing_team = MagicMock(spec=Team) + existing_team.name = "Dream Team" + mock_syn.getTeam.return_value = existing_team + mock_confirm.return_value = True + + result = participant.create_team(name="Dream Team") + + assert result == existing_team + mock_confirm.assert_called_once_with( + "Team 'Dream Team' already exists. Use this team?" + ) + + @patch("cnb_tools.modules.participant.typer.confirm") + @patch("cnb_tools.modules.participant.get_synapse_client") + def test_create_team_existing_declined( + self, mock_get_client, mock_confirm, mock_syn + ): + """Test declining to use existing team exits""" + mock_get_client.return_value = mock_syn + existing_team = MagicMock(spec=Team) + mock_syn.getTeam.return_value = existing_team + mock_confirm.return_value = False + + with pytest.raises(SystemExit) as exc_info: + participant.create_team(name="Existing Team") + + assert "Try again with a new challenge name" in str(exc_info.value) diff --git a/tests/test_queue.py b/tests/test_queue.py new file mode 100644 index 0000000..6a58d1b --- /dev/null +++ b/tests/test_queue.py @@ -0,0 +1,73 @@ +"""Unit tests for cnb_tools.modules.queue""" + +from unittest.mock import MagicMock, Mock, patch + +import pytest +from synapseclient.core.exceptions import SynapseHTTPError + +from cnb_tools.modules import queue +from cnb_tools.modules.base import UnknownSynapseID + + +class TestGetEvaluation: + """Tests for get_evaluation function""" + + @patch("cnb_tools.modules.queue.get_synapse_client") + def test_get_evaluation_success(self, mock_get_client, mock_syn, mock_evaluation): + """Test successfully getting an evaluation""" + mock_get_client.return_value = mock_syn + mock_syn.getEvaluation.return_value = mock_evaluation + + result = queue.get_evaluation(98765) + + mock_syn.getEvaluation.assert_called_once_with(98765) + assert result == mock_evaluation + + @patch("cnb_tools.modules.queue.get_synapse_client") + def test_get_evaluation_invalid_id(self, mock_get_client, mock_syn): + """Test error handling for invalid evaluation ID""" + mock_get_client.return_value = mock_syn + mock_response = Mock() + mock_response.json.return_value = {"reason": "Evaluation not found"} + mock_syn.getEvaluation.side_effect = SynapseHTTPError(response=mock_response) + + with pytest.raises(UnknownSynapseID) as exc_info: + queue.get_evaluation(99999) + + assert "Evaluation not found" in str(exc_info.value) + + +class TestGetEvaluationName: + """Tests for get_evaluation_name function""" + + @patch("cnb_tools.modules.queue.get_evaluation") + def test_get_evaluation_name(self, mock_get_eval, mock_evaluation): + """Test getting evaluation name""" + mock_get_eval.return_value = mock_evaluation + + result = queue.get_evaluation_name(98765) + + assert result == "Test Queue" + mock_get_eval.assert_called_once_with(98765) + + +class TestGetChallengeNameFromEvaluation: + """Tests for get_challenge_name_from_evaluation function""" + + @patch("cnb_tools.modules.queue.get_synapse_client") + @patch("cnb_tools.modules.queue.get_evaluation") + def test_get_challenge_name_from_evaluation( + self, mock_get_eval, mock_get_client, mock_syn, mock_evaluation + ): + """Test getting challenge name from evaluation""" + mock_get_eval.return_value = mock_evaluation + mock_get_client.return_value = mock_syn + + mock_challenge = MagicMock() + mock_challenge.name = "Amazing Challenge" + mock_syn.get.return_value = mock_challenge + + result = queue.get_challenge_name_from_evaluation(98765) + + assert result == "Amazing Challenge" + mock_syn.get.assert_called_once_with("syn12345") diff --git a/tests/test_submission.py b/tests/test_submission.py new file mode 100644 index 0000000..76f778c --- /dev/null +++ b/tests/test_submission.py @@ -0,0 +1,196 @@ +"""Unit tests for cnb_tools.modules.submission""" + +from unittest.mock import MagicMock, Mock, patch + +import pytest +from synapseclient.core.exceptions import SynapseHTTPError + +from cnb_tools.modules import submission +from cnb_tools.modules.base import UnknownSynapseID + + +class TestGetSubmission: + """Tests for get_submission function""" + + @patch("cnb_tools.modules.submission.get_synapse_client") + def test_get_submission_success( + self, mock_get_client, mock_syn, mock_submission_file + ): + """Test successfully getting a submission""" + mock_get_client.return_value = mock_syn + mock_syn.getSubmission.return_value = mock_submission_file + + result = submission.get_submission(12345, download_file=False) + + mock_syn.getSubmission.assert_called_once_with(12345, downloadFile=False) + assert result == mock_submission_file + + @patch("cnb_tools.modules.submission.get_synapse_client") + def test_get_submission_invalid_id(self, mock_get_client, mock_syn): + """Test error handling for invalid submission ID""" + mock_get_client.return_value = mock_syn + mock_response = Mock() + mock_response.json.return_value = {"reason": "Submission not found"} + mock_syn.getSubmission.side_effect = SynapseHTTPError(response=mock_response) + + with pytest.raises(UnknownSynapseID) as exc_info: + submission.get_submission(99999) + + assert "Submission not found" in str(exc_info.value) + + +class TestDeleteSubmission: + """Tests for delete_submission function""" + + @patch("cnb_tools.modules.submission.get_synapse_client") + @patch("cnb_tools.modules.submission.get_submission") + def test_delete_submission( + self, mock_get_sub, mock_get_client, mock_syn, mock_submission_file, capsys + ): + """Test deleting a submission""" + mock_get_client.return_value = mock_syn + mock_get_sub.return_value = mock_submission_file + + submission.delete_submission(12345) + + mock_get_sub.assert_called_once_with(12345) + mock_syn.delete.assert_called_once_with(mock_submission_file) + + captured = capsys.readouterr() + assert "Submission deleted: 12345" in captured.out + + +class TestDownloadSubmission: + """Tests for download_submission function""" + + @patch("cnb_tools.modules.submission.get_synapse_client") + @patch("cnb_tools.modules.submission.get_submission") + def test_download_submission_file( + self, mock_get_sub, mock_get_client, mock_syn, mock_submission_file, capsys + ): + """Test downloading a file submission""" + mock_get_client.return_value = mock_syn + mock_get_sub.return_value = mock_submission_file + + submission.download_submission(12345, dest="/tmp") + + mock_syn.getSubmission.assert_called_once_with(12345, downloadLocation="/tmp") + + captured = capsys.readouterr() + assert "Submission ID 12345 downloaded to:" in captured.out + + @patch("cnb_tools.modules.submission.get_synapse_client") + @patch("cnb_tools.modules.submission.get_submission") + def test_download_submission_docker( + self, mock_get_sub, mock_get_client, mock_syn, mock_submission_docker, capsys + ): + """Test 'downloading' a Docker submission (displays pull command)""" + mock_get_client.return_value = mock_syn + mock_get_sub.return_value = mock_submission_docker + + submission.download_submission(12345) + + captured = capsys.readouterr() + assert "Docker image" in captured.out + assert "docker pull" in captured.out + assert "docker.synapse.org/syn12345/model@sha256:abc123" in captured.out + + +class TestGetSubmitterName: + """Tests for get_submitter_name function""" + + @patch("cnb_tools.modules.submission.get_synapse_client") + def test_get_submitter_name_team(self, mock_get_client, mock_syn): + """Test getting team name""" + mock_get_client.return_value = mock_syn + mock_syn.getTeam.return_value = {"name": "Test Team"} + + result = submission.get_submitter_name(111) + + assert result == "Test Team" + + @patch("cnb_tools.modules.submission.get_synapse_client") + def test_get_submitter_name_user(self, mock_get_client, mock_syn): + """Test getting username when team lookup fails""" + mock_get_client.return_value = mock_syn + mock_syn.getTeam.side_effect = SynapseHTTPError(response=Mock()) + mock_syn.getUserProfile.return_value = {"userName": "testuser"} + + result = submission.get_submitter_name(222) + + assert result == "testuser" + + +class TestGetChallengeName: + """Tests for get_challenge_name function""" + + @patch("cnb_tools.modules.submission.get_synapse_client") + def test_get_challenge_name(self, mock_get_client, mock_syn): + """Test getting challenge name from evaluation""" + mock_get_client.return_value = mock_syn + mock_eval = MagicMock() + mock_eval.contentSource = "syn98765" + mock_syn.getEvaluation.return_value = mock_eval + + mock_challenge = MagicMock() + mock_challenge.name = "Test Challenge" + mock_syn.get.return_value = mock_challenge + + result = submission.get_challenge_name(12345) + + assert result == "Test Challenge" + mock_syn.get.assert_called_once_with("syn98765") + + +class TestPrintSubmissionInfo: + """Tests for print_submission_info function""" + + @patch("cnb_tools.modules.submission.get_submission") + @patch("cnb_tools.modules.submission.get_challenge_name") + @patch("cnb_tools.modules.submission.get_submitter_name") + def test_print_submission_info_basic( + self, + mock_get_submitter, + mock_get_challenge, + mock_get_sub, + mock_submission_file, + capsys, + ): + """Test printing basic submission info""" + mock_get_sub.return_value = mock_submission_file + mock_get_challenge.return_value = "Test Challenge" + mock_get_submitter.return_value = "Test Team" + + submission.print_submission_info(12345, verbose=False) + + captured = capsys.readouterr() + assert "ID: 12345" in captured.out + assert "Challenge: Test Challenge" in captured.out + assert "Date: 2025-11-26" in captured.out + assert "Submitter: Test Team" in captured.out + + @patch("cnb_tools.modules.submission.annotation") + @patch("cnb_tools.modules.submission.get_submission") + @patch("cnb_tools.modules.submission.get_challenge_name") + @patch("cnb_tools.modules.submission.get_submitter_name") + def test_print_submission_info_verbose( + self, + mock_get_submitter, + mock_get_challenge, + mock_get_sub, + mock_annotation, + mock_submission_file, + ): + """Test printing submission info with annotations""" + mock_get_sub.return_value = mock_submission_file + mock_get_challenge.return_value = "Test Challenge" + mock_get_submitter.return_value = "Test Team" + + mock_status = MagicMock() + mock_annotation.get_submission_status.return_value = mock_status + mock_annotation.format_annotations.return_value = "Formatted annotations" + + submission.print_submission_info(12345, verbose=True) + + mock_annotation.get_submission_status.assert_called_once_with(12345) + mock_annotation.format_annotations.assert_called_once_with(mock_status) diff --git a/tests/test_validation_toolkit.py b/tests/test_validation_toolkit.py new file mode 100644 index 0000000..35ce01d --- /dev/null +++ b/tests/test_validation_toolkit.py @@ -0,0 +1,125 @@ +"""Unit tests for cnb_tools.validation_toolkit""" + +from cnb_tools import validation_toolkit + + +class TestCheckMissingKeys: + """Tests for check_missing_keys function""" + + def test_no_missing_keys(self, truth_ids, pred_ids_valid): + """Test when all keys are present""" + result = validation_toolkit.check_missing_keys(truth_ids, pred_ids_valid) + assert result == "" + + def test_missing_keys(self, truth_ids, pred_ids_invalid): + """Test when keys are missing""" + result = validation_toolkit.check_missing_keys(truth_ids, pred_ids_invalid) + assert "Found 2 missing ID(s)" in result + + def test_missing_keys_verbose(self, truth_ids, pred_ids_invalid): + """Test missing keys with verbose output""" + result = validation_toolkit.check_missing_keys( + truth_ids, pred_ids_invalid, verbose=True + ) + assert "Found 2 missing ID(s)" in result + assert "id2" in result + assert "id3" in result + + +class TestCheckUnknownKeys: + """Tests for check_unknown_keys function""" + + def test_no_unknown_keys(self, truth_ids, pred_ids_valid): + """Test when no unknown keys are present""" + result = validation_toolkit.check_unknown_keys(truth_ids, pred_ids_valid) + assert result == "" + + def test_unknown_keys(self, truth_ids, pred_ids_invalid): + """Test when unknown keys are present""" + result = validation_toolkit.check_unknown_keys(truth_ids, pred_ids_invalid) + assert "Found 1 unknown ID(s)" in result + + def test_unknown_keys_verbose(self, truth_ids, pred_ids_invalid): + """Test unknown keys with verbose output""" + result = validation_toolkit.check_unknown_keys( + truth_ids, pred_ids_invalid, verbose=True + ) + assert "Found 1 unknown ID(s)" in result + assert "id4" in result + + +class TestCheckDuplicateKeys: + """Tests for check_duplicate_keys function""" + + def test_no_duplicates(self, pred_ids_valid): + """Test when no duplicate keys are present""" + result = validation_toolkit.check_duplicate_keys(pred_ids_valid) + assert result == "" + + def test_duplicates(self, pred_ids_invalid): + """Test when duplicate keys are present""" + result = validation_toolkit.check_duplicate_keys(pred_ids_invalid) + assert "Found 1 duplicate ID(s)" in result + + def test_duplicates_verbose(self, pred_ids_invalid): + """Test duplicate keys with verbose output""" + result = validation_toolkit.check_duplicate_keys(pred_ids_invalid, verbose=True) + assert "Found 1 duplicate ID(s)" in result + assert "id1" in result + + +class TestCheckNanValues: + """Tests for check_nan_values function""" + + def test_no_nan_values(self, pred_values_valid): + """Test when no NaN values are present""" + result = validation_toolkit.check_nan_values(pred_values_valid["predictions"]) + assert result == "" + + def test_with_nan_values(self, pred_values_invalid): + """Test when NaN values are present""" + result = validation_toolkit.check_nan_values(pred_values_invalid["predictions"]) + assert "'predictions' column contains 1 NaN value(s)" in result + + +class TestCheckBinaryValues: + """Tests for check_binary_values function""" + + def test_valid_binary_values_default(self, pred_values_valid): + """Test valid binary values with default labels (0, 1)""" + result = validation_toolkit.check_binary_values( + pred_values_valid["predictions"] + ) + assert result == "" + + def test_invalid_binary_values(self, pred_values_invalid): + """Test invalid binary values""" + result = validation_toolkit.check_binary_values( + pred_values_invalid["predictions"] + ) + assert "'predictions' values should only be 0 or 1" in result + + +class TestCheckValuesRange: + """Tests for check_values_range function""" + + def test_valid_range_default(self, pred_values_valid): + """Test values within default range [0, 1]""" + result = validation_toolkit.check_values_range( + pred_values_valid["probabilities"] + ) + assert result == "" + + def test_invalid_range_below_min(self, pred_values_invalid): + """Test values below minimum""" + result = validation_toolkit.check_values_range( + pred_values_invalid["probabilities"] + ) + assert "'probabilities' values should be between [0, 1]" in result + + def test_invalid_range_above_max(self, pred_values_invalid): + """Test values above maximum""" + result = validation_toolkit.check_values_range( + pred_values_invalid["probabilities"] + ) + assert "'probabilities' values should be between [0, 1]" in result From c1a2ee3596531621b4b2cc6fa9c77ea97699a18e Mon Sep 17 00:00:00 2001 From: verena <9377970+vpchung@users.noreply.github.com> Date: Mon, 1 Dec 2025 14:37:49 -0800 Subject: [PATCH 11/11] constrain click version for typer compatibility --- poetry.lock | 281 ++++++++++++++++++++++++++++++++++++++++++++++++- pyproject.toml | 2 + 2 files changed, 280 insertions(+), 3 deletions(-) diff --git a/poetry.lock b/poetry.lock index 4251d05..2927b42 100644 --- a/poetry.lock +++ b/poetry.lock @@ -711,6 +711,157 @@ files = [ {file = "nest_asyncio-1.6.0.tar.gz", hash = "sha256:6f172d5449aca15afd6c646851f4e31e02c598d553a667e38cafa997cfec55fe"}, ] +[[package]] +name = "numpy" +version = "2.2.6" +description = "Fundamental package for array computing in Python" +optional = false +python-versions = ">=3.10" +groups = ["main"] +markers = "python_version == \"3.10\"" +files = [ + {file = "numpy-2.2.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b412caa66f72040e6d268491a59f2c43bf03eb6c96dd8f0307829feb7fa2b6fb"}, + {file = "numpy-2.2.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8e41fd67c52b86603a91c1a505ebaef50b3314de0213461c7a6e99c9a3beff90"}, + {file = "numpy-2.2.6-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:37e990a01ae6ec7fe7fa1c26c55ecb672dd98b19c3d0e1d1f326fa13cb38d163"}, + {file = "numpy-2.2.6-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:5a6429d4be8ca66d889b7cf70f536a397dc45ba6faeb5f8c5427935d9592e9cf"}, + {file = "numpy-2.2.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:efd28d4e9cd7d7a8d39074a4d44c63eda73401580c5c76acda2ce969e0a38e83"}, + {file = "numpy-2.2.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc7b73d02efb0e18c000e9ad8b83480dfcd5dfd11065997ed4c6747470ae8915"}, + {file = "numpy-2.2.6-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:74d4531beb257d2c3f4b261bfb0fc09e0f9ebb8842d82a7b4209415896adc680"}, + {file = "numpy-2.2.6-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8fc377d995680230e83241d8a96def29f204b5782f371c532579b4f20607a289"}, + {file = "numpy-2.2.6-cp310-cp310-win32.whl", hash = "sha256:b093dd74e50a8cba3e873868d9e93a85b78e0daf2e98c6797566ad8044e8363d"}, + {file = "numpy-2.2.6-cp310-cp310-win_amd64.whl", hash = "sha256:f0fd6321b839904e15c46e0d257fdd101dd7f530fe03fd6359c1ea63738703f3"}, + {file = "numpy-2.2.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f9f1adb22318e121c5c69a09142811a201ef17ab257a1e66ca3025065b7f53ae"}, + {file = "numpy-2.2.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c820a93b0255bc360f53eca31a0e676fd1101f673dda8da93454a12e23fc5f7a"}, + {file = "numpy-2.2.6-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:3d70692235e759f260c3d837193090014aebdf026dfd167834bcba43e30c2a42"}, + {file = "numpy-2.2.6-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:481b49095335f8eed42e39e8041327c05b0f6f4780488f61286ed3c01368d491"}, + {file = "numpy-2.2.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b64d8d4d17135e00c8e346e0a738deb17e754230d7e0810ac5012750bbd85a5a"}, + {file = "numpy-2.2.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba10f8411898fc418a521833e014a77d3ca01c15b0c6cdcce6a0d2897e6dbbdf"}, + {file = "numpy-2.2.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:bd48227a919f1bafbdda0583705e547892342c26fb127219d60a5c36882609d1"}, + {file = "numpy-2.2.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9551a499bf125c1d4f9e250377c1ee2eddd02e01eac6644c080162c0c51778ab"}, + {file = "numpy-2.2.6-cp311-cp311-win32.whl", hash = "sha256:0678000bb9ac1475cd454c6b8c799206af8107e310843532b04d49649c717a47"}, + {file = "numpy-2.2.6-cp311-cp311-win_amd64.whl", hash = "sha256:e8213002e427c69c45a52bbd94163084025f533a55a59d6f9c5b820774ef3303"}, + {file = "numpy-2.2.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:41c5a21f4a04fa86436124d388f6ed60a9343a6f767fced1a8a71c3fbca038ff"}, + {file = "numpy-2.2.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:de749064336d37e340f640b05f24e9e3dd678c57318c7289d222a8a2f543e90c"}, + {file = "numpy-2.2.6-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:894b3a42502226a1cac872f840030665f33326fc3dac8e57c607905773cdcde3"}, + {file = "numpy-2.2.6-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:71594f7c51a18e728451bb50cc60a3ce4e6538822731b2933209a1f3614e9282"}, + {file = "numpy-2.2.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f2618db89be1b4e05f7a1a847a9c1c0abd63e63a1607d892dd54668dd92faf87"}, + {file = "numpy-2.2.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd83c01228a688733f1ded5201c678f0c53ecc1006ffbc404db9f7a899ac6249"}, + {file = "numpy-2.2.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:37c0ca431f82cd5fa716eca9506aefcabc247fb27ba69c5062a6d3ade8cf8f49"}, + {file = "numpy-2.2.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fe27749d33bb772c80dcd84ae7e8df2adc920ae8297400dabec45f0dedb3f6de"}, + {file = "numpy-2.2.6-cp312-cp312-win32.whl", hash = "sha256:4eeaae00d789f66c7a25ac5f34b71a7035bb474e679f410e5e1a94deb24cf2d4"}, + {file = "numpy-2.2.6-cp312-cp312-win_amd64.whl", hash = "sha256:c1f9540be57940698ed329904db803cf7a402f3fc200bfe599334c9bd84a40b2"}, + {file = "numpy-2.2.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0811bb762109d9708cca4d0b13c4f67146e3c3b7cf8d34018c722adb2d957c84"}, + {file = "numpy-2.2.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:287cc3162b6f01463ccd86be154f284d0893d2b3ed7292439ea97eafa8170e0b"}, + {file = "numpy-2.2.6-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:f1372f041402e37e5e633e586f62aa53de2eac8d98cbfb822806ce4bbefcb74d"}, + {file = "numpy-2.2.6-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:55a4d33fa519660d69614a9fad433be87e5252f4b03850642f88993f7b2ca566"}, + {file = "numpy-2.2.6-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f92729c95468a2f4f15e9bb94c432a9229d0d50de67304399627a943201baa2f"}, + {file = "numpy-2.2.6-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1bc23a79bfabc5d056d106f9befb8d50c31ced2fbc70eedb8155aec74a45798f"}, + {file = "numpy-2.2.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e3143e4451880bed956e706a3220b4e5cf6172ef05fcc397f6f36a550b1dd868"}, + {file = "numpy-2.2.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b4f13750ce79751586ae2eb824ba7e1e8dba64784086c98cdbbcc6a42112ce0d"}, + {file = "numpy-2.2.6-cp313-cp313-win32.whl", hash = "sha256:5beb72339d9d4fa36522fc63802f469b13cdbe4fdab4a288f0c441b74272ebfd"}, + {file = "numpy-2.2.6-cp313-cp313-win_amd64.whl", hash = "sha256:b0544343a702fa80c95ad5d3d608ea3599dd54d4632df855e4c8d24eb6ecfa1c"}, + {file = "numpy-2.2.6-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0bca768cd85ae743b2affdc762d617eddf3bcf8724435498a1e80132d04879e6"}, + {file = "numpy-2.2.6-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:fc0c5673685c508a142ca65209b4e79ed6740a4ed6b2267dbba90f34b0b3cfda"}, + {file = "numpy-2.2.6-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:5bd4fc3ac8926b3819797a7c0e2631eb889b4118a9898c84f585a54d475b7e40"}, + {file = "numpy-2.2.6-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:fee4236c876c4e8369388054d02d0e9bb84821feb1a64dd59e137e6511a551f8"}, + {file = "numpy-2.2.6-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e1dda9c7e08dc141e0247a5b8f49cf05984955246a327d4c48bda16821947b2f"}, + {file = "numpy-2.2.6-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f447e6acb680fd307f40d3da4852208af94afdfab89cf850986c3ca00562f4fa"}, + {file = "numpy-2.2.6-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:389d771b1623ec92636b0786bc4ae56abafad4a4c513d36a55dce14bd9ce8571"}, + {file = "numpy-2.2.6-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8e9ace4a37db23421249ed236fdcdd457d671e25146786dfc96835cd951aa7c1"}, + {file = "numpy-2.2.6-cp313-cp313t-win32.whl", hash = "sha256:038613e9fb8c72b0a41f025a7e4c3f0b7a1b5d768ece4796b674c8f3fe13efff"}, + {file = "numpy-2.2.6-cp313-cp313t-win_amd64.whl", hash = "sha256:6031dd6dfecc0cf9f668681a37648373bddd6421fff6c66ec1624eed0180ee06"}, + {file = "numpy-2.2.6-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:0b605b275d7bd0c640cad4e5d30fa701a8d59302e127e5f79138ad62762c3e3d"}, + {file = "numpy-2.2.6-pp310-pypy310_pp73-macosx_14_0_x86_64.whl", hash = "sha256:7befc596a7dc9da8a337f79802ee8adb30a552a94f792b9c9d18c840055907db"}, + {file = "numpy-2.2.6-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce47521a4754c8f4593837384bd3424880629f718d87c5d44f8ed763edd63543"}, + {file = "numpy-2.2.6-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:d042d24c90c41b54fd506da306759e06e568864df8ec17ccc17e9e884634fd00"}, + {file = "numpy-2.2.6.tar.gz", hash = "sha256:e29554e2bef54a90aa5cc07da6ce955accb83f21ab5de01a62c8478897b264fd"}, +] + +[[package]] +name = "numpy" +version = "2.3.5" +description = "Fundamental package for array computing in Python" +optional = false +python-versions = ">=3.11" +groups = ["main"] +markers = "python_version >= \"3.11\"" +files = [ + {file = "numpy-2.3.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:de5672f4a7b200c15a4127042170a694d4df43c992948f5e1af57f0174beed10"}, + {file = "numpy-2.3.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:acfd89508504a19ed06ef963ad544ec6664518c863436306153e13e94605c218"}, + {file = "numpy-2.3.5-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:ffe22d2b05504f786c867c8395de703937f934272eb67586817b46188b4ded6d"}, + {file = "numpy-2.3.5-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:872a5cf366aec6bb1147336480fef14c9164b154aeb6542327de4970282cd2f5"}, + {file = "numpy-2.3.5-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3095bdb8dd297e5920b010e96134ed91d852d81d490e787beca7e35ae1d89cf7"}, + {file = "numpy-2.3.5-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8cba086a43d54ca804ce711b2a940b16e452807acebe7852ff327f1ecd49b0d4"}, + {file = "numpy-2.3.5-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6cf9b429b21df6b99f4dee7a1218b8b7ffbbe7df8764dc0bd60ce8a0708fed1e"}, + {file = "numpy-2.3.5-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:396084a36abdb603546b119d96528c2f6263921c50df3c8fd7cb28873a237748"}, + {file = "numpy-2.3.5-cp311-cp311-win32.whl", hash = "sha256:b0c7088a73aef3d687c4deef8452a3ac7c1be4e29ed8bf3b366c8111128ac60c"}, + {file = "numpy-2.3.5-cp311-cp311-win_amd64.whl", hash = "sha256:a414504bef8945eae5f2d7cb7be2d4af77c5d1cb5e20b296c2c25b61dff2900c"}, + {file = "numpy-2.3.5-cp311-cp311-win_arm64.whl", hash = "sha256:0cd00b7b36e35398fa2d16af7b907b65304ef8bb4817a550e06e5012929830fa"}, + {file = "numpy-2.3.5-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:74ae7b798248fe62021dbf3c914245ad45d1a6b0cb4a29ecb4b31d0bfbc4cc3e"}, + {file = "numpy-2.3.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ee3888d9ff7c14604052b2ca5535a30216aa0a58e948cdd3eeb8d3415f638769"}, + {file = "numpy-2.3.5-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:612a95a17655e213502f60cfb9bf9408efdc9eb1d5f50535cc6eb365d11b42b5"}, + {file = "numpy-2.3.5-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:3101e5177d114a593d79dd79658650fe28b5a0d8abeb8ce6f437c0e6df5be1a4"}, + {file = "numpy-2.3.5-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8b973c57ff8e184109db042c842423ff4f60446239bd585a5131cc47f06f789d"}, + {file = "numpy-2.3.5-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0d8163f43acde9a73c2a33605353a4f1bc4798745a8b1d73183b28e5b435ae28"}, + {file = "numpy-2.3.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:51c1e14eb1e154ebd80e860722f9e6ed6ec89714ad2db2d3aa33c31d7c12179b"}, + {file = "numpy-2.3.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b46b4ec24f7293f23adcd2d146960559aaf8020213de8ad1909dba6c013bf89c"}, + {file = "numpy-2.3.5-cp312-cp312-win32.whl", hash = "sha256:3997b5b3c9a771e157f9aae01dd579ee35ad7109be18db0e85dbdbe1de06e952"}, + {file = "numpy-2.3.5-cp312-cp312-win_amd64.whl", hash = "sha256:86945f2ee6d10cdfd67bcb4069c1662dd711f7e2a4343db5cecec06b87cf31aa"}, + {file = "numpy-2.3.5-cp312-cp312-win_arm64.whl", hash = "sha256:f28620fe26bee16243be2b7b874da327312240a7cdc38b769a697578d2100013"}, + {file = "numpy-2.3.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d0f23b44f57077c1ede8c5f26b30f706498b4862d3ff0a7298b8411dd2f043ff"}, + {file = "numpy-2.3.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:aa5bc7c5d59d831d9773d1170acac7893ce3a5e130540605770ade83280e7188"}, + {file = "numpy-2.3.5-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:ccc933afd4d20aad3c00bcef049cb40049f7f196e0397f1109dba6fed63267b0"}, + {file = "numpy-2.3.5-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:afaffc4393205524af9dfa400fa250143a6c3bc646c08c9f5e25a9f4b4d6a903"}, + {file = "numpy-2.3.5-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9c75442b2209b8470d6d5d8b1c25714270686f14c749028d2199c54e29f20b4d"}, + {file = "numpy-2.3.5-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:11e06aa0af8c0f05104d56450d6093ee639e15f24ecf62d417329d06e522e017"}, + {file = "numpy-2.3.5-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ed89927b86296067b4f81f108a2271d8926467a8868e554eaf370fc27fa3ccaf"}, + {file = "numpy-2.3.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:51c55fe3451421f3a6ef9a9c1439e82101c57a2c9eab9feb196a62b1a10b58ce"}, + {file = "numpy-2.3.5-cp313-cp313-win32.whl", hash = "sha256:1978155dd49972084bd6ef388d66ab70f0c323ddee6f693d539376498720fb7e"}, + {file = "numpy-2.3.5-cp313-cp313-win_amd64.whl", hash = "sha256:00dc4e846108a382c5869e77c6ed514394bdeb3403461d25a829711041217d5b"}, + {file = "numpy-2.3.5-cp313-cp313-win_arm64.whl", hash = "sha256:0472f11f6ec23a74a906a00b48a4dcf3849209696dff7c189714511268d103ae"}, + {file = "numpy-2.3.5-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:414802f3b97f3c1eef41e530aaba3b3c1620649871d8cb38c6eaff034c2e16bd"}, + {file = "numpy-2.3.5-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5ee6609ac3604fa7780e30a03e5e241a7956f8e2fcfe547d51e3afa5247ac47f"}, + {file = "numpy-2.3.5-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:86d835afea1eaa143012a2d7a3f45a3adce2d7adc8b4961f0b362214d800846a"}, + {file = "numpy-2.3.5-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:30bc11310e8153ca664b14c5f1b73e94bd0503681fcf136a163de856f3a50139"}, + {file = "numpy-2.3.5-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1062fde1dcf469571705945b0f221b73928f34a20c904ffb45db101907c3454e"}, + {file = "numpy-2.3.5-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ce581db493ea1a96c0556360ede6607496e8bf9b3a8efa66e06477267bc831e9"}, + {file = "numpy-2.3.5-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:cc8920d2ec5fa99875b670bb86ddeb21e295cb07aa331810d9e486e0b969d946"}, + {file = "numpy-2.3.5-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:9ee2197ef8c4f0dfe405d835f3b6a14f5fee7782b5de51ba06fb65fc9b36e9f1"}, + {file = "numpy-2.3.5-cp313-cp313t-win32.whl", hash = "sha256:70b37199913c1bd300ff6e2693316c6f869c7ee16378faf10e4f5e3275b299c3"}, + {file = "numpy-2.3.5-cp313-cp313t-win_amd64.whl", hash = "sha256:b501b5fa195cc9e24fe102f21ec0a44dffc231d2af79950b451e0d99cea02234"}, + {file = "numpy-2.3.5-cp313-cp313t-win_arm64.whl", hash = "sha256:a80afd79f45f3c4a7d341f13acbe058d1ca8ac017c165d3fa0d3de6bc1a079d7"}, + {file = "numpy-2.3.5-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:bf06bc2af43fa8d32d30fae16ad965663e966b1a3202ed407b84c989c3221e82"}, + {file = "numpy-2.3.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:052e8c42e0c49d2575621c158934920524f6c5da05a1d3b9bab5d8e259e045f0"}, + {file = "numpy-2.3.5-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:1ed1ec893cff7040a02c8aa1c8611b94d395590d553f6b53629a4461dc7f7b63"}, + {file = "numpy-2.3.5-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:2dcd0808a421a482a080f89859a18beb0b3d1e905b81e617a188bd80422d62e9"}, + {file = "numpy-2.3.5-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:727fd05b57df37dc0bcf1a27767a3d9a78cbbc92822445f32cc3436ba797337b"}, + {file = "numpy-2.3.5-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fffe29a1ef00883599d1dc2c51aa2e5d80afe49523c261a74933df395c15c520"}, + {file = "numpy-2.3.5-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:8f7f0e05112916223d3f438f293abf0727e1181b5983f413dfa2fefc4098245c"}, + {file = "numpy-2.3.5-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2e2eb32ddb9ccb817d620ac1d8dae7c3f641c1e5f55f531a33e8ab97960a75b8"}, + {file = "numpy-2.3.5-cp314-cp314-win32.whl", hash = "sha256:66f85ce62c70b843bab1fb14a05d5737741e74e28c7b8b5a064de10142fad248"}, + {file = "numpy-2.3.5-cp314-cp314-win_amd64.whl", hash = "sha256:e6a0bc88393d65807d751a614207b7129a310ca4fe76a74e5c7da5fa5671417e"}, + {file = "numpy-2.3.5-cp314-cp314-win_arm64.whl", hash = "sha256:aeffcab3d4b43712bb7a60b65f6044d444e75e563ff6180af8f98dd4b905dfd2"}, + {file = "numpy-2.3.5-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:17531366a2e3a9e30762c000f2c43a9aaa05728712e25c11ce1dbe700c53ad41"}, + {file = "numpy-2.3.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:d21644de1b609825ede2f48be98dfde4656aefc713654eeee280e37cadc4e0ad"}, + {file = "numpy-2.3.5-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:c804e3a5aba5460c73955c955bdbd5c08c354954e9270a2c1565f62e866bdc39"}, + {file = "numpy-2.3.5-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:cc0a57f895b96ec78969c34f682c602bf8da1a0270b09bc65673df2e7638ec20"}, + {file = "numpy-2.3.5-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:900218e456384ea676e24ea6a0417f030a3b07306d29d7ad843957b40a9d8d52"}, + {file = "numpy-2.3.5-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:09a1bea522b25109bf8e6f3027bd810f7c1085c64a0c7ce050c1676ad0ba010b"}, + {file = "numpy-2.3.5-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:04822c00b5fd0323c8166d66c701dc31b7fbd252c100acd708c48f763968d6a3"}, + {file = "numpy-2.3.5-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:d6889ec4ec662a1a37eb4b4fb26b6100841804dac55bd9df579e326cdc146227"}, + {file = "numpy-2.3.5-cp314-cp314t-win32.whl", hash = "sha256:93eebbcf1aafdf7e2ddd44c2923e2672e1010bddc014138b229e49725b4d6be5"}, + {file = "numpy-2.3.5-cp314-cp314t-win_amd64.whl", hash = "sha256:c8a9958e88b65c3b27e22ca2a076311636850b612d6bbfb76e8d156aacde2aaf"}, + {file = "numpy-2.3.5-cp314-cp314t-win_arm64.whl", hash = "sha256:6203fdf9f3dc5bdaed7319ad8698e685c7a3be10819f41d32a0723e611733b42"}, + {file = "numpy-2.3.5-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:f0963b55cdd70fad460fa4c1341f12f976bb26cb66021a5580329bd498988310"}, + {file = "numpy-2.3.5-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:f4255143f5160d0de972d28c8f9665d882b5f61309d8362fdd3e103cf7bf010c"}, + {file = "numpy-2.3.5-pp311-pypy311_pp73-macosx_14_0_arm64.whl", hash = "sha256:a4b9159734b326535f4dd01d947f919c6eefd2d9827466a696c44ced82dfbc18"}, + {file = "numpy-2.3.5-pp311-pypy311_pp73-macosx_14_0_x86_64.whl", hash = "sha256:2feae0d2c91d46e59fcd62784a3a83b3fb677fead592ce51b5a6fbb4f95965ff"}, + {file = "numpy-2.3.5-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ffac52f28a7849ad7576293c0cb7b9f08304e8f7d738a8cb8a90ec4c55a998eb"}, + {file = "numpy-2.3.5-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:63c0e9e7eea69588479ebf4a8a270d5ac22763cc5854e9a7eae952a3908103f7"}, + {file = "numpy-2.3.5-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:f16417ec91f12f814b10bafe79ef77e70113a2f5f7018640e7425ff979253425"}, + {file = "numpy-2.3.5.tar.gz", hash = "sha256:784db1dcdab56bf0517743e746dfb0f885fc68d948aba86eeec2cba234bdf1c0"}, +] + [[package]] name = "opentelemetry-api" version = "1.38.0" @@ -942,6 +1093,106 @@ files = [ {file = "paginate-0.5.6.tar.gz", hash = "sha256:5e6007b6a9398177a7e1648d04fdd9f8c9766a1a945bceac82f1929e8c78af2d"}, ] +[[package]] +name = "pandas" +version = "2.3.3" +description = "Powerful data structures for data analysis, time series, and statistics" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "pandas-2.3.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:376c6446ae31770764215a6c937f72d917f214b43560603cd60da6408f183b6c"}, + {file = "pandas-2.3.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e19d192383eab2f4ceb30b412b22ea30690c9e618f78870357ae1d682912015a"}, + {file = "pandas-2.3.3-cp310-cp310-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5caf26f64126b6c7aec964f74266f435afef1c1b13da3b0636c7518a1fa3e2b1"}, + {file = "pandas-2.3.3-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dd7478f1463441ae4ca7308a70e90b33470fa593429f9d4c578dd00d1fa78838"}, + {file = "pandas-2.3.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:4793891684806ae50d1288c9bae9330293ab4e083ccd1c5e383c34549c6e4250"}, + {file = "pandas-2.3.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:28083c648d9a99a5dd035ec125d42439c6c1c525098c58af0fc38dd1a7a1b3d4"}, + {file = "pandas-2.3.3-cp310-cp310-win_amd64.whl", hash = "sha256:503cf027cf9940d2ceaa1a93cfb5f8c8c7e6e90720a2850378f0b3f3b1e06826"}, + {file = "pandas-2.3.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:602b8615ebcc4a0c1751e71840428ddebeb142ec02c786e8ad6b1ce3c8dec523"}, + {file = "pandas-2.3.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8fe25fc7b623b0ef6b5009149627e34d2a4657e880948ec3c840e9402e5c1b45"}, + {file = "pandas-2.3.3-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b468d3dad6ff947df92dcb32ede5b7bd41a9b3cceef0a30ed925f6d01fb8fa66"}, + {file = "pandas-2.3.3-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b98560e98cb334799c0b07ca7967ac361a47326e9b4e5a7dfb5ab2b1c9d35a1b"}, + {file = "pandas-2.3.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1d37b5848ba49824e5c30bedb9c830ab9b7751fd049bc7914533e01c65f79791"}, + {file = "pandas-2.3.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:db4301b2d1f926ae677a751eb2bd0e8c5f5319c9cb3f88b0becbbb0b07b34151"}, + {file = "pandas-2.3.3-cp311-cp311-win_amd64.whl", hash = "sha256:f086f6fe114e19d92014a1966f43a3e62285109afe874f067f5abbdcbb10e59c"}, + {file = "pandas-2.3.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6d21f6d74eb1725c2efaa71a2bfc661a0689579b58e9c0ca58a739ff0b002b53"}, + {file = "pandas-2.3.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3fd2f887589c7aa868e02632612ba39acb0b8948faf5cc58f0850e165bd46f35"}, + {file = "pandas-2.3.3-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ecaf1e12bdc03c86ad4a7ea848d66c685cb6851d807a26aa245ca3d2017a1908"}, + {file = "pandas-2.3.3-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b3d11d2fda7eb164ef27ffc14b4fcab16a80e1ce67e9f57e19ec0afaf715ba89"}, + {file = "pandas-2.3.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a68e15f780eddf2b07d242e17a04aa187a7ee12b40b930bfdd78070556550e98"}, + {file = "pandas-2.3.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:371a4ab48e950033bcf52b6527eccb564f52dc826c02afd9a1bc0ab731bba084"}, + {file = "pandas-2.3.3-cp312-cp312-win_amd64.whl", hash = "sha256:a16dcec078a01eeef8ee61bf64074b4e524a2a3f4b3be9326420cabe59c4778b"}, + {file = "pandas-2.3.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:56851a737e3470de7fa88e6131f41281ed440d29a9268dcbf0002da5ac366713"}, + {file = "pandas-2.3.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bdcd9d1167f4885211e401b3036c0c8d9e274eee67ea8d0758a256d60704cfe8"}, + {file = "pandas-2.3.3-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e32e7cc9af0f1cc15548288a51a3b681cc2a219faa838e995f7dc53dbab1062d"}, + {file = "pandas-2.3.3-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:318d77e0e42a628c04dc56bcef4b40de67918f7041c2b061af1da41dcff670ac"}, + {file = "pandas-2.3.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4e0a175408804d566144e170d0476b15d78458795bb18f1304fb94160cabf40c"}, + {file = "pandas-2.3.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:93c2d9ab0fc11822b5eece72ec9587e172f63cff87c00b062f6e37448ced4493"}, + {file = "pandas-2.3.3-cp313-cp313-win_amd64.whl", hash = "sha256:f8bfc0e12dc78f777f323f55c58649591b2cd0c43534e8355c51d3fede5f4dee"}, + {file = "pandas-2.3.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:75ea25f9529fdec2d2e93a42c523962261e567d250b0013b16210e1d40d7c2e5"}, + {file = "pandas-2.3.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:74ecdf1d301e812db96a465a525952f4dde225fdb6d8e5a521d47e1f42041e21"}, + {file = "pandas-2.3.3-cp313-cp313t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6435cb949cb34ec11cc9860246ccb2fdc9ecd742c12d3304989017d53f039a78"}, + {file = "pandas-2.3.3-cp313-cp313t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:900f47d8f20860de523a1ac881c4c36d65efcb2eb850e6948140fa781736e110"}, + {file = "pandas-2.3.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:a45c765238e2ed7d7c608fc5bc4a6f88b642f2f01e70c0c23d2224dd21829d86"}, + {file = "pandas-2.3.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:c4fc4c21971a1a9f4bdb4c73978c7f7256caa3e62b323f70d6cb80db583350bc"}, + {file = "pandas-2.3.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:ee15f284898e7b246df8087fc82b87b01686f98ee67d85a17b7ab44143a3a9a0"}, + {file = "pandas-2.3.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1611aedd912e1ff81ff41c745822980c49ce4a7907537be8692c8dbc31924593"}, + {file = "pandas-2.3.3-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6d2cefc361461662ac48810cb14365a365ce864afe85ef1f447ff5a1e99ea81c"}, + {file = "pandas-2.3.3-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ee67acbbf05014ea6c763beb097e03cd629961c8a632075eeb34247120abcb4b"}, + {file = "pandas-2.3.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c46467899aaa4da076d5abc11084634e2d197e9460643dd455ac3db5856b24d6"}, + {file = "pandas-2.3.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:6253c72c6a1d990a410bc7de641d34053364ef8bcd3126f7e7450125887dffe3"}, + {file = "pandas-2.3.3-cp314-cp314-win_amd64.whl", hash = "sha256:1b07204a219b3b7350abaae088f451860223a52cfb8a6c53358e7948735158e5"}, + {file = "pandas-2.3.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:2462b1a365b6109d275250baaae7b760fd25c726aaca0054649286bcfbb3e8ec"}, + {file = "pandas-2.3.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:0242fe9a49aa8b4d78a4fa03acb397a58833ef6199e9aa40a95f027bb3a1b6e7"}, + {file = "pandas-2.3.3-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a21d830e78df0a515db2b3d2f5570610f5e6bd2e27749770e8bb7b524b89b450"}, + {file = "pandas-2.3.3-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2e3ebdb170b5ef78f19bfb71b0dc5dc58775032361fa188e814959b74d726dd5"}, + {file = "pandas-2.3.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:d051c0e065b94b7a3cea50eb1ec32e912cd96dba41647eb24104b6c6c14c5788"}, + {file = "pandas-2.3.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:3869faf4bd07b3b66a9f462417d0ca3a9df29a9f6abd5d0d0dbab15dac7abe87"}, + {file = "pandas-2.3.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c503ba5216814e295f40711470446bc3fd00f0faea8a086cbc688808e26f92a2"}, + {file = "pandas-2.3.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a637c5cdfa04b6d6e2ecedcb81fc52ffb0fd78ce2ebccc9ea964df9f658de8c8"}, + {file = "pandas-2.3.3-cp39-cp39-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:854d00d556406bffe66a4c0802f334c9ad5a96b4f1f868adf036a21b11ef13ff"}, + {file = "pandas-2.3.3-cp39-cp39-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bf1f8a81d04ca90e32a0aceb819d34dbd378a98bf923b6398b9a3ec0bf44de29"}, + {file = "pandas-2.3.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:23ebd657a4d38268c7dfbdf089fbc31ea709d82e4923c5ffd4fbd5747133ce73"}, + {file = "pandas-2.3.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:5554c929ccc317d41a5e3d1234f3be588248e61f08a74dd17c9eabb535777dc9"}, + {file = "pandas-2.3.3-cp39-cp39-win_amd64.whl", hash = "sha256:d3e28b3e83862ccf4d85ff19cf8c20b2ae7e503881711ff2d534dc8f761131aa"}, + {file = "pandas-2.3.3.tar.gz", hash = "sha256:e05e1af93b977f7eafa636d043f9f94c7ee3ac81af99c13508215942e64c993b"}, +] + +[package.dependencies] +numpy = [ + {version = ">=1.22.4", markers = "python_version < \"3.11\""}, + {version = ">=1.23.2", markers = "python_version == \"3.11\""}, + {version = ">=1.26.0", markers = "python_version >= \"3.12\""}, +] +python-dateutil = ">=2.8.2" +pytz = ">=2020.1" +tzdata = ">=2022.7" + +[package.extras] +all = ["PyQt5 (>=5.15.9)", "SQLAlchemy (>=2.0.0)", "adbc-driver-postgresql (>=0.8.0)", "adbc-driver-sqlite (>=0.8.0)", "beautifulsoup4 (>=4.11.2)", "bottleneck (>=1.3.6)", "dataframe-api-compat (>=0.1.7)", "fastparquet (>=2022.12.0)", "fsspec (>=2022.11.0)", "gcsfs (>=2022.11.0)", "html5lib (>=1.1)", "hypothesis (>=6.46.1)", "jinja2 (>=3.1.2)", "lxml (>=4.9.2)", "matplotlib (>=3.6.3)", "numba (>=0.56.4)", "numexpr (>=2.8.4)", "odfpy (>=1.4.1)", "openpyxl (>=3.1.0)", "pandas-gbq (>=0.19.0)", "psycopg2 (>=2.9.6)", "pyarrow (>=10.0.1)", "pymysql (>=1.0.2)", "pyreadstat (>=1.2.0)", "pytest (>=7.3.2)", "pytest-xdist (>=2.2.0)", "python-calamine (>=0.1.7)", "pyxlsb (>=1.0.10)", "qtpy (>=2.3.0)", "s3fs (>=2022.11.0)", "scipy (>=1.10.0)", "tables (>=3.8.0)", "tabulate (>=0.9.0)", "xarray (>=2022.12.0)", "xlrd (>=2.0.1)", "xlsxwriter (>=3.0.5)", "zstandard (>=0.19.0)"] +aws = ["s3fs (>=2022.11.0)"] +clipboard = ["PyQt5 (>=5.15.9)", "qtpy (>=2.3.0)"] +compression = ["zstandard (>=0.19.0)"] +computation = ["scipy (>=1.10.0)", "xarray (>=2022.12.0)"] +consortium-standard = ["dataframe-api-compat (>=0.1.7)"] +excel = ["odfpy (>=1.4.1)", "openpyxl (>=3.1.0)", "python-calamine (>=0.1.7)", "pyxlsb (>=1.0.10)", "xlrd (>=2.0.1)", "xlsxwriter (>=3.0.5)"] +feather = ["pyarrow (>=10.0.1)"] +fss = ["fsspec (>=2022.11.0)"] +gcp = ["gcsfs (>=2022.11.0)", "pandas-gbq (>=0.19.0)"] +hdf5 = ["tables (>=3.8.0)"] +html = ["beautifulsoup4 (>=4.11.2)", "html5lib (>=1.1)", "lxml (>=4.9.2)"] +mysql = ["SQLAlchemy (>=2.0.0)", "pymysql (>=1.0.2)"] +output-formatting = ["jinja2 (>=3.1.2)", "tabulate (>=0.9.0)"] +parquet = ["pyarrow (>=10.0.1)"] +performance = ["bottleneck (>=1.3.6)", "numba (>=0.56.4)", "numexpr (>=2.8.4)"] +plot = ["matplotlib (>=3.6.3)"] +postgresql = ["SQLAlchemy (>=2.0.0)", "adbc-driver-postgresql (>=0.8.0)", "psycopg2 (>=2.9.6)"] +pyarrow = ["pyarrow (>=10.0.1)"] +spss = ["pyreadstat (>=1.2.0)"] +sql-other = ["SQLAlchemy (>=2.0.0)", "adbc-driver-postgresql (>=0.8.0)", "adbc-driver-sqlite (>=0.8.0)"] +test = ["hypothesis (>=6.46.1)", "pytest (>=7.3.2)", "pytest-xdist (>=2.2.0)"] +xml = ["lxml (>=4.9.2)"] + [[package]] name = "pathspec" version = "0.12.1" @@ -1099,7 +1350,7 @@ version = "2.9.0.post0" description = "Extensions to the standard Python datetime module" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" -groups = ["docs"] +groups = ["main", "docs"] files = [ {file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"}, {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"}, @@ -1108,6 +1359,18 @@ files = [ [package.dependencies] six = ">=1.5" +[[package]] +name = "pytz" +version = "2025.2" +description = "World timezone definitions, modern and historical" +optional = false +python-versions = "*" +groups = ["main"] +files = [ + {file = "pytz-2025.2-py2.py3-none-any.whl", hash = "sha256:5ddf76296dd8c44c26eb8f4b6f35488f3ccbf6fbbd7adee0b7262d43f0ec2f00"}, + {file = "pytz-2025.2.tar.gz", hash = "sha256:360b9e3dbb49a209c21ad61809c7fb453643e048b38924c765813546746e81c3"}, +] + [[package]] name = "pyyaml" version = "6.0.1" @@ -1332,7 +1595,7 @@ version = "1.16.0" description = "Python 2 and 3 compatibility utilities" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" -groups = ["docs"] +groups = ["main", "docs"] files = [ {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, @@ -1478,6 +1741,18 @@ files = [ {file = "typing_extensions-4.11.0.tar.gz", hash = "sha256:83f085bd5ca59c80295fc2a82ab5dac679cbe02b9f33f7d83af68e241bea51b0"}, ] +[[package]] +name = "tzdata" +version = "2025.2" +description = "Provider of IANA time zone data" +optional = false +python-versions = ">=2" +groups = ["main"] +files = [ + {file = "tzdata-2025.2-py2.py3-none-any.whl", hash = "sha256:1a403fada01ff9221ca8044d701868fa132215d84beb92242d9acd2147f667a8"}, + {file = "tzdata-2025.2.tar.gz", hash = "sha256:b60a638fcc0daffadf82fe0f57e53d06bdec2f36c4df66280ae79bce6bd6f2b9"}, +] + [[package]] name = "urllib3" version = "1.26.18" @@ -1636,4 +1911,4 @@ testing = ["big-O", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more [metadata] lock-version = "2.1" python-versions = ">=3.10,<3.14" -content-hash = "b6337569ee7288887df90fbd50b7f3b375c3f0a7671a643945707a3e793c9448" +content-hash = "dffc126d383906d864314633e479606d4dc927bfc97db6c58f115e950b54a102" diff --git a/pyproject.toml b/pyproject.toml index 499e0ef..68aeec6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -30,6 +30,8 @@ classifiers = [ python = ">=3.10,<3.14" typer = {extras = ["all"], version = "^0.9.0"} synapseclient = "^4.7.0" +click = ">=8.0.0,<8.2.0" +pandas = ">=2.0.3" [tool.poetry.scripts] cnb-tools = "cnb_tools.main_cli:app"