diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 2304b01e..b96a5264 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -58,14 +58,12 @@ jobs: # run python style checks - name: Poetry Lock Check run: poetry check --lock - - name: isort - run: poetry run isort snakebids -c - - name: Black - run: poetry run black snakebids --check + - name: Formatting + run: poetry run ruff format snakebids --check + - name: Linting + run: poetry run ruff check snakebids - name: pyright run: poetry run pyright snakebids - - name: ruff - run: poetry run ruff snakebids build-cache-env: runs-on: ubuntu-latest diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 82a7cd6e..7a11ed77 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -10,23 +10,16 @@ repos: - id: trailing-whitespace - repo: local hooks: - - id: isort - entry: poetry run isort - name: isort - language: system - types_or: [cython, pyi, python] - - repo: local - hooks: - - id: black - entry: poetry run black - name: black + - id: format + entry: poetry run ruff format + name: Ruff Format language: system types_or: [cython, pyi, python] - repo: local hooks: - id: system - name: Ruff - entry: poetry run ruff + name: Ruff Lint + entry: poetry run ruff check language: system exclude: \._py\}\}$ types_or: [cython, pyi, python] diff --git a/poetry.lock b/poetry.lock index c110fce8..140c1410 100644 --- a/poetry.lock +++ b/poetry.lock @@ -95,48 +95,6 @@ files = [ {file = "bids_validator-1.14.0-py3-none-any.whl", hash = "sha256:bab89f97a8d833235562cf0f3ac981bfca71acf2793a6c12a8af631c29ee0b8f"}, ] -[[package]] -name = "black" -version = "23.11.0" -description = "The uncompromising code formatter." -optional = false -python-versions = ">=3.8" -files = [ - {file = "black-23.11.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:dbea0bb8575c6b6303cc65017b46351dc5953eea5c0a59d7b7e3a2d2f433a911"}, - {file = "black-23.11.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:412f56bab20ac85927f3a959230331de5614aecda1ede14b373083f62ec24e6f"}, - {file = "black-23.11.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d136ef5b418c81660ad847efe0e55c58c8208b77a57a28a503a5f345ccf01394"}, - {file = "black-23.11.0-cp310-cp310-win_amd64.whl", hash = "sha256:6c1cac07e64433f646a9a838cdc00c9768b3c362805afc3fce341af0e6a9ae9f"}, - {file = "black-23.11.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cf57719e581cfd48c4efe28543fea3d139c6b6f1238b3f0102a9c73992cbb479"}, - {file = "black-23.11.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:698c1e0d5c43354ec5d6f4d914d0d553a9ada56c85415700b81dc90125aac244"}, - {file = "black-23.11.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:760415ccc20f9e8747084169110ef75d545f3b0932ee21368f63ac0fee86b221"}, - {file = "black-23.11.0-cp311-cp311-win_amd64.whl", hash = "sha256:58e5f4d08a205b11800332920e285bd25e1a75c54953e05502052738fe16b3b5"}, - {file = "black-23.11.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:45aa1d4675964946e53ab81aeec7a37613c1cb71647b5394779e6efb79d6d187"}, - {file = "black-23.11.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4c44b7211a3a0570cc097e81135faa5f261264f4dfaa22bd5ee2875a4e773bd6"}, - {file = "black-23.11.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2a9acad1451632021ee0d146c8765782a0c3846e0e0ea46659d7c4f89d9b212b"}, - {file = "black-23.11.0-cp38-cp38-win_amd64.whl", hash = "sha256:fc7f6a44d52747e65a02558e1d807c82df1d66ffa80a601862040a43ec2e3142"}, - {file = "black-23.11.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7f622b6822f02bfaf2a5cd31fdb7cd86fcf33dab6ced5185c35f5db98260b055"}, - {file = "black-23.11.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:250d7e60f323fcfc8ea6c800d5eba12f7967400eb6c2d21ae85ad31c204fb1f4"}, - {file = "black-23.11.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5133f5507007ba08d8b7b263c7aa0f931af5ba88a29beacc4b2dc23fcefe9c06"}, - {file = "black-23.11.0-cp39-cp39-win_amd64.whl", hash = "sha256:421f3e44aa67138ab1b9bfbc22ee3780b22fa5b291e4db8ab7eee95200726b07"}, - {file = "black-23.11.0-py3-none-any.whl", hash = "sha256:54caaa703227c6e0c87b76326d0862184729a69b73d3b7305b6288e1d830067e"}, - {file = "black-23.11.0.tar.gz", hash = "sha256:4c68855825ff432d197229846f971bc4d6666ce90492e5b02013bcaca4d9ab05"}, -] - -[package.dependencies] -click = ">=8.0.0" -mypy-extensions = ">=0.4.3" -packaging = ">=22.0" -pathspec = ">=0.9.0" -platformdirs = ">=2" -tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} -typing-extensions = {version = ">=4.0.1", markers = "python_version < \"3.11\""} - -[package.extras] -colorama = ["colorama (>=0.4.3)"] -d = ["aiohttp (>=3.7.4)"] -jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] -uvloop = ["uvloop (>=0.15.2)"] - [[package]] name = "boutiques" version = "0.5.26" @@ -402,13 +360,13 @@ files = [ [[package]] name = "distlib" -version = "0.3.7" +version = "0.3.8" description = "Distribution utilities" optional = false python-versions = "*" files = [ - {file = "distlib-0.3.7-py2.py3-none-any.whl", hash = "sha256:2e24928bc811348f0feb63014e97aaae3037f2cf48712d51ae61df7fd6075057"}, - {file = "distlib-0.3.7.tar.gz", hash = "sha256:9dafe54b34a028eafd95039d5e5d4851a13734540f1331060d31c9916e7147a8"}, + {file = "distlib-0.3.8-py2.py3-none-any.whl", hash = "sha256:034db59a0b96f8ca18035f36290806a9a6e6bd9d1ff91e45a7f172eb17e51784"}, + {file = "distlib-0.3.8.tar.gz", hash = "sha256:1530ea13e350031b6312d8580ddb6b27a104275a31106523b8f123787f494f64"}, ] [[package]] @@ -595,68 +553,69 @@ files = [ [[package]] name = "greenlet" -version = "3.0.1" +version = "3.0.2" description = "Lightweight in-process concurrent programming" optional = false python-versions = ">=3.7" files = [ - {file = "greenlet-3.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:f89e21afe925fcfa655965ca8ea10f24773a1791400989ff32f467badfe4a064"}, - {file = "greenlet-3.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:28e89e232c7593d33cac35425b58950789962011cc274aa43ef8865f2e11f46d"}, - {file = "greenlet-3.0.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b8ba29306c5de7717b5761b9ea74f9c72b9e2b834e24aa984da99cbfc70157fd"}, - {file = "greenlet-3.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:19bbdf1cce0346ef7341705d71e2ecf6f41a35c311137f29b8a2dc2341374565"}, - {file = "greenlet-3.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:599daf06ea59bfedbec564b1692b0166a0045f32b6f0933b0dd4df59a854caf2"}, - {file = "greenlet-3.0.1-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b641161c302efbb860ae6b081f406839a8b7d5573f20a455539823802c655f63"}, - {file = "greenlet-3.0.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d57e20ba591727da0c230ab2c3f200ac9d6d333860d85348816e1dca4cc4792e"}, - {file = "greenlet-3.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:5805e71e5b570d490938d55552f5a9e10f477c19400c38bf1d5190d760691846"}, - {file = "greenlet-3.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:52e93b28db27ae7d208748f45d2db8a7b6a380e0d703f099c949d0f0d80b70e9"}, - {file = "greenlet-3.0.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f7bfb769f7efa0eefcd039dd19d843a4fbfbac52f1878b1da2ed5793ec9b1a65"}, - {file = "greenlet-3.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:91e6c7db42638dc45cf2e13c73be16bf83179f7859b07cfc139518941320be96"}, - {file = "greenlet-3.0.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1757936efea16e3f03db20efd0cd50a1c86b06734f9f7338a90c4ba85ec2ad5a"}, - {file = "greenlet-3.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:19075157a10055759066854a973b3d1325d964d498a805bb68a1f9af4aaef8ec"}, - {file = "greenlet-3.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e9d21aaa84557d64209af04ff48e0ad5e28c5cca67ce43444e939579d085da72"}, - {file = "greenlet-3.0.1-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2847e5d7beedb8d614186962c3d774d40d3374d580d2cbdab7f184580a39d234"}, - {file = "greenlet-3.0.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:97e7ac860d64e2dcba5c5944cfc8fa9ea185cd84061c623536154d5a89237884"}, - {file = "greenlet-3.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:b2c02d2ad98116e914d4f3155ffc905fd0c025d901ead3f6ed07385e19122c94"}, - {file = "greenlet-3.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:22f79120a24aeeae2b4471c711dcf4f8c736a2bb2fabad2a67ac9a55ea72523c"}, - {file = "greenlet-3.0.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:100f78a29707ca1525ea47388cec8a049405147719f47ebf3895e7509c6446aa"}, - {file = "greenlet-3.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:60d5772e8195f4e9ebf74046a9121bbb90090f6550f81d8956a05387ba139353"}, - {file = "greenlet-3.0.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:daa7197b43c707462f06d2c693ffdbb5991cbb8b80b5b984007de431493a319c"}, - {file = "greenlet-3.0.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ea6b8aa9e08eea388c5f7a276fabb1d4b6b9d6e4ceb12cc477c3d352001768a9"}, - {file = "greenlet-3.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d11ebbd679e927593978aa44c10fc2092bc454b7d13fdc958d3e9d508aba7d0"}, - {file = "greenlet-3.0.1-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dbd4c177afb8a8d9ba348d925b0b67246147af806f0b104af4d24f144d461cd5"}, - {file = "greenlet-3.0.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:20107edf7c2c3644c67c12205dc60b1bb11d26b2610b276f97d666110d1b511d"}, - {file = "greenlet-3.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8bef097455dea90ffe855286926ae02d8faa335ed8e4067326257cb571fc1445"}, - {file = "greenlet-3.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:b2d3337dcfaa99698aa2377c81c9ca72fcd89c07e7eb62ece3f23a3fe89b2ce4"}, - {file = "greenlet-3.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:80ac992f25d10aaebe1ee15df45ca0d7571d0f70b645c08ec68733fb7a020206"}, - {file = "greenlet-3.0.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:337322096d92808f76ad26061a8f5fccb22b0809bea39212cd6c406f6a7060d2"}, - {file = "greenlet-3.0.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b9934adbd0f6e476f0ecff3c94626529f344f57b38c9a541f87098710b18af0a"}, - {file = "greenlet-3.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dc4d815b794fd8868c4d67602692c21bf5293a75e4b607bb92a11e821e2b859a"}, - {file = "greenlet-3.0.1-cp37-cp37m-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:41bdeeb552d814bcd7fb52172b304898a35818107cc8778b5101423c9017b3de"}, - {file = "greenlet-3.0.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:6e6061bf1e9565c29002e3c601cf68569c450be7fc3f7336671af7ddb4657166"}, - {file = "greenlet-3.0.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:fa24255ae3c0ab67e613556375a4341af04a084bd58764731972bcbc8baeba36"}, - {file = "greenlet-3.0.1-cp37-cp37m-win32.whl", hash = "sha256:b489c36d1327868d207002391f662a1d163bdc8daf10ab2e5f6e41b9b96de3b1"}, - {file = "greenlet-3.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:f33f3258aae89da191c6ebaa3bc517c6c4cbc9b9f689e5d8452f7aedbb913fa8"}, - {file = "greenlet-3.0.1-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:d2905ce1df400360463c772b55d8e2518d0e488a87cdea13dd2c71dcb2a1fa16"}, - {file = "greenlet-3.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a02d259510b3630f330c86557331a3b0e0c79dac3d166e449a39363beaae174"}, - {file = "greenlet-3.0.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:55d62807f1c5a1682075c62436702aaba941daa316e9161e4b6ccebbbf38bda3"}, - {file = "greenlet-3.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3fcc780ae8edbb1d050d920ab44790201f027d59fdbd21362340a85c79066a74"}, - {file = "greenlet-3.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4eddd98afc726f8aee1948858aed9e6feeb1758889dfd869072d4465973f6bfd"}, - {file = "greenlet-3.0.1-cp38-cp38-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:eabe7090db68c981fca689299c2d116400b553f4b713266b130cfc9e2aa9c5a9"}, - {file = "greenlet-3.0.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:f2f6d303f3dee132b322a14cd8765287b8f86cdc10d2cb6a6fae234ea488888e"}, - {file = "greenlet-3.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:d923ff276f1c1f9680d32832f8d6c040fe9306cbfb5d161b0911e9634be9ef0a"}, - {file = "greenlet-3.0.1-cp38-cp38-win32.whl", hash = "sha256:0b6f9f8ca7093fd4433472fd99b5650f8a26dcd8ba410e14094c1e44cd3ceddd"}, - {file = "greenlet-3.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:990066bff27c4fcf3b69382b86f4c99b3652bab2a7e685d968cd4d0cfc6f67c6"}, - {file = "greenlet-3.0.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:ce85c43ae54845272f6f9cd8320d034d7a946e9773c693b27d620edec825e376"}, - {file = "greenlet-3.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:89ee2e967bd7ff85d84a2de09df10e021c9b38c7d91dead95b406ed6350c6997"}, - {file = "greenlet-3.0.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:87c8ceb0cf8a5a51b8008b643844b7f4a8264a2c13fcbcd8a8316161725383fe"}, - {file = "greenlet-3.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d6a8c9d4f8692917a3dc7eb25a6fb337bff86909febe2f793ec1928cd97bedfc"}, - {file = "greenlet-3.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fbc5b8f3dfe24784cee8ce0be3da2d8a79e46a276593db6868382d9c50d97b1"}, - {file = "greenlet-3.0.1-cp39-cp39-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:85d2b77e7c9382f004b41d9c72c85537fac834fb141b0296942d52bf03fe4a3d"}, - {file = "greenlet-3.0.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:696d8e7d82398e810f2b3622b24e87906763b6ebfd90e361e88eb85b0e554dc8"}, - {file = "greenlet-3.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:329c5a2e5a0ee942f2992c5e3ff40be03e75f745f48847f118a3cfece7a28546"}, - {file = "greenlet-3.0.1-cp39-cp39-win32.whl", hash = "sha256:cf868e08690cb89360eebc73ba4be7fb461cfbc6168dd88e2fbbe6f31812cd57"}, - {file = "greenlet-3.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:ac4a39d1abae48184d420aa8e5e63efd1b75c8444dd95daa3e03f6c6310e9619"}, - {file = "greenlet-3.0.1.tar.gz", hash = "sha256:816bd9488a94cba78d93e1abb58000e8266fa9cc2aa9ccdd6eb0696acb24005b"}, + {file = "greenlet-3.0.2-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:9acd8fd67c248b8537953cb3af8787c18a87c33d4dcf6830e410ee1f95a63fd4"}, + {file = "greenlet-3.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:339c0272a62fac7e602e4e6ec32a64ff9abadc638b72f17f6713556ed011d493"}, + {file = "greenlet-3.0.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:38878744926cec29b5cc3654ef47f3003f14bfbba7230e3c8492393fe29cc28b"}, + {file = "greenlet-3.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b3f0497db77cfd034f829678b28267eeeeaf2fc21b3f5041600f7617139e6773"}, + {file = "greenlet-3.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ed1a8a08de7f68506a38f9a2ddb26bbd1480689e66d788fcd4b5f77e2d9ecfcc"}, + {file = "greenlet-3.0.2-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:89a6f6ddcbef4000cda7e205c4c20d319488ff03db961d72d4e73519d2465309"}, + {file = "greenlet-3.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:c1f647fe5b94b51488b314c82fdda10a8756d650cee8d3cd29f657c6031bdf73"}, + {file = "greenlet-3.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:9560c580c896030ff9c311c603aaf2282234643c90d1dec738a1d93e3e53cd51"}, + {file = "greenlet-3.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:2e9c5423046eec21f6651268cb674dfba97280701e04ef23d312776377313206"}, + {file = "greenlet-3.0.2-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:b1fd25dfc5879a82103b3d9e43fa952e3026c221996ff4d32a9c72052544835d"}, + {file = "greenlet-3.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cecfdc950dd25f25d6582952e58521bca749cf3eeb7a9bad69237024308c8196"}, + {file = "greenlet-3.0.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:edf7a1daba1f7c54326291a8cde58da86ab115b78c91d502be8744f0aa8e3ffa"}, + {file = "greenlet-3.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f4cf532bf3c58a862196b06947b1b5cc55503884f9b63bf18582a75228d9950e"}, + {file = "greenlet-3.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e79fb5a9fb2d0bd3b6573784f5e5adabc0b0566ad3180a028af99523ce8f6138"}, + {file = "greenlet-3.0.2-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:006c1028ac0cfcc4e772980cfe73f5476041c8c91d15d64f52482fc571149d46"}, + {file = "greenlet-3.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:fefd5eb2c0b1adffdf2802ff7df45bfe65988b15f6b972706a0e55d451bffaea"}, + {file = "greenlet-3.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:0c0fdb8142742ee68e97c106eb81e7d3e883cc739d9c5f2b28bc38a7bafeb6d1"}, + {file = "greenlet-3.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:8f8d14a0a4e8c670fbce633d8b9a1ee175673a695475acd838e372966845f764"}, + {file = "greenlet-3.0.2-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:654b84c9527182036747938b81938f1d03fb8321377510bc1854a9370418ab66"}, + {file = "greenlet-3.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cd5bc4fde0842ff2b9cf33382ad0b4db91c2582db836793d58d174c569637144"}, + {file = "greenlet-3.0.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c27b142a9080bdd5869a2fa7ebf407b3c0b24bd812db925de90e9afe3c417fd6"}, + {file = "greenlet-3.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0df7eed98ea23b20e9db64d46eb05671ba33147df9405330695bcd81a73bb0c9"}, + {file = "greenlet-3.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fb5d60805057d8948065338be6320d35e26b0a72f45db392eb32b70dd6dc9227"}, + {file = "greenlet-3.0.2-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e0e28f5233d64c693382f66d47c362b72089ebf8ac77df7e12ac705c9fa1163d"}, + {file = "greenlet-3.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:3e4bfa752b3688d74ab1186e2159779ff4867644d2b1ebf16db14281f0445377"}, + {file = "greenlet-3.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:c42bb589e6e9f9d8bdd79f02f044dff020d30c1afa6e84c0b56d1ce8a324553c"}, + {file = "greenlet-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:b2cedf279ca38ef3f4ed0d013a6a84a7fc3d9495a716b84a5fc5ff448965f251"}, + {file = "greenlet-3.0.2-cp37-cp37m-macosx_11_0_universal2.whl", hash = "sha256:6d65bec56a7bc352bcf11b275b838df618651109074d455a772d3afe25390b7d"}, + {file = "greenlet-3.0.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0acadbc3f72cb0ee85070e8d36bd2a4673d2abd10731ee73c10222cf2dd4713c"}, + {file = "greenlet-3.0.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:14b5d999aefe9ffd2049ad19079f733c3aaa426190ffecadb1d5feacef8fe397"}, + {file = "greenlet-3.0.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f27aa32466993c92d326df982c4acccd9530fe354e938d9e9deada563e71ce76"}, + {file = "greenlet-3.0.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f34a765c5170c0673eb747213a0275ecc749ab3652bdbec324621ed5b2edaef"}, + {file = "greenlet-3.0.2-cp37-cp37m-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:520fcb53a39ef90f5021c77606952dbbc1da75d77114d69b8d7bded4a8e1a813"}, + {file = "greenlet-3.0.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d1fceb5351ab1601903e714c3028b37f6ea722be6873f46e349a960156c05650"}, + {file = "greenlet-3.0.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:7363756cc439a503505b67983237d1cc19139b66488263eb19f5719a32597836"}, + {file = "greenlet-3.0.2-cp37-cp37m-win32.whl", hash = "sha256:d5547b462b8099b84746461e882a3eb8a6e3f80be46cb6afb8524eeb191d1a30"}, + {file = "greenlet-3.0.2-cp37-cp37m-win_amd64.whl", hash = "sha256:950e21562818f9c771989b5b65f990e76f4ac27af66e1bb34634ae67886ede2a"}, + {file = "greenlet-3.0.2-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:d64643317e76b4b41fdba659e7eca29634e5739b8bc394eda3a9127f697ed4b0"}, + {file = "greenlet-3.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5f9ea7c2c9795549653b6f7569f6bc75d2c7d1f6b2854eb8ce0bc6ec3cb2dd88"}, + {file = "greenlet-3.0.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:db4233358d3438369051a2f290f1311a360d25c49f255a6c5d10b5bcb3aa2b49"}, + {file = "greenlet-3.0.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ed9bf77b41798e8417657245b9f3649314218a4a17aefb02bb3992862df32495"}, + {file = "greenlet-3.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d4d0df07a38e41a10dfb62c6fc75ede196572b580f48ee49b9282c65639f3965"}, + {file = "greenlet-3.0.2-cp38-cp38-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:10d247260db20887ae8857c0cbc750b9170f0b067dd7d38fb68a3f2334393bd3"}, + {file = "greenlet-3.0.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:a37ae53cca36823597fd5f65341b6f7bac2dd69ecd6ca01334bb795460ab150b"}, + {file = "greenlet-3.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:80d068e4b6e2499847d916ef64176811ead6bf210a610859220d537d935ec6fd"}, + {file = "greenlet-3.0.2-cp38-cp38-win32.whl", hash = "sha256:b1405614692ac986490d10d3e1a05e9734f473750d4bee3cf7d1286ef7af7da6"}, + {file = "greenlet-3.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:8756a94ed8f293450b0e91119eca2a36332deba69feb2f9ca410d35e74eae1e4"}, + {file = "greenlet-3.0.2-cp39-cp39-macosx_11_0_universal2.whl", hash = "sha256:2c93cd03acb1499ee4de675e1a4ed8eaaa7227f7949dc55b37182047b006a7aa"}, + {file = "greenlet-3.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1dac09e3c0b78265d2e6d3cbac2d7c48bd1aa4b04a8ffeda3adde9f1688df2c3"}, + {file = "greenlet-3.0.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2ee59c4627c8c4bb3e15949fbcd499abd6b7f4ad9e0bfcb62c65c5e2cabe0ec4"}, + {file = "greenlet-3.0.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:18fe39d70d482b22f0014e84947c5aaa7211fb8e13dc4cc1c43ed2aa1db06d9a"}, + {file = "greenlet-3.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e84bef3cfb6b6bfe258c98c519811c240dbc5b33a523a14933a252e486797c90"}, + {file = "greenlet-3.0.2-cp39-cp39-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:aecea0442975741e7d69daff9b13c83caff8c13eeb17485afa65f6360a045765"}, + {file = "greenlet-3.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:f260e6c2337871a52161824058923df2bbddb38bc11a5cbe71f3474d877c5bd9"}, + {file = "greenlet-3.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:fc14dd9554f88c9c1fe04771589ae24db76cd56c8f1104e4381b383d6b71aff8"}, + {file = "greenlet-3.0.2-cp39-cp39-win32.whl", hash = "sha256:bfcecc984d60b20ffe30173b03bfe9ba6cb671b0be1e95c3e2056d4fe7006590"}, + {file = "greenlet-3.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:c235131bf59d2546bb3ebaa8d436126267392f2e51b85ff45ac60f3a26549af0"}, + {file = "greenlet-3.0.2.tar.gz", hash = "sha256:1c1129bc47266d83444c85a8e990ae22688cf05fb20d7951fd2866007c2ba9bc"}, ] [package.extras] @@ -679,17 +638,17 @@ pyreadline3 = {version = "*", markers = "sys_platform == \"win32\" and python_ve [[package]] name = "hypothesis" -version = "6.91.0" +version = "6.92.0" description = "A library for property-based testing" optional = false python-versions = ">=3.8" files = [ - {file = "hypothesis-6.91.0-py3-none-any.whl", hash = "sha256:316e06d6f7d5f8ab87bcc7417fca750a2b082ed3ce902b979816b413276680b3"}, - {file = "hypothesis-6.91.0.tar.gz", hash = "sha256:a9f61a2bcfc342febcc1d04b80a99e789c57b700f91cbd43bbdb5d651af385cd"}, + {file = "hypothesis-6.92.0-py3-none-any.whl", hash = "sha256:d4577f99b912acc725bea684899b7cb62591a0412e2446c618be0b4855995276"}, + {file = "hypothesis-6.92.0.tar.gz", hash = "sha256:65b72c7dc7da3e16144db54fe093c6b74a33631b933a8063eb754c5a61361ae6"}, ] [package.dependencies] -attrs = ">=19.2.0" +attrs = ">=22.2.0" exceptiongroup = {version = ">=1.0.0", markers = "python_version < \"3.11\""} sortedcontainers = ">=2.1.0,<3.0.0" @@ -711,13 +670,13 @@ zoneinfo = ["backports.zoneinfo (>=0.2.1)", "tzdata (>=2023.3)"] [[package]] name = "identify" -version = "2.5.32" +version = "2.5.33" description = "File identification library for Python" optional = false python-versions = ">=3.8" files = [ - {file = "identify-2.5.32-py2.py3-none-any.whl", hash = "sha256:0b7656ef6cba81664b783352c73f8c24b39cf82f926f78f4550eda928e5e0545"}, - {file = "identify-2.5.32.tar.gz", hash = "sha256:5d9979348ec1a21c768ae07e0a652924538e8bce67313a73cb0f681cf08ba407"}, + {file = "identify-2.5.33-py2.py3-none-any.whl", hash = "sha256:d40ce5fcd762817627670da8a7d8d8e65f24342d14539c59488dc603bf662e34"}, + {file = "identify-2.5.33.tar.gz", hash = "sha256:161558f9fe4559e1557e1bff323e8631f6a0e4837f7497767c1782832f16b62d"}, ] [package.extras] @@ -793,23 +752,6 @@ files = [ {file = "interface_meta-1.3.0.tar.gz", hash = "sha256:8a4493f8bdb73fb9655dcd5115bc897e207319e36c8835f39c516a2d7e9d79a1"}, ] -[[package]] -name = "isort" -version = "5.12.0" -description = "A Python utility / library to sort Python imports." -optional = false -python-versions = ">=3.8.0" -files = [ - {file = "isort-5.12.0-py3-none-any.whl", hash = "sha256:f84c2818376e66cf843d497486ea8fed8700b340f308f076c6fb1229dff318b6"}, - {file = "isort-5.12.0.tar.gz", hash = "sha256:8bef7dde241278824a6d83f44a544709b065191b95b6e50894bdc722fcba0504"}, -] - -[package.extras] -colors = ["colorama (>=0.4.3)"] -pipfile-deprecated-finder = ["pip-shims (>=0.5.2)", "pipreqs", "requirementslib"] -plugins = ["setuptools"] -requirements-deprecated-finder = ["pip-api", "pipreqs"] - [[package]] name = "jaraco-classes" version = "3.3.0" @@ -830,21 +772,21 @@ testing = ["pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", [[package]] name = "jaraco-collections" -version = "4.3.0" +version = "5.0.0" description = "Collection objects similar to those in stdlib by jaraco" optional = false python-versions = ">=3.8" files = [ - {file = "jaraco.collections-4.3.0-py3-none-any.whl", hash = "sha256:d8dbb518c6d7a3ac9a10130fc7578439c32ac0cdbeb57451dd6345fcb5191660"}, - {file = "jaraco.collections-4.3.0.tar.gz", hash = "sha256:74ffc23fccfee4de0a2ebf556a33675b6a3c003d6335947d3122a0bc8822c8e4"}, + {file = "jaraco.collections-5.0.0-py3-none-any.whl", hash = "sha256:a693d06b12718656921d79ba4f665f905014da09b35aa3deef43be4f14fdaa0d"}, + {file = "jaraco.collections-5.0.0.tar.gz", hash = "sha256:1680e8d09f295f625c7ba926880175a26fdbe7092b4c76d198e30476b21cfe68"}, ] [package.dependencies] "jaraco.text" = "*" [package.extras] -docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] -testing = ["pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-mypy (>=0.9.1)", "pytest-ruff"] +docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-lint"] +testing = ["pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-ruff"] [[package]] name = "jaraco-context" @@ -1034,13 +976,13 @@ format-nongpl = ["fqdn", "idna", "isoduration", "jsonpointer (>1.13)", "rfc3339- [[package]] name = "jsonschema-specifications" -version = "2023.11.1" +version = "2023.11.2" description = "The JSON Schema meta-schemas and vocabularies, exposed as a Registry" optional = false python-versions = ">=3.8" files = [ - {file = "jsonschema_specifications-2023.11.1-py3-none-any.whl", hash = "sha256:f596778ab612b3fd29f72ea0d990393d0540a5aab18bf0407a46632eab540779"}, - {file = "jsonschema_specifications-2023.11.1.tar.gz", hash = "sha256:c9b234904ffe02f079bf91b14d79987faa685fd4b39c377a0996954c0090b9ca"}, + {file = "jsonschema_specifications-2023.11.2-py3-none-any.whl", hash = "sha256:e74ba7c0a65e8cb49dc26837d6cfe576557084a8b423ed16a420984228104f93"}, + {file = "jsonschema_specifications-2023.11.2.tar.gz", hash = "sha256:9472fc4fea474cd74bea4a2b190daeccb5a9e4db2ea80efcf7a1b582fc9a81b8"}, ] [package.dependencies] @@ -1160,17 +1102,6 @@ files = [ {file = "more_itertools-10.1.0-py3-none-any.whl", hash = "sha256:64e0735fcfdc6f3464ea133afe8ea4483b1c5fe3a3d69852e6503b43a0b222e6"}, ] -[[package]] -name = "mypy-extensions" -version = "1.0.0" -description = "Type system extensions for programs checked with the mypy type checker." -optional = false -python-versions = ">=3.5" -files = [ - {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, - {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, -] - [[package]] name = "nbformat" version = "5.9.2" @@ -1194,32 +1125,32 @@ test = ["pep440", "pre-commit", "pytest", "testpath"] [[package]] name = "nibabel" -version = "5.1.0" +version = "5.2.0" description = "Access a multitude of neuroimaging data formats" optional = false python-versions = ">=3.8" files = [ - {file = "nibabel-5.1.0-py3-none-any.whl", hash = "sha256:b3deb8130c835b9d26e80880b0d5e443d9e3f30972b3b0302dd2fafa3ca629f8"}, - {file = "nibabel-5.1.0.tar.gz", hash = "sha256:ce73ca5e957209e7219a223cb71f77235c9df2acf4d3f27f861ba38e9481ac53"}, + {file = "nibabel-5.2.0-py3-none-any.whl", hash = "sha256:77724af6e29fd9c4173702e4d031e7d8c45b5963887905a0f90edab880381b7f"}, + {file = "nibabel-5.2.0.tar.gz", hash = "sha256:3df8f1ab981d1bd92f4331d565528d126ab9717fdbd4cfe68f43fcd1c2bf3f52"}, ] [package.dependencies] importlib-resources = {version = ">=1.3", markers = "python_version < \"3.9\""} -numpy = ">=1.19" +numpy = ">=1.20" packaging = ">=17" [package.extras] -all = ["nibabel[dev,dicomfs,doc,minc2,spm,style,test,zstd]"] -dev = ["gitpython", "nibabel[style]", "twine"] +all = ["nibabel[dicomfs,minc2,spm,zstd]"] +dev = ["tox"] dicom = ["pydicom (>=1.0.0)"] dicomfs = ["nibabel[dicom]", "pillow"] -doc = ["matplotlib (>=1.5.3)", "numpydoc", "sphinx (>=5.3,<6.0)", "texext", "tomli"] -doctest = ["nibabel[doc,test]"] +doc = ["matplotlib (>=1.5.3)", "numpydoc", "sphinx", "texext", "tomli"] +doctest = ["tox"] minc2 = ["h5py"] spm = ["scipy"] -style = ["blue", "flake8", "isort"] -test = ["coverage", "pytest (!=5.3.4)", "pytest-cov", "pytest-doctestplus", "pytest-httpserver", "pytest-xdist"] -typing = ["importlib-resources", "mypy", "pydicom", "pytest", "pyzstd", "types-pillow", "types-setuptools"] +style = ["tox"] +test = ["pytest", "pytest-cov", "pytest-doctestplus", "pytest-httpserver", "pytest-xdist"] +typing = ["tox"] zstd = ["pyzstd (>=0.14.3)"] [[package]] @@ -1423,28 +1354,28 @@ files = [ [[package]] name = "path" -version = "16.7.1" +version = "16.9.0" description = "A module wrapper for os.path" optional = false python-versions = ">=3.8" files = [ - {file = "path-16.7.1-py3-none-any.whl", hash = "sha256:57f6ac8209c2b8fd3c515cf013e3b288af43460dddacf1d0249839aabcc14517"}, - {file = "path-16.7.1.tar.gz", hash = "sha256:2b477f5887033f3cbea1cfd8553ee6a6a498eb2540a19f4aa082822aadcea30a"}, + {file = "path-16.9.0-py3-none-any.whl", hash = "sha256:d78c59194bce7362aae4798540f10bafa3eede0153a477556b23e915428f707d"}, + {file = "path-16.9.0.tar.gz", hash = "sha256:dfd31c2af60e8889a13538bef302ade7adacdb5351836be22638e2349ddd5d7b"}, ] [package.extras] -docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] -testing = ["appdirs", "packaging", "pygments", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-ruff", "pywin32"] +docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-lint"] +testing = ["appdirs", "more-itertools", "packaging", "pygments", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-ruff", "pywin32"] [[package]] name = "pathspec" -version = "0.11.2" +version = "0.12.1" description = "Utility library for gitignore style pattern matching of file paths." optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "pathspec-0.11.2-py3-none-any.whl", hash = "sha256:1d6ed233af05e679efb96b1851550ea95bbb64b7c490b0f5aa52996c11e92a20"}, - {file = "pathspec-0.11.2.tar.gz", hash = "sha256:e0d8d0ac2f12da61956eb2306b69f9469b42f4deb0f3cb6ed47b9cce9996ced3"}, + {file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"}, + {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"}, ] [[package]] @@ -1475,24 +1406,24 @@ files = [ [[package]] name = "plac" -version = "1.4.1" +version = "1.4.2" description = "The smartest command line arguments parser in the world" optional = false python-versions = "*" files = [ - {file = "plac-1.4.1-py2.py3-none-any.whl", hash = "sha256:5b02d38cfb07c6b95d9803dfdd108a618fdb4691c1759b9156bfb34e566d28c4"}, - {file = "plac-1.4.1.tar.gz", hash = "sha256:8276d038466f22210ca4f7fa0b270473bea2ec85fff495de179f56934372d5fa"}, + {file = "plac-1.4.2-py2.py3-none-any.whl", hash = "sha256:675b93468a3761e1fb657c2b2c80bd59f1fb884bcfcd5d4a53bcee8baada66a6"}, + {file = "plac-1.4.2.tar.gz", hash = "sha256:b0d04d9bc4875625df45982bc900e9d9826861c221850dbfda096eab82fe3330"}, ] [[package]] name = "platformdirs" -version = "4.0.0" +version = "4.1.0" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "platformdirs-4.0.0-py3-none-any.whl", hash = "sha256:118c954d7e949b35437270383a3f2531e99dd93cf7ce4dc8340d3356d30f173b"}, - {file = "platformdirs-4.0.0.tar.gz", hash = "sha256:cb633b2bcf10c51af60beb0ab06d2f1d69064b43abf4c185ca6b28865f3f9731"}, + {file = "platformdirs-4.1.0-py3-none-any.whl", hash = "sha256:11c8f37bcca40db96d8144522d925583bdb7a31f7b0e37e3ed4318400a8e2380"}, + {file = "platformdirs-4.1.0.tar.gz", hash = "sha256:906d548203468492d432bcb294d4bc2fff751bf84971fbb2c10918cc206ee420"}, ] [package.extras] @@ -1535,13 +1466,13 @@ ssh = ["paramiko"] [[package]] name = "poethepoet" -version = "0.22.1" +version = "0.24.4" description = "A task runner that works well with poetry." optional = false python-versions = ">=3.8" files = [ - {file = "poethepoet-0.22.1-py3-none-any.whl", hash = "sha256:1da4cd00d3b2c44b811c91616a744cf71094a26a299ea9956025162d34eef1a5"}, - {file = "poethepoet-0.22.1.tar.gz", hash = "sha256:e758bcac731fa9ac0b812389589541e32b825c4a1894e16fa90aeb1946ba2823"}, + {file = "poethepoet-0.24.4-py3-none-any.whl", hash = "sha256:fb4ea35d7f40fe2081ea917d2e4102e2310fda2cde78974050ca83896e229075"}, + {file = "poethepoet-0.24.4.tar.gz", hash = "sha256:ff4220843a87c888cbcb5312c8905214701d0af60ac7271795baa8369b428fef"}, ] [package.dependencies] @@ -1650,13 +1581,13 @@ files = [ [[package]] name = "pybids" -version = "0.16.3" +version = "0.16.4" description = "bids: interface with datasets conforming to BIDS" optional = false python-versions = ">=3.8" files = [ - {file = "pybids-0.16.3-py3-none-any.whl", hash = "sha256:b9f47cd964c046a03cac607e771e96a92c63d9a3f0ac2e193a9561cda39f5c68"}, - {file = "pybids-0.16.3.tar.gz", hash = "sha256:10e279350c8d14ca602c0d4469a5e4bf7ff393e8643c831a546ae735b6b82cc3"}, + {file = "pybids-0.16.4-py3-none-any.whl", hash = "sha256:44618312c000eae70f21f0f25443b0b122b7109d2b071331d63654ec7dd70f22"}, + {file = "pybids-0.16.4.tar.gz", hash = "sha256:a5a865f308ba49ff00b95aa4be2ec7f745621ebfbdbad6f5e1956628adeb166e"}, ] [package.dependencies] @@ -1819,13 +1750,13 @@ typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" [[package]] name = "pyfakefs" -version = "5.3.1" +version = "5.3.2" description = "pyfakefs implements a fake file system that mocks the Python file system modules." optional = false python-versions = ">=3.7" files = [ - {file = "pyfakefs-5.3.1-py3-none-any.whl", hash = "sha256:dbe268b70da64f1506baf7d7a2a2248b96b56d28d61a68859272b5fdc321c39e"}, - {file = "pyfakefs-5.3.1.tar.gz", hash = "sha256:dd1fb374039fadccf35d3f3df7aa5d239482e0650dcd240e053d3b9e78740918"}, + {file = "pyfakefs-5.3.2-py3-none-any.whl", hash = "sha256:5a62194cfa24542a3c9080b66ce65d78b2e977957edfd3cd6fe98e8349bcca32"}, + {file = "pyfakefs-5.3.2.tar.gz", hash = "sha256:a83776a3c1046d4d103f2f530029aa6cdff5f0386dffd59c15ee16926135493c"}, ] [[package]] @@ -1870,13 +1801,13 @@ files = [ [[package]] name = "pyright" -version = "1.1.324" +version = "1.1.339" description = "Command line wrapper for pyright" optional = false python-versions = ">=3.7" files = [ - {file = "pyright-1.1.324-py3-none-any.whl", hash = "sha256:0edb712afbbad474e347de12ca1bd9368aa85d3365a1c7b795012e48e6a65111"}, - {file = "pyright-1.1.324.tar.gz", hash = "sha256:0c48e3bca3d081bba0dddd0c1f075aaa965c59bba691f7b9bd9d73a98e44e0cf"}, + {file = "pyright-1.1.339-py3-none-any.whl", hash = "sha256:662f3df170fdeda76fd21b158ab20c518dad99c2f14b0a7f84c2bfd60d5a8d2a"}, + {file = "pyright-1.1.339.tar.gz", hash = "sha256:581ce4e281575814380dd67a331e75c0ccdca31eb848005ee1ae46e7bfa8b4f9"}, ] [package.dependencies] @@ -2119,13 +2050,13 @@ prompt_toolkit = ">=2.0,<=3.0.36" [[package]] name = "referencing" -version = "0.31.0" +version = "0.32.0" description = "JSON Referencing + Python" optional = false python-versions = ">=3.8" files = [ - {file = "referencing-0.31.0-py3-none-any.whl", hash = "sha256:381b11e53dd93babb55696c71cf42aef2d36b8a150c49bf0bc301e36d536c882"}, - {file = "referencing-0.31.0.tar.gz", hash = "sha256:cc28f2c88fbe7b961a7817a0abc034c09a1e36358f82fedb4ffdf29a25398863"}, + {file = "referencing-0.32.0-py3-none-any.whl", hash = "sha256:bdcd3efb936f82ff86f993093f6da7435c7de69a3b3a5a06678a6050184bee99"}, + {file = "referencing-0.32.0.tar.gz", hash = "sha256:689e64fe121843dcfd57b71933318ef1f91188ffb45367332700a86ac8fd6161"}, ] [package.dependencies] @@ -2185,136 +2116,136 @@ files = [ [[package]] name = "rpds-py" -version = "0.13.1" +version = "0.13.2" description = "Python bindings to Rust's persistent data structures (rpds)" optional = false python-versions = ">=3.8" files = [ - {file = "rpds_py-0.13.1-cp310-cp310-macosx_10_7_x86_64.whl", hash = "sha256:83feb0f682d75a09ddc11aa37ba5c07dd9b824b22915207f6176ea458474ff75"}, - {file = "rpds_py-0.13.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fa84bbe22ffa108f91631935c28a623001e335d66e393438258501e618fb0dde"}, - {file = "rpds_py-0.13.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e04f8c76b8d5c70695b4e8f1d0b391d8ef91df00ef488c6c1ffb910176459bc6"}, - {file = "rpds_py-0.13.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:032c242a595629aacace44128f9795110513ad27217b091e834edec2fb09e800"}, - {file = "rpds_py-0.13.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:91276caef95556faeb4b8f09fe4439670d3d6206fee78d47ddb6e6de837f0b4d"}, - {file = "rpds_py-0.13.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d22f2cb82e0b40e427a74a93c9a4231335bbc548aed79955dde0b64ea7f88146"}, - {file = "rpds_py-0.13.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:63c9e2794329ef070844ff9bfc012004aeddc0468dc26970953709723f76c8a5"}, - {file = "rpds_py-0.13.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c797ea56f36c6f248656f0223b11307fdf4a1886f3555eba371f34152b07677f"}, - {file = "rpds_py-0.13.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:82dbcd6463e580bcfb7561cece35046aaabeac5a9ddb775020160b14e6c58a5d"}, - {file = "rpds_py-0.13.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:736817dbbbd030a69a1faf5413a319976c9c8ba8cdcfa98c022d3b6b2e01eca6"}, - {file = "rpds_py-0.13.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:1f36a1e80ef4ed1996445698fd91e0d3e54738bf597c9995118b92da537d7a28"}, - {file = "rpds_py-0.13.1-cp310-none-win32.whl", hash = "sha256:4f13d3f6585bd07657a603780e99beda96a36c86acaba841f131e81393958336"}, - {file = "rpds_py-0.13.1-cp310-none-win_amd64.whl", hash = "sha256:545e94c84575057d3d5c62634611858dac859702b1519b6ffc58eca7fb1adfcf"}, - {file = "rpds_py-0.13.1-cp311-cp311-macosx_10_7_x86_64.whl", hash = "sha256:6bfe72b249264cc1ff2f3629be240d7d2fdc778d9d298087cdec8524c91cd11f"}, - {file = "rpds_py-0.13.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:edc91c50e17f5cd945d821f0f1af830522dba0c10267c3aab186dc3dbaab8def"}, - {file = "rpds_py-0.13.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2eca04a365be380ca1f8fa48b334462e19e3382c0bb7386444d8ca43aa01c481"}, - {file = "rpds_py-0.13.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3e3ac5b602fea378243f993d8b707189f9061e55ebb4e56cb9fdef8166060f28"}, - {file = "rpds_py-0.13.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dfb5d2ab183c0efe5e7b8917e4eaa2e837aacafad8a69b89aa6bc81550eed857"}, - {file = "rpds_py-0.13.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d9793d46d3e6522ae58e9321032827c9c0df1e56cbe5d3de965facb311aed6aa"}, - {file = "rpds_py-0.13.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9cd935c0220d012a27c20135c140f9cdcbc6249d5954345c81bfb714071b985c"}, - {file = "rpds_py-0.13.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:37b08df45f02ff1866043b95096cbe91ac99de05936dd09d6611987a82a3306a"}, - {file = "rpds_py-0.13.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:ad666a904212aa9a6c77da7dce9d5170008cda76b7776e6731928b3f8a0d40fa"}, - {file = "rpds_py-0.13.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8a6ad8429340e0a4de89353447c6441329def3632e7b2293a7d6e873217d3c2b"}, - {file = "rpds_py-0.13.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:7c40851b659d958c5245c1236e34f0d065cc53dca8d978b49a032c8e0adfda6e"}, - {file = "rpds_py-0.13.1-cp311-none-win32.whl", hash = "sha256:4145172ab59b6c27695db6d78d040795f635cba732cead19c78cede74800949a"}, - {file = "rpds_py-0.13.1-cp311-none-win_amd64.whl", hash = "sha256:46a07a258bda12270de02b34c4884f200f864bba3dcd6e3a37fef36a168b859d"}, - {file = "rpds_py-0.13.1-cp312-cp312-macosx_10_7_x86_64.whl", hash = "sha256:ba4432301ad7eeb1b00848cf46fae0e5fecfd18a8cb5fdcf856c67985f79ecc7"}, - {file = "rpds_py-0.13.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d22e0660de24bd8e9ac82f4230a22a5fe4e397265709289d61d5fb333839ba50"}, - {file = "rpds_py-0.13.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76a8374b294e4ccb39ccaf11d39a0537ed107534139c00b4393ca3b542cc66e5"}, - {file = "rpds_py-0.13.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7d152ec7bb431040af2500e01436c9aa0d993f243346f0594a15755016bf0be1"}, - {file = "rpds_py-0.13.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:74a2044b870df7c9360bb3ce7e12f9ddf8e72e49cd3a353a1528cbf166ad2383"}, - {file = "rpds_py-0.13.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:960e7e460fda2d0af18c75585bbe0c99f90b8f09963844618a621b804f8c3abe"}, - {file = "rpds_py-0.13.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:37f79f4f1f06cc96151f4a187528c3fd4a7e1065538a4af9eb68c642365957f7"}, - {file = "rpds_py-0.13.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:cd4ea56c9542ad0091dfdef3e8572ae7a746e1e91eb56c9e08b8d0808b40f1d1"}, - {file = "rpds_py-0.13.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0290712eb5603a725769b5d857f7cf15cf6ca93dda3128065bbafe6fdb709beb"}, - {file = "rpds_py-0.13.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:0b70c1f800059c92479dc94dda41288fd6607f741f9b1b8f89a21a86428f6383"}, - {file = "rpds_py-0.13.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3dd5fb7737224e1497c886fb3ca681c15d9c00c76171f53b3c3cc8d16ccfa7fb"}, - {file = "rpds_py-0.13.1-cp312-none-win32.whl", hash = "sha256:74be3b215a5695690a0f1a9f68b1d1c93f8caad52e23242fcb8ba56aaf060281"}, - {file = "rpds_py-0.13.1-cp312-none-win_amd64.whl", hash = "sha256:f47eef55297799956464efc00c74ae55c48a7b68236856d56183fe1ddf866205"}, - {file = "rpds_py-0.13.1-cp38-cp38-macosx_10_7_x86_64.whl", hash = "sha256:e4a45ba34f904062c63049a760790c6a2fa7a4cc4bd160d8af243b12371aaa05"}, - {file = "rpds_py-0.13.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:20147996376be452cd82cd6c17701daba69a849dc143270fa10fe067bb34562a"}, - {file = "rpds_py-0.13.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42b9535aa22ab023704cfc6533e968f7e420affe802d85e956d8a7b4c0b0b5ea"}, - {file = "rpds_py-0.13.1-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d4fa1eeb9bea6d9b64ac91ec51ee94cc4fc744955df5be393e1c923c920db2b0"}, - {file = "rpds_py-0.13.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2b2415d5a7b7ee96aa3a54d4775c1fec140476a17ee12353806297e900eaeddc"}, - {file = "rpds_py-0.13.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:577d40a72550eac1386b77b43836151cb61ff6700adacda2ad4d883ca5a0b6f2"}, - {file = "rpds_py-0.13.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:af2d1648eb625a460eee07d3e1ea3a4a6e84a1fb3a107f6a8e95ac19f7dcce67"}, - {file = "rpds_py-0.13.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5b769396eb358d6b55dbf78f3f7ca631ca1b2fe02136faad5af74f0111b4b6b7"}, - {file = "rpds_py-0.13.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:249c8e0055ca597707d71c5ad85fd2a1c8fdb99386a8c6c257e1b47b67a9bec1"}, - {file = "rpds_py-0.13.1-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:fe30ef31172bdcf946502a945faad110e8fff88c32c4bec9a593df0280e64d8a"}, - {file = "rpds_py-0.13.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:2647192facf63be9ed2d7a49ceb07efe01dc6cfb083bd2cc53c418437400cb99"}, - {file = "rpds_py-0.13.1-cp38-none-win32.whl", hash = "sha256:4011d5c854aa804c833331d38a2b6f6f2fe58a90c9f615afdb7aa7cf9d31f721"}, - {file = "rpds_py-0.13.1-cp38-none-win_amd64.whl", hash = "sha256:7cfae77da92a20f56cf89739a557b76e5c6edc094f6ad5c090b9e15fbbfcd1a4"}, - {file = "rpds_py-0.13.1-cp39-cp39-macosx_10_7_x86_64.whl", hash = "sha256:e9be1f7c5f9673616f875299339984da9447a40e3aea927750c843d6e5e2e029"}, - {file = "rpds_py-0.13.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:839676475ac2ccd1532d36af3d10d290a2ca149b702ed464131e450a767550df"}, - {file = "rpds_py-0.13.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a90031658805c63fe488f8e9e7a88b260ea121ba3ee9cdabcece9c9ddb50da39"}, - {file = "rpds_py-0.13.1-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8ba9fbc5d6e36bfeb5292530321cc56c4ef3f98048647fabd8f57543c34174ec"}, - {file = "rpds_py-0.13.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:08832078767545c5ee12561ce980714e1e4c6619b5b1e9a10248de60cddfa1fd"}, - {file = "rpds_py-0.13.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:19f5aa7f5078d35ed8e344bcba40f35bc95f9176dddb33fc4f2084e04289fa63"}, - {file = "rpds_py-0.13.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80080972e1d000ad0341c7cc58b6855c80bd887675f92871221451d13a975072"}, - {file = "rpds_py-0.13.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:181ee352691c4434eb1c01802e9daa5edcc1007ff15023a320e2693fed6a661b"}, - {file = "rpds_py-0.13.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:d20da6b4c7aa9ee75ad0730beaba15d65157f5beeaca54a038bb968f92bf3ce3"}, - {file = "rpds_py-0.13.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:faa12a9f34671a30ea6bb027f04ec4e1fb8fa3fb3ed030893e729d4d0f3a9791"}, - {file = "rpds_py-0.13.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:7cf241dbb50ea71c2e628ab2a32b5bfcd36e199152fc44e5c1edb0b773f1583e"}, - {file = "rpds_py-0.13.1-cp39-none-win32.whl", hash = "sha256:dab979662da1c9fbb464e310c0b06cb5f1d174d09a462553af78f0bfb3e01920"}, - {file = "rpds_py-0.13.1-cp39-none-win_amd64.whl", hash = "sha256:a2b3c79586636f1fa69a7bd59c87c15fca80c0d34b5c003d57f2f326e5276575"}, - {file = "rpds_py-0.13.1-pp310-pypy310_pp73-macosx_10_7_x86_64.whl", hash = "sha256:5967fa631d0ed9f8511dede08bc943a9727c949d05d1efac4ac82b2938024fb7"}, - {file = "rpds_py-0.13.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:8308a8d49d1354278d5c068c888a58d7158a419b2e4d87c7839ed3641498790c"}, - {file = "rpds_py-0.13.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d0580faeb9def6d0beb7aa666294d5604e569c4e24111ada423cf9936768d95c"}, - {file = "rpds_py-0.13.1-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2da81c1492291c1a90987d76a47c7b2d310661bf7c93a9de0511e27b796a8b46"}, - {file = "rpds_py-0.13.1-pp310-pypy310_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1c9a1dc5e898ce30e2f9c0aa57181cddd4532b22b7780549441d6429d22d3b58"}, - {file = "rpds_py-0.13.1-pp310-pypy310_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f4ae6f423cb7d1c6256b7482025ace2825728f53b7ac58bcd574de6ee9d242c2"}, - {file = "rpds_py-0.13.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc3179e0815827cf963e634095ae5715ee73a5af61defbc8d6ca79f1bdae1d1d"}, - {file = "rpds_py-0.13.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0d9f8930092558fd15c9e07198625efb698f7cc00b3dc311c83eeec2540226a8"}, - {file = "rpds_py-0.13.1-pp310-pypy310_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:d1d388d2f5f5a6065cf83c54dd12112b7389095669ff395e632003ae8999c6b8"}, - {file = "rpds_py-0.13.1-pp310-pypy310_pp73-musllinux_1_2_i686.whl", hash = "sha256:08b335fb0c45f0a9e2478a9ece6a1bfb00b6f4c4780f9be3cf36479c5d8dd374"}, - {file = "rpds_py-0.13.1-pp310-pypy310_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:d11afdc5992bbd7af60ed5eb519873690d921425299f51d80aa3099ed49f2bcc"}, - {file = "rpds_py-0.13.1-pp38-pypy38_pp73-macosx_10_7_x86_64.whl", hash = "sha256:8c1f6c8df23be165eb0cb78f305483d00c6827a191e3a38394c658d5b9c80bbd"}, - {file = "rpds_py-0.13.1-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:528e2afaa56d815d2601b857644aeb395afe7e59212ab0659906dc29ae68d9a6"}, - {file = "rpds_py-0.13.1-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:df2af1180b8eeececf4f819d22cc0668bfadadfd038b19a90bd2fb2ee419ec6f"}, - {file = "rpds_py-0.13.1-pp38-pypy38_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:88956c993a20201744282362e3fd30962a9d86dc4f1dcf2bdb31fab27821b61f"}, - {file = "rpds_py-0.13.1-pp38-pypy38_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ee70ee5f4144a45a9e6169000b5b525d82673d5dab9f7587eccc92794814e7ac"}, - {file = "rpds_py-0.13.1-pp38-pypy38_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c5fd099acaee2325f01281a130a39da08d885e4dedf01b84bf156ec2737d78fe"}, - {file = "rpds_py-0.13.1-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9656a09653b18b80764647d585750df2dff8928e03a706763ab40ec8c4872acc"}, - {file = "rpds_py-0.13.1-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7ba239bb37663b2b4cd08e703e79e13321512dccd8e5f0e9451d9e53a6b8509a"}, - {file = "rpds_py-0.13.1-pp38-pypy38_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:3f55ae773abd96b1de25fc5c3fb356f491bd19116f8f854ba705beffc1ddc3c5"}, - {file = "rpds_py-0.13.1-pp38-pypy38_pp73-musllinux_1_2_i686.whl", hash = "sha256:f4b15a163448ec79241fb2f1bc5a8ae1a4a304f7a48d948d208a2935b26bf8a5"}, - {file = "rpds_py-0.13.1-pp38-pypy38_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:1a3b2583c86bbfbf417304eeb13400ce7f8725376dc7d3efbf35dc5d7052ad48"}, - {file = "rpds_py-0.13.1-pp39-pypy39_pp73-macosx_10_7_x86_64.whl", hash = "sha256:f1059ca9a51c936c9a8d46fbc2c9a6b4c15ab3f13a97f1ad32f024b39666ba85"}, - {file = "rpds_py-0.13.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:f55601fb58f92e4f4f1d05d80c24cb77505dc42103ddfd63ddfdc51d3da46fa2"}, - {file = "rpds_py-0.13.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fcfd5f91b882eedf8d9601bd21261d6ce0e61a8c66a7152d1f5df08d3f643ab1"}, - {file = "rpds_py-0.13.1-pp39-pypy39_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6574f619e8734140d96c59bfa8a6a6e7a3336820ccd1bfd95ffa610673b650a2"}, - {file = "rpds_py-0.13.1-pp39-pypy39_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a4b9d3f5c48bbe8d9e3758e498b3c34863f2c9b1ac57a4e6310183740e59c980"}, - {file = "rpds_py-0.13.1-pp39-pypy39_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cdd6f8738e1f1d9df5b1603bb03cb30e442710e5672262b95d0f9fcb4edb0dab"}, - {file = "rpds_py-0.13.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a8c2bf286e5d755a075e5e97ba56b3de08cccdad6b323ab0b21cc98875176b03"}, - {file = "rpds_py-0.13.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b3d4b390ee70ca9263b331ccfaf9819ee20e90dfd0201a295e23eb64a005dbef"}, - {file = "rpds_py-0.13.1-pp39-pypy39_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:db8d0f0ad92f74feb61c4e4a71f1d573ef37c22ef4dc19cab93e501bfdad8cbd"}, - {file = "rpds_py-0.13.1-pp39-pypy39_pp73-musllinux_1_2_i686.whl", hash = "sha256:2abd669a39be69cdfe145927c7eb53a875b157740bf1e2d49e9619fc6f43362e"}, - {file = "rpds_py-0.13.1-pp39-pypy39_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:2c173f529666bab8e3f948b74c6d91afa22ea147e6ebae49a48229d9020a47c4"}, - {file = "rpds_py-0.13.1.tar.gz", hash = "sha256:264f3a5906c62b9df3a00ad35f6da1987d321a053895bd85f9d5c708de5c0fbf"}, + {file = "rpds_py-0.13.2-cp310-cp310-macosx_10_7_x86_64.whl", hash = "sha256:1ceebd0ae4f3e9b2b6b553b51971921853ae4eebf3f54086be0565d59291e53d"}, + {file = "rpds_py-0.13.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:46e1ed994a0920f350a4547a38471217eb86f57377e9314fbaaa329b71b7dfe3"}, + {file = "rpds_py-0.13.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ee353bb51f648924926ed05e0122b6a0b1ae709396a80eb583449d5d477fcdf7"}, + {file = "rpds_py-0.13.2-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:530190eb0cd778363bbb7596612ded0bb9fef662daa98e9d92a0419ab27ae914"}, + {file = "rpds_py-0.13.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:29d311e44dd16d2434d5506d57ef4d7036544fc3c25c14b6992ef41f541b10fb"}, + {file = "rpds_py-0.13.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2e72f750048b32d39e87fc85c225c50b2a6715034848dbb196bf3348aa761fa1"}, + {file = "rpds_py-0.13.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db09b98c7540df69d4b47218da3fbd7cb466db0fb932e971c321f1c76f155266"}, + {file = "rpds_py-0.13.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2ac26f50736324beb0282c819668328d53fc38543fa61eeea2c32ea8ea6eab8d"}, + {file = "rpds_py-0.13.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:12ecf89bd54734c3c2c79898ae2021dca42750c7bcfb67f8fb3315453738ac8f"}, + {file = "rpds_py-0.13.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:3a44c8440183b43167fd1a0819e8356692bf5db1ad14ce140dbd40a1485f2dea"}, + {file = "rpds_py-0.13.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:bcef4f2d3dc603150421de85c916da19471f24d838c3c62a4f04c1eb511642c1"}, + {file = "rpds_py-0.13.2-cp310-none-win32.whl", hash = "sha256:ee6faebb265e28920a6f23a7d4c362414b3f4bb30607141d718b991669e49ddc"}, + {file = "rpds_py-0.13.2-cp310-none-win_amd64.whl", hash = "sha256:ac96d67b37f28e4b6ecf507c3405f52a40658c0a806dffde624a8fcb0314d5fd"}, + {file = "rpds_py-0.13.2-cp311-cp311-macosx_10_7_x86_64.whl", hash = "sha256:b5f6328e8e2ae8238fc767703ab7b95785521c42bb2b8790984e3477d7fa71ad"}, + {file = "rpds_py-0.13.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:729408136ef8d45a28ee9a7411917c9e3459cf266c7e23c2f7d4bb8ef9e0da42"}, + {file = "rpds_py-0.13.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:65cfed9c807c27dee76407e8bb29e6f4e391e436774bcc769a037ff25ad8646e"}, + {file = "rpds_py-0.13.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:aefbdc934115d2f9278f153952003ac52cd2650e7313750390b334518c589568"}, + {file = "rpds_py-0.13.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d48db29bd47814671afdd76c7652aefacc25cf96aad6daefa82d738ee87461e2"}, + {file = "rpds_py-0.13.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3c55d7f2d817183d43220738270efd3ce4e7a7b7cbdaefa6d551ed3d6ed89190"}, + {file = "rpds_py-0.13.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6aadae3042f8e6db3376d9e91f194c606c9a45273c170621d46128f35aef7cd0"}, + {file = "rpds_py-0.13.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5feae2f9aa7270e2c071f488fab256d768e88e01b958f123a690f1cc3061a09c"}, + {file = "rpds_py-0.13.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:51967a67ea0d7b9b5cd86036878e2d82c0b6183616961c26d825b8c994d4f2c8"}, + {file = "rpds_py-0.13.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:4d0c10d803549427f427085ed7aebc39832f6e818a011dcd8785e9c6a1ba9b3e"}, + {file = "rpds_py-0.13.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:603d5868f7419081d616dab7ac3cfa285296735e7350f7b1e4f548f6f953ee7d"}, + {file = "rpds_py-0.13.2-cp311-none-win32.whl", hash = "sha256:b8996ffb60c69f677245f5abdbcc623e9442bcc91ed81b6cd6187129ad1fa3e7"}, + {file = "rpds_py-0.13.2-cp311-none-win_amd64.whl", hash = "sha256:5379e49d7e80dca9811b36894493d1c1ecb4c57de05c36f5d0dd09982af20211"}, + {file = "rpds_py-0.13.2-cp312-cp312-macosx_10_7_x86_64.whl", hash = "sha256:8a776a29b77fe0cc28fedfd87277b0d0f7aa930174b7e504d764e0b43a05f381"}, + {file = "rpds_py-0.13.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2a1472956c5bcc49fb0252b965239bffe801acc9394f8b7c1014ae9258e4572b"}, + {file = "rpds_py-0.13.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f252dfb4852a527987a9156cbcae3022a30f86c9d26f4f17b8c967d7580d65d2"}, + {file = "rpds_py-0.13.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f0d320e70b6b2300ff6029e234e79fe44e9dbbfc7b98597ba28e054bd6606a57"}, + {file = "rpds_py-0.13.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ade2ccb937060c299ab0dfb2dea3d2ddf7e098ed63ee3d651ebfc2c8d1e8632a"}, + {file = "rpds_py-0.13.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b9d121be0217787a7d59a5c6195b0842d3f701007333426e5154bf72346aa658"}, + {file = "rpds_py-0.13.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8fa6bd071ec6d90f6e7baa66ae25820d57a8ab1b0a3c6d3edf1834d4b26fafa2"}, + {file = "rpds_py-0.13.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c918621ee0a3d1fe61c313f2489464f2ae3d13633e60f520a8002a5e910982ee"}, + {file = "rpds_py-0.13.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:25b28b3d33ec0a78e944aaaed7e5e2a94ac811bcd68b557ca48a0c30f87497d2"}, + {file = "rpds_py-0.13.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:31e220a040b89a01505128c2f8a59ee74732f666439a03e65ccbf3824cdddae7"}, + {file = "rpds_py-0.13.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:15253fff410873ebf3cfba1cc686a37711efcd9b8cb30ea21bb14a973e393f60"}, + {file = "rpds_py-0.13.2-cp312-none-win32.whl", hash = "sha256:b981a370f8f41c4024c170b42fbe9e691ae2dbc19d1d99151a69e2c84a0d194d"}, + {file = "rpds_py-0.13.2-cp312-none-win_amd64.whl", hash = "sha256:4c4e314d36d4f31236a545696a480aa04ea170a0b021e9a59ab1ed94d4c3ef27"}, + {file = "rpds_py-0.13.2-cp38-cp38-macosx_10_7_x86_64.whl", hash = "sha256:80e5acb81cb49fd9f2d5c08f8b74ffff14ee73b10ca88297ab4619e946bcb1e1"}, + {file = "rpds_py-0.13.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:efe093acc43e869348f6f2224df7f452eab63a2c60a6c6cd6b50fd35c4e075ba"}, + {file = "rpds_py-0.13.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c2a61c0e4811012b0ba9f6cdcb4437865df5d29eab5d6018ba13cee1c3064a0"}, + {file = "rpds_py-0.13.2-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:751758d9dd04d548ec679224cc00e3591f5ebf1ff159ed0d4aba6a0746352452"}, + {file = "rpds_py-0.13.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6ba8858933f0c1a979781272a5f65646fca8c18c93c99c6ddb5513ad96fa54b1"}, + {file = "rpds_py-0.13.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bfdfbe6a36bc3059fff845d64c42f2644cf875c65f5005db54f90cdfdf1df815"}, + {file = "rpds_py-0.13.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa0379c1935c44053c98826bc99ac95f3a5355675a297ac9ce0dfad0ce2d50ca"}, + {file = "rpds_py-0.13.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d5593855b5b2b73dd8413c3fdfa5d95b99d657658f947ba2c4318591e745d083"}, + {file = "rpds_py-0.13.2-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:2a7bef6977043673750a88da064fd513f89505111014b4e00fbdd13329cd4e9a"}, + {file = "rpds_py-0.13.2-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:3ab96754d23372009638a402a1ed12a27711598dd49d8316a22597141962fe66"}, + {file = "rpds_py-0.13.2-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:e06cfea0ece444571d24c18ed465bc93afb8c8d8d74422eb7026662f3d3f779b"}, + {file = "rpds_py-0.13.2-cp38-none-win32.whl", hash = "sha256:5493569f861fb7b05af6d048d00d773c6162415ae521b7010197c98810a14cab"}, + {file = "rpds_py-0.13.2-cp38-none-win_amd64.whl", hash = "sha256:b07501b720cf060c5856f7b5626e75b8e353b5f98b9b354a21eb4bfa47e421b1"}, + {file = "rpds_py-0.13.2-cp39-cp39-macosx_10_7_x86_64.whl", hash = "sha256:881df98f0a8404d32b6de0fd33e91c1b90ed1516a80d4d6dc69d414b8850474c"}, + {file = "rpds_py-0.13.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d79c159adea0f1f4617f54aa156568ac69968f9ef4d1e5fefffc0a180830308e"}, + {file = "rpds_py-0.13.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:38d4f822ee2f338febcc85aaa2547eb5ba31ba6ff68d10b8ec988929d23bb6b4"}, + {file = "rpds_py-0.13.2-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5d75d6d220d55cdced2f32cc22f599475dbe881229aeddba6c79c2e9df35a2b3"}, + {file = "rpds_py-0.13.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5d97e9ae94fb96df1ee3cb09ca376c34e8a122f36927230f4c8a97f469994bff"}, + {file = "rpds_py-0.13.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:67a429520e97621a763cf9b3ba27574779c4e96e49a27ff8a1aa99ee70beb28a"}, + {file = "rpds_py-0.13.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:188435794405c7f0573311747c85a96b63c954a5f2111b1df8018979eca0f2f0"}, + {file = "rpds_py-0.13.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:38f9bf2ad754b4a45b8210a6c732fe876b8a14e14d5992a8c4b7c1ef78740f53"}, + {file = "rpds_py-0.13.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:a6ba2cb7d676e9415b9e9ac7e2aae401dc1b1e666943d1f7bc66223d3d73467b"}, + {file = "rpds_py-0.13.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:eaffbd8814bb1b5dc3ea156a4c5928081ba50419f9175f4fc95269e040eff8f0"}, + {file = "rpds_py-0.13.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:5a4c1058cdae6237d97af272b326e5f78ee7ee3bbffa6b24b09db4d828810468"}, + {file = "rpds_py-0.13.2-cp39-none-win32.whl", hash = "sha256:b5267feb19070bef34b8dea27e2b504ebd9d31748e3ecacb3a4101da6fcb255c"}, + {file = "rpds_py-0.13.2-cp39-none-win_amd64.whl", hash = "sha256:ddf23960cb42b69bce13045d5bc66f18c7d53774c66c13f24cf1b9c144ba3141"}, + {file = "rpds_py-0.13.2-pp310-pypy310_pp73-macosx_10_7_x86_64.whl", hash = "sha256:97163a1ab265a1073a6372eca9f4eeb9f8c6327457a0b22ddfc4a17dcd613e74"}, + {file = "rpds_py-0.13.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:25ea41635d22b2eb6326f58e608550e55d01df51b8a580ea7e75396bafbb28e9"}, + {file = "rpds_py-0.13.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76d59d4d451ba77f08cb4cd9268dec07be5bc65f73666302dbb5061989b17198"}, + {file = "rpds_py-0.13.2-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e7c564c58cf8f248fe859a4f0fe501b050663f3d7fbc342172f259124fb59933"}, + {file = "rpds_py-0.13.2-pp310-pypy310_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:61dbc1e01dc0c5875da2f7ae36d6e918dc1b8d2ce04e871793976594aad8a57a"}, + {file = "rpds_py-0.13.2-pp310-pypy310_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fdb82eb60d31b0c033a8e8ee9f3fc7dfbaa042211131c29da29aea8531b4f18f"}, + {file = "rpds_py-0.13.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d204957169f0b3511fb95395a9da7d4490fb361763a9f8b32b345a7fe119cb45"}, + {file = "rpds_py-0.13.2-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c45008ca79bad237cbc03c72bc5205e8c6f66403773929b1b50f7d84ef9e4d07"}, + {file = "rpds_py-0.13.2-pp310-pypy310_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:79bf58c08f0756adba691d480b5a20e4ad23f33e1ae121584cf3a21717c36dfa"}, + {file = "rpds_py-0.13.2-pp310-pypy310_pp73-musllinux_1_2_i686.whl", hash = "sha256:e86593bf8637659e6a6ed58854b6c87ec4e9e45ee8a4adfd936831cef55c2d21"}, + {file = "rpds_py-0.13.2-pp310-pypy310_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:d329896c40d9e1e5c7715c98529e4a188a1f2df51212fd65102b32465612b5dc"}, + {file = "rpds_py-0.13.2-pp38-pypy38_pp73-macosx_10_7_x86_64.whl", hash = "sha256:4a5375c5fff13f209527cd886dc75394f040c7d1ecad0a2cb0627f13ebe78a12"}, + {file = "rpds_py-0.13.2-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:06d218e4464d31301e943b65b2c6919318ea6f69703a351961e1baaf60347276"}, + {file = "rpds_py-0.13.2-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c1f41d32a2ddc5a94df4b829b395916a4b7f103350fa76ba6de625fcb9e773ac"}, + {file = "rpds_py-0.13.2-pp38-pypy38_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6bc568b05e02cd612be53900c88aaa55012e744930ba2eeb56279db4c6676eb3"}, + {file = "rpds_py-0.13.2-pp38-pypy38_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9d94d78418203904730585efa71002286ac4c8ac0689d0eb61e3c465f9e608ff"}, + {file = "rpds_py-0.13.2-pp38-pypy38_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bed0252c85e21cf73d2d033643c945b460d6a02fc4a7d644e3b2d6f5f2956c64"}, + {file = "rpds_py-0.13.2-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:244e173bb6d8f3b2f0c4d7370a1aa341f35da3e57ffd1798e5b2917b91731fd3"}, + {file = "rpds_py-0.13.2-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7f55cd9cf1564b7b03f238e4c017ca4794c05b01a783e9291065cb2858d86ce4"}, + {file = "rpds_py-0.13.2-pp38-pypy38_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:f03a1b3a4c03e3e0161642ac5367f08479ab29972ea0ffcd4fa18f729cd2be0a"}, + {file = "rpds_py-0.13.2-pp38-pypy38_pp73-musllinux_1_2_i686.whl", hash = "sha256:f5f4424cb87a20b016bfdc157ff48757b89d2cc426256961643d443c6c277007"}, + {file = "rpds_py-0.13.2-pp38-pypy38_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:c82bbf7e03748417c3a88c1b0b291288ce3e4887a795a3addaa7a1cfd9e7153e"}, + {file = "rpds_py-0.13.2-pp39-pypy39_pp73-macosx_10_7_x86_64.whl", hash = "sha256:c0095b8aa3e432e32d372e9a7737e65b58d5ed23b9620fea7cb81f17672f1fa1"}, + {file = "rpds_py-0.13.2-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:4c2d26aa03d877c9730bf005621c92da263523a1e99247590abbbe252ccb7824"}, + {file = "rpds_py-0.13.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:96f2975fb14f39c5fe75203f33dd3010fe37d1c4e33177feef1107b5ced750e3"}, + {file = "rpds_py-0.13.2-pp39-pypy39_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4dcc5ee1d0275cb78d443fdebd0241e58772a354a6d518b1d7af1580bbd2c4e8"}, + {file = "rpds_py-0.13.2-pp39-pypy39_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:61d42d2b08430854485135504f672c14d4fc644dd243a9c17e7c4e0faf5ed07e"}, + {file = "rpds_py-0.13.2-pp39-pypy39_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d3a61e928feddc458a55110f42f626a2a20bea942ccedb6fb4cee70b4830ed41"}, + {file = "rpds_py-0.13.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7de12b69d95072394998c622cfd7e8cea8f560db5fca6a62a148f902a1029f8b"}, + {file = "rpds_py-0.13.2-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:87a90f5545fd61f6964e65eebde4dc3fa8660bb7d87adb01d4cf17e0a2b484ad"}, + {file = "rpds_py-0.13.2-pp39-pypy39_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:9c95a1a290f9acf7a8f2ebbdd183e99215d491beea52d61aa2a7a7d2c618ddc6"}, + {file = "rpds_py-0.13.2-pp39-pypy39_pp73-musllinux_1_2_i686.whl", hash = "sha256:35f53c76a712e323c779ca39b9a81b13f219a8e3bc15f106ed1e1462d56fcfe9"}, + {file = "rpds_py-0.13.2-pp39-pypy39_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:96fb0899bb2ab353f42e5374c8f0789f54e0a94ef2f02b9ac7149c56622eaf31"}, + {file = "rpds_py-0.13.2.tar.gz", hash = "sha256:f8eae66a1304de7368932b42d801c67969fd090ddb1a7a24f27b435ed4bed68f"}, ] [[package]] name = "ruff" -version = "0.0.290" -description = "An extremely fast Python linter, written in Rust." +version = "0.1.7" +description = "An extremely fast Python linter and code formatter, written in Rust." optional = false python-versions = ">=3.7" files = [ - {file = "ruff-0.0.290-py3-none-macosx_10_7_x86_64.whl", hash = "sha256:0e2b09ac4213b11a3520221083866a5816616f3ae9da123037b8ab275066fbac"}, - {file = "ruff-0.0.290-py3-none-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:4ca6285aa77b3d966be32c9a3cd531655b3d4a0171e1f9bf26d66d0372186767"}, - {file = "ruff-0.0.290-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:35e3550d1d9f2157b0fcc77670f7bb59154f223bff281766e61bdd1dd854e0c5"}, - {file = "ruff-0.0.290-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d748c8bd97874f5751aed73e8dde379ce32d16338123d07c18b25c9a2796574a"}, - {file = "ruff-0.0.290-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:982af5ec67cecd099e2ef5e238650407fb40d56304910102d054c109f390bf3c"}, - {file = "ruff-0.0.290-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:bbd37352cea4ee007c48a44c9bc45a21f7ba70a57edfe46842e346651e2b995a"}, - {file = "ruff-0.0.290-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1d9be6351b7889462912e0b8185a260c0219c35dfd920fb490c7f256f1d8313e"}, - {file = "ruff-0.0.290-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:75cdc7fe32dcf33b7cec306707552dda54632ac29402775b9e212a3c16aad5e6"}, - {file = "ruff-0.0.290-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eb07f37f7aecdbbc91d759c0c09870ce0fb3eed4025eebedf9c4b98c69abd527"}, - {file = "ruff-0.0.290-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:2ab41bc0ba359d3f715fc7b705bdeef19c0461351306b70a4e247f836b9350ed"}, - {file = "ruff-0.0.290-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:150bf8050214cea5b990945b66433bf9a5e0cef395c9bc0f50569e7de7540c86"}, - {file = "ruff-0.0.290-py3-none-musllinux_1_2_i686.whl", hash = "sha256:75386ebc15fe5467248c039f5bf6a0cfe7bfc619ffbb8cd62406cd8811815fca"}, - {file = "ruff-0.0.290-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:ac93eadf07bc4ab4c48d8bb4e427bf0f58f3a9c578862eb85d99d704669f5da0"}, - {file = "ruff-0.0.290-py3-none-win32.whl", hash = "sha256:461fbd1fb9ca806d4e3d5c745a30e185f7cf3ca77293cdc17abb2f2a990ad3f7"}, - {file = "ruff-0.0.290-py3-none-win_amd64.whl", hash = "sha256:f1f49f5ec967fd5778813780b12a5650ab0ebcb9ddcca28d642c689b36920796"}, - {file = "ruff-0.0.290-py3-none-win_arm64.whl", hash = "sha256:ae5a92dfbdf1f0c689433c223f8dac0782c2b2584bd502dfdbc76475669f1ba1"}, - {file = "ruff-0.0.290.tar.gz", hash = "sha256:949fecbc5467bb11b8db810a7fa53c7e02633856ee6bd1302b2f43adcd71b88d"}, + {file = "ruff-0.1.7-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:7f80496854fdc65b6659c271d2c26e90d4d401e6a4a31908e7e334fab4645aac"}, + {file = "ruff-0.1.7-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:1ea109bdb23c2a4413f397ebd8ac32cb498bee234d4191ae1a310af760e5d287"}, + {file = "ruff-0.1.7-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b0c2de9dd9daf5e07624c24add25c3a490dbf74b0e9bca4145c632457b3b42a"}, + {file = "ruff-0.1.7-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:69a4bed13bc1d5dabf3902522b5a2aadfebe28226c6269694283c3b0cecb45fd"}, + {file = "ruff-0.1.7-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:de02ca331f2143195a712983a57137c5ec0f10acc4aa81f7c1f86519e52b92a1"}, + {file = "ruff-0.1.7-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:45b38c3f8788a65e6a2cab02e0f7adfa88872696839d9882c13b7e2f35d64c5f"}, + {file = "ruff-0.1.7-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6c64cb67b2025b1ac6d58e5ffca8f7b3f7fd921f35e78198411237e4f0db8e73"}, + {file = "ruff-0.1.7-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9dcc6bb2f4df59cb5b4b40ff14be7d57012179d69c6565c1da0d1f013d29951b"}, + {file = "ruff-0.1.7-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:df2bb4bb6bbe921f6b4f5b6fdd8d8468c940731cb9406f274ae8c5ed7a78c478"}, + {file = "ruff-0.1.7-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:276a89bcb149b3d8c1b11d91aa81898fe698900ed553a08129b38d9d6570e717"}, + {file = "ruff-0.1.7-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:90c958fe950735041f1c80d21b42184f1072cc3975d05e736e8d66fc377119ea"}, + {file = "ruff-0.1.7-py3-none-musllinux_1_2_i686.whl", hash = "sha256:6b05e3b123f93bb4146a761b7a7d57af8cb7384ccb2502d29d736eaade0db519"}, + {file = "ruff-0.1.7-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:290ecab680dce94affebefe0bbca2322a6277e83d4f29234627e0f8f6b4fa9ce"}, + {file = "ruff-0.1.7-py3-none-win32.whl", hash = "sha256:416dfd0bd45d1a2baa3b1b07b1b9758e7d993c256d3e51dc6e03a5e7901c7d80"}, + {file = "ruff-0.1.7-py3-none-win_amd64.whl", hash = "sha256:4af95fd1d3b001fc41325064336db36e3d27d2004cdb6d21fd617d45a172dd96"}, + {file = "ruff-0.1.7-py3-none-win_arm64.whl", hash = "sha256:0683b7bfbb95e6df3c7c04fe9d78f631f8e8ba4868dfc932d43d690698057e2e"}, + {file = "ruff-0.1.7.tar.gz", hash = "sha256:dffd699d07abf54833e5f6cc50b85a6ff043715da8788c4a79bcd4ab4734d306"}, ] [[package]] @@ -2729,13 +2660,13 @@ widechars = ["wcwidth"] [[package]] name = "termcolor" -version = "2.3.0" +version = "2.4.0" description = "ANSI color formatting for output in terminal" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "termcolor-2.3.0-py3-none-any.whl", hash = "sha256:3afb05607b89aed0ffe25202399ee0867ad4d3cb4180d98aaf8eefa6a5f7d475"}, - {file = "termcolor-2.3.0.tar.gz", hash = "sha256:b5b08f68937f138fe92f6c089b99f1e2da0ae56c52b78bf7075fd95420fd9a5a"}, + {file = "termcolor-2.4.0-py3-none-any.whl", hash = "sha256:9297c0df9c99445c2412e832e882a7884038a25617c60cea2ad69488d4040d63"}, + {file = "termcolor-2.4.0.tar.gz", hash = "sha256:aab9e56047c8ac41ed798fa36d892a37aca6b3e9159f3e0c24bc64a9b3ac7b7a"}, ] [package.extras] @@ -2805,13 +2736,13 @@ files = [ [[package]] name = "typing-extensions" -version = "4.8.0" +version = "4.9.0" description = "Backported and Experimental Type Hints for Python 3.8+" optional = false python-versions = ">=3.8" files = [ - {file = "typing_extensions-4.8.0-py3-none-any.whl", hash = "sha256:8f92fc8806f9a6b641eaa5318da32b44d401efaac0f6678c9bc448ba3605faa0"}, - {file = "typing_extensions-4.8.0.tar.gz", hash = "sha256:df8e4339e9cb77357558cbdbceca33c303714cf861d1eef15e1070055ae8b7ef"}, + {file = "typing_extensions-4.9.0-py3-none-any.whl", hash = "sha256:af72aea155e91adfc61c3ae9e0e342dbc0cba726d6cba4b6c72c1f34e47291cd"}, + {file = "typing_extensions-4.9.0.tar.gz", hash = "sha256:23478f88c37f27d76ac8aee6c905017a143b0b1b886c3c9f66bc2fd94f9f5783"}, ] [[package]] @@ -2868,13 +2799,13 @@ zstd = ["zstandard (>=0.18.0)"] [[package]] name = "virtualenv" -version = "20.24.7" +version = "20.25.0" description = "Virtual Python Environment builder" optional = false python-versions = ">=3.7" files = [ - {file = "virtualenv-20.24.7-py3-none-any.whl", hash = "sha256:a18b3fd0314ca59a2e9f4b556819ed07183b3e9a3702ecfe213f593d44f7b3fd"}, - {file = "virtualenv-20.24.7.tar.gz", hash = "sha256:69050ffb42419c91f6c1284a7b24e0475d793447e35929b488bf6a0aade39353"}, + {file = "virtualenv-20.25.0-py3-none-any.whl", hash = "sha256:4238949c5ffe6876362d9c0180fc6c3a824a7b12b80604eeb8085f2ed7460de3"}, + {file = "virtualenv-20.25.0.tar.gz", hash = "sha256:bf51c0d9c7dd63ea8e44086fa1e4fb1093a31e963b86959257378aef020e1f1b"}, ] [package.dependencies] @@ -2978,17 +2909,17 @@ files = [ [[package]] name = "yte" -version = "1.5.1" +version = "1.5.4" description = "A YAML template engine with Python expressions" optional = false python-versions = ">=3.7" files = [ - {file = "yte-1.5.1-py3-none-any.whl", hash = "sha256:fd646bc47c355f202f14b7476996de4a31501cf1e107ac7ad8e19edcd786d30b"}, - {file = "yte-1.5.1.tar.gz", hash = "sha256:6d0b315b78af83276d78f5f67c107c84238f772a76d74f4fc77905b46f3731f5"}, + {file = "yte-1.5.4-py3-none-any.whl", hash = "sha256:14ccfcb57d60b7652041b606129851423805140b22f52f5152f7c2692cd7b905"}, + {file = "yte-1.5.4.tar.gz", hash = "sha256:d2d77e53eafca74f58234fcd3fea28cc0a719e4f3784911511e35e86594bc880"}, ] [package.dependencies] -dpath = ">=2.0,<3.0" +dpath = ">=2.1,<3.0" plac = ">=1.3.4,<2.0.0" pyyaml = ">=6.0,<7.0" @@ -3010,4 +2941,4 @@ testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "p [metadata] lock-version = "2.0" python-versions = ">=3.8,<3.13" -content-hash = "83348b004474cbde05e86bfcc98488b376d29f36c437467eb0a2426fa9054673" +content-hash = "1f089de40b77f3ccf219e47797fd31f7c2609f55f534d6ae714f912689e2b0a4" diff --git a/pyproject.toml b/pyproject.toml index 09e1676d..18564cb3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -70,11 +70,9 @@ jinja2-time = ">=0.2.0" requests = ">=2.31.0" [tool.poetry.group.dev.dependencies] -black = "^23.1.0" pytest = "^7.0.0" pytest-mock = "^3.7.0" -isort = "^5.10.1" -poethepoet = "^0.22.0" +poethepoet = "^0.24.0" pre-commit = "^3.0.0" mkinit = "^1.0.0" hypothesis = "^6.34.1" @@ -82,8 +80,8 @@ pytest-benchmark = "^4.0.0" pyfakefs = "^5.1.0" pyparsing = "^3.0.9" pathvalidate = "^3.0.0" -pyright = "==1.1.324" -ruff = "^0.0.290" +pyright = "^1.1.339" +ruff = "^0.1.7" pytest-xdist = "^3.3.1" pytest-split = "^0.8.1" tomli = "^2.0.1" @@ -98,13 +96,13 @@ build-backend = "poetry_dynamic_versioning.backend" [tool.poe.tasks] setup = "pre-commit install" -quality.shell = "isort snakebids && black snakebids && ruff snakebids && pyright snakebids" -fix.shell = "ruff --fix snakebids && isort snakebids && black snakebids" +quality.shell = "ruff format --check snakebids && ruff check snakebids && pyright snakebids" +fix.shell = "ruff format snakebids && ruff check --fix snakebids" test = """ pytest --doctest-modules --ignore=docs \ --ignore=snakebids/project_template --benchmark-disable """ -mkinit = "mkinit --recursive --nomods --black -i snakebids" +mkinit.shell = "mkinit --recursive --nomods -i snakebids && ruff format snakebids && ruff check --fix --select I001 snakebids " benchmark = "pytest --benchmark-only --benchmark-autosave" [tool.poe.tasks._get_version] @@ -139,11 +137,100 @@ reportImportCycles = false [tool.ruff] select = [ "E", # pycodestyle error + "W", # pycodestyle warning "F", # pyflakes + "N", # pep8-naming + "UP", # pyupgrade + "YTT", # flake8-2020 + "S", # flake8-bandit + "BLE", # flake8-blind-except + "B", # flake8-bugbear + "A", # flake8-builtins + "COM", # flake8-commas + "C4", # flake8-comprehensions + "T10", # flake8-debugger + "EM", # flake8-errmsg + "FA", # flake8-future-annotations + "ISC", # flake8-implicit-str-concat + "ICN", # flake8-import-conventions + "G", # flake8-logging-format + "INP", # flake8-no-pep420 + "PIE", # flake8-pie + "PT", # flake8-pytest-style + "RSE", # flake8-raise + "RET", # flake8-return + "SLF", # flake8-self + "SIM", # flake8-simplify + # We use os.path a lot in "legitimate" ways, so this ruleset has too many + # false positives + # "PTH", # flake8-use-pathlib + "TRY", # tryceratops + "FLY", # flynt + "D", # pydocstyle "PL", # pylint "RUF", # ruff - "UP", #pyupgrade "T100", # debugger + "I", # isort + # Other perf rules give more false positives than benefits + "PERF101", # do not cast iterable to list ] -ignore = ["PLR0913"] +ignore = [ + "PLR0913", + "D105", # missing docstring in magic method + "D100", # Require docstring in public modules + "D104", # Require docstring in public packages + "S603", # Subprocess without shell equals true + "S607", # start-process-with-partial-path + # recommended ignores for ruff format + "W191", + "E111", + "E114", + "E117", + "D206", + "D300", + "Q000", + "Q001", + "Q002", + "Q003", + "COM812", + "COM819", + "ISC001", + "ISC002", +] + + target-version = "py38" + +namespace-packages = ["snakebids/plugins"] + +[tool.ruff.per-file-ignores] +"snakebids/project_template/**" = ["N999"] +"snakebids/tests/**" = [ + "D", + "S101", # assert + "S307", # Use of eval + "SLF", +] + +[tool.ruff.pydocstyle] +convention = "numpy" +ignore-decorators = ["snakebids.utils.utils.property_alias"] + +[tool.ruff.flake8-builtins] +builtins-ignorelist = ["filter"] + +[tool.ruff.flake8-import-conventions.extend-aliases] +itertools = "it" +functools = "ft" +operator = "op" +subprocess = "sp" +importlib_resources = "impr" +"importlib.resources" = "impr" +"importlib.metadata" = "impm" +more_itertools = "itx" +"hypothesis.strategies" = "st" +"snakebids.tests.strategies" = "sb_st" +"snakebids.utils.sb_itertools" = "sb_it" + +[tool.ruff.flake8-pytest-style] +fixture-parentheses = false diff --git a/snakebids/__init__.py b/snakebids/__init__.py index 0c2f466c..d47997f1 100644 --- a/snakebids/__init__.py +++ b/snakebids/__init__.py @@ -19,7 +19,9 @@ get_wildcard_constraints, write_derivative_json, ) -from snakebids.paths import bids +from snakebids.paths import ( + bids, +) __all__ = [ "BidsComponent", @@ -35,5 +37,4 @@ "get_wildcard_constraints", "write_derivative_json", ] - # diff --git a/snakebids/admin.py b/snakebids/admin.py index c75110a5..4393e963 100644 --- a/snakebids/admin.py +++ b/snakebids/admin.py @@ -15,6 +15,7 @@ def create_app(args: argparse.Namespace) -> None: + """Implement the ``snakebids create`` command.""" output = Path(args.output_dir).resolve() if not output.parent.exists(): print( @@ -47,6 +48,7 @@ def create_app(args: argparse.Namespace) -> None: def create_descriptor(args: argparse.Namespace) -> None: + """Implement the ``snakebids boutiques`` command.""" app = SnakeBidsApp(args.app_dir.resolve()) add_dynamic_args(app.parser, app.config["parse_args"], app.config["pybids_inputs"]) app.create_descriptor(args.out_path) @@ -54,6 +56,7 @@ def create_descriptor(args: argparse.Namespace) -> None: def gen_parser() -> argparse.ArgumentParser: + """Generate the CLI parser for ``snakebids``.""" parser = argparse.ArgumentParser( description="Perform administrative Snakebids tasks." ) @@ -85,7 +88,6 @@ def gen_parser() -> argparse.ArgumentParser: def main() -> None: """Invoke Snakebids cli.""" - parser = gen_parser() args = parser.parse_args() args.func(args) diff --git a/snakebids/app.py b/snakebids/app.py index d0a6be5b..81f18fd6 100644 --- a/snakebids/app.py +++ b/snakebids/app.py @@ -4,7 +4,7 @@ import argparse import logging import sys -from importlib import metadata +from importlib import metadata as impm from os import PathLike from pathlib import Path from typing import Any, Callable @@ -67,9 +67,8 @@ def wrapper(self: SnakeBidsApp): # else, snakefile return Path(self.snakemake_dir, path) - raise ConfigError( - f"Error: no {file_name} file found, tried {', '.join(choices)}." - ) + msg = f"Error: no {file_name} file found, tried {', '.join(choices)}." + raise ConfigError(msg) return wrapper @@ -85,8 +84,8 @@ def _get_app_version(self: SnakeBidsApp) -> str | None: SnakeBidsApp's snakemake_dir """ try: - return metadata.version(self.snakemake_dir.name) - except metadata.PackageNotFoundError: + return impm.version(self.snakemake_dir.name) + except impm.PackageNotFoundError: logger.warning( "App version not found; will be recorded in output as 'unknown'. " "If this is unexpected, please contact the app maintainer." @@ -158,8 +157,7 @@ class SnakeBidsApp: args: SnakebidsArgs | None = None def run_snakemake(self) -> None: - """Run snakemake with the given config, after applying plugins""" - + """Run snakemake with the given config, after applying plugins.""" # If no SnakebidsArgs were provided on class instantiation, we compute args # using the provided parser if not self.args: @@ -240,8 +238,8 @@ def run_snakemake(self) -> None: config_file=new_config_file, data=dict( app.config, - snakemake_version=metadata.version("snakemake"), - snakebids_version=metadata.version("snakebids"), + snakemake_version=impm.version("snakemake"), + snakebids_version=impm.version("snakebids"), app_version=app.version or "unknown", ), force_overwrite=True, @@ -286,7 +284,7 @@ def update_config(config: dict[str, Any], snakebids_args: SnakebidsArgs) -> None # filter_{input_type} dict pybids_inputs = config["pybids_inputs"] args = snakebids_args.args_dict - for input_type in pybids_inputs.keys(): + for input_type in pybids_inputs: arg_filter_dict = args[f"filter_{input_type}"] if arg_filter_dict is not None: pybids_inputs[input_type].setdefault("filters", {}) @@ -299,7 +297,7 @@ def update_config(config: dict[str, Any], snakebids_args: SnakebidsArgs) -> None # add cmdline defined wildcards from the list: # wildcards_{input_type} - for input_type in pybids_inputs.keys(): + for input_type in pybids_inputs: wildcards_list = args[f"wildcards_{input_type}"] if wildcards_list is not None: pybids_inputs[input_type].setdefault("wildcards", []) @@ -308,7 +306,7 @@ def update_config(config: dict[str, Any], snakebids_args: SnakebidsArgs) -> None # add custom input paths to # config['pybids_inputs'][input_type]['custom_path'] - for input_type in pybids_inputs.keys(): + for input_type in pybids_inputs: custom_path = args[f"path_{input_type}"] if custom_path is not None: pybids_inputs[input_type]["custom_path"] = Path(custom_path).resolve() diff --git a/snakebids/cli.py b/snakebids/cli.py index b67143f1..75168190 100644 --- a/snakebids/cli.py +++ b/snakebids/cli.py @@ -9,6 +9,7 @@ import attr import snakemake +from typing_extensions import override from snakebids.exceptions import MisspecifiedCliFilterError from snakebids.types import InputsConfig, OptionalFilter @@ -23,9 +24,10 @@ class FilterParse(argparse.Action): - """Class for parsing CLI filters in argparse""" + """Class for parsing CLI filters in argparse.""" # Constructor calling + @override def __call__( self, parser: argparse.ArgumentParser, @@ -61,8 +63,9 @@ def __call__( class SnakemakeHelpAction(argparse.Action): - """Class for printing snakemake usage in argparse""" + """Class for printing snakemake usage in argparse.""" + @override def __call__( self, parser: argparse.ArgumentParser, @@ -75,7 +78,7 @@ def __call__( @attr.frozen class SnakebidsArgs: - """Arguments for Snakebids App + """Arguments for Snakebids App. Organizes the various options available for a generic Snakebids App, and store project specific arguments in a dict. Snakemake args are to be put in a list @@ -105,11 +108,10 @@ class SnakebidsArgs: def create_parser(include_snakemake: bool = False) -> argparse.ArgumentParser: - """Generate basic Snakebids Parser + """Generate basic Snakebids Parser. Includes the standard Snakebids arguments. """ - # The snakemake parser functionality did not seem to be implemented in the original # factoring. I left the logic here, but it should probably be removed if it's not # needed. @@ -207,6 +209,7 @@ def add_dynamic_args( parse_args: Mapping[str, Any], pybids_inputs: InputsConfig, ) -> None: + """Add --filter- and --wildcards- arguments to CLI.""" # create parser group for app options app_group = parser.add_argument_group("SNAKEBIDS", "Options for snakebids app") @@ -220,9 +223,8 @@ def add_dynamic_args( try: arg_dict = {**arg, "type": globals()[str(arg["type"])]} except KeyError as err: - raise TypeError( - f"{arg['type']} is not available as a type for {name}" - ) from err + msg = f"{arg['type']} is not available as a type for {name}" + raise TypeError(msg) from err else: arg_dict = arg app_group.add_argument( @@ -240,7 +242,7 @@ def add_dynamic_args( "absence of values for that key.", ) - for input_type in pybids_inputs.keys(): + for input_type in pybids_inputs: argnames = (f"--filter-{input_type}", f"--filter_{input_type}") filters = pybids_inputs[input_type].get("filters", {}) arglist_default = [f"{key}={value}" for (key, value) in filters.items()] @@ -260,7 +262,7 @@ def add_dynamic_args( "File path entities to use as wildcards in snakemake", ) - for input_type in pybids_inputs.keys(): + for input_type in pybids_inputs: argnames = (f"--wildcards-{input_type}", f"--wildcards_{input_type}") arglist_default = [ f"{wc}" for wc in pybids_inputs[input_type].get("wildcards", []) @@ -282,12 +284,13 @@ def add_dynamic_args( ) # create path override parser - for input_type in pybids_inputs.keys(): + for input_type in pybids_inputs: argnames = (f"--path-{input_type}", f"--path_{input_type}") override_opts.add_argument(*argnames, default=None) def parse_snakebids_args(parser: argparse.ArgumentParser) -> SnakebidsArgs: + """Parse built-in snakebids arguments.""" all_args = parser.parse_known_args() if all_args[0].workflow_mode: logger.warning( @@ -324,7 +327,7 @@ def parse_snakebids_args(parser: argparse.ArgumentParser) -> SnakebidsArgs: def _make_underscore_dash_aliases(name: str) -> set[str]: - """Generate --dash-arg aliases for --dash_args and vice versa + """Generate --dash-arg aliases for --dash_args and vice versa. If no dashes or underscores are in the argument name, a tuple containing just the original arg name will be returned @@ -363,9 +366,7 @@ def _resolve_path(path_candidate: _T) -> _T: def _resolve_path(path_candidate: Any) -> Any: - """Helper function to resolve any paths or list - of paths it's passed. Otherwise, returns the argument - unchanged. + """Resolve paths or list of paths, or return argument unchanged. Parameters ---------- @@ -374,7 +375,6 @@ def _resolve_path(path_candidate: Any) -> Any: Returns ------- - list, Path, object If os.Pathlike or list of os.Pathlike, the same paths resolved. Otherwise, the argument unchanged. """ diff --git a/snakebids/core/__init__.py b/snakebids/core/__init__.py index c05d9473..ac43327c 100644 --- a/snakebids/core/__init__.py +++ b/snakebids/core/__init__.py @@ -11,7 +11,10 @@ ) # -from snakebids.core.filtering import filter_list, get_filtered_ziplist_index +from snakebids.core.filtering import ( + filter_list, + get_filtered_ziplist_index, +) from snakebids.core.input_generation import ( generate_inputs, get_wildcard_constraints, @@ -30,5 +33,4 @@ "get_wildcard_constraints", "write_derivative_json", ] - # diff --git a/snakebids/core/datasets.py b/snakebids/core/datasets.py index babd22f2..5acf232d 100644 --- a/snakebids/core/datasets.py +++ b/snakebids/core/datasets.py @@ -32,7 +32,7 @@ class BidsDatasetDict(TypedDict): - """Dict equivalent of BidsInputs, for backwards-compatibility""" + """Dict equivalent of BidsInputs, for backwards-compatibility.""" input_path: dict[str, str] input_zip_lists: dict[str, dict[str, list[str]]] @@ -44,7 +44,7 @@ class BidsDatasetDict(TypedDict): class BidsComponentRow(ImmutableList[str]): - """A single row from a BidsComponent + """A single row from a BidsComponent. This class is derived by indexing a single entity from a :class:`BidsComponent` or :class:`BidsPartialComponent`. It should not be constructed manually. @@ -70,18 +70,19 @@ def __repr__(self) -> str: @property def entities(self) -> tuple[str, ...]: - """The unique values associated with the component""" + """The unique values associated with the component.""" return tuple(set(self._data)) @property def wildcards(self) -> str: - """The entity name wrapped in wildcard braces""" + """The entity name wrapped in wildcard braces.""" return f"{{{self.entity}}}" @property def zip_lists(self) -> ZipList: - """ - Dictionary where each key is a wildcard entity and each value is a list of the + """Table of entities and values. + + Each key is a wildcard entity and each value is a list of the values found for that entity. Each of these lists has length equal to the number of images matched for this modality, so they can be zipped together to get a list of the wildcard values for each file. @@ -101,7 +102,7 @@ def expand( allow_missing: bool | str | Iterable[str] = False, **wildcards: str | Iterable[str], ) -> list[str]: - """Safely expand over given paths with component wildcards + """Safely expand over given paths with component wildcards. Uses the entity-values represented by this row to expand over the given paths. Extra wildcards can be specified as keyword arguments. @@ -114,7 +115,7 @@ def expand( Uses the snakemake :ref:`expand ` under the hood. Parameters - ========== + ---------- paths: Path or list of paths to expand over allow_missing: @@ -146,7 +147,7 @@ def filter( regex_search: bool | str | Iterable[str] = False, **filters: str | Iterable[str], ) -> Self: - """Filter component based on provided entity filters + """Filter component based on provided entity filters. Extracts a subset of the entity-values present in the row. @@ -177,9 +178,8 @@ def filter( raise TypeError(msg) if spec is not None: if filters: - raise ValueError( - "Both __spec and filters cannot be used simultaneously" - ) + msg = "Both __spec and filters cannot be used simultaneously" + raise ValueError(msg) filters = {self.entity: spec} entity, data = itx.first( filter_list( @@ -191,7 +191,7 @@ def filter( @attr.define(kw_only=True) class BidsPartialComponent: - """Primitive representation of a bids data component + """Primitive representation of a bids data component. See :class:`BidsComponent` for an extended definition of a data component. @@ -221,7 +221,8 @@ class BidsPartialComponent: def _validate_zip_lists(self, __attr: str, value: dict[str, list[str]]) -> None: lengths = {len(val) for val in value.values()} if len(lengths) > 1: - raise ValueError("zip_lists must all be of equal length") + msg = "zip_lists must all be of equal length" + raise ValueError(msg) def __repr__(self) -> str: return self.pformat() @@ -245,7 +246,7 @@ def __getitem__( return BidsComponentRow(self.zip_lists[key], entity=key) def __bool__(self) -> bool: - """Truth of a BidsComponent is based on whether it has values + """Truth of a BidsComponent is based on whether it has values. It is not based on whether it has any entities. This is because :meth:`~BidsPartialComponent.filter` returns a component retaining all entities @@ -256,13 +257,23 @@ def __bool__(self) -> bool: return bool(itx.first(self.zip_lists)) def _pformat_body(self) -> None | str | list[str]: - """Extra properties to be printed within pformat + """Extra properties to be printed within pformat. Meant for implementation in subclasses """ return None def pformat(self, max_width: int | float | None = None, tabstop: int = 4) -> str: + """Pretty-format component. + + Parameters + ---------- + max_width + Maximum width of characters for output. If possible, zip_list table will be + elided to fit within this width + tabstop + Number of spaces for output indentation + """ width = max_width or get_console_size()[0] or inf body = it.chain( itx.always_iterable(self._pformat_body() or []), @@ -302,7 +313,7 @@ def zip_lists(self): @property def entities(self) -> MultiSelectDict[str, list[str]]: - """Component entities and their associated values + """Component entities and their associated values. Dictionary where each key is an entity and each value is a list of the unique values found for that entity. These lists might not be the same length. @@ -315,7 +326,7 @@ def entities(self) -> MultiSelectDict[str, list[str]]: @property def wildcards(self) -> MultiSelectDict[str, str]: - """Wildcards in brace-wrapped syntax + """Wildcards in brace-wrapped syntax. Dictionary where each key is the name of a wildcard entity, and each value is the Snakemake wildcard used for that entity. @@ -326,7 +337,7 @@ def wildcards(self) -> MultiSelectDict[str, str]: @property def input_zip_lists(self) -> ZipList: - """Alias of :attr:`zip_lists ` + """Alias of :attr:`zip_lists `. Dictionary where each key is a wildcard entity and each value is a list of the values found for that entity. Each of these lists has length equal to the number @@ -356,7 +367,7 @@ def expand( allow_missing: bool | str | Iterable[str] = False, **wildcards: str | Iterable[str], ) -> list[str]: - """Safely expand over given paths with component wildcards + """Safely expand over given paths with component wildcards. Uses the entity-value combinations found in the dataset to expand over the given paths. Extra wildcards can be specifed as keyword arguments. @@ -369,7 +380,7 @@ def expand( Uses the snakemake :ref:`expand ` under the hood. Parameters - ========== + ---------- paths: Path or list of paths to expand over allow_missing: @@ -419,7 +430,7 @@ def filter( regex_search: bool | str | Iterable[str] = False, **filters: str | Iterable[str], ) -> Self: - """Filter component based on provided entity filters + """Filter component based on provided entity filters. This method allows you to expand over a subset of your wildcards. This could be useful for extracting subjects from a specific patient group, running different @@ -455,7 +466,7 @@ def filter( @attr.define(kw_only=True) class BidsComponent(BidsPartialComponent): - """Representation of a bids data component + """Representation of a bids data component. A component is a set of data entries all corresponding to the same type of object. Entries vary over a set of entities. For example, a component may represent all the @@ -505,10 +516,11 @@ def _validate_zip_lists(self, __attr: str, value: dict[str, list[str]]) -> None: zip(*Formatter().parse(self.path)), [[], [], []] ) if (fields := set(filter(None, raw_fields))) != set(value): - raise ValueError( + msg = ( "zip_lists entries must match the wildcards in input_path: " f"{self.path}: {fields} != zip_lists: {set(value)}" ) + raise ValueError(msg) def __repr__(self) -> str: return self.pformat() @@ -538,7 +550,7 @@ def expand( allow_missing: bool | str | Iterable[str] = False, **wildcards: str | Iterable[str], ) -> list[str]: - """Safely expand over given paths with component wildcards + """Safely expand over given paths with component wildcards. Uses the entity-value combinations found in the dataset to expand over the given paths. If no path is provided, expands over the component @@ -553,7 +565,7 @@ def expand( Uses the snakemake :ref:`expand ` under the hood. Parameters - ========== + ---------- paths: Path or list of paths to expand over. If not provided, the component's own :attr:`~BidsComponent.path` will be expanded over. @@ -571,7 +583,7 @@ def expand( @property def input_name(self) -> str: - """Alias of :attr:`name ` + """Alias of :attr:`name `. Name of the component """ @@ -579,7 +591,7 @@ def input_name(self) -> str: @property def input_path(self) -> str: - """Alias of :attr:`path ` + """Alias of :attr:`path `. Wildcard-filled path that matches the files for this component. """ @@ -624,25 +636,35 @@ def __getitem__(self, key: str) -> BidsComponent: "sessions", "subj_wildcards", }: - raise KeyError( + msg = ( "As of v0.8, generate_inputs() no longer returns a dict by " f"default, but an instance of BidsDataset. As such, '{key}' can no " "longer be accessed via brackets '[]' as before. The original dict " "can be returned by setting `use_bids_inputs` to False in the call " "to generate_inputs(). However, we encourage you to transition to " "the use of `BidsDataset` for long term support" - ) from err - raise err + ) + raise KeyError(msg) from err + raise def __setitem__(self, _: Any, __: Any) -> NoReturn: - raise NotImplementedError( - f"Modification of {self.__class__.__name__} is not yet supported" - ) + msg = f"Modification of {self.__class__.__name__} is not yet supported" + raise NotImplementedError(msg) def __repr__(self) -> str: return self.pformat() def pformat(self, max_width: int | float | None = None, tabstop: int = 4) -> str: + """Pretty-format dataset. + + Parameters + ---------- + max_width + Maximum width of characters for output. If possible, zip_list table will be + elided to fit within this width + tabstop + Number of spaces for output indentation + """ width = max_width or get_console_size()[0] or inf body = [ f"{quote_wrap(name)}: {comp.pformat(width - tabstop, tabstop)}," @@ -666,9 +688,7 @@ def pformat(self, max_width: int | float | None = None, tabstop: int = 4) -> str admonition="warning", ) def path(self) -> dict[str, str]: - """Dict mapping :class:`BidsComponents ` names to \ - their ``paths``. - """ + """Dict mapping :class:`BidsComponent` names to their ``paths``.""" return {key: value.path for key, value in self.items()} @ft.cached_property @@ -683,9 +703,7 @@ def path(self) -> dict[str, str]: admonition="warning", ) def zip_lists(self) -> dict[str, ZipList]: - """Dict mapping :class:`BidsComponents ` names to \ - their ``zip_lists`` - """ + """Dict mapping :class:`BidsComponent` names to their ``zip_lists``.""" return {key: value.zip_lists for key, value in self.items()} @ft.cached_property @@ -700,9 +718,7 @@ def zip_lists(self) -> dict[str, ZipList]: admonition="warning", ) def entities(self) -> dict[str, MultiSelectDict[str, list[str]]]: - """Dict mapping :class:`BidsComponents ` names to \ - to their :attr:`entities ` - """ + """Dict mapping :class:`BidsComponent` names to their :attr:`~BidsComponent.entities`.""" # noqa: E501 return {key: value.entities for key, value in self.items()} @ft.cached_property @@ -717,9 +733,7 @@ def entities(self) -> dict[str, MultiSelectDict[str, list[str]]]: admonition="warning", ) def wildcards(self) -> dict[str, MultiSelectDict[str, str]]: - """Dict mapping :class:`BidsComponents ` names to \ - their :attr:`wildcards ` - """ + """Dict mapping :class:`BidsComponent` names to their :attr:`~BidsComponent.wildcards`.""" # noqa: E501 return {key: value.input_wildcards for key, value in self.items()} @ft.cached_property @@ -776,7 +790,7 @@ def input_wildcards(self) -> dict[str, MultiSelectDict[str, str]]: @property def as_dict(self) -> BidsDatasetDict: - """Get the layout as a legacy dict + """Get the layout as a legacy dict. Included primarily for backward compatability with older versions of snakebids, where generate_inputs() returned a dict rather than the `BidsDataset` class @@ -805,7 +819,7 @@ def as_dict(self) -> BidsDatasetDict: def from_iterable( cls, iterable: Iterable[BidsComponent], layout: BIDSLayout | None = None ) -> BidsDataset: - """Construct Dataset from iterable of BidsComponents + """Construct Dataset from iterable of BidsComponents. Parameters ---------- diff --git a/snakebids/core/filtering.py b/snakebids/core/filtering.py index 87a6b570..b8517bfc 100644 --- a/snakebids/core/filtering.py +++ b/snakebids/core/filtering.py @@ -37,9 +37,7 @@ def filter_list( return_indices_only: bool = False, regex_search: bool = False, ) -> ZipList | list[int]: - """This function is used when you are expanding over some subset of the - wildcards i.e. if your output file doesn't contain all the wildcards in - :attr:`BidsComponent.wildcards ` + """Filter zip_list, including only entries with provided entity values. Parameters ---------- @@ -160,9 +158,7 @@ def get_filtered_ziplist_index( wildcards: dict[str, str], subj_wildcards: dict[str, str], ) -> int | list[int]: - """Use this function when you have wildcards for a single scan instance, - and want to know the index of that scan, amongst that subject's scan - instances. + """Return the indices of all entries matching the filter query. Parameters ---------- @@ -176,7 +172,6 @@ def get_filtered_ziplist_index( Examples -------- - >>> import snakebids In this example, we have a dataset where with scans from two subjects, @@ -244,7 +239,7 @@ def get_filtered_ziplist_index( 3 """ # get the subject/(session) dict: - subj_dict = {key: wildcards[key] for key in subj_wildcards.keys()} + subj_dict = {key: wildcards[key] for key in subj_wildcards} # now filter the list based on subj_wildcards zip_list_filtered = filter_list(zip_list, subj_dict) @@ -257,7 +252,7 @@ def get_filtered_ziplist_index( def _get_zip_list_indices(zip_list: ZipListLike) -> Iterator[int]: - """Convert a zip_list into its indices + """Convert a zip_list into its indices. Generates a sequence of numbers from 0 up to the length of the zip_lists. For example, the zip list: @@ -277,11 +272,10 @@ def _get_zip_list_indices(zip_list: ZipListLike) -> Iterator[int]: Zip_list to be converted Yields - ------- + ------ integers The indices of the zip_list """ - if not zip_list: return diff --git a/snakebids/core/input_generation.py b/snakebids/core/input_generation.py index 49766d37..03ba996a 100644 --- a/snakebids/core/input_generation.py +++ b/snakebids/core/input_generation.py @@ -90,7 +90,7 @@ def generate_inputs( pybids_database_dir: Path | str | None = None, pybids_reset_database: bool | None = None, ) -> BidsDataset | BidsDatasetDict: - """Dynamically generate snakemake inputs using pybids_inputs + """Dynamically generate snakemake inputs using pybids_inputs. Pybids is used to parse the bids_dir. Custom paths can also be parsed by including the custom_paths entry under the pybids_inputs descriptor. @@ -255,7 +255,6 @@ def generate_inputs( ), }) """ - subject_filter, regex_search = _generate_filters( participant_label, exclude_participant_label ) @@ -299,9 +298,10 @@ def generate_inputs( try: dataset = BidsDataset.from_iterable(bids_inputs, layout) except DuplicateComponentError as err: - raise ConfigError( + msg = ( f"Multiple components found with the same name: {err.duplicated_names_str}" - ) from err + ) + raise ConfigError(msg) from err if use_bids_inputs: return dataset @@ -314,18 +314,20 @@ def _normalize_database_args( pybids_database_dir: Path | str | None, pybids_reset_database: str | bool | None, ) -> tuple[Path | str | None, bool]: - """This function exists to help handle deprecation for pybidsdb""" + """Handle deprecated arguments for pybidsdb.""" if pybids_database_dir is not None: warnings.warn( "The parameter `pybids_database_dir` in generate_inputs() is deprecated " "and will be removed in the next release. To set the pybids database, use " - "the `pybidsdb_dir` parameter instead." + "the `pybidsdb_dir` parameter instead.", + stacklevel=1, ) if pybids_reset_database is not None: warnings.warn( "The parameter `pybids_reset_database` in generate_inputs() is deprecated " "and will be removed in the next release. To reset the pybids database, " - "use the `pybidsdb_reset` parameter instead." + "use the `pybidsdb_reset` parameter instead.", + stacklevel=1, ) pybidsdb_dir = pybidsdb_dir or pybids_database_dir @@ -346,7 +348,8 @@ def _normalize_database_args( warnings.warn( "The config value `pybids_db_dir` is deprecated and will be removed in a " "future release. To access a CLI-specified pybids database directory, use " - '`config.get("pybidsdb_dir")` instead.' + '`config.get("pybidsdb_dir")` instead.', + stacklevel=1, ) pybidsdb_dir = str(pybidsdb_dir)[depr_len:-depr_len] try: @@ -359,19 +362,22 @@ def _normalize_database_args( warnings.warn( "The config value `pybids_db_reset` is deprecated and will be removed " "in a future release. To access CLI-specified pybids database reset " - 'instructions, use `config.get("pybidsdb_reset")` instead.' + 'instructions, use `config.get("pybidsdb_reset")` instead.', + stacklevel=1, ) - except ValueError: - raise TypeError("pybidsdb_reset must be a boolean") + except ValueError as err: + msg = "pybidsdb_reset must be a boolean" + raise TypeError(msg) from err if not isinstance(pybidsdb_reset, bool): - raise TypeError("pybidsdb_reset must be a boolean") + msg = "pybidsdb_reset must be a boolean" + raise TypeError(msg) return pybidsdb_dir, pybidsdb_reset def _all_custom_paths(config: InputsConfig): - """Check that all input components have a custom path""" + """Check that all input components have a custom path.""" return all(comp.get("custom_path") for comp in config.values()) @@ -384,10 +390,9 @@ def _gen_bids_layout( pybids_config: Path | str | None = None, validate: bool = False, ) -> BIDSLayout: - """Create (or reindex) the BIDSLayout if one doesn't exist, - which is only saved if a database directory path is provided + """Create (or reindex) the BIDSLayout. - Parameters + Parameters ---------- bids_dir Path to bids directory @@ -413,7 +418,6 @@ def _gen_bids_layout( layout : BIDSLayout Layout from pybids for accessing the BIDS dataset to grab paths """ - # Check for database_dir # If blank, assume db not to be used if not pybidsdb_dir: @@ -435,8 +439,9 @@ def _gen_bids_layout( def write_derivative_json(snakemake: Snakemake, **kwargs: dict[str, Any]) -> None: - """Snakemake function to read a json file, and write to a new one, - adding BIDS derivatives fields for Sources and Parameters. + """Update sidecar file with provided sources and parameters. + + Intended for usage in snakemake scripts. Parameters ---------- @@ -446,9 +451,7 @@ def write_derivative_json(snakemake: Snakemake, **kwargs: dict[str, Any]) -> Non This function requires input.json and output.json to be defined, as it will read and write json files """ - - with open(snakemake.input.json, encoding="utf-8") as input_json: - sidecar = json.load(input_json) + sidecar = json.loads(Path(snakemake.input.json).read_text()) sidecar.update( { @@ -466,7 +469,7 @@ def _generate_filters( include: Iterable[str] | str | None = None, exclude: Iterable[str] | str | None = None, ) -> tuple[list[str], bool]: - """Generate Pybids filter based on inclusion or exclusion criteria + """Generate Pybids filter based on inclusion or exclusion criteria. Converts either a list of values to include or exclude in a list of Pybids compatible filters. Unlike inclusion values, exclusion requires regex filtering. The @@ -494,10 +497,11 @@ def _generate_filters( ValueError Raised of both include and exclude values are stipulated. """ if include is not None and exclude is not None: - raise ValueError( + msg = ( "Cannot define both participant_label and " "exclude_participant_label at the same time" ) + raise ValueError(msg) # add participant_label or exclude_participant_label to search terms (if # defined) @@ -521,7 +525,7 @@ def _parse_custom_path( regex_search: bool = False, **filters: Sequence[str | bool] | str | bool, ) -> ZipList: - """Glob wildcards from a custom path and apply filters + """Glob wildcards from a custom path and apply filters. This replicates pybids path globbing for any custom path. Input path should have wildcards in braces as in "path/of/{wildcard_1}/{wildcard_2}_{wildcard_3}" Output @@ -557,9 +561,8 @@ def _parse_custom_path( if any( isinstance(v, bool) for f in filters.values() for v in itx.always_iterable(f) ): - raise TypeError( - "boolean filters are not currently supported in custom path filtering" - ) + msg = "boolean filters are not currently supported in custom path filtering" + raise TypeError(msg) return filter_list( wildcards, cast("Mapping[str, str | Sequence[str]]", filters), @@ -589,7 +592,7 @@ def _parse_bids_path(path: str, entities: Iterable[str]) -> tuple[str, dict[str, # If path is relative, we need to get a slash in front of it to ensure parsing works # correctly. So prepend "./" or ".\" and run function again, then strip before # returning - if not os.path.isabs(path) and not get_first_dir(path) == ".": + if not os.path.isabs(path) and get_first_dir(path) != ".": path_, wildcard_values = _parse_bids_path(os.path.join(".", path), entities) return str(Path(path_)), wildcard_values @@ -610,7 +613,7 @@ def _parse_bids_path(path: str, entities: Iterable[str]) -> tuple[str, dict[str, if len(wildcard_values) != len(entities): unmatched = ( set(map(BidsEntity, entities)) - .difference(set(match[0] for match in matches)) + .difference(match[0] for match in matches) .pop() ) raise BidsParseError(path=path, entity=unmatched) @@ -661,11 +664,12 @@ def get_matching_files( try: return bids_layout.get(regex_search=regex_search, **filters) except AttributeError as err: - raise PybidsError( + msg = ( "Pybids has encountered a problem that Snakebids cannot handle. This " "may indicate a missing or invalid dataset_description.json for this " "dataset." - ) from err + ) + raise PybidsError(msg) from err def _get_lists_from_bids( @@ -676,7 +680,7 @@ def _get_lists_from_bids( regex_search: bool = False, **filters: str | Sequence[str], ) -> Generator[BidsComponent, None, None]: - """Grabs files using pybids and creates snakemake-friendly lists + """Grabs files using pybids and creates snakemake-friendly lists. Parameters ---------- @@ -722,10 +726,11 @@ def _get_lists_from_bids( continue if bids_layout is None: - raise RunError( + msg = ( f"No valid bids dir given, but {input_name} does not have a " "custom_path specified." ) + raise RunError(msg) zip_lists: dict[str, list[str]] = defaultdict(list) paths: set[str] = set() @@ -745,7 +750,7 @@ def _get_lists_from_bids( try: path, parsed_wildcards = _parse_bids_path(img.path, wildcards) except BidsParseError as err: - raise ConfigError( + msg = ( "Parsing failed:\n" f" Entity: {err.entity.entity}\n" f" Pattern: {err.entity.regex}\n" @@ -761,7 +766,8 @@ def _get_lists_from_bids( f'\t"pattern":"{err.entity.entity}-()"\n}}\n' f"If {err.entity.entity} is an official pybids entity, please " "ensure you are using the latest version of snakebids" - ) from err + ) + raise ConfigError(msg) from err for wildcard_name, value in parsed_wildcards.items(): zip_lists[wildcard_name].append(value) @@ -791,23 +797,26 @@ def _get_lists_from_bids( continue try: path = itx.one(paths) - except ValueError: - raise ConfigError( + except ValueError as err: + msg = ( f"More than one snakemake filename for {input_name}, taking the " f"first. To correct this, use the --filter_{input_name} option to " f"narrow the search. Found filenames: {paths}" ) + raise ConfigError(msg) from err yield BidsComponent(name=input_name, path=path, zip_lists=zip_lists) def get_wildcard_constraints(image_types: InputsConfig) -> dict[str, str]: - """Return a wildcard_constraints dict for snakemake to use, containing - all the wildcards that are in the dynamically grabbed inputs + """Return a wildcard_constraints dict for use in snakemake. + + Contains constraints for all wildcards in the dynamically grabbed inputs. Parameters ---------- - image_types : dict + image_types + Component configuration dict Returns ------- @@ -818,6 +827,6 @@ def get_wildcard_constraints(image_types: InputsConfig) -> dict[str, str]: bids_constraints = "[a-zA-Z0-9]+" return { entity: bids_constraints - for imgtype in image_types.keys() - for entity in image_types[imgtype].keys() + for imgtype in image_types + for entity in image_types[imgtype] } diff --git a/snakebids/exceptions.py b/snakebids/exceptions.py index 1749fc8e..56dbc6df 100644 --- a/snakebids/exceptions.py +++ b/snakebids/exceptions.py @@ -47,4 +47,4 @@ def __init__(self, misspecified_filter: str): class SnakebidsPluginError(Exception): - """Exception raised when a Snakebids plugin encounters a problem""" + """Exception raised when a Snakebids plugin encounters a problem.""" diff --git a/snakebids/io/__init__.py b/snakebids/io/__init__.py new file mode 100644 index 00000000..5bb0a68c --- /dev/null +++ b/snakebids/io/__init__.py @@ -0,0 +1,5 @@ +__submodules__ = [] + +# +__all__ = [] +# diff --git a/snakebids/io/console.py b/snakebids/io/console.py index 35ce95ad..8dbdd51f 100644 --- a/snakebids/io/console.py +++ b/snakebids/io/console.py @@ -1,5 +1,4 @@ -""" -Internal module for console introspection +"""Internal module for console introspection. Module modified from [pandas](https://github.com/pandas-dev/pandas) `pandas.io.formats.console` under BSD 3-Clause License @@ -46,11 +45,10 @@ def get_console_size() -> tuple[int | None, int | None]: - """ - Return console size as tuple = (width, height). + """Return console size as tuple = (width, height). + Returns (None,None) in non-interactive session. """ - # Consider # interactive shell terminal, can detect term size # interactive non-shell terminal (ipnb/ipqtconsole), cannot detect term @@ -86,8 +84,8 @@ def get_console_size() -> tuple[int | None, int | None]: def in_interactive_session() -> bool: - """ - Check if we're running in an interactive shell. + """Check if we're running in an interactive shell. + Returns ------- bool @@ -109,8 +107,8 @@ def check_main() -> bool: def in_ipython_frontend() -> bool: - """ - Check if we're inside an IPython zmq frontend. + """Check if we're inside an IPython zmq frontend. + Returns ------- bool diff --git a/snakebids/io/printing.py b/snakebids/io/printing.py index ae9a59e2..6ae89d58 100644 --- a/snakebids/io/printing.py +++ b/snakebids/io/printing.py @@ -13,6 +13,7 @@ def quote_wrap(val: str) -> str: + """Wrap string in quotes, with additional character escapes for printing.""" return ( json.dumps(val, ensure_ascii=False) .replace("\x85", "\\x85") @@ -24,6 +25,18 @@ def quote_wrap(val: str) -> str: def format_zip_lists( zip_list: ZipList, max_width: int | float | None = None, tabstop: int = 4 ) -> str: + """Pretty-format zip-lists in a tablar format. + + Parameters + ---------- + zip_list + zip_list to format + max_width + Maximum character width. If possible, zip_list values will be elided to fit + within this width + tabstop + Number of spaces to include in each level of indentation + """ if not zip_list: return "{}" table = [_format_zip_row(key, row) for key, row in zip_list.items()] diff --git a/snakebids/jinja2_ext/__init__.py b/snakebids/jinja2_ext/__init__.py index e69de29b..ce90561e 100644 --- a/snakebids/jinja2_ext/__init__.py +++ b/snakebids/jinja2_ext/__init__.py @@ -0,0 +1,23 @@ +from snakebids.jinja2_ext.colorama import ( + ColoramaExtension, +) +from snakebids.jinja2_ext.snakebids_version import ( + SnakebidsVersionExtension, +) +from snakebids.jinja2_ext.toml_encode import ( + TomlEncodeExtension, + toml_string, +) +from snakebids.jinja2_ext.vcs import ( + GitConfigExtension, + executable, +) + +__all__ = [ + "ColoramaExtension", + "GitConfigExtension", + "SnakebidsVersionExtension", + "TomlEncodeExtension", + "executable", + "toml_string", +] diff --git a/snakebids/jinja2_ext/colorama.py b/snakebids/jinja2_ext/colorama.py index 927b40bf..58df6ab6 100644 --- a/snakebids/jinja2_ext/colorama.py +++ b/snakebids/jinja2_ext/colorama.py @@ -6,6 +6,8 @@ class ColoramaExtension(Extension): + """Include colorama foreground colors in the global environment.""" + def __init__(self, env: jinja2.Environment): super().__init__(env) env.globals["Fore"] = Fore # type: ignore diff --git a/snakebids/jinja2_ext/snakebids_version.py b/snakebids/jinja2_ext/snakebids_version.py index 04c9eb7d..358e1c22 100644 --- a/snakebids/jinja2_ext/snakebids_version.py +++ b/snakebids/jinja2_ext/snakebids_version.py @@ -9,21 +9,24 @@ class SnakebidsVersionExtension(Extension): + """Retrieve the latest snakebids vesion from pypi. + + Stores value in the ``snakebids_version`` global variable. + """ + def __init__(self, env: jinja2.Environment): env.globals["snakebids_version"] = self._lookup_version() # type: ignore super().__init__(env) def _lookup_version(self): - request = requests.get("https://pypi.org/pypi/snakebids/json") + request = requests.get("https://pypi.org/pypi/snakebids/json", timeout=10) version_regex = re.compile(r"\d+\.\d+\.\d+") try: request.raise_for_status() version = request.json()["info"]["version"] - if not re.fullmatch(version_regex, version): - raise TypeError() - return version - except (requests.HTTPError, KeyError, TypeError): + version = version_regex.fullmatch(version).group() # type: ignore + except (requests.HTTPError, KeyError, AttributeError, TypeError): version = impm.version("snakebids") - if version == "0.0.0" or not re.fullmatch(version_regex, version): + if version == "0.0.0" or not version_regex.fullmatch(version): return None - return version + return version diff --git a/snakebids/jinja2_ext/toml_encode.py b/snakebids/jinja2_ext/toml_encode.py index c76e475c..c811e09d 100644 --- a/snakebids/jinja2_ext/toml_encode.py +++ b/snakebids/jinja2_ext/toml_encode.py @@ -5,9 +5,16 @@ def toml_string(item: str): + """Encode string for inclusion in toml. + + Technically encodes as json, a (mostly) strict subset of toml, with some encoding + fixes + """ return json.dumps(item, ensure_ascii=False).replace("\x7F", "\\u007f") class TomlEncodeExtension(Extension): + """Enable the toml_string filter, which encodes provided value as toml.""" + def __init__(self, env: jinja2.Environment): env.filters["toml_string"] = toml_string # type: ignore diff --git a/snakebids/jinja2_ext/vcs.py b/snakebids/jinja2_ext/vcs.py index b7478ca6..5bc8d4ef 100644 --- a/snakebids/jinja2_ext/vcs.py +++ b/snakebids/jinja2_ext/vcs.py @@ -1,16 +1,24 @@ from __future__ import annotations import re -import subprocess +import subprocess as sp import sys from pathlib import Path import jinja2.parser from jinja2 import nodes from jinja2.ext import Extension +from typing_extensions import override class GitConfigExtension(Extension): + """Retrieve settings from git configuration. + + Shortcode activated with ``gitconfig`` plus the key name:: + + {% gitconfig "user.name" %} + """ + tags = {"gitconfig"} # noqa: RUF012 _config: dict[str, str] @@ -18,30 +26,33 @@ def __init__(self, env: jinja2.Environment) -> None: self._config = {} try: - config_list = subprocess.check_output( - [executable(), "config", "-l"], stderr=subprocess.STDOUT + config_list = sp.check_output( + [executable(), "config", "-l"], stderr=sp.STDOUT ).decode() m = re.findall("(?ms)^([^=]+)=(.*?)$", config_list) if m: for group in m: self._config[group[0]] = group[1] - except (subprocess.CalledProcessError, OSError): + except (sp.CalledProcessError, OSError): pass def get(self, key: str, default: str | None = None) -> str | None: + """Get specified configuration key.""" return self._config.get(key, default) def __getitem__(self, item: str) -> str: return self._config[item] + @override def parse(self, parser: jinja2.parser.Parser): lineno = next(parser.stream).lineno node = parser.parse_expression() if not isinstance(node, nodes.Const): - raise ValueError("Argument to `gitconfig` must be a string") + msg = "Argument to `gitconfig` must be a string" + raise TypeError(msg) call_method = self.call_method( "get", [node], @@ -51,14 +62,13 @@ def parse(self, parser: jinja2.parser.Parser): def executable() -> str: + """Retrieve os-dependent command for git.""" _executable = None if sys.platform == "win32": # Finding git via where.exe where = "%WINDIR%\\System32\\where.exe" - paths = subprocess.check_output( - [where, "git"], shell=True, encoding="oem" - ).split("\n") + paths = sp.check_output([where, "git"], encoding="oem").split("\n") for path in paths: if not path: continue @@ -74,6 +84,7 @@ def executable() -> str: _executable = "git" if _executable is None: # type: ignore - raise RuntimeError("Unable to find a valid git executable") + msg = "Unable to find a valid git executable" + raise RuntimeError(msg) return _executable diff --git a/snakebids/paths/__init__.py b/snakebids/paths/__init__.py index c94beb20..246ff1b0 100644 --- a/snakebids/paths/__init__.py +++ b/snakebids/paths/__init__.py @@ -1,8 +1,9 @@ __submodules__ = ["presets"] # -from snakebids.paths.presets import bids +from snakebids.paths.presets import ( + bids, +) __all__ = ["bids"] - # diff --git a/snakebids/paths/presets.py b/snakebids/paths/presets.py index d92cc657..19fc2e86 100644 --- a/snakebids/paths/presets.py +++ b/snakebids/paths/presets.py @@ -54,7 +54,7 @@ def bids( extension: str | None = None, **entities: str | bool, ) -> str: - """Helper function for generating bids paths for snakemake workflows. + """Generate bids paths based on provided entities according to standardized schema. File path is of the form:: @@ -92,7 +92,6 @@ def bids( Examples -------- - Below is a rule using bids naming for input and output:: rule proc_img: @@ -155,7 +154,6 @@ def bids( Notes ----- - * For maximum flexibility all arguments are optional (if none are specified, will return empty string). Note that datatype and prefix may not be used in isolation, but must be given with another entity. diff --git a/snakebids/paths/resources/__init__.py b/snakebids/paths/resources/__init__.py index e69de29b..a9a2c5b3 100644 --- a/snakebids/paths/resources/__init__.py +++ b/snakebids/paths/resources/__init__.py @@ -0,0 +1 @@ +__all__ = [] diff --git a/snakebids/paths/specs.py b/snakebids/paths/specs.py index de4697d4..32a14cc1 100644 --- a/snakebids/paths/specs.py +++ b/snakebids/paths/specs.py @@ -7,6 +7,8 @@ class BidsPathEntitySpec(TypedDict): + """Interface for BIDS path specification.""" + entity: str tag: NotRequired[str] dir: NotRequired[bool] @@ -20,7 +22,7 @@ def _find_entity(spec: BidsPathSpec, entity: str): def v0_0_0(subject_dir: bool = True, session_dir: bool = True) -> BidsPathSpec: - """Get the v0.0.0 BidsPathSpec + """Get the v0.0.0 BidsPathSpec. This is the legacy spec used since the beginning of snakebids. diff --git a/snakebids/plugins/validator.py b/snakebids/plugins/validator.py index 7dbe311e..142b810d 100644 --- a/snakebids/plugins/validator.py +++ b/snakebids/plugins/validator.py @@ -1,6 +1,6 @@ import json import logging -import subprocess +import subprocess as sp import tempfile import attr @@ -17,9 +17,10 @@ class InvalidBidsError(SnakebidsPluginError): @attr.define class BidsValidator: - """Snakebids plugin to perform validation of a BIDS dataset using the - bids-validator. If the dataset is not valid according to the BIDS - specifications, an InvalidBidsError is raised. + """Perform validation of a BIDS dataset using the bids-validator. + + If the dataset is not valid according to the BIDS specifications, an + InvalidBidsError is raised. Parameters ---------- @@ -55,7 +56,7 @@ def __call__(self, app: SnakeBidsApp) -> None: temp.write(json.dumps(validator_config_dict)) temp.flush() try: - subprocess.check_call( + sp.check_call( ["bids-validator", app.config["bids_dir"], "-c", temp.name] ) @@ -69,7 +70,7 @@ def __call__(self, app: SnakeBidsApp) -> None: "validation." ) # Any other bids-validator error - except subprocess.CalledProcessError as err: + except sp.CalledProcessError as err: app.config["plugins.validator.success"] = False if self.raise_invalid_bids: raise InvalidBidsError from err diff --git a/snakebids/tests/__init__.py b/snakebids/tests/__init__.py index 15353d16..2dbb986f 100644 --- a/snakebids/tests/__init__.py +++ b/snakebids/tests/__init__.py @@ -2,5 +2,4 @@ __submodules__ = [] # __all__ = [] - # diff --git a/snakebids/tests/helpers.py b/snakebids/tests/helpers.py index 0260c2f2..2e57f8f2 100644 --- a/snakebids/tests/helpers.py +++ b/snakebids/tests/helpers.py @@ -55,7 +55,7 @@ def get_zip_list( """ def strlist() -> list[str]: - return list() + return [] lists: Iterable[Sequence[str]] = list(zip(*combinations)) or itx.repeatfunc(strlist) return MultiSelectDict( @@ -103,9 +103,7 @@ def get_tag(entity: BidsEntity) -> tuple[str, str]: return bids( **dict(get_tag(BidsEntity(entity)) for entity in sorted(entities)), - **dict( - (BidsEntity(entity).wildcard, value) for entity, value in extras.items() - ), + **{BidsEntity(entity).wildcard: value for entity, value in extras.items()}, ) @@ -143,9 +141,10 @@ def debug(**overrides: Any): def inner(func: Callable[_P, _T]) -> Callable[_P, _T]: if not hasattr(func, "hypothesis"): - raise TypeError(f"{func} is not decorated with hypothesis.given") + msg = f"{func} is not decorated with hypothesis.given" + raise TypeError(msg) - test = getattr(func, "hypothesis").inner_test + test = func.hypothesis.inner_test # type: ignore @pytest.mark.disable_fakefs(True) @ft.wraps(func) @@ -224,7 +223,7 @@ def reindex_dataset( return generate_inputs(root, config) -def allow_function_scoped(callable: _T, /) -> _T: +def allow_function_scoped(func: _T, /) -> _T: """Allow function_scoped fixtures in hypothesis tests This is primarily useful for using tmpdirs, hence, the name @@ -233,7 +232,7 @@ def allow_function_scoped(callable: _T, /) -> _T: suppress_health_check=[ HealthCheck.function_scoped_fixture, ], - )(callable) + )(func) def deadline(time: int | float | timedelta | None) -> Callable[[_T], _T]: @@ -242,8 +241,8 @@ def deadline(time: int | float | timedelta | None) -> Callable[[_T], _T]: Numbers refer to time in milliseconds. Set to None to disable entirely """ - def inner(callable: _T, /) -> _T: - return settings(deadline=time)(callable) + def inner(func: _T, /) -> _T: + return settings(deadline=time)(func) return inner @@ -271,7 +270,7 @@ def expand_zip_list( def needs_docker(container: str): def decorator(func: Callable[_P, _T]) -> Callable[_P, _T]: - @pytest.mark.docker + @pytest.mark.docker() @ft.wraps(func) def wrapper(*args: _P.args, **kwargs: _P.kwargs): try: diff --git a/snakebids/tests/strategies.py b/snakebids/tests/strategies.py index 3700204e..3c6959e9 100644 --- a/snakebids/tests/strategies.py +++ b/snakebids/tests/strategies.py @@ -5,7 +5,7 @@ from os import PathLike from pathlib import Path from string import ascii_letters, digits -from typing import Any, Container, Hashable, Iterable, Sequence, TypeVar +from typing import TYPE_CHECKING, Any, Container, Hashable, Iterable, Sequence, TypeVar import hypothesis.strategies as st from bids.layout import Config as BidsConfig @@ -25,7 +25,7 @@ _T = TypeVar("_T") alphanum = ascii_letters + digits -valid_entities: tuple[str] = tuple(BidsConfig.load("bids").entities.keys()) +valid_entities: tuple[str, ...] = tuple(BidsConfig.load("bids").entities.keys()) def nothing() -> Any: @@ -72,8 +72,10 @@ def _filter_invalid_entity_lists(entities: Sequence[BidsEntity | str]): """ return all( [ - # If suffix is in the path, extension must be too - ("suffix" not in entities or "extension" in entities), + ( + # If suffix is in the path, extension must be too + "suffix" not in entities or "extension" in entities + ), # Cannot have paths with just datatype, just extension, or just datatype and # extension set(map(str, entities)) @@ -537,32 +539,41 @@ def multiselect_dicts( ) -def everything() -> st.SearchStrategy[Any]: - return st.from_type(type).flatmap(st.from_type) +if TYPE_CHECKING: + def everything() -> st.SearchStrategy[Any]: + ... -def everything_except(*excluded_types: type[Any]) -> st.SearchStrategy[Any]: - return ( - st.from_type(type) - .flatmap(st.from_type) - .filter(lambda s: not isinstance(s, excluded_types)) - ) + def everything_except(*excluded_types: type[Any]) -> st.SearchStrategy[Any]: + ... + +else: + + def everything() -> st.SearchStrategy[Any]: + return st.from_type(type).flatmap(st.from_type) + + def everything_except(*excluded_types: type[Any]) -> st.SearchStrategy[Any]: + return ( + st.from_type(type) + .flatmap(st.from_type) + .filter(lambda s: not isinstance(s, excluded_types)) + ) def _is_hashable(item: Any, /): try: hash(item) - return True except TypeError: return False + return True def _supports_eq(item: Any, /): try: - item == 0 # type: ignore - return True - except Exception: + item == 0 # type: ignore # noqa: B015 + except Exception: # noqa: BLE001 return False + return True def hashables() -> st.SearchStrategy[Hashable]: diff --git a/snakebids/tests/test_app.py b/snakebids/tests/test_app.py index 4402ee01..61698716 100644 --- a/snakebids/tests/test_app.py +++ b/snakebids/tests/test_app.py @@ -3,7 +3,7 @@ import copy import json import sys -from importlib import metadata +from importlib import metadata as impm from pathlib import Path from typing import Any, cast @@ -171,19 +171,21 @@ def test_runs_in_correct_mode( ) # Prepare expected config + # dummy db_path: all io functions are mocked, so this can be arbitrary str + db_path = "/path/to/db" expected_config = copy.deepcopy(app.config) expected_config.update( { "root": "", "snakemake_dir": Path("app").resolve(), - "pybidsdb_dir": Path("/tmp/output/.db"), + "pybidsdb_dir": Path(db_path), "pybidsdb_reset": True, - "pybids_db_dir": f"{DEPRECATION_FLAG}/tmp/output/.db{DEPRECATION_FLAG}", + "pybids_db_dir": f"{DEPRECATION_FLAG}{db_path}{DEPRECATION_FLAG}", "pybids_db_reset": f"{DEPRECATION_FLAG}1{DEPRECATION_FLAG}", "snakefile": Path("Snakefile"), "output_dir": outputdir.resolve(), - "snakemake_version": metadata.version("snakemake"), - "snakebids_version": metadata.version("snakebids"), + "snakemake_version": impm.version("snakemake"), + "snakebids_version": impm.version("snakebids"), "app_version": "unknown", # not installing a snakebids app here } ) @@ -198,7 +200,7 @@ def test_runs_in_correct_mode( # This Dict is necessary for updating config, since "update_config" is # patched args_dict={"output_dir": outputdir.resolve()}, - pybidsdb_dir=Path("/tmp/output/.db"), + pybidsdb_dir=Path(db_path), pybidsdb_reset=True, ) diff --git a/snakebids/tests/test_cli.py b/snakebids/tests/test_cli.py index ff657353..c0180324 100644 --- a/snakebids/tests/test_cli.py +++ b/snakebids/tests/test_cli.py @@ -198,7 +198,7 @@ def test_converts_type_path_into_pathlike( ): mocker.patch.object(sys, "argv", self.mock_all_args) args = parser.parse_args() - assert isinstance(getattr(args, "derivatives")[0], PathLike) + assert isinstance(args.derivatives[0], PathLike) def test_fails_if_undefined_type_given(self): parse_args_copy = copy.deepcopy(parse_args) diff --git a/snakebids/tests/test_datasets.py b/snakebids/tests/test_datasets.py index 3ea115f4..ec474c9e 100644 --- a/snakebids/tests/test_datasets.py +++ b/snakebids/tests/test_datasets.py @@ -60,11 +60,10 @@ class TestBidsComponentValidation: @given(sb_st.zip_lists().filter(lambda v: len(v) > 1)) def test_zip_lists_must_be_same_length(self, zip_lists: ZipList): itx.first(zip_lists.values()).append("foo") - with pytest.raises(ValueError) as err: + with pytest.raises(ValueError, match="zip_lists must all be of equal length"): BidsComponent( name="foo", path=get_bids_path(zip_lists), zip_lists=zip_lists ) - assert err.value.args[0] == "zip_lists must all be of equal length" @given(sb_st.zip_lists(), sb_st.bids_entity()) def test_path_cannot_have_extra_entities( @@ -72,12 +71,10 @@ def test_path_cannot_have_extra_entities( ): assume(entity.wildcard not in zip_lists) path = get_bids_path(it.chain(zip_lists, [entity.entity])) - with pytest.raises(ValueError) as err: + with pytest.raises( + ValueError, match="zip_lists entries must match the wildcards in input_path" + ): BidsComponent(name="foo", path=path, zip_lists=zip_lists) - assert ( - "zip_lists entries must match the wildcards in input_path" - in err.value.args[0] - ) @given(sb_st.zip_lists().filter(lambda v: len(v) > 1)) def test_path_cannot_have_missing_entities(self, zip_lists: ZipList): @@ -87,20 +84,18 @@ def test_path_cannot_have_missing_entities(self, zip_lists: ZipList): assume(set(path_entities) - {"datatype"}) path = get_bids_path(path_entities) - with pytest.raises(ValueError) as err: + with pytest.raises( + ValueError, match="zip_lists entries must match the wildcards in input_path" + ): BidsComponent(name="foo", path=path, zip_lists=zip_lists) - assert ( - "zip_lists entries must match the wildcards in input_path" - in err.value.args[0] - ) class TestBidsComponentEq: @given(sb_st.bids_components(), sb_st.everything_except(BidsComponent)) - def test_other_types_are_unequal(self, input: BidsComponent, other: Any): - assert input != other + def test_other_types_are_unequal(self, comp: BidsComponent, other: Any): + assert comp != other - def test_empty_BidsInput_are_equal(self): + def test_empty_bidsinput_are_equal(self): assert BidsComponent(name="", path="", zip_lists={}) == BidsComponent( name="", path="", zip_lists={} ) @@ -115,43 +110,43 @@ def test_empty_BidsInput_are_equal(self): ) @given(sb_st.bids_components()) - def test_copies_are_equal(self, input: BidsComponent): - cp = copy.deepcopy(input) - assert cp == input + def test_copies_are_equal(self, comp: BidsComponent): + cp = copy.deepcopy(comp) + assert cp == comp @given(sb_st.bids_components()) - def test_mutation_makes_unequal(self, input: BidsComponent): - cp = copy.deepcopy(input) + def test_mutation_makes_unequal(self, comp: BidsComponent): + cp = copy.deepcopy(comp) itx.first(cp.zip_lists.values())[0] += "foo" - assert cp != input + assert cp != comp @given(sb_st.bids_components(), st.data()) def test_extra_entities_makes_unequal( - self, input: BidsComponent, data: st.DataObject + self, comp: BidsComponent, data: st.DataObject ): - cp = copy.deepcopy(input) + cp = copy.deepcopy(comp) new_entity = data.draw( - sb_st.bids_value().filter(lambda s: s not in input.zip_lists) + sb_st.bids_value().filter(lambda s: s not in comp.zip_lists) ) cp.zip_lists[new_entity] = [] itx.first(cp.zip_lists.values())[0] += "foo" - assert cp != input + assert cp != comp @given(sb_st.bids_components()) - def test_order_doesnt_affect_equality(self, input: BidsComponent): - cp = copy.deepcopy(input) + def test_order_doesnt_affect_equality(self, comp: BidsComponent): + cp = copy.deepcopy(comp) for list_ in cp.zip_lists: cp.zip_lists[list_].reverse() - assert cp == input + assert cp == comp @given(sb_st.bids_components()) - def test_paths_must_be_identical(self, input: BidsComponent): + def test_paths_must_be_identical(self, comp: BidsComponent): cp = BidsComponent( - name=input.input_name, - path=input.input_path + "foo", - zip_lists=input.zip_lists, + name=comp.input_name, + path=comp.input_path + "foo", + zip_lists=comp.zip_lists, ) - assert cp != input + assert cp != comp class TestBidsComponentProperties: @@ -246,10 +241,7 @@ def _get_novel_path(prefix: str, component: Expandable): # use the "comp-" prefix to give a constant part to the novel template, # otherwise the trivial template "{foo}" globs everything return Path( - *map( - lambda s: f"{prefix}-{s}", - get_wildcard_dict(component.zip_lists).values(), - ) + *(f"{prefix}-{s}" for s in get_wildcard_dict(component.zip_lists).values()) ) @@ -596,9 +588,8 @@ def test_providing_both_spec_and_filters_gives_error( self, component: BidsComponentRow, data: st.DataObject ): spec = self.get_filter_spec(data, component) - with pytest.raises(ValueError) as err: + with pytest.raises(ValueError, match="__spec and filters cannot"): component.filter(spec, foo="bar") - assert "__spec and filters cannot" in err.value.args[0] class TestBidsComponentIndexing: @@ -610,7 +601,7 @@ def get_selectors( unique: bool = False, ) -> tuple[str, ...]: if not dicts.zip_lists and not use_nonexistant_keys: - return tuple() + return () sampler = st.sampled_from(list(dicts.zip_lists)) val_strat = ( st.text().filter(lambda s: s not in dicts.zip_lists) @@ -620,14 +611,14 @@ def get_selectors( return tuple(data.draw(st.lists(val_strat, unique=unique))) @given(dicts=sb_st.bids_partial_components(), data=st.data()) - def test_multiple_selection_returns_BidsPartialComponent( + def test_multiple_selection_returns_bidspartialcomponent( self, dicts: BidsPartialComponent, data: st.DataObject ): selectors = self.get_selectors(data, dicts) assert type(dicts[selectors]) == BidsPartialComponent @given(dicts=sb_st.bids_partial_components(), data=st.data()) - def test_single_selection_returns_BidsComponentRow( + def test_single_selection_returns_bidscomponentrow( self, dicts: BidsPartialComponent, data: st.DataObject ): selectors = data.draw(st.sampled_from(list(dicts.zip_lists))) diff --git a/snakebids/tests/test_generate_inputs.py b/snakebids/tests/test_generate_inputs.py index 18620009..2008dd65 100644 --- a/snakebids/tests/test_generate_inputs.py +++ b/snakebids/tests/test_generate_inputs.py @@ -203,7 +203,7 @@ def test_ambiguous_paths_with_extra_entities_leads_to_error( shorter.input_name: { "wildcards": [ BidsEntity.from_tag(wildcard).entity - for wildcard in shorter.input_wildcards.keys() + for wildcard in shorter.input_wildcards ], } } @@ -229,7 +229,7 @@ def test_ambiguous_paths_with_missing_entity_leads_to_error( longer.input_name: { "wildcards": [ BidsEntity.from_tag(wildcard).entity - for wildcard in longer.input_wildcards.keys() + for wildcard in longer.input_wildcards ], } } @@ -256,7 +256,7 @@ def test_entity_excluded_when_filter_false( shorter.input_name: { "wildcards": [ BidsEntity.from_tag(wildcard).entity - for wildcard in shorter.input_wildcards.keys() + for wildcard in shorter.input_wildcards ], "filters": {BidsEntity.from_tag(extra_entity).entity: False}, } @@ -334,7 +334,7 @@ def test_entity_excluded_when_filter_true(self, tmpdir: Path, dataset: BidsDatas longer.input_name: { "wildcards": [ BidsEntity.from_tag(wildcard).entity - for wildcard in longer.input_wildcards.keys() + for wildcard in longer.input_wildcards ], "filters": {BidsEntity.from_tag(extra_entity).entity: True}, } @@ -405,7 +405,7 @@ def add_entity(component: BidsComponent, entity: str, value: str): "target": { "wildcards": [ BidsEntity.from_tag(wildcard).entity - for wildcard in template.wildcards.keys() + for wildcard in template.wildcards ], "filters": {entity.entity: [target, False]}, } @@ -543,7 +543,11 @@ class TestGenerateFilter: def test_throws_error_if_labels_and_excludes_are_given( self, args: tuple[list[str] | str, list[str] | str] ): - with pytest.raises(ValueError): + with pytest.raises( + ValueError, + match="Cannot define both participant_label and " + "exclude_participant_label at the same time", + ): _generate_filters(*args) @given(st_lists_or_text) @@ -663,7 +667,7 @@ def get_subset(of: Iterable[T]) -> list[T]: class TestCustomPaths: - @pytest.fixture() + @pytest.fixture def temp_dir(self, fakefs_tmpdir: Path, bids_fs: Path): return fakefs_tmpdir @@ -868,7 +872,11 @@ def test_t1w(): } # Can't define particpant_label and exclude_participant_label - with pytest.raises(ValueError) as v_error: + with pytest.raises( + ValueError, + match="Cannot define both participant_label and " + "exclude_participant_label at the same time", + ): result = generate_inputs( pybids_inputs=pybids_inputs, bids_dir=real_bids_dir, @@ -876,10 +884,6 @@ def test_t1w(): participant_label="001", exclude_participant_label="002", ) - assert v_error.value.args[0] == ( - "Cannot define both participant_label and " - "exclude_participant_label at the same time" - ) # Simplest case -- one input type, using pybids result = generate_inputs( @@ -1195,7 +1199,7 @@ def test_gen_layout_returns_valid_dataset(self, dataset: BidsDataset, tmpdir: Pa ) def test_invalid_path_raises_error(self, tmpdir: Path): - with pytest.raises(ValueError): + with pytest.raises(ValueError, match="BIDS root does not exist"): _gen_bids_layout( bids_dir=tmpdir / "foo", derivatives=False, @@ -1299,7 +1303,7 @@ def test_one_match_found_for_each_entity(self, component: BidsComponent): path = component.expand()[0] entities = [BidsEntity.normalize(e).entity for e in component.zip_lists] _, matches = _parse_bids_path(path, entities) - assert {(key, val) for key, val in matches.items()} == { + assert set(matches.items()) == { (key, val[0]) for key, val in component.zip_lists.items() } @@ -1322,7 +1326,7 @@ def test_missing_match_leads_to_error( class TestDB: @pytest.fixture(autouse=True) - def start(self, tmp_path: Path): + def _start(self, tmp_path: Path): self.tmpdir = str(tmp_path) # Copy over test data diff --git a/snakebids/tests/test_paths/__init__.py b/snakebids/tests/test_paths/__init__.py index 15353d16..2dbb986f 100644 --- a/snakebids/tests/test_paths/__init__.py +++ b/snakebids/tests/test_paths/__init__.py @@ -2,5 +2,4 @@ __submodules__ = [] # __all__ = [] - # diff --git a/snakebids/tests/test_paths/test_bids.py b/snakebids/tests/test_paths/test_bids.py index a1eccf5c..c19168c4 100644 --- a/snakebids/tests/test_paths/test_bids.py +++ b/snakebids/tests/test_paths/test_bids.py @@ -185,7 +185,9 @@ def test_full_entity_names_not_in_path(entities: dict[str, str]): def test_long_and_short_names_cannot_be_used_simultaneously(entities: dict[str, str]): entities = {BidsEntity.normalize(e).entity: v for e, v in entities.items()} tags = {BidsEntity.normalize(e).tag: v for e, v in entities.items()} - with pytest.raises(ValueError) as err: + with pytest.raises( + ValueError, match="Long and short names of an entity cannot be used in the same" + ) as err: bids(**entities, **tags) assert itx.first(entities) in err.value.args[0] assert itx.first(tags) in err.value.args[0] @@ -278,7 +280,9 @@ def test_bids_with_no_args_gives_empty_path(): args=st.dictionaries(st.sampled_from(["datatype", "prefix"]), _values(), min_size=1) ) def test_missing_essential_entities_gives_error(args: dict[str, str]): - with pytest.raises(ValueError): + with pytest.raises( + ValueError, match="At least one of suffix, extension, or an entity must be" + ): bids(**args) diff --git a/snakebids/tests/test_printing.py b/snakebids/tests/test_printing.py index 8c075147..a068984c 100644 --- a/snakebids/tests/test_printing.py +++ b/snakebids/tests/test_printing.py @@ -149,7 +149,7 @@ def test_line_never_longer_than_max_width(zip_list: ZipList, width: int, tab: in formatted = format_zip_lists(zip_list, width, tab) parsed = zip_list_parser().parse_string(formatted) assume("left" in parsed[0]) - assert all(list(len(line) <= width for line in formatted.splitlines())) + assert all(len(line) <= width for line in formatted.splitlines()) def get_indent_length(line: str): diff --git a/snakebids/tests/test_template.py b/snakebids/tests/test_template.py index d7fcc4f4..534e0918 100644 --- a/snakebids/tests/test_template.py +++ b/snakebids/tests/test_template.py @@ -106,7 +106,7 @@ def test_invalid_email_raises_error(email: str, tmp_path: Path): @pytest.mark.parametrize("build", BUILD_BACKENDS) @pytest.mark.parametrize( - ["server", "server_status", "metadata", "expected"], + ("server", "server_status", "metadata", "expected"), [ # Server returns valid version ("1.1.1", 200, "2.2.2", "1.1.1"), @@ -138,8 +138,9 @@ def test_gets_correct_snakebids_version( json={"info": {"version": server}}, status_code=server_status, ) - metadata_patch = mocker.patch("importlib.metadata.version") - metadata_patch.return_value = metadata + from snakebids.jinja2_ext.snakebids_version import impm + + mocker.patch.object(impm, "version", return_value=metadata) data = get_empty_data("testapp", build) copier.run_copy( str(Path(itx.first(snakebids.__path__), "project_template")), @@ -175,7 +176,7 @@ def test_invalid_app_name_raises_error(name: str, tmp_path: Path): @pytest.mark.parametrize( - ["build", "build_backend"], + ("build", "build_backend"), [ ("poetry", "poetry.core.masonry.api"), ("hatch", "hatchling.build"), @@ -264,7 +265,7 @@ def test_pyproject_correctly_formatted( @needs_docker(f"snakebids/test-template:{platform.python_version()}") @pytest.mark.parametrize( - ["build", "venv"], + ("build", "venv"), [ ("setuptools", "setuptools"), ("poetry", "poetry"), @@ -298,8 +299,8 @@ def test_template_dry_runs_successfully(tmp_path: Path, build: BuildBackend, ven ) try: cmd.check_returncode() - except Exception as err: + except Exception: print(cmd.stdout) print(cmd.stderr, file=sys.stderr) - raise err + raise assert "All set" in cmd.stdout.decode() diff --git a/snakebids/tests/test_utils.py b/snakebids/tests/test_utils.py index f5574716..e2793e1d 100644 --- a/snakebids/tests/test_utils.py +++ b/snakebids/tests/test_utils.py @@ -21,6 +21,23 @@ ) +@st.composite +def non_matching_match_list(draw: st.DrawFn) -> tuple[str, list[str]]: + item = draw(st.text()) + match_list = draw( + st.lists( + st.text( + # blank match strings (e.g. ['']) will match anything, and are not + # considered here + min_size=1 + ) + .map(re.escape) + .filter(lambda s: not re.match(s, item)) + ) + ) + return item, match_list + + class TestMatchesAny: @given(st.text(), st.lists(st.text())) def test_with_eq_operator(self, item: str, match_list: list[str]): @@ -29,23 +46,7 @@ def test_with_eq_operator(self, item: str, match_list: list[str]): match_list.append(item) assert matches_any(item, match_list, op.eq) - @st.composite - def NonMatchingMatchList(draw: st.DrawFn) -> tuple[str, list[str]]: - item = draw(st.text()) - match_list = draw( - st.lists( - st.text( - # blank match strings (e.g. ['']) will match anything, and are not - # considered here - min_size=1 - ) - .map(re.escape) - .filter(lambda s: not re.match(s, item)) - ) - ) - return item, match_list - - @given(NonMatchingMatchList()) + @given(non_matching_match_list()) def test_with_re_match(self, args: tuple[str, list[str]]): item, match_list = args @@ -79,7 +80,7 @@ def get_selectors( unique: bool = False, ) -> tuple[str, ...]: if not dicts and not use_nonexistant_keys: - return tuple() + return () sampler = st.sampled_from(list(dicts)) val_strat = ( st.text().filter(lambda s: s not in dicts) @@ -331,7 +332,7 @@ def test_get_wildcard_dict(zip_list: dict[str, str]): class TestRegexContainer: DDWW = r"^\d{3}[a-zA-Z]{3}$" - bDDWW = rb"^\d{3}[a-zA-Z]{3}$" + bDDWW = rb"^\d{3}[a-zA-Z]{3}$" # noqa: N815 WWDD = r"^[a-zA-Z]{3}\d{3}$" @given(sample=st.from_regex(DDWW)) diff --git a/snakebids/tests/test_validate_plugin.py b/snakebids/tests/test_validate_plugin.py index b469f1b2..cc539747 100644 --- a/snakebids/tests/test_validate_plugin.py +++ b/snakebids/tests/test_validate_plugin.py @@ -1,7 +1,7 @@ from __future__ import annotations import copy -import subprocess +import subprocess as sp from pathlib import Path import pytest @@ -64,9 +64,7 @@ def test_missing_bids_validator( def test_raise_validation_error(self, app: SnakeBidsApp, mocker: MockerFixture): # Test for any other bids-validation error - mocker.patch( - "subprocess.check_call", side_effect=subprocess.CalledProcessError(1, "") - ) + mocker.patch("subprocess.check_call", side_effect=sp.CalledProcessError(1, "")) app.config["bids_dir"] = "path/to/bids/dir" app.config["plugins.validator.skip"] = False @@ -81,9 +79,7 @@ def test_raise_validation_error(self, app: SnakeBidsApp, mocker: MockerFixture): def test_ignore_validation_error(self, app: SnakeBidsApp, mocker: MockerFixture): # Test for any other bids-validation error - mocker.patch( - "subprocess.check_call", side_effect=subprocess.CalledProcessError(1, "") - ) + mocker.patch("subprocess.check_call", side_effect=sp.CalledProcessError(1, "")) app.config["bids_dir"] = "path/to/bids/dir" app.config["plugins.validator.skip"] = False diff --git a/snakebids/types.py b/snakebids/types.py index 6726da63..72a0a7be 100644 --- a/snakebids/types.py +++ b/snakebids/types.py @@ -21,7 +21,7 @@ class InputConfig(TypedDict, total=False): - """Configuration for a single bids component""" + """Configuration for a single bids component.""" filters: dict[str, str | bool | list[str | bool]] """Filters to pass on to :class:`BIDSLayout.get() ` @@ -58,21 +58,23 @@ class InputConfig(TypedDict, total=False): class BinaryOperator(Protocol, Generic[_T_contra, _S_co]): - def __call__(self, first: _T_contra, second: _T_contra, /) -> _S_co: + """Callables that act on two objects of identical type.""" + + def __call__(self, first: _T_contra, second: _T_contra, /) -> _S_co: # noqa: D102 ... class Expandable(Protocol): - """Protocol represents objects that hold an entity table and can expand over a path + """Protocol represents objects that hold an entity table and can expand over a path. Includes BidsComponent, BidsPartialComponent, and BidsComponentRow """ @property - def zip_lists(self) -> ZipList: + def zip_lists(self) -> ZipList: # noqa: D102 ... - def expand( + def expand( # noqa: D102 self, paths: Iterable[Path | str] | Path | str, /, @@ -81,7 +83,7 @@ def expand( ) -> list[str]: ... - def filter( + def filter( # noqa: D102 self, *, regex_search: bool | str | Iterable[str] = False, @@ -96,6 +98,8 @@ def filter( class MultiSelectable(Protocol, Generic[_K_contra, _V_co, _Valt_co]): + """Mappings supporting selection with multiple keys.""" + @overload def __getitem__(self, key: _K_contra, /) -> _V_co: ... @@ -112,12 +116,12 @@ def __getitem__(self, key: tuple[_K_contra, ...], /) -> _Valt_co: if TYPE_CHECKING: class UserDictPy38(Dict[_K, _V]): - pass + """Wrapper around dict, used for subclassing with static typing.""" else: class UserDictPy38(dict, Generic[_K, _V]): - pass + """Wrapper around dict, used for subclassing with static typing.""" # for py38, we need to import this AFTER we initialize UserDictPy38 to avoid a circular diff --git a/snakebids/utils/__init__.py b/snakebids/utils/__init__.py index ad1770bd..5bb0a68c 100644 --- a/snakebids/utils/__init__.py +++ b/snakebids/utils/__init__.py @@ -2,5 +2,4 @@ # __all__ = [] - # diff --git a/snakebids/utils/output.py b/snakebids/utils/output.py index 125be5bb..d27dd6a9 100644 --- a/snakebids/utils/output.py +++ b/snakebids/utils/output.py @@ -2,9 +2,7 @@ from __future__ import annotations -import hashlib import json -import time from collections import OrderedDict from pathlib import Path, PosixPath, WindowsPath from typing import Any, Literal @@ -18,7 +16,7 @@ def prepare_bidsapp_output(outputdir: Path, force_output: bool = False) -> None: - """Ensure output directory is in the correct mode and is ready for snakemake to run + """Ensure output directory is in the correct mode and is ready for snakemake to run. Checks for existing output at the directory, creating it if necessary. Creates a .snakebids file to track output mode and other workflow information. If outputdir @@ -49,9 +47,9 @@ def prepare_bidsapp_output(outputdir: Path, force_output: bool = False) -> None: # If it does exist but there's no .snakebids file, an error will be raised. try: _get_snakebids_file(outputdir) - except RunError as err: + except RunError: if not force_output: - raise err + raise outputdir.mkdir(exist_ok=True) @@ -59,7 +57,7 @@ def prepare_bidsapp_output(outputdir: Path, force_output: bool = False) -> None: def write_output_mode(dotfile: Path, mode: Mode) -> None: - """Write output mode to .snakebids + """Write output mode to .snakebids. Parameters ---------- @@ -128,30 +126,37 @@ def _get_snakebids_file(outputdir: Path) -> dict[str, str] | None: # We have an occupied directory without a .snakebids file, so we have no idea # what's there. - raise RunError( + msg = ( f"Output dir `{outputdir.resolve()}` exists, but it doesn't look like this " "app has been run there before. If you're sure you got the directory correct, " - "run the app again using `--force-output`", + "run the app again using `--force-output`" + ) + raise RunError( + msg, ) - - -def get_time_hash() -> str: - """currently unused""" - - time_hash = hashlib.sha1() - time_hash.update(str(time.time()).encode("utf-8")) - return time_hash.hexdigest()[:8] def write_config_file( config_file: Path, data: dict[str, Any], force_overwrite: bool = False ) -> None: + """Write provided data as yaml to provided path. + + Parameters + ---------- + config_file + Path of yaml file + data + Data to format + force_overwrite + If True, force overwrite of already existing files, otherwise error out + """ if (config_file.exists()) and not force_overwrite: - raise RunError( + msg = ( f"A config file named {config_file.name} already exists:\n" f"\t- {config_file.resolve()}\n" "Please move or rename either the existing or incoming config." ) + raise RunError(msg) config_file.parent.mkdir(exist_ok=True) # TODO: copy to a time-hashed file for provenance purposes? @@ -170,14 +175,16 @@ def write_config_file( yaml.add_representer( OrderedDict, lambda dumper, data: dumper.represent_mapping( # type: ignore - "tag:yaml.org,2002:map", data.items() # type: ignore + "tag:yaml.org,2002:map", + data.items(), # type: ignore ), ) # Represent any PathLikes as str. def path2str(dumper, data): # type: ignore return dumper.represent_scalar( # type: ignore - "tag:yaml.org,2002:str", str(data) # type: ignore + "tag:yaml.org,2002:str", + str(data), # type: ignore ) yaml.add_representer(PosixPath, path2str) # type: ignore diff --git a/snakebids/utils/sb_itertools.py b/snakebids/utils/sb_itertools.py index 57e086bf..0ec024ef 100644 --- a/snakebids/utils/sb_itertools.py +++ b/snakebids/utils/sb_itertools.py @@ -7,7 +7,7 @@ def unpack(iterable: Iterable[T], default: Sequence[T]) -> Iterable[T]: - """Return default if iterable has no elements + """Return default if iterable has no elements. Allows safe unpacking of possibly empty iterables @@ -29,7 +29,7 @@ def unpack(iterable: Iterable[T], default: Sequence[T]) -> Iterable[T]: def drop(n: int, iterable: Iterable[T]) -> Iterable[T]: - """Returns all items of an iterable except the first *n* as a list + """Return all items of an iterable except the first *n* as a list. >>> drop(7, range(10)) [7, 8, 9] diff --git a/snakebids/utils/snakemake_io.py b/snakebids/utils/snakemake_io.py index ca665b47..a5c2eae3 100644 --- a/snakebids/utils/snakemake_io.py +++ b/snakebids/utils/snakemake_io.py @@ -1,4 +1,4 @@ -"""File globbing functions based on snakemake.io library""" +"""File globbing functions based on snakemake.io library.""" from __future__ import annotations import collections @@ -22,10 +22,11 @@ def regex(filepattern: str) -> str: wildcard = match.group("name") if wildcard in wildcards: if match.group("constraint"): - raise ValueError( + msg = ( "Constraint regex must be defined only in the first " "occurence of the wildcard in a string." ) + raise ValueError(msg) regex_list.append(f"(?P={wildcard})") else: wildcards.add(wildcard) @@ -151,6 +152,4 @@ def replace_constraint(match: re.Match[str]): return match.group(0) examined_names: set[str] = set() - updated = _wildcard_regex.sub(replace_constraint, pattern) - - return updated + return _wildcard_regex.sub(replace_constraint, pattern) diff --git a/snakebids/utils/user_property.py b/snakebids/utils/user_property.py index 71bfc3da..e57949d9 100644 --- a/snakebids/utils/user_property.py +++ b/snakebids/utils/user_property.py @@ -1,2 +1,2 @@ class UserProperty(property): - """Simple wrapper around property for typing purposes""" + """Simple wrapper around property for typing purposes.""" diff --git a/snakebids/utils/utils.py b/snakebids/utils/utils.py index 8c6192e5..7f49e1a9 100644 --- a/snakebids/utils/utils.py +++ b/snakebids/utils/utils.py @@ -28,7 +28,7 @@ import attrs import importlib_resources as impr import more_itertools as itx -from typing_extensions import NotRequired, Self, TypeAlias, TypedDict +from typing_extensions import NotRequired, Self, TypeAlias, TypedDict, override from snakebids import resources, types from snakebids.utils.user_property import UserProperty @@ -37,6 +37,8 @@ class BidsTag(TypedDict): + """Interface for BidsTag configuration.""" + tag: str before: NotRequired[str] match: str @@ -72,14 +74,13 @@ def read_bids_tags(bids_json: Path | None = None) -> BidsTags: """ if bids_json: with bids_json.open("r") as infile: - bids_tags = json.load(infile) - return bids_tags + return json.load(infile) return json.loads(impr.files(resources).joinpath("bids_tags.json").read_text()) @attrs.frozen(hash=True) class BidsEntity: - """Bids entities with tag and wildcard representations""" + """Bids entities with tag and wildcard representations.""" entity: str = attrs.field(converter=str) @@ -100,7 +101,7 @@ def __lt__(self, other: BidsEntity | str): @property def tag(self) -> str: - """Get the bids tag version of the entity + """Get the bids tag version of the entity. For entities in the bids spec, the tag is the short version of the entity name. Otherwise, the tag is equal to the entity. @@ -114,7 +115,7 @@ def tag(self) -> str: @property def match(self) -> str: - """Get regex of acceptable value matches + """Get regex of acceptable value matches. If no pattern is associated with the entity, the default pattern is a word with letters and numbers @@ -128,7 +129,7 @@ def match(self) -> str: @property def before(self) -> str: - """regex str to search before value in paths""" + """Regex str to search before value in paths.""" tags = read_bids_tags() # Need to explicitly annotate the default here and in .after because tags is a # dict of `BidsTag`, a `TypedDict`. So putting unannotated dicts directly as @@ -138,7 +139,7 @@ def before(self) -> str: @property def after(self) -> str: - """regex str to search after value in paths""" + """Regex str to search after value in paths.""" tags = read_bids_tags() # See note in .before _def: dict[Any, Any] = {} @@ -146,7 +147,7 @@ def after(self) -> str: @property def regex(self) -> re.Pattern[str]: - """Complete pattern to match when searching in paths + """Complete pattern to match when searching in paths. Contains three capture groups, the first corresponding to "before", the second to "value", and the third to "after" @@ -155,7 +156,7 @@ def regex(self) -> re.Pattern[str]: @property def wildcard(self) -> str: - """Get the snakebids {wildcard} + """Get the snakebids {wildcard}. The wildcard is generally equal to the tag, i.e. the short version of the entity name, except for subject and session, which use the full name name. This is to @@ -172,7 +173,7 @@ def wildcard(self) -> str: @classmethod def from_tag(cls, tag: str) -> BidsEntity: - """Return the entity associated with the given tag, if found + """Return the entity associated with the given tag, if found. If not associated entity is found, the tag itself is used as the entity name @@ -192,7 +193,7 @@ def from_tag(cls, tag: str) -> BidsEntity: @classmethod def normalize(cls, item: str | BidsEntity, /) -> BidsEntity: - """Return the entity associated with the given item, if found + """Return the entity associated with the given item, if found. Supports both strings and BidsEntities as input. Unlike the constructor, if a tag name is given, the associated entity will be returned. If no associated @@ -200,12 +201,8 @@ def normalize(cls, item: str | BidsEntity, /) -> BidsEntity: Parameters ---------- - tag : str + item tag to search - - Returns - ------- - BidsEntity """ if isinstance(item, BidsEntity): return item @@ -218,14 +215,22 @@ def matches_any( match_func: types.BinaryOperator[_T, object], *args: Any, ) -> bool: - for match in match_list: - if match_func(match, item, *args): - return True - return False + """Test if item matches any of the items in match_list. + + Parameters + ---------- + item + Item to test + match_list + Items to compare with + match_func + Function to test equality. Defaults to basic equality (``==``) check + """ + return any(match_func(match, item, *args) for match in match_list) class BidsParseError(Exception): - """Exception raised for errors encountered in the parsing of Bids paths""" + """Exception raised for errors encountered in the parsing of Bids paths.""" def __init__(self, path: str, entity: BidsEntity) -> None: self.path = path @@ -234,7 +239,7 @@ def __init__(self, path: str, entity: BidsEntity) -> None: class _Documented(Protocol): - __doc__: str + __doc__: str # noqa: A003 def property_alias( @@ -243,7 +248,7 @@ def property_alias( ref: str | None = None, copy_extended_docstring: bool = False, ) -> Callable[[Callable[[Any], _T]], UserProperty[_T]]: - """Set property as an alias for another property + """Set property as an alias for another property. Copies the docstring from the aliased property to the alias @@ -280,7 +285,7 @@ def inner(func: Callable[[Any], _T], /) -> UserProperty[_T]: def surround(s: Iterable[str] | str, object_: str, /) -> Iterable[str]: - """Surround a string or each string in an iterable with characters""" + """Surround a string or each string in an iterable with characters.""" for item in itx.always_iterable(s): yield object_ + item + object_ @@ -290,7 +295,7 @@ def surround(s: Iterable[str] | str, object_: str, /) -> Iterable[str]: class MultiSelectDict(types.UserDictPy38[_K, _V]): - """Dict supporting selection of multiple keys using tuples + """Dict supporting selection of multiple keys using tuples. If a single key is given, the item associated with that key is returned just as in a regular dict. If multiple, comma-seperated keys are given, (e.g. a tuple), a new @@ -356,10 +361,10 @@ def __getitem__(self, key: _K, /) -> _V: ... @overload - def __getitem__(self, key: tuple[_K], /) -> Self: + def __getitem__(self, key: tuple[_K, ...], /) -> Self: ... - def __getitem__(self, key: _K | tuple[_K], /) -> _V | Self: + def __getitem__(self, key: _K | tuple[_K, ...], /) -> _V | Self: if isinstance(key, tuple): # Use dict.fromkeys for de-duplication return self.__class__({key: self[key] for key in dict.fromkeys(key)}) @@ -367,7 +372,7 @@ def __getitem__(self, key: _K | tuple[_K], /) -> _V | Self: def zip_list_eq(first: types.ZipListLike, second: types.ZipListLike, /): - """Compare two zip lists, allowing the order of columns to be irrelevant""" + """Compare two zip lists, allowing the order of columns to be irrelevant.""" def sorted_items(dictionary: Mapping[str, Sequence[str]]): return sorted(dictionary.items(), key=op.itemgetter(0)) @@ -388,7 +393,7 @@ def get_values(zlist: types.ZipListLike): def get_first_dir(path: str) -> str: - """Return the top level directory in a path + """Return the top level directory in a path. If absolute, return the root. This function is necessary to handle paths with ``./``, as ``pathlib.Path`` filters this out. @@ -402,6 +407,7 @@ def get_first_dir(path: str) -> str: def to_resolved_path(path: str | PathLike[str]): + """Convert provided object into resolved path.""" return Path(path).resolve() @@ -409,7 +415,7 @@ def to_resolved_path(path: str | PathLike[str]): class ImmutableList(Sequence[_T_co], Generic[_T_co]): - """Subclassable tuple equivalent + """Subclassable tuple equivalent. Mimics a tuple in every way, but readily supports subclassing. Data is stored on a private attribute ``_data``. Subclasses must not override this attribute. To avoid @@ -423,24 +429,30 @@ class ImmutableList(Sequence[_T_co], Generic[_T_co]): be specified as ``ImmutableList[str | int]`` """ - def __init__(self, iterable: Iterable[_T_co] = tuple(), /): + def __init__(self, iterable: Iterable[_T_co] = (), /): self._data = tuple(iterable) + @override def __repr__(self) -> str: return f"{self.__class__.__name__}({list(self._data)})" + @override def __contains__(self, item: object, /) -> bool: return item in self._data + @override def __hash__(self): return hash(self._data) + @override def __iter__(self) -> Iterator[_T_co]: return iter(self._data) + @override def __reversed__(self) -> Iterator[_T_co]: return reversed(self._data) + @override def __len__(self) -> int: return len(self._data) @@ -455,6 +467,7 @@ def __getitem__(self, index: int) -> _T_co: def __getitem__(self, index: slice) -> Self: ... + @override def __getitem__(self, index: int | slice) -> _T_co | Self: if isinstance(index, slice): return self.__class__(self._data[index]) @@ -488,6 +501,7 @@ def __ge__(self, value: tuple[_T_co, ...] | Self, /) -> bool: return self._data >= value._data return False + @override def __eq__(self, value: object, /) -> bool: if isinstance(value, tuple): return self._data == value @@ -506,9 +520,11 @@ def __mul__(self, value: SupportsIndex, /) -> Self: def __rmul__(self, value: SupportsIndex, /) -> Self: return self.__class__(value * self._data) + @override def count(self, value: Any) -> int: return self._data.count(value) + @override def index( self, value: Any, start: SupportsIndex = 0, stop: SupportsIndex = sys.maxsize ) -> int: @@ -516,12 +532,12 @@ def index( def get_wildcard_dict(entities: str | Iterable[str], /) -> dict[str, str]: - """Turn entity strings into wildcard dicts as {"entity": "{entity}"}""" + """Turn entity strings into wildcard dicts as {"entity": "{entity}"}.""" return {entity: f"{{{entity}}}" for entity in itx.always_iterable(entities)} class RegexContainer(Generic[AnyStr], Container[AnyStr]): - """Container that tests if a string matches a regex using the ``in`` operator + """Container that tests if a string matches a regex using the ``in`` operator. Constructed with a regex expression. Supports inclusion tests for strings using ``in``. Strings matching the regex (using ``re.match``) will return ``True`` @@ -537,7 +553,7 @@ def __contains__(self, x: object, /): class ContainerBag(Container[_T]): - """Container to hold other containers + """Container to hold other containers. Useful because list(Container) isn't guaranteed to work, so this lets us merge Containers in a type safe way. @@ -547,7 +563,4 @@ def __init__(self, *entries: Container[_T]): self.entries = entries def __contains__(self, x: object, /) -> bool: - for entry in self.entries: - if x in entry: - return True - return False + return any(x in entry for entry in self.entries)