From e689678cbedf46d794b4ab93b019980ef7e9131d Mon Sep 17 00:00:00 2001 From: Synchon Mandal Date: Wed, 17 Dec 2025 11:26:16 +0100 Subject: [PATCH 01/32] update: add yaml dep and config --- julio/_yaml.py | 16 +++++++++++ pyproject.toml | 1 + uv.lock | 72 ++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 89 insertions(+) create mode 100644 julio/_yaml.py diff --git a/julio/_yaml.py b/julio/_yaml.py new file mode 100644 index 0000000..732ca75 --- /dev/null +++ b/julio/_yaml.py @@ -0,0 +1,16 @@ +"""Provide YAML config definition for junifer use.""" + +# Authors: Synchon Mandal +# License: AGPL + +from ruamel.yaml import YAML + + +__all__ = ["yaml"] + + +# Configure YAML class once for further use +yaml = YAML() +yaml.default_flow_style = False +yaml.allow_unicode = True +yaml.indent(mapping=2, sequence=4, offset=2) diff --git a/pyproject.toml b/pyproject.toml index 7fe3fda..5017ba0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -39,6 +39,7 @@ dependencies = [ "datalad>=1.0.0,<1.3.0", "lazy_loader==0.4", "structlog>=25.0.0,<26.0.0", + "ruamel.yaml>=0.17,<0.19", ] dynamic = ["version"] diff --git a/uv.lock b/uv.lock index 8b60db2..bbd033a 100644 --- a/uv.lock +++ b/uv.lock @@ -488,6 +488,7 @@ dependencies = [ { name = "click" }, { name = "datalad" }, { name = "lazy-loader" }, + { name = "ruamel-yaml" }, { name = "structlog" }, ] @@ -505,6 +506,7 @@ requires-dist = [ { name = "datalad", specifier = ">=1.0.0,<1.3.0" }, { name = "lazy-loader", specifier = "==0.4" }, { name = "pre-commit", marker = "extra == 'dev'" }, + { name = "ruamel-yaml", specifier = ">=0.17,<0.19" }, { name = "ruff", marker = "extra == 'dev'" }, { name = "structlog", specifier = ">=25.0.0,<26.0.0" }, { name = "towncrier", marker = "extra == 'dev'" }, @@ -942,6 +944,76 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/3f/51/d4db610ef29373b879047326cbf6fa98b6c1969d6f6dc423279de2b1be2c/requests_toolbelt-1.0.0-py2.py3-none-any.whl", hash = "sha256:cccfdd665f0a24fcf4726e690f65639d272bb0637b9b92dfd91a5568ccf6bd06", size = 54481, upload-time = "2023-05-01T04:11:28.427Z" }, ] +[[package]] +name = "ruamel-yaml" +version = "0.18.16" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "ruamel-yaml-clib", marker = "python_full_version < '3.14' and platform_python_implementation == 'CPython'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9f/c7/ee630b29e04a672ecfc9b63227c87fd7a37eb67c1bf30fe95376437f897c/ruamel.yaml-0.18.16.tar.gz", hash = "sha256:a6e587512f3c998b2225d68aa1f35111c29fad14aed561a26e73fab729ec5e5a", size = 147269, upload-time = "2025-10-22T17:54:02.346Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0f/73/bb1bc2529f852e7bf64a2dec885e89ff9f5cc7bbf6c9340eed30ff2c69c5/ruamel.yaml-0.18.16-py3-none-any.whl", hash = "sha256:048f26d64245bae57a4f9ef6feb5b552a386830ef7a826f235ffb804c59efbba", size = 119858, upload-time = "2025-10-22T17:53:59.012Z" }, +] + +[[package]] +name = "ruamel-yaml-clib" +version = "0.2.15" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ea/97/60fda20e2fb54b83a61ae14648b0817c8f5d84a3821e40bfbdae1437026a/ruamel_yaml_clib-0.2.15.tar.gz", hash = "sha256:46e4cc8c43ef6a94885f72512094e482114a8a706d3c555a34ed4b0d20200600", size = 225794, upload-time = "2025-11-16T16:12:59.761Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f7/5a/4ab767cd42dcd65b83c323e1620d7c01ee60a52f4032fb7b61501f45f5c2/ruamel_yaml_clib-0.2.15-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:88eea8baf72f0ccf232c22124d122a7f26e8a24110a0273d9bcddcb0f7e1fa03", size = 147454, upload-time = "2025-11-16T16:13:02.54Z" }, + { url = "https://files.pythonhosted.org/packages/40/44/184173ac1e74fd35d308108bcbf83904d6ef8439c70763189225a166b238/ruamel_yaml_clib-0.2.15-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9b6f7d74d094d1f3a4e157278da97752f16ee230080ae331fcc219056ca54f77", size = 132467, upload-time = "2025-11-16T16:13:03.539Z" }, + { url = "https://files.pythonhosted.org/packages/49/1b/2d2077a25fe682ae335007ca831aff42e3cbc93c14066675cf87a6c7fc3e/ruamel_yaml_clib-0.2.15-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:4be366220090d7c3424ac2b71c90d1044ea34fca8c0b88f250064fd06087e614", size = 693454, upload-time = "2025-11-16T20:22:41.083Z" }, + { url = "https://files.pythonhosted.org/packages/90/16/e708059c4c429ad2e33be65507fc1730641e5f239fb2964efc1ba6edea94/ruamel_yaml_clib-0.2.15-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1f66f600833af58bea694d5892453f2270695b92200280ee8c625ec5a477eed3", size = 700345, upload-time = "2025-11-16T16:13:04.771Z" }, + { url = "https://files.pythonhosted.org/packages/d9/79/0e8ef51df1f0950300541222e3332f20707a9c210b98f981422937d1278c/ruamel_yaml_clib-0.2.15-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:da3d6adadcf55a93c214d23941aef4abfd45652110aed6580e814152f385b862", size = 731306, upload-time = "2025-11-16T16:13:06.312Z" }, + { url = "https://files.pythonhosted.org/packages/a6/f4/2cdb54b142987ddfbd01fc45ac6bd882695fbcedb9d8bbf796adc3fc3746/ruamel_yaml_clib-0.2.15-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:e9fde97ecb7bb9c41261c2ce0da10323e9227555c674989f8d9eb7572fc2098d", size = 692415, upload-time = "2025-11-16T16:13:07.465Z" }, + { url = "https://files.pythonhosted.org/packages/a0/07/40b5fc701cce8240a3e2d26488985d3bbdc446e9fe397c135528d412fea6/ruamel_yaml_clib-0.2.15-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:05c70f7f86be6f7bee53794d80050a28ae7e13e4a0087c1839dcdefd68eb36b6", size = 705007, upload-time = "2025-11-16T20:22:42.856Z" }, + { url = "https://files.pythonhosted.org/packages/82/19/309258a1df6192fb4a77ffa8eae3e8150e8d0ffa56c1b6fa92e450ba2740/ruamel_yaml_clib-0.2.15-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:6f1d38cbe622039d111b69e9ca945e7e3efebb30ba998867908773183357f3ed", size = 723974, upload-time = "2025-11-16T16:13:08.72Z" }, + { url = "https://files.pythonhosted.org/packages/67/3a/d6ee8263b521bfceb5cd2faeb904a15936480f2bb01c7ff74a14ec058ca4/ruamel_yaml_clib-0.2.15-cp310-cp310-win32.whl", hash = "sha256:fe239bdfdae2302e93bd6e8264bd9b71290218fff7084a9db250b55caaccf43f", size = 102836, upload-time = "2025-11-16T16:13:10.27Z" }, + { url = "https://files.pythonhosted.org/packages/ed/03/92aeb5c69018387abc49a8bb4f83b54a0471d9ef48e403b24bac68f01381/ruamel_yaml_clib-0.2.15-cp310-cp310-win_amd64.whl", hash = "sha256:468858e5cbde0198337e6a2a78eda8c3fb148bdf4c6498eaf4bc9ba3f8e780bd", size = 121917, upload-time = "2025-11-16T16:13:12.145Z" }, + { url = "https://files.pythonhosted.org/packages/2c/80/8ce7b9af532aa94dd83360f01ce4716264db73de6bc8efd22c32341f6658/ruamel_yaml_clib-0.2.15-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c583229f336682b7212a43d2fa32c30e643d3076178fb9f7a6a14dde85a2d8bd", size = 147998, upload-time = "2025-11-16T16:13:13.241Z" }, + { url = "https://files.pythonhosted.org/packages/53/09/de9d3f6b6701ced5f276d082ad0f980edf08ca67114523d1b9264cd5e2e0/ruamel_yaml_clib-0.2.15-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:56ea19c157ed8c74b6be51b5fa1c3aff6e289a041575f0556f66e5fb848bb137", size = 132743, upload-time = "2025-11-16T16:13:14.265Z" }, + { url = "https://files.pythonhosted.org/packages/0e/f7/73a9b517571e214fe5c246698ff3ed232f1ef863c8ae1667486625ec688a/ruamel_yaml_clib-0.2.15-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:5fea0932358e18293407feb921d4f4457db837b67ec1837f87074667449f9401", size = 731459, upload-time = "2025-11-16T20:22:44.338Z" }, + { url = "https://files.pythonhosted.org/packages/9b/a2/0dc0013169800f1c331a6f55b1282c1f4492a6d32660a0cf7b89e6684919/ruamel_yaml_clib-0.2.15-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ef71831bd61fbdb7aa0399d5c4da06bea37107ab5c79ff884cc07f2450910262", size = 749289, upload-time = "2025-11-16T16:13:15.633Z" }, + { url = "https://files.pythonhosted.org/packages/aa/ed/3fb20a1a96b8dc645d88c4072df481fe06e0289e4d528ebbdcc044ebc8b3/ruamel_yaml_clib-0.2.15-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:617d35dc765715fa86f8c3ccdae1e4229055832c452d4ec20856136acc75053f", size = 777630, upload-time = "2025-11-16T16:13:16.898Z" }, + { url = "https://files.pythonhosted.org/packages/60/50/6842f4628bc98b7aa4733ab2378346e1441e150935ad3b9f3c3c429d9408/ruamel_yaml_clib-0.2.15-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1b45498cc81a4724a2d42273d6cfc243c0547ad7c6b87b4f774cb7bcc131c98d", size = 744368, upload-time = "2025-11-16T16:13:18.117Z" }, + { url = "https://files.pythonhosted.org/packages/d3/b0/128ae8e19a7d794c2e36130a72b3bb650ce1dd13fb7def6cf10656437dcf/ruamel_yaml_clib-0.2.15-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:def5663361f6771b18646620fca12968aae730132e104688766cf8a3b1d65922", size = 745233, upload-time = "2025-11-16T20:22:45.833Z" }, + { url = "https://files.pythonhosted.org/packages/75/05/91130633602d6ba7ce3e07f8fc865b40d2a09efd4751c740df89eed5caf9/ruamel_yaml_clib-0.2.15-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:014181cdec565c8745b7cbc4de3bf2cc8ced05183d986e6d1200168e5bb59490", size = 770963, upload-time = "2025-11-16T16:13:19.344Z" }, + { url = "https://files.pythonhosted.org/packages/fd/4b/fd4542e7f33d7d1bc64cc9ac9ba574ce8cf145569d21f5f20133336cdc8c/ruamel_yaml_clib-0.2.15-cp311-cp311-win32.whl", hash = "sha256:d290eda8f6ada19e1771b54e5706b8f9807e6bb08e873900d5ba114ced13e02c", size = 102640, upload-time = "2025-11-16T16:13:20.498Z" }, + { url = "https://files.pythonhosted.org/packages/bb/eb/00ff6032c19c7537371e3119287999570867a0eafb0154fccc80e74bf57a/ruamel_yaml_clib-0.2.15-cp311-cp311-win_amd64.whl", hash = "sha256:bdc06ad71173b915167702f55d0f3f027fc61abd975bd308a0968c02db4a4c3e", size = 121996, upload-time = "2025-11-16T16:13:21.855Z" }, + { url = "https://files.pythonhosted.org/packages/72/4b/5fde11a0722d676e469d3d6f78c6a17591b9c7e0072ca359801c4bd17eee/ruamel_yaml_clib-0.2.15-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:cb15a2e2a90c8475df45c0949793af1ff413acfb0a716b8b94e488ea95ce7cff", size = 149088, upload-time = "2025-11-16T16:13:22.836Z" }, + { url = "https://files.pythonhosted.org/packages/85/82/4d08ac65ecf0ef3b046421985e66301a242804eb9a62c93ca3437dc94ee0/ruamel_yaml_clib-0.2.15-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:64da03cbe93c1e91af133f5bec37fd24d0d4ba2418eaf970d7166b0a26a148a2", size = 134553, upload-time = "2025-11-16T16:13:24.151Z" }, + { url = "https://files.pythonhosted.org/packages/b9/cb/22366d68b280e281a932403b76da7a988108287adff2bfa5ce881200107a/ruamel_yaml_clib-0.2.15-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:f6d3655e95a80325b84c4e14c080b2470fe4f33b6846f288379ce36154993fb1", size = 737468, upload-time = "2025-11-16T20:22:47.335Z" }, + { url = "https://files.pythonhosted.org/packages/71/73/81230babf8c9e33770d43ed9056f603f6f5f9665aea4177a2c30ae48e3f3/ruamel_yaml_clib-0.2.15-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:71845d377c7a47afc6592aacfea738cc8a7e876d586dfba814501d8c53c1ba60", size = 753349, upload-time = "2025-11-16T16:13:26.269Z" }, + { url = "https://files.pythonhosted.org/packages/61/62/150c841f24cda9e30f588ef396ed83f64cfdc13b92d2f925bb96df337ba9/ruamel_yaml_clib-0.2.15-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:11e5499db1ccbc7f4b41f0565e4f799d863ea720e01d3e99fa0b7b5fcd7802c9", size = 788211, upload-time = "2025-11-16T16:13:27.441Z" }, + { url = "https://files.pythonhosted.org/packages/30/93/e79bd9cbecc3267499d9ead919bd61f7ddf55d793fb5ef2b1d7d92444f35/ruamel_yaml_clib-0.2.15-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4b293a37dc97e2b1e8a1aec62792d1e52027087c8eea4fc7b5abd2bdafdd6642", size = 743203, upload-time = "2025-11-16T16:13:28.671Z" }, + { url = "https://files.pythonhosted.org/packages/8d/06/1eb640065c3a27ce92d76157f8efddb184bd484ed2639b712396a20d6dce/ruamel_yaml_clib-0.2.15-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:512571ad41bba04eac7268fe33f7f4742210ca26a81fe0c75357fa682636c690", size = 747292, upload-time = "2025-11-16T20:22:48.584Z" }, + { url = "https://files.pythonhosted.org/packages/a5/21/ee353e882350beab65fcc47a91b6bdc512cace4358ee327af2962892ff16/ruamel_yaml_clib-0.2.15-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e5e9f630c73a490b758bf14d859a39f375e6999aea5ddd2e2e9da89b9953486a", size = 771624, upload-time = "2025-11-16T16:13:29.853Z" }, + { url = "https://files.pythonhosted.org/packages/57/34/cc1b94057aa867c963ecf9ea92ac59198ec2ee3a8d22a126af0b4d4be712/ruamel_yaml_clib-0.2.15-cp312-cp312-win32.whl", hash = "sha256:f4421ab780c37210a07d138e56dd4b51f8642187cdfb433eb687fe8c11de0144", size = 100342, upload-time = "2025-11-16T16:13:31.067Z" }, + { url = "https://files.pythonhosted.org/packages/b3/e5/8925a4208f131b218f9a7e459c0d6fcac8324ae35da269cb437894576366/ruamel_yaml_clib-0.2.15-cp312-cp312-win_amd64.whl", hash = "sha256:2b216904750889133d9222b7b873c199d48ecbb12912aca78970f84a5aa1a4bc", size = 119013, upload-time = "2025-11-16T16:13:32.164Z" }, + { url = "https://files.pythonhosted.org/packages/17/5e/2f970ce4c573dc30c2f95825f2691c96d55560268ddc67603dc6ea2dd08e/ruamel_yaml_clib-0.2.15-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:4dcec721fddbb62e60c2801ba08c87010bd6b700054a09998c4d09c08147b8fb", size = 147450, upload-time = "2025-11-16T16:13:33.542Z" }, + { url = "https://files.pythonhosted.org/packages/d6/03/a1baa5b94f71383913f21b96172fb3a2eb5576a4637729adbf7cd9f797f8/ruamel_yaml_clib-0.2.15-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:65f48245279f9bb301d1276f9679b82e4c080a1ae25e679f682ac62446fac471", size = 133139, upload-time = "2025-11-16T16:13:34.587Z" }, + { url = "https://files.pythonhosted.org/packages/dc/19/40d676802390f85784235a05788fd28940923382e3f8b943d25febbb98b7/ruamel_yaml_clib-0.2.15-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:46895c17ead5e22bea5e576f1db7e41cb273e8d062c04a6a49013d9f60996c25", size = 731474, upload-time = "2025-11-16T20:22:49.934Z" }, + { url = "https://files.pythonhosted.org/packages/ce/bb/6ef5abfa43b48dd55c30d53e997f8f978722f02add61efba31380d73e42e/ruamel_yaml_clib-0.2.15-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3eb199178b08956e5be6288ee0b05b2fb0b5c1f309725ad25d9c6ea7e27f962a", size = 748047, upload-time = "2025-11-16T16:13:35.633Z" }, + { url = "https://files.pythonhosted.org/packages/ff/5d/e4f84c9c448613e12bd62e90b23aa127ea4c46b697f3d760acc32cb94f25/ruamel_yaml_clib-0.2.15-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4d1032919280ebc04a80e4fb1e93f7a738129857eaec9448310e638c8bccefcf", size = 782129, upload-time = "2025-11-16T16:13:36.781Z" }, + { url = "https://files.pythonhosted.org/packages/de/4b/e98086e88f76c00c88a6bcf15eae27a1454f661a9eb72b111e6bbb69024d/ruamel_yaml_clib-0.2.15-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ab0df0648d86a7ecbd9c632e8f8d6b21bb21b5fc9d9e095c796cacf32a728d2d", size = 736848, upload-time = "2025-11-16T16:13:37.952Z" }, + { url = "https://files.pythonhosted.org/packages/0c/5c/5964fcd1fd9acc53b7a3a5d9a05ea4f95ead9495d980003a557deb9769c7/ruamel_yaml_clib-0.2.15-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:331fb180858dd8534f0e61aa243b944f25e73a4dae9962bd44c46d1761126bbf", size = 741630, upload-time = "2025-11-16T20:22:51.718Z" }, + { url = "https://files.pythonhosted.org/packages/07/1e/99660f5a30fceb58494598e7d15df883a07292346ef5696f0c0ae5dee8c6/ruamel_yaml_clib-0.2.15-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fd4c928ddf6bce586285daa6d90680b9c291cfd045fc40aad34e445d57b1bf51", size = 766619, upload-time = "2025-11-16T16:13:39.178Z" }, + { url = "https://files.pythonhosted.org/packages/36/2f/fa0344a9327b58b54970e56a27b32416ffbcfe4dcc0700605516708579b2/ruamel_yaml_clib-0.2.15-cp313-cp313-win32.whl", hash = "sha256:bf0846d629e160223805db9fe8cc7aec16aaa11a07310c50c8c7164efa440aec", size = 100171, upload-time = "2025-11-16T16:13:40.456Z" }, + { url = "https://files.pythonhosted.org/packages/06/c4/c124fbcef0684fcf3c9b72374c2a8c35c94464d8694c50f37eef27f5a145/ruamel_yaml_clib-0.2.15-cp313-cp313-win_amd64.whl", hash = "sha256:45702dfbea1420ba3450bb3dd9a80b33f0badd57539c6aac09f42584303e0db6", size = 118845, upload-time = "2025-11-16T16:13:41.481Z" }, + { url = "https://files.pythonhosted.org/packages/3e/bd/ab8459c8bb759c14a146990bf07f632c1cbec0910d4853feeee4be2ab8bb/ruamel_yaml_clib-0.2.15-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:753faf20b3a5906faf1fc50e4ddb8c074cb9b251e00b14c18b28492f933ac8ef", size = 147248, upload-time = "2025-11-16T16:13:42.872Z" }, + { url = "https://files.pythonhosted.org/packages/69/f2/c4cec0a30f1955510fde498aac451d2e52b24afdbcb00204d3a951b772c3/ruamel_yaml_clib-0.2.15-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:480894aee0b29752560a9de46c0e5f84a82602f2bc5c6cde8db9a345319acfdf", size = 133764, upload-time = "2025-11-16T16:13:43.932Z" }, + { url = "https://files.pythonhosted.org/packages/82/c7/2480d062281385a2ea4f7cc9476712446e0c548cd74090bff92b4b49e898/ruamel_yaml_clib-0.2.15-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:4d3b58ab2454b4747442ac76fab66739c72b1e2bb9bd173d7694b9f9dbc9c000", size = 730537, upload-time = "2025-11-16T20:22:52.918Z" }, + { url = "https://files.pythonhosted.org/packages/75/08/e365ee305367559f57ba6179d836ecc3d31c7d3fdff2a40ebf6c32823a1f/ruamel_yaml_clib-0.2.15-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bfd309b316228acecfa30670c3887dcedf9b7a44ea39e2101e75d2654522acd4", size = 746944, upload-time = "2025-11-16T16:13:45.338Z" }, + { url = "https://files.pythonhosted.org/packages/a1/5c/8b56b08db91e569d0a4fbfa3e492ed2026081bdd7e892f63ba1c88a2f548/ruamel_yaml_clib-0.2.15-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2812ff359ec1f30129b62372e5f22a52936fac13d5d21e70373dbca5d64bb97c", size = 778249, upload-time = "2025-11-16T16:13:46.871Z" }, + { url = "https://files.pythonhosted.org/packages/6a/1d/70dbda370bd0e1a92942754c873bd28f513da6198127d1736fa98bb2a16f/ruamel_yaml_clib-0.2.15-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7e74ea87307303ba91073b63e67f2c667e93f05a8c63079ee5b7a5c8d0d7b043", size = 737140, upload-time = "2025-11-16T16:13:48.349Z" }, + { url = "https://files.pythonhosted.org/packages/5b/87/822d95874216922e1120afb9d3fafa795a18fdd0c444f5c4c382f6dac761/ruamel_yaml_clib-0.2.15-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:713cd68af9dfbe0bb588e144a61aad8dcc00ef92a82d2e87183ca662d242f524", size = 741070, upload-time = "2025-11-16T20:22:54.151Z" }, + { url = "https://files.pythonhosted.org/packages/b9/17/4e01a602693b572149f92c983c1f25bd608df02c3f5cf50fd1f94e124a59/ruamel_yaml_clib-0.2.15-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:542d77b72786a35563f97069b9379ce762944e67055bea293480f7734b2c7e5e", size = 765882, upload-time = "2025-11-16T16:13:49.526Z" }, + { url = "https://files.pythonhosted.org/packages/9f/17/7999399081d39ebb79e807314de6b611e1d1374458924eb2a489c01fc5ad/ruamel_yaml_clib-0.2.15-cp314-cp314-win32.whl", hash = "sha256:424ead8cef3939d690c4b5c85ef5b52155a231ff8b252961b6516ed7cf05f6aa", size = 102567, upload-time = "2025-11-16T16:13:50.78Z" }, + { url = "https://files.pythonhosted.org/packages/d2/67/be582a7370fdc9e6846c5be4888a530dcadd055eef5b932e0e85c33c7d73/ruamel_yaml_clib-0.2.15-cp314-cp314-win_amd64.whl", hash = "sha256:ac9b8d5fa4bb7fd2917ab5027f60d4234345fd366fe39aa711d5dca090aa1467", size = 122847, upload-time = "2025-11-16T16:13:51.807Z" }, +] + [[package]] name = "ruff" version = "0.14.8" From c023dcea31d69a8e3f66b99ec04ec22209f03d71 Mon Sep 17 00:00:00 2001 From: Synchon Mandal Date: Wed, 17 Dec 2025 11:28:12 +0100 Subject: [PATCH 02/32] update: add h5io, tqdm as deps and _utils.py --- julio/_utils.py | 398 ++++++++++++++++++++++++++++++++++++++++++++++++ pyproject.toml | 2 + uv.lock | 220 +++++++++++++++++++++++++- 3 files changed, 617 insertions(+), 3 deletions(-) create mode 100644 julio/_utils.py diff --git a/julio/_utils.py b/julio/_utils.py new file mode 100644 index 0000000..099b2f1 --- /dev/null +++ b/julio/_utils.py @@ -0,0 +1,398 @@ +"""Utilities for julio.""" + +# Authors: Synchon Mandal +# License: AGPL + +import pathlib +import re +from pathlib import Path +from typing import Any + +import click +import datalad.api as dl +import structlog +from h5io import read_hdf5, write_hdf5 +from tqdm import tqdm + +from ._yaml import yaml + + +__all__ = ["PathOrURL", "is_julio_registry", "process_features"] + +logger = structlog.get_logger() + +# The following is taken from: +# https://validators.readthedocs.io/en/latest/_modules/validators/url.html#url +# and based on: +# https://gist.github.com/dperini/729294 +ip_middle_octet = r"(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5]))" +ip_last_octet = r"(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))" + +regex = re.compile( + "^" + # protocol identifier + "(?:(?:https?|ftp)://)" + # user:pass authentication + r"(?:\S+(?::\S*)?@)?" + "(?:" + "(?P" + # IP address exclusion + # private & local networks + "(?:(?:10|127)" + ip_middle_octet + "{2}" + ip_last_octet + ")|" + r"(?:(?:169\.254|192\.168)" + ip_middle_octet + ip_last_octet + ")|" + r"(?:172\.(?:1[6-9]|2\d|3[0-1])" + ip_middle_octet + ip_last_octet + "))" + "|" + # IP address dotted notation octets + # excludes loopback network 0.0.0.0 + # excludes reserved space >= 224.0.0.0 + # excludes network & broadcast addresses + # (first & last IP address of each class) + "(?P" + r"(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])" + "" + ip_middle_octet + "{2}" + "" + ip_last_octet + ")" + "|" + # host name + "(?:(?:[a-z\u00a1-\uffff0-9]-?)*[a-z\u00a1-\uffff0-9]+)" + # domain name + r"(?:\.(?:[a-z\u00a1-\uffff0-9]-?)*[a-z\u00a1-\uffff0-9]+)*" + # TLD identifier + r"(?:\.(?:[a-z\u00a1-\uffff]{2,}))" + ")" + # port number + r"(?::\d{2,5})?" + # resource path + r"(?:/\S*)?" + # query string + r"(?:\?\S*)?" + "$", + re.UNICODE | re.IGNORECASE, +) + +pattern = re.compile(regex) + + +class PathOrURLParamType(click.ParamType): + name = "path_or_url" + + def convert(self, value, param, ctx): + # URL + if value.startswith("http"): + if pattern.match(value) is not None: + return value + else: + self.fail(f"{value!r} is not a valid url.", param, ctx) + # Path + try: + p = click.Path( + exists=True, + readable=True, + writable=True, + file_okay=False, + path_type=pathlib.Path, + ).convert(value, param, ctx) + except click.BadParameter as e: + self.fail(f"{e}", param, ctx) + else: + return p + + +PathOrURL = PathOrURLParamType() + + +def is_julio_registry(ds: dl.Dataset) -> bool: + """Check if the dataset is a julio registry. + + Parameters + ---------- + ds : dl.Dataset + Dataset to check. + + Returns + ------- + bool + True if the dataset is a julio registry, False otherwise. + + """ + if (ds.pathobj / "registry-config.yml").is_file(): + return True + return False + + +def _make_absolute_path(path: str, base_path: Path) -> str: + """Make a path absolute. + + Parameters + ---------- + path : str + Path to make absolute. + base_path : Path + Base path to use. + + Returns + ------- + str + Absolute path. + + """ + log = logger.bind(cmd="make_absolute_path", path=str(path)) + log.debug("Making path absolute") + path = Path(path) + if not path.is_absolute(): + path = base_path / path + log.debug("Made path absolute") + return str(path.resolve()) + + +def _adjust_paths(data: dict, yaml_path: Path) -> dict: + """Adjust paths in the data dictionary. + + Parameters + ---------- + data : dict + Data dictionary to adjust. + yaml_path : Path + Path to the YAML file. + + Returns + ------- + dict + Adjusted data dictionary. + + """ + log = logger.bind(cmd="adjust_paths", path=str(yaml_path.resolve())) + log.debug("Adjusting paths") + if "workdir" in data: + if isinstance(data["workdir"], str): + data["workdir"] = _make_absolute_path( + data["workdir"], yaml_path.parent + ) + else: + data["workdir"]["path"] = _make_absolute_path( + data["workdir"]["path"], yaml_path.parent + ) + if "with" in data: + if not isinstance(data["with"], list): + data["with"] = list(data["with"]) + mods = [] + for w in data["with"]: + if w.endswith(".py"): + mods.append(_make_absolute_path(w, yaml_path.parent)) + else: + mods.append(w) + data["with"] = mods + data["storage"]["uri"] = _make_absolute_path( + data["storage"]["uri"], yaml_path.parent + ) + log.debug("Adjusted paths") + return data + + +def _parse_yaml(yaml_path: Path) -> dict: + """Parse the junifer YAML. + + Parameters + ---------- + yaml_path : Path + Path to the junifer YAML. + + Returns + ------- + dict + Parsed YAML content. + + Raises + ------ + RuntimeError + If the YAML file is invalid or + storage file does not exist. + + """ + log = logger.bind(cmd="parse_yaml", path=str(yaml_path.resolve())) + log.debug("Parsing junifer YAML") + contents = yaml.load(yaml_path) + # Validate mandatory sections + mandatory = ("workdir", "datagrabber", "markers", "storage") + for s in mandatory: + if s not in contents.keys(): + raise RuntimeError( + f"`{s}` section not defined in {yaml_path.resolve()!s}" + ) + # Validate optional sections + optional = ("with", "preprocess", "queue") + for k in contents.keys(): + if k not in mandatory + optional: + raise RuntimeError( + f"Unknown section `{k}` in {yaml_path.resolve()!s}" + ) + # Validate storage + if "uri" not in contents["storage"]: + raise RuntimeError( + f"`uri` missing from `storage` section in {yaml_path.resolve()!s}" + ) + # Replace relative file paths with absolute + contents = _adjust_paths(contents, yaml_path) + # Validate storage file exists + if not Path(contents["storage"]["uri"]).exists(): + raise RuntimeError( + f"Storage file does not exist: {contents['storage']['uri']}" + ) + # Remove elements key if empty + if "elements" in contents: + if contents["elements"] is None: + _ = contents.pop("elements") + log.debug("Parsed junifer YAML") + return contents + + +def _feature_yaml_from_meta(data: dict, feature: str) -> dict: + """Generate a feature YAML from metadata. + + Parameters + ---------- + data : dict + Dictionary containing the HDF5 metadata. + feature : str + MD5 hash of the feature. + + Returns + ------- + dict + Feature YAML. + + """ + y: dict[str, Any] = {} + y["workdir"] = "" + if "with" in data: + y["with"] = data["with"].copy() + # Set datagrabber + y["datagrabber"] = data["datagrabber"].copy() + a = y["datagrabber"].pop("class") + y["datagrabber"]["kind"] = a + if a not in ("PatternDataGrabber", "PatternDataladDataGrabber"): + _ = y["datagrabber"].pop("uri") + _ = y["datagrabber"].pop("rootdir") + _ = y["datagrabber"].pop("patterns") + _ = y["datagrabber"].pop("replacements") + _ = y["datagrabber"].pop("confounds_format") + _ = y["datagrabber"].pop("partial_pattern_ok") + for k in data[ + "datagrabber" + ].keys(): # use data instead of y to avoid .copy() + if k.startswith("datalad"): + _ = y["datagrabber"].pop(k) + # Set preprocess + if "preprocess" in data: + y["preprocess"] = data["preprocess"].copy() + b = y["preprocess"].pop("class") + y["preprocess"]["kind"] = b + # Set markers + y["markers"] = [] + y["markers"].append(data["marker"].copy()) + c = y["markers"][0].pop("class") + y["markers"][0]["kind"] = c + if y["markers"][0]["masks"] is None: + _ = y["markers"][0].pop("masks") + # Set storage + y["storage"] = { + "kind": "HDF5FeatureStorage", + "uri": "", + } + # Set queue + y["queue"] = { + "jobname": data["name"], + "kind": "", + } + return y + + +def _process_hdf5(data: dict, ds: dl.Dataset) -> dl.Dataset: + """Read and write HDF5 data. + + Parameters + ---------- + data : dict + Dictionary containing the HDF5 data. + ds : dl.Dataset + Dataset to add features to. + + Returns + ------- + dl.Dataset + Dataset with features added. + + """ + log = logger.bind(cmd="process_hdf5", path=data["storage"]["uri"]) + log.debug("Processing HDF5 file") + metadata = read_hdf5( + fname=data["storage"]["uri"], + title="meta", + slash="ignore", + ) + feature_dir = ds.pathobj / "features" + feature_dir.mkdir(exist_ok=True) + for k, v in tqdm(metadata.items(), desc="Processing features"): + # Metadata + meta_path = feature_dir / f"feature-{k}-meta.yaml" + yaml.dump(v, stream=meta_path.open("w")) + # YAML + yaml_data = _feature_yaml_from_meta(v, k) + yaml_path = feature_dir / f"feature-{k}.yml" + yaml.dump(yaml_data, stream=yaml_path.open("w")) + # Data + data = read_hdf5(fname=data["storage"]["uri"], title=k, slash="ignore") + data_path = feature_dir / f"feature-{k}.h5" + write_hdf5( + fname=str(data_path.resolve()), + data=data, + overwrite=True, + title=k, + slash="error", + use_json=False, + ) + log.debug("Processed HDF5 file") + return ds + + +def process_features(yaml_path: Path, ds: dl.Dataset) -> dl.Dataset: + """Parse the junifer YAML and add features to the dataset. + + Parameters + ---------- + yaml_path : Path + Path to the junifer YAML. + ds : dl.Dataset + Dataset to add features to. + + Returns + ------- + dl.Dataset + Dataset with features added. + + Raises + ------ + RuntimeError + If the storage file does not exist or + is not a valid format. + + """ + log = logger.bind(cmd="process_features", path=str(yaml_path.resolve())) + log.debug("Processing features") + data = _parse_yaml(yaml_path) + if data["storage"]["uri"].endswith(".hdf5"): + ds = _process_hdf5(data, ds) + elif data["storage"]["uri"].endswith(".sqlite"): + # TODO(synchon): add support for SQLite + pass + else: + raise RuntimeError( + "Unsupported storage format extension: " + f"{Path(data['storage']['uri']).suffix}" + ) + ds.save( + message=f"[julio] add features for {yaml_path.name}", + on_failure="stop", + result_renderer="disabled", + ) + log.debug("Processed features") + return ds diff --git a/pyproject.toml b/pyproject.toml index 5017ba0..332697c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -40,6 +40,8 @@ dependencies = [ "lazy_loader==0.4", "structlog>=25.0.0,<26.0.0", "ruamel.yaml>=0.17,<0.19", + "h5io@git+https://github.com/juaml/h5io@600d9e25c625b11520d86b08f9f0b761f71b0542", + "tqdm>=4.66.1,<4.67.0", ] dynamic = ["version"] diff --git a/uv.lock b/uv.lock index bbd033a..abdd7d9 100644 --- a/uv.lock +++ b/uv.lock @@ -367,6 +367,67 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/76/91/7216b27286936c16f5b4d0c530087e4a54eead683e6b0b73dd0c64844af6/filelock-3.20.0-py3-none-any.whl", hash = "sha256:339b4732ffda5cd79b13f4e2711a31b0365ce445d95d243bb996273d072546a2", size = 16054, upload-time = "2025-10-08T18:03:48.35Z" }, ] +[[package]] +name = "h5io" +version = "0.1.7" +source = { git = "https://github.com/juaml/h5io?rev=600d9e25c625b11520d86b08f9f0b761f71b0542#600d9e25c625b11520d86b08f9f0b761f71b0542" } +dependencies = [ + { name = "h5py" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "numpy", version = "2.3.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, +] + +[[package]] +name = "h5py" +version = "3.15.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "numpy", version = "2.3.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/4d/6a/0d79de0b025aa85dc8864de8e97659c94cf3d23148394a954dc5ca52f8c8/h5py-3.15.1.tar.gz", hash = "sha256:c86e3ed45c4473564de55aa83b6fc9e5ead86578773dfbd93047380042e26b69", size = 426236, upload-time = "2025-10-16T10:35:27.404Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/86/30/8fa61698b438dd751fa46a359792e801191dadab560d0a5f1c709443ef8e/h5py-3.15.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:67e59f6c2f19a32973a40f43d9a088ae324fe228c8366e25ebc57ceebf093a6b", size = 3414477, upload-time = "2025-10-16T10:33:24.201Z" }, + { url = "https://files.pythonhosted.org/packages/16/16/db2f63302937337c4e9e51d97a5984b769bdb7488e3d37632a6ac297f8ef/h5py-3.15.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0e2f471688402c3404fa4e13466e373e622fd4b74b47b56cfdff7cc688209422", size = 2850298, upload-time = "2025-10-16T10:33:27.747Z" }, + { url = "https://files.pythonhosted.org/packages/fc/2e/f1bb7de9b05112bfd14d5206090f0f92f1e75bbb412fbec5d4653c3d44dd/h5py-3.15.1-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4c45802bcb711e128a6839cb6c01e9ac648dc55df045c9542a675c771f15c8d5", size = 4523605, upload-time = "2025-10-16T10:33:31.168Z" }, + { url = "https://files.pythonhosted.org/packages/05/8a/63f4b08f3628171ce8da1a04681a65ee7ac338fde3cb3e9e3c9f7818e4da/h5py-3.15.1-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:64ce3f6470adb87c06e3a8dd1b90e973699f1759ad79bfa70c230939bff356c9", size = 4735346, upload-time = "2025-10-16T10:33:34.759Z" }, + { url = "https://files.pythonhosted.org/packages/74/48/f16d12d9de22277605bcc11c0dcab5e35f06a54be4798faa2636b5d44b3c/h5py-3.15.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:4411c1867b9899a25e983fff56d820a66f52ac326bbe10c7cdf7d832c9dcd883", size = 4175305, upload-time = "2025-10-16T10:33:38.83Z" }, + { url = "https://files.pythonhosted.org/packages/d6/2f/47cdbff65b2ce53c27458c6df63a232d7bb1644b97df37b2342442342c84/h5py-3.15.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:2cbc4104d3d4aca9d6db8c0c694555e255805bfeacf9eb1349bda871e26cacbe", size = 4653602, upload-time = "2025-10-16T10:33:42.188Z" }, + { url = "https://files.pythonhosted.org/packages/c3/28/dc08de359c2f43a67baa529cb70d7f9599848750031975eed92d6ae78e1d/h5py-3.15.1-cp310-cp310-win_amd64.whl", hash = "sha256:01f55111ca516f5568ae7a7fc8247dfce607de331b4467ee8a9a6ed14e5422c7", size = 2873601, upload-time = "2025-10-16T10:33:45.323Z" }, + { url = "https://files.pythonhosted.org/packages/41/fd/8349b48b15b47768042cff06ad6e1c229f0a4bd89225bf6b6894fea27e6d/h5py-3.15.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5aaa330bcbf2830150c50897ea5dcbed30b5b6d56897289846ac5b9e529ec243", size = 3434135, upload-time = "2025-10-16T10:33:47.954Z" }, + { url = "https://files.pythonhosted.org/packages/c1/b0/1c628e26a0b95858f54aba17e1599e7f6cd241727596cc2580b72cb0a9bf/h5py-3.15.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c970fb80001fffabb0109eaf95116c8e7c0d3ca2de854e0901e8a04c1f098509", size = 2870958, upload-time = "2025-10-16T10:33:50.907Z" }, + { url = "https://files.pythonhosted.org/packages/f9/e3/c255cafc9b85e6ea04e2ad1bba1416baa1d7f57fc98a214be1144087690c/h5py-3.15.1-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:80e5bb5b9508d5d9da09f81fd00abbb3f85da8143e56b1585d59bc8ceb1dba8b", size = 4504770, upload-time = "2025-10-16T10:33:54.357Z" }, + { url = "https://files.pythonhosted.org/packages/8b/23/4ab1108e87851ccc69694b03b817d92e142966a6c4abd99e17db77f2c066/h5py-3.15.1-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5b849ba619a066196169763c33f9f0f02e381156d61c03e000bb0100f9950faf", size = 4700329, upload-time = "2025-10-16T10:33:57.616Z" }, + { url = "https://files.pythonhosted.org/packages/a4/e4/932a3a8516e4e475b90969bf250b1924dbe3612a02b897e426613aed68f4/h5py-3.15.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:e7f6c841efd4e6e5b7e82222eaf90819927b6d256ab0f3aca29675601f654f3c", size = 4152456, upload-time = "2025-10-16T10:34:00.843Z" }, + { url = "https://files.pythonhosted.org/packages/2a/0a/f74d589883b13737021b2049ac796328f188dbb60c2ed35b101f5b95a3fc/h5py-3.15.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ca8a3a22458956ee7b40d8e39c9a9dc01f82933e4c030c964f8b875592f4d831", size = 4617295, upload-time = "2025-10-16T10:34:04.154Z" }, + { url = "https://files.pythonhosted.org/packages/23/95/499b4e56452ef8b6c95a271af0dde08dac4ddb70515a75f346d4f400579b/h5py-3.15.1-cp311-cp311-win_amd64.whl", hash = "sha256:550e51131376889656feec4aff2170efc054a7fe79eb1da3bb92e1625d1ac878", size = 2882129, upload-time = "2025-10-16T10:34:06.886Z" }, + { url = "https://files.pythonhosted.org/packages/ce/bb/cfcc70b8a42222ba3ad4478bcef1791181ea908e2adbd7d53c66395edad5/h5py-3.15.1-cp311-cp311-win_arm64.whl", hash = "sha256:b39239947cb36a819147fc19e86b618dcb0953d1cd969f5ed71fc0de60392427", size = 2477121, upload-time = "2025-10-16T10:34:09.579Z" }, + { url = "https://files.pythonhosted.org/packages/62/b8/c0d9aa013ecfa8b7057946c080c0c07f6fa41e231d2e9bd306a2f8110bdc/h5py-3.15.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:316dd0f119734f324ca7ed10b5627a2de4ea42cc4dfbcedbee026aaa361c238c", size = 3399089, upload-time = "2025-10-16T10:34:12.135Z" }, + { url = "https://files.pythonhosted.org/packages/a4/5e/3c6f6e0430813c7aefe784d00c6711166f46225f5d229546eb53032c3707/h5py-3.15.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b51469890e58e85d5242e43aab29f5e9c7e526b951caab354f3ded4ac88e7b76", size = 2847803, upload-time = "2025-10-16T10:34:14.564Z" }, + { url = "https://files.pythonhosted.org/packages/00/69/ba36273b888a4a48d78f9268d2aee05787e4438557450a8442946ab8f3ec/h5py-3.15.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8a33bfd5dfcea037196f7778534b1ff7e36a7f40a89e648c8f2967292eb6898e", size = 4914884, upload-time = "2025-10-16T10:34:18.452Z" }, + { url = "https://files.pythonhosted.org/packages/3a/30/d1c94066343a98bb2cea40120873193a4fed68c4ad7f8935c11caf74c681/h5py-3.15.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:25c8843fec43b2cc368aa15afa1cdf83fc5e17b1c4e10cd3771ef6c39b72e5ce", size = 5109965, upload-time = "2025-10-16T10:34:21.853Z" }, + { url = "https://files.pythonhosted.org/packages/81/3d/d28172116eafc3bc9f5991b3cb3fd2c8a95f5984f50880adfdf991de9087/h5py-3.15.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a308fd8681a864c04423c0324527237a0484e2611e3441f8089fd00ed56a8171", size = 4561870, upload-time = "2025-10-16T10:34:26.69Z" }, + { url = "https://files.pythonhosted.org/packages/a5/83/393a7226024238b0f51965a7156004eaae1fcf84aa4bfecf7e582676271b/h5py-3.15.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f4a016df3f4a8a14d573b496e4d1964deb380e26031fc85fb40e417e9131888a", size = 5037161, upload-time = "2025-10-16T10:34:30.383Z" }, + { url = "https://files.pythonhosted.org/packages/cf/51/329e7436bf87ca6b0fe06dd0a3795c34bebe4ed8d6c44450a20565d57832/h5py-3.15.1-cp312-cp312-win_amd64.whl", hash = "sha256:59b25cf02411bf12e14f803fef0b80886444c7fe21a5ad17c6a28d3f08098a1e", size = 2874165, upload-time = "2025-10-16T10:34:33.461Z" }, + { url = "https://files.pythonhosted.org/packages/09/a8/2d02b10a66747c54446e932171dd89b8b4126c0111b440e6bc05a7c852ec/h5py-3.15.1-cp312-cp312-win_arm64.whl", hash = "sha256:61d5a58a9851e01ee61c932bbbb1c98fe20aba0a5674776600fb9a361c0aa652", size = 2458214, upload-time = "2025-10-16T10:34:35.733Z" }, + { url = "https://files.pythonhosted.org/packages/88/b3/40207e0192415cbff7ea1d37b9f24b33f6d38a5a2f5d18a678de78f967ae/h5py-3.15.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c8440fd8bee9500c235ecb7aa1917a0389a2adb80c209fa1cc485bd70e0d94a5", size = 3376511, upload-time = "2025-10-16T10:34:38.596Z" }, + { url = "https://files.pythonhosted.org/packages/31/96/ba99a003c763998035b0de4c299598125df5fc6c9ccf834f152ddd60e0fb/h5py-3.15.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ab2219dbc6fcdb6932f76b548e2b16f34a1f52b7666e998157a4dfc02e2c4123", size = 2826143, upload-time = "2025-10-16T10:34:41.342Z" }, + { url = "https://files.pythonhosted.org/packages/6a/c2/fc6375d07ea3962df7afad7d863fe4bde18bb88530678c20d4c90c18de1d/h5py-3.15.1-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d8cb02c3a96255149ed3ac811eeea25b655d959c6dd5ce702c9a95ff11859eb5", size = 4908316, upload-time = "2025-10-16T10:34:44.619Z" }, + { url = "https://files.pythonhosted.org/packages/d9/69/4402ea66272dacc10b298cca18ed73e1c0791ff2ae9ed218d3859f9698ac/h5py-3.15.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:121b2b7a4c1915d63737483b7bff14ef253020f617c2fb2811f67a4bed9ac5e8", size = 5103710, upload-time = "2025-10-16T10:34:48.639Z" }, + { url = "https://files.pythonhosted.org/packages/e0/f6/11f1e2432d57d71322c02a97a5567829a75f223a8c821764a0e71a65cde8/h5py-3.15.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:59b0d63b318bf3cc06687def2b45afd75926bbc006f7b8cd2b1a231299fc8599", size = 4556042, upload-time = "2025-10-16T10:34:51.841Z" }, + { url = "https://files.pythonhosted.org/packages/18/88/3eda3ef16bfe7a7dbc3d8d6836bbaa7986feb5ff091395e140dc13927bcc/h5py-3.15.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e02fe77a03f652500d8bff288cbf3675f742fc0411f5a628fa37116507dc7cc0", size = 5030639, upload-time = "2025-10-16T10:34:55.257Z" }, + { url = "https://files.pythonhosted.org/packages/e5/ea/fbb258a98863f99befb10ed727152b4ae659f322e1d9c0576f8a62754e81/h5py-3.15.1-cp313-cp313-win_amd64.whl", hash = "sha256:dea78b092fd80a083563ed79a3171258d4a4d307492e7cf8b2313d464c82ba52", size = 2864363, upload-time = "2025-10-16T10:34:58.099Z" }, + { url = "https://files.pythonhosted.org/packages/5d/c9/35021cc9cd2b2915a7da3026e3d77a05bed1144a414ff840953b33937fb9/h5py-3.15.1-cp313-cp313-win_arm64.whl", hash = "sha256:c256254a8a81e2bddc0d376e23e2a6d2dc8a1e8a2261835ed8c1281a0744cd97", size = 2449570, upload-time = "2025-10-16T10:35:00.473Z" }, + { url = "https://files.pythonhosted.org/packages/a0/2c/926eba1514e4d2e47d0e9eb16c784e717d8b066398ccfca9b283917b1bfb/h5py-3.15.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:5f4fb0567eb8517c3ecd6b3c02c4f4e9da220c8932604960fd04e24ee1254763", size = 3380368, upload-time = "2025-10-16T10:35:03.117Z" }, + { url = "https://files.pythonhosted.org/packages/65/4b/d715ed454d3baa5f6ae1d30b7eca4c7a1c1084f6a2edead9e801a1541d62/h5py-3.15.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:954e480433e82d3872503104f9b285d369048c3a788b2b1a00e53d1c47c98dd2", size = 2833793, upload-time = "2025-10-16T10:35:05.623Z" }, + { url = "https://files.pythonhosted.org/packages/ef/d4/ef386c28e4579314610a8bffebbee3b69295b0237bc967340b7c653c6c10/h5py-3.15.1-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fd125c131889ebbef0849f4a0e29cf363b48aba42f228d08b4079913b576bb3a", size = 4903199, upload-time = "2025-10-16T10:35:08.972Z" }, + { url = "https://files.pythonhosted.org/packages/33/5d/65c619e195e0b5e54ea5a95c1bb600c8ff8715e0d09676e4cce56d89f492/h5py-3.15.1-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:28a20e1a4082a479b3d7db2169f3a5034af010b90842e75ebbf2e9e49eb4183e", size = 5097224, upload-time = "2025-10-16T10:35:12.808Z" }, + { url = "https://files.pythonhosted.org/packages/30/30/5273218400bf2da01609e1292f562c94b461fcb73c7a9e27fdadd43abc0a/h5py-3.15.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:fa8df5267f545b4946df8ca0d93d23382191018e4cda2deda4c2cedf9a010e13", size = 4551207, upload-time = "2025-10-16T10:35:16.24Z" }, + { url = "https://files.pythonhosted.org/packages/d3/39/a7ef948ddf4d1c556b0b2b9559534777bccc318543b3f5a1efdf6b556c9c/h5py-3.15.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:99d374a21f7321a4c6ab327c4ab23bd925ad69821aeb53a1e75dd809d19f67fa", size = 5025426, upload-time = "2025-10-16T10:35:19.831Z" }, + { url = "https://files.pythonhosted.org/packages/b6/d8/7368679b8df6925b8415f9dcc9ab1dab01ddc384d2b2c24aac9191bd9ceb/h5py-3.15.1-cp314-cp314-win_amd64.whl", hash = "sha256:9c73d1d7cdb97d5b17ae385153472ce118bed607e43be11e9a9deefaa54e0734", size = 2865704, upload-time = "2025-10-16T10:35:22.658Z" }, + { url = "https://files.pythonhosted.org/packages/d3/b7/4a806f85d62c20157e62e58e03b27513dc9c55499768530acc4f4c5ce4be/h5py-3.15.1-cp314-cp314-win_arm64.whl", hash = "sha256:a6d8c5a05a76aca9a494b4c53ce8a9c29023b7f64f625c6ce1841e92a362ccdf", size = 2465544, upload-time = "2025-10-16T10:35:25.695Z" }, +] + [[package]] name = "humanize" version = "4.14.0" @@ -487,9 +548,11 @@ source = { editable = "." } dependencies = [ { name = "click" }, { name = "datalad" }, + { name = "h5io" }, { name = "lazy-loader" }, { name = "ruamel-yaml" }, { name = "structlog" }, + { name = "tqdm" }, ] [package.optional-dependencies] @@ -504,6 +567,7 @@ dev = [ requires-dist = [ { name = "click", specifier = ">=8.1.3,<8.2" }, { name = "datalad", specifier = ">=1.0.0,<1.3.0" }, + { name = "h5io", git = "https://github.com/juaml/h5io?rev=600d9e25c625b11520d86b08f9f0b761f71b0542" }, { name = "lazy-loader", specifier = "==0.4" }, { name = "pre-commit", marker = "extra == 'dev'" }, { name = "ruamel-yaml", specifier = ">=0.17,<0.19" }, @@ -511,6 +575,7 @@ requires-dist = [ { name = "structlog", specifier = ">=25.0.0,<26.0.0" }, { name = "towncrier", marker = "extra == 'dev'" }, { name = "tox", marker = "extra == 'dev'" }, + { name = "tqdm", specifier = ">=4.66.1,<4.67.0" }, ] provides-extras = ["dev"] @@ -730,6 +795,155 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d2/1d/1b658dbd2b9fa9c4c9f32accbfc0205d532c8c6194dc0f2a4c0428e7128a/nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9", size = 22314, upload-time = "2024-06-04T18:44:08.352Z" }, ] +[[package]] +name = "numpy" +version = "2.2.6" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.11'", +] +sdist = { url = "https://files.pythonhosted.org/packages/76/21/7d2a95e4bba9dc13d043ee156a356c0a8f0c6309dff6b21b4d71a073b8a8/numpy-2.2.6.tar.gz", hash = "sha256:e29554e2bef54a90aa5cc07da6ce955accb83f21ab5de01a62c8478897b264fd", size = 20276440, upload-time = "2025-05-17T22:38:04.611Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9a/3e/ed6db5be21ce87955c0cbd3009f2803f59fa08df21b5df06862e2d8e2bdd/numpy-2.2.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b412caa66f72040e6d268491a59f2c43bf03eb6c96dd8f0307829feb7fa2b6fb", size = 21165245, upload-time = "2025-05-17T21:27:58.555Z" }, + { url = "https://files.pythonhosted.org/packages/22/c2/4b9221495b2a132cc9d2eb862e21d42a009f5a60e45fc44b00118c174bff/numpy-2.2.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8e41fd67c52b86603a91c1a505ebaef50b3314de0213461c7a6e99c9a3beff90", size = 14360048, upload-time = "2025-05-17T21:28:21.406Z" }, + { url = "https://files.pythonhosted.org/packages/fd/77/dc2fcfc66943c6410e2bf598062f5959372735ffda175b39906d54f02349/numpy-2.2.6-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:37e990a01ae6ec7fe7fa1c26c55ecb672dd98b19c3d0e1d1f326fa13cb38d163", size = 5340542, upload-time = "2025-05-17T21:28:30.931Z" }, + { url = "https://files.pythonhosted.org/packages/7a/4f/1cb5fdc353a5f5cc7feb692db9b8ec2c3d6405453f982435efc52561df58/numpy-2.2.6-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:5a6429d4be8ca66d889b7cf70f536a397dc45ba6faeb5f8c5427935d9592e9cf", size = 6878301, upload-time = "2025-05-17T21:28:41.613Z" }, + { url = "https://files.pythonhosted.org/packages/eb/17/96a3acd228cec142fcb8723bd3cc39c2a474f7dcf0a5d16731980bcafa95/numpy-2.2.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:efd28d4e9cd7d7a8d39074a4d44c63eda73401580c5c76acda2ce969e0a38e83", size = 14297320, upload-time = "2025-05-17T21:29:02.78Z" }, + { url = "https://files.pythonhosted.org/packages/b4/63/3de6a34ad7ad6646ac7d2f55ebc6ad439dbbf9c4370017c50cf403fb19b5/numpy-2.2.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc7b73d02efb0e18c000e9ad8b83480dfcd5dfd11065997ed4c6747470ae8915", size = 16801050, upload-time = "2025-05-17T21:29:27.675Z" }, + { url = "https://files.pythonhosted.org/packages/07/b6/89d837eddef52b3d0cec5c6ba0456c1bf1b9ef6a6672fc2b7873c3ec4e2e/numpy-2.2.6-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:74d4531beb257d2c3f4b261bfb0fc09e0f9ebb8842d82a7b4209415896adc680", size = 15807034, upload-time = "2025-05-17T21:29:51.102Z" }, + { url = "https://files.pythonhosted.org/packages/01/c8/dc6ae86e3c61cfec1f178e5c9f7858584049b6093f843bca541f94120920/numpy-2.2.6-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8fc377d995680230e83241d8a96def29f204b5782f371c532579b4f20607a289", size = 18614185, upload-time = "2025-05-17T21:30:18.703Z" }, + { url = "https://files.pythonhosted.org/packages/5b/c5/0064b1b7e7c89137b471ccec1fd2282fceaae0ab3a9550f2568782d80357/numpy-2.2.6-cp310-cp310-win32.whl", hash = "sha256:b093dd74e50a8cba3e873868d9e93a85b78e0daf2e98c6797566ad8044e8363d", size = 6527149, upload-time = "2025-05-17T21:30:29.788Z" }, + { url = "https://files.pythonhosted.org/packages/a3/dd/4b822569d6b96c39d1215dbae0582fd99954dcbcf0c1a13c61783feaca3f/numpy-2.2.6-cp310-cp310-win_amd64.whl", hash = "sha256:f0fd6321b839904e15c46e0d257fdd101dd7f530fe03fd6359c1ea63738703f3", size = 12904620, upload-time = "2025-05-17T21:30:48.994Z" }, + { url = "https://files.pythonhosted.org/packages/da/a8/4f83e2aa666a9fbf56d6118faaaf5f1974d456b1823fda0a176eff722839/numpy-2.2.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f9f1adb22318e121c5c69a09142811a201ef17ab257a1e66ca3025065b7f53ae", size = 21176963, upload-time = "2025-05-17T21:31:19.36Z" }, + { url = "https://files.pythonhosted.org/packages/b3/2b/64e1affc7972decb74c9e29e5649fac940514910960ba25cd9af4488b66c/numpy-2.2.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c820a93b0255bc360f53eca31a0e676fd1101f673dda8da93454a12e23fc5f7a", size = 14406743, upload-time = "2025-05-17T21:31:41.087Z" }, + { url = "https://files.pythonhosted.org/packages/4a/9f/0121e375000b5e50ffdd8b25bf78d8e1a5aa4cca3f185d41265198c7b834/numpy-2.2.6-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:3d70692235e759f260c3d837193090014aebdf026dfd167834bcba43e30c2a42", size = 5352616, upload-time = "2025-05-17T21:31:50.072Z" }, + { url = "https://files.pythonhosted.org/packages/31/0d/b48c405c91693635fbe2dcd7bc84a33a602add5f63286e024d3b6741411c/numpy-2.2.6-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:481b49095335f8eed42e39e8041327c05b0f6f4780488f61286ed3c01368d491", size = 6889579, upload-time = "2025-05-17T21:32:01.712Z" }, + { url = "https://files.pythonhosted.org/packages/52/b8/7f0554d49b565d0171eab6e99001846882000883998e7b7d9f0d98b1f934/numpy-2.2.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b64d8d4d17135e00c8e346e0a738deb17e754230d7e0810ac5012750bbd85a5a", size = 14312005, upload-time = "2025-05-17T21:32:23.332Z" }, + { url = "https://files.pythonhosted.org/packages/b3/dd/2238b898e51bd6d389b7389ffb20d7f4c10066d80351187ec8e303a5a475/numpy-2.2.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba10f8411898fc418a521833e014a77d3ca01c15b0c6cdcce6a0d2897e6dbbdf", size = 16821570, upload-time = "2025-05-17T21:32:47.991Z" }, + { url = "https://files.pythonhosted.org/packages/83/6c/44d0325722cf644f191042bf47eedad61c1e6df2432ed65cbe28509d404e/numpy-2.2.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:bd48227a919f1bafbdda0583705e547892342c26fb127219d60a5c36882609d1", size = 15818548, upload-time = "2025-05-17T21:33:11.728Z" }, + { url = "https://files.pythonhosted.org/packages/ae/9d/81e8216030ce66be25279098789b665d49ff19eef08bfa8cb96d4957f422/numpy-2.2.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9551a499bf125c1d4f9e250377c1ee2eddd02e01eac6644c080162c0c51778ab", size = 18620521, upload-time = "2025-05-17T21:33:39.139Z" }, + { url = "https://files.pythonhosted.org/packages/6a/fd/e19617b9530b031db51b0926eed5345ce8ddc669bb3bc0044b23e275ebe8/numpy-2.2.6-cp311-cp311-win32.whl", hash = "sha256:0678000bb9ac1475cd454c6b8c799206af8107e310843532b04d49649c717a47", size = 6525866, upload-time = "2025-05-17T21:33:50.273Z" }, + { url = "https://files.pythonhosted.org/packages/31/0a/f354fb7176b81747d870f7991dc763e157a934c717b67b58456bc63da3df/numpy-2.2.6-cp311-cp311-win_amd64.whl", hash = "sha256:e8213002e427c69c45a52bbd94163084025f533a55a59d6f9c5b820774ef3303", size = 12907455, upload-time = "2025-05-17T21:34:09.135Z" }, + { url = "https://files.pythonhosted.org/packages/82/5d/c00588b6cf18e1da539b45d3598d3557084990dcc4331960c15ee776ee41/numpy-2.2.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:41c5a21f4a04fa86436124d388f6ed60a9343a6f767fced1a8a71c3fbca038ff", size = 20875348, upload-time = "2025-05-17T21:34:39.648Z" }, + { url = "https://files.pythonhosted.org/packages/66/ee/560deadcdde6c2f90200450d5938f63a34b37e27ebff162810f716f6a230/numpy-2.2.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:de749064336d37e340f640b05f24e9e3dd678c57318c7289d222a8a2f543e90c", size = 14119362, upload-time = "2025-05-17T21:35:01.241Z" }, + { url = "https://files.pythonhosted.org/packages/3c/65/4baa99f1c53b30adf0acd9a5519078871ddde8d2339dc5a7fde80d9d87da/numpy-2.2.6-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:894b3a42502226a1cac872f840030665f33326fc3dac8e57c607905773cdcde3", size = 5084103, upload-time = "2025-05-17T21:35:10.622Z" }, + { url = "https://files.pythonhosted.org/packages/cc/89/e5a34c071a0570cc40c9a54eb472d113eea6d002e9ae12bb3a8407fb912e/numpy-2.2.6-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:71594f7c51a18e728451bb50cc60a3ce4e6538822731b2933209a1f3614e9282", size = 6625382, upload-time = "2025-05-17T21:35:21.414Z" }, + { url = "https://files.pythonhosted.org/packages/f8/35/8c80729f1ff76b3921d5c9487c7ac3de9b2a103b1cd05e905b3090513510/numpy-2.2.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f2618db89be1b4e05f7a1a847a9c1c0abd63e63a1607d892dd54668dd92faf87", size = 14018462, upload-time = "2025-05-17T21:35:42.174Z" }, + { url = "https://files.pythonhosted.org/packages/8c/3d/1e1db36cfd41f895d266b103df00ca5b3cbe965184df824dec5c08c6b803/numpy-2.2.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd83c01228a688733f1ded5201c678f0c53ecc1006ffbc404db9f7a899ac6249", size = 16527618, upload-time = "2025-05-17T21:36:06.711Z" }, + { url = "https://files.pythonhosted.org/packages/61/c6/03ed30992602c85aa3cd95b9070a514f8b3c33e31124694438d88809ae36/numpy-2.2.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:37c0ca431f82cd5fa716eca9506aefcabc247fb27ba69c5062a6d3ade8cf8f49", size = 15505511, upload-time = "2025-05-17T21:36:29.965Z" }, + { url = "https://files.pythonhosted.org/packages/b7/25/5761d832a81df431e260719ec45de696414266613c9ee268394dd5ad8236/numpy-2.2.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fe27749d33bb772c80dcd84ae7e8df2adc920ae8297400dabec45f0dedb3f6de", size = 18313783, upload-time = "2025-05-17T21:36:56.883Z" }, + { url = "https://files.pythonhosted.org/packages/57/0a/72d5a3527c5ebffcd47bde9162c39fae1f90138c961e5296491ce778e682/numpy-2.2.6-cp312-cp312-win32.whl", hash = "sha256:4eeaae00d789f66c7a25ac5f34b71a7035bb474e679f410e5e1a94deb24cf2d4", size = 6246506, upload-time = "2025-05-17T21:37:07.368Z" }, + { url = "https://files.pythonhosted.org/packages/36/fa/8c9210162ca1b88529ab76b41ba02d433fd54fecaf6feb70ef9f124683f1/numpy-2.2.6-cp312-cp312-win_amd64.whl", hash = "sha256:c1f9540be57940698ed329904db803cf7a402f3fc200bfe599334c9bd84a40b2", size = 12614190, upload-time = "2025-05-17T21:37:26.213Z" }, + { url = "https://files.pythonhosted.org/packages/f9/5c/6657823f4f594f72b5471f1db1ab12e26e890bb2e41897522d134d2a3e81/numpy-2.2.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0811bb762109d9708cca4d0b13c4f67146e3c3b7cf8d34018c722adb2d957c84", size = 20867828, upload-time = "2025-05-17T21:37:56.699Z" }, + { url = "https://files.pythonhosted.org/packages/dc/9e/14520dc3dadf3c803473bd07e9b2bd1b69bc583cb2497b47000fed2fa92f/numpy-2.2.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:287cc3162b6f01463ccd86be154f284d0893d2b3ed7292439ea97eafa8170e0b", size = 14143006, upload-time = "2025-05-17T21:38:18.291Z" }, + { url = "https://files.pythonhosted.org/packages/4f/06/7e96c57d90bebdce9918412087fc22ca9851cceaf5567a45c1f404480e9e/numpy-2.2.6-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:f1372f041402e37e5e633e586f62aa53de2eac8d98cbfb822806ce4bbefcb74d", size = 5076765, upload-time = "2025-05-17T21:38:27.319Z" }, + { url = "https://files.pythonhosted.org/packages/73/ed/63d920c23b4289fdac96ddbdd6132e9427790977d5457cd132f18e76eae0/numpy-2.2.6-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:55a4d33fa519660d69614a9fad433be87e5252f4b03850642f88993f7b2ca566", size = 6617736, upload-time = "2025-05-17T21:38:38.141Z" }, + { url = "https://files.pythonhosted.org/packages/85/c5/e19c8f99d83fd377ec8c7e0cf627a8049746da54afc24ef0a0cb73d5dfb5/numpy-2.2.6-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f92729c95468a2f4f15e9bb94c432a9229d0d50de67304399627a943201baa2f", size = 14010719, upload-time = "2025-05-17T21:38:58.433Z" }, + { url = "https://files.pythonhosted.org/packages/19/49/4df9123aafa7b539317bf6d342cb6d227e49f7a35b99c287a6109b13dd93/numpy-2.2.6-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1bc23a79bfabc5d056d106f9befb8d50c31ced2fbc70eedb8155aec74a45798f", size = 16526072, upload-time = "2025-05-17T21:39:22.638Z" }, + { url = "https://files.pythonhosted.org/packages/b2/6c/04b5f47f4f32f7c2b0e7260442a8cbcf8168b0e1a41ff1495da42f42a14f/numpy-2.2.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e3143e4451880bed956e706a3220b4e5cf6172ef05fcc397f6f36a550b1dd868", size = 15503213, upload-time = "2025-05-17T21:39:45.865Z" }, + { url = "https://files.pythonhosted.org/packages/17/0a/5cd92e352c1307640d5b6fec1b2ffb06cd0dabe7d7b8227f97933d378422/numpy-2.2.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b4f13750ce79751586ae2eb824ba7e1e8dba64784086c98cdbbcc6a42112ce0d", size = 18316632, upload-time = "2025-05-17T21:40:13.331Z" }, + { url = "https://files.pythonhosted.org/packages/f0/3b/5cba2b1d88760ef86596ad0f3d484b1cbff7c115ae2429678465057c5155/numpy-2.2.6-cp313-cp313-win32.whl", hash = "sha256:5beb72339d9d4fa36522fc63802f469b13cdbe4fdab4a288f0c441b74272ebfd", size = 6244532, upload-time = "2025-05-17T21:43:46.099Z" }, + { url = "https://files.pythonhosted.org/packages/cb/3b/d58c12eafcb298d4e6d0d40216866ab15f59e55d148a5658bb3132311fcf/numpy-2.2.6-cp313-cp313-win_amd64.whl", hash = "sha256:b0544343a702fa80c95ad5d3d608ea3599dd54d4632df855e4c8d24eb6ecfa1c", size = 12610885, upload-time = "2025-05-17T21:44:05.145Z" }, + { url = "https://files.pythonhosted.org/packages/6b/9e/4bf918b818e516322db999ac25d00c75788ddfd2d2ade4fa66f1f38097e1/numpy-2.2.6-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0bca768cd85ae743b2affdc762d617eddf3bcf8724435498a1e80132d04879e6", size = 20963467, upload-time = "2025-05-17T21:40:44Z" }, + { url = "https://files.pythonhosted.org/packages/61/66/d2de6b291507517ff2e438e13ff7b1e2cdbdb7cb40b3ed475377aece69f9/numpy-2.2.6-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:fc0c5673685c508a142ca65209b4e79ed6740a4ed6b2267dbba90f34b0b3cfda", size = 14225144, upload-time = "2025-05-17T21:41:05.695Z" }, + { url = "https://files.pythonhosted.org/packages/e4/25/480387655407ead912e28ba3a820bc69af9adf13bcbe40b299d454ec011f/numpy-2.2.6-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:5bd4fc3ac8926b3819797a7c0e2631eb889b4118a9898c84f585a54d475b7e40", size = 5200217, upload-time = "2025-05-17T21:41:15.903Z" }, + { url = "https://files.pythonhosted.org/packages/aa/4a/6e313b5108f53dcbf3aca0c0f3e9c92f4c10ce57a0a721851f9785872895/numpy-2.2.6-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:fee4236c876c4e8369388054d02d0e9bb84821feb1a64dd59e137e6511a551f8", size = 6712014, upload-time = "2025-05-17T21:41:27.321Z" }, + { url = "https://files.pythonhosted.org/packages/b7/30/172c2d5c4be71fdf476e9de553443cf8e25feddbe185e0bd88b096915bcc/numpy-2.2.6-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e1dda9c7e08dc141e0247a5b8f49cf05984955246a327d4c48bda16821947b2f", size = 14077935, upload-time = "2025-05-17T21:41:49.738Z" }, + { url = "https://files.pythonhosted.org/packages/12/fb/9e743f8d4e4d3c710902cf87af3512082ae3d43b945d5d16563f26ec251d/numpy-2.2.6-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f447e6acb680fd307f40d3da4852208af94afdfab89cf850986c3ca00562f4fa", size = 16600122, upload-time = "2025-05-17T21:42:14.046Z" }, + { url = "https://files.pythonhosted.org/packages/12/75/ee20da0e58d3a66f204f38916757e01e33a9737d0b22373b3eb5a27358f9/numpy-2.2.6-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:389d771b1623ec92636b0786bc4ae56abafad4a4c513d36a55dce14bd9ce8571", size = 15586143, upload-time = "2025-05-17T21:42:37.464Z" }, + { url = "https://files.pythonhosted.org/packages/76/95/bef5b37f29fc5e739947e9ce5179ad402875633308504a52d188302319c8/numpy-2.2.6-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8e9ace4a37db23421249ed236fdcdd457d671e25146786dfc96835cd951aa7c1", size = 18385260, upload-time = "2025-05-17T21:43:05.189Z" }, + { url = "https://files.pythonhosted.org/packages/09/04/f2f83279d287407cf36a7a8053a5abe7be3622a4363337338f2585e4afda/numpy-2.2.6-cp313-cp313t-win32.whl", hash = "sha256:038613e9fb8c72b0a41f025a7e4c3f0b7a1b5d768ece4796b674c8f3fe13efff", size = 6377225, upload-time = "2025-05-17T21:43:16.254Z" }, + { url = "https://files.pythonhosted.org/packages/67/0e/35082d13c09c02c011cf21570543d202ad929d961c02a147493cb0c2bdf5/numpy-2.2.6-cp313-cp313t-win_amd64.whl", hash = "sha256:6031dd6dfecc0cf9f668681a37648373bddd6421fff6c66ec1624eed0180ee06", size = 12771374, upload-time = "2025-05-17T21:43:35.479Z" }, + { url = "https://files.pythonhosted.org/packages/9e/3b/d94a75f4dbf1ef5d321523ecac21ef23a3cd2ac8b78ae2aac40873590229/numpy-2.2.6-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:0b605b275d7bd0c640cad4e5d30fa701a8d59302e127e5f79138ad62762c3e3d", size = 21040391, upload-time = "2025-05-17T21:44:35.948Z" }, + { url = "https://files.pythonhosted.org/packages/17/f4/09b2fa1b58f0fb4f7c7963a1649c64c4d315752240377ed74d9cd878f7b5/numpy-2.2.6-pp310-pypy310_pp73-macosx_14_0_x86_64.whl", hash = "sha256:7befc596a7dc9da8a337f79802ee8adb30a552a94f792b9c9d18c840055907db", size = 6786754, upload-time = "2025-05-17T21:44:47.446Z" }, + { url = "https://files.pythonhosted.org/packages/af/30/feba75f143bdc868a1cc3f44ccfa6c4b9ec522b36458e738cd00f67b573f/numpy-2.2.6-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce47521a4754c8f4593837384bd3424880629f718d87c5d44f8ed763edd63543", size = 16643476, upload-time = "2025-05-17T21:45:11.871Z" }, + { url = "https://files.pythonhosted.org/packages/37/48/ac2a9584402fb6c0cd5b5d1a91dcf176b15760130dd386bbafdbfe3640bf/numpy-2.2.6-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:d042d24c90c41b54fd506da306759e06e568864df8ec17ccc17e9e884634fd00", size = 12812666, upload-time = "2025-05-17T21:45:31.426Z" }, +] + +[[package]] +name = "numpy" +version = "2.3.5" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.11'", +] +sdist = { url = "https://files.pythonhosted.org/packages/76/65/21b3bc86aac7b8f2862db1e808f1ea22b028e30a225a34a5ede9bf8678f2/numpy-2.3.5.tar.gz", hash = "sha256:784db1dcdab56bf0517743e746dfb0f885fc68d948aba86eeec2cba234bdf1c0", size = 20584950, upload-time = "2025-11-16T22:52:42.067Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/43/77/84dd1d2e34d7e2792a236ba180b5e8fcc1e3e414e761ce0253f63d7f572e/numpy-2.3.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:de5672f4a7b200c15a4127042170a694d4df43c992948f5e1af57f0174beed10", size = 17034641, upload-time = "2025-11-16T22:49:19.336Z" }, + { url = "https://files.pythonhosted.org/packages/2a/ea/25e26fa5837106cde46ae7d0b667e20f69cbbc0efd64cba8221411ab26ae/numpy-2.3.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:acfd89508504a19ed06ef963ad544ec6664518c863436306153e13e94605c218", size = 12528324, upload-time = "2025-11-16T22:49:22.582Z" }, + { url = "https://files.pythonhosted.org/packages/4d/1a/e85f0eea4cf03d6a0228f5c0256b53f2df4bc794706e7df019fc622e47f1/numpy-2.3.5-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:ffe22d2b05504f786c867c8395de703937f934272eb67586817b46188b4ded6d", size = 5356872, upload-time = "2025-11-16T22:49:25.408Z" }, + { url = "https://files.pythonhosted.org/packages/5c/bb/35ef04afd567f4c989c2060cde39211e4ac5357155c1833bcd1166055c61/numpy-2.3.5-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:872a5cf366aec6bb1147336480fef14c9164b154aeb6542327de4970282cd2f5", size = 6893148, upload-time = "2025-11-16T22:49:27.549Z" }, + { url = "https://files.pythonhosted.org/packages/f2/2b/05bbeb06e2dff5eab512dfc678b1cc5ee94d8ac5956a0885c64b6b26252b/numpy-2.3.5-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3095bdb8dd297e5920b010e96134ed91d852d81d490e787beca7e35ae1d89cf7", size = 14557282, upload-time = "2025-11-16T22:49:30.964Z" }, + { url = "https://files.pythonhosted.org/packages/65/fb/2b23769462b34398d9326081fad5655198fcf18966fcb1f1e49db44fbf31/numpy-2.3.5-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8cba086a43d54ca804ce711b2a940b16e452807acebe7852ff327f1ecd49b0d4", size = 16897903, upload-time = "2025-11-16T22:49:34.191Z" }, + { url = "https://files.pythonhosted.org/packages/ac/14/085f4cf05fc3f1e8aa95e85404e984ffca9b2275a5dc2b1aae18a67538b8/numpy-2.3.5-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6cf9b429b21df6b99f4dee7a1218b8b7ffbbe7df8764dc0bd60ce8a0708fed1e", size = 16341672, upload-time = "2025-11-16T22:49:37.2Z" }, + { url = "https://files.pythonhosted.org/packages/6f/3b/1f73994904142b2aa290449b3bb99772477b5fd94d787093e4f24f5af763/numpy-2.3.5-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:396084a36abdb603546b119d96528c2f6263921c50df3c8fd7cb28873a237748", size = 18838896, upload-time = "2025-11-16T22:49:39.727Z" }, + { url = "https://files.pythonhosted.org/packages/cd/b9/cf6649b2124f288309ffc353070792caf42ad69047dcc60da85ee85fea58/numpy-2.3.5-cp311-cp311-win32.whl", hash = "sha256:b0c7088a73aef3d687c4deef8452a3ac7c1be4e29ed8bf3b366c8111128ac60c", size = 6563608, upload-time = "2025-11-16T22:49:42.079Z" }, + { url = "https://files.pythonhosted.org/packages/aa/44/9fe81ae1dcc29c531843852e2874080dc441338574ccc4306b39e2ff6e59/numpy-2.3.5-cp311-cp311-win_amd64.whl", hash = "sha256:a414504bef8945eae5f2d7cb7be2d4af77c5d1cb5e20b296c2c25b61dff2900c", size = 13078442, upload-time = "2025-11-16T22:49:43.99Z" }, + { url = "https://files.pythonhosted.org/packages/6d/a7/f99a41553d2da82a20a2f22e93c94f928e4490bb447c9ff3c4ff230581d3/numpy-2.3.5-cp311-cp311-win_arm64.whl", hash = "sha256:0cd00b7b36e35398fa2d16af7b907b65304ef8bb4817a550e06e5012929830fa", size = 10458555, upload-time = "2025-11-16T22:49:47.092Z" }, + { url = "https://files.pythonhosted.org/packages/44/37/e669fe6cbb2b96c62f6bbedc6a81c0f3b7362f6a59230b23caa673a85721/numpy-2.3.5-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:74ae7b798248fe62021dbf3c914245ad45d1a6b0cb4a29ecb4b31d0bfbc4cc3e", size = 16733873, upload-time = "2025-11-16T22:49:49.84Z" }, + { url = "https://files.pythonhosted.org/packages/c5/65/df0db6c097892c9380851ab9e44b52d4f7ba576b833996e0080181c0c439/numpy-2.3.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ee3888d9ff7c14604052b2ca5535a30216aa0a58e948cdd3eeb8d3415f638769", size = 12259838, upload-time = "2025-11-16T22:49:52.863Z" }, + { url = "https://files.pythonhosted.org/packages/5b/e1/1ee06e70eb2136797abe847d386e7c0e830b67ad1d43f364dd04fa50d338/numpy-2.3.5-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:612a95a17655e213502f60cfb9bf9408efdc9eb1d5f50535cc6eb365d11b42b5", size = 5088378, upload-time = "2025-11-16T22:49:55.055Z" }, + { url = "https://files.pythonhosted.org/packages/6d/9c/1ca85fb86708724275103b81ec4cf1ac1d08f465368acfc8da7ab545bdae/numpy-2.3.5-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:3101e5177d114a593d79dd79658650fe28b5a0d8abeb8ce6f437c0e6df5be1a4", size = 6628559, upload-time = "2025-11-16T22:49:57.371Z" }, + { url = "https://files.pythonhosted.org/packages/74/78/fcd41e5a0ce4f3f7b003da85825acddae6d7ecb60cf25194741b036ca7d6/numpy-2.3.5-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8b973c57ff8e184109db042c842423ff4f60446239bd585a5131cc47f06f789d", size = 14250702, upload-time = "2025-11-16T22:49:59.632Z" }, + { url = "https://files.pythonhosted.org/packages/b6/23/2a1b231b8ff672b4c450dac27164a8b2ca7d9b7144f9c02d2396518352eb/numpy-2.3.5-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0d8163f43acde9a73c2a33605353a4f1bc4798745a8b1d73183b28e5b435ae28", size = 16606086, upload-time = "2025-11-16T22:50:02.127Z" }, + { url = "https://files.pythonhosted.org/packages/a0/c5/5ad26fbfbe2012e190cc7d5003e4d874b88bb18861d0829edc140a713021/numpy-2.3.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:51c1e14eb1e154ebd80e860722f9e6ed6ec89714ad2db2d3aa33c31d7c12179b", size = 16025985, upload-time = "2025-11-16T22:50:04.536Z" }, + { url = "https://files.pythonhosted.org/packages/d2/fa/dd48e225c46c819288148d9d060b047fd2a6fb1eb37eae25112ee4cb4453/numpy-2.3.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b46b4ec24f7293f23adcd2d146960559aaf8020213de8ad1909dba6c013bf89c", size = 18542976, upload-time = "2025-11-16T22:50:07.557Z" }, + { url = "https://files.pythonhosted.org/packages/05/79/ccbd23a75862d95af03d28b5c6901a1b7da4803181513d52f3b86ed9446e/numpy-2.3.5-cp312-cp312-win32.whl", hash = "sha256:3997b5b3c9a771e157f9aae01dd579ee35ad7109be18db0e85dbdbe1de06e952", size = 6285274, upload-time = "2025-11-16T22:50:10.746Z" }, + { url = "https://files.pythonhosted.org/packages/2d/57/8aeaf160312f7f489dea47ab61e430b5cb051f59a98ae68b7133ce8fa06a/numpy-2.3.5-cp312-cp312-win_amd64.whl", hash = "sha256:86945f2ee6d10cdfd67bcb4069c1662dd711f7e2a4343db5cecec06b87cf31aa", size = 12782922, upload-time = "2025-11-16T22:50:12.811Z" }, + { url = "https://files.pythonhosted.org/packages/78/a6/aae5cc2ca78c45e64b9ef22f089141d661516856cf7c8a54ba434576900d/numpy-2.3.5-cp312-cp312-win_arm64.whl", hash = "sha256:f28620fe26bee16243be2b7b874da327312240a7cdc38b769a697578d2100013", size = 10194667, upload-time = "2025-11-16T22:50:16.16Z" }, + { url = "https://files.pythonhosted.org/packages/db/69/9cde09f36da4b5a505341180a3f2e6fadc352fd4d2b7096ce9778db83f1a/numpy-2.3.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d0f23b44f57077c1ede8c5f26b30f706498b4862d3ff0a7298b8411dd2f043ff", size = 16728251, upload-time = "2025-11-16T22:50:19.013Z" }, + { url = "https://files.pythonhosted.org/packages/79/fb/f505c95ceddd7027347b067689db71ca80bd5ecc926f913f1a23e65cf09b/numpy-2.3.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:aa5bc7c5d59d831d9773d1170acac7893ce3a5e130540605770ade83280e7188", size = 12254652, upload-time = "2025-11-16T22:50:21.487Z" }, + { url = "https://files.pythonhosted.org/packages/78/da/8c7738060ca9c31b30e9301ee0cf6c5ffdbf889d9593285a1cead337f9a5/numpy-2.3.5-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:ccc933afd4d20aad3c00bcef049cb40049f7f196e0397f1109dba6fed63267b0", size = 5083172, upload-time = "2025-11-16T22:50:24.562Z" }, + { url = "https://files.pythonhosted.org/packages/a4/b4/ee5bb2537fb9430fd2ef30a616c3672b991a4129bb1c7dcc42aa0abbe5d7/numpy-2.3.5-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:afaffc4393205524af9dfa400fa250143a6c3bc646c08c9f5e25a9f4b4d6a903", size = 6622990, upload-time = "2025-11-16T22:50:26.47Z" }, + { url = "https://files.pythonhosted.org/packages/95/03/dc0723a013c7d7c19de5ef29e932c3081df1c14ba582b8b86b5de9db7f0f/numpy-2.3.5-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9c75442b2209b8470d6d5d8b1c25714270686f14c749028d2199c54e29f20b4d", size = 14248902, upload-time = "2025-11-16T22:50:28.861Z" }, + { url = "https://files.pythonhosted.org/packages/f5/10/ca162f45a102738958dcec8023062dad0cbc17d1ab99d68c4e4a6c45fb2b/numpy-2.3.5-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:11e06aa0af8c0f05104d56450d6093ee639e15f24ecf62d417329d06e522e017", size = 16597430, upload-time = "2025-11-16T22:50:31.56Z" }, + { url = "https://files.pythonhosted.org/packages/2a/51/c1e29be863588db58175175f057286900b4b3327a1351e706d5e0f8dd679/numpy-2.3.5-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ed89927b86296067b4f81f108a2271d8926467a8868e554eaf370fc27fa3ccaf", size = 16024551, upload-time = "2025-11-16T22:50:34.242Z" }, + { url = "https://files.pythonhosted.org/packages/83/68/8236589d4dbb87253d28259d04d9b814ec0ecce7cb1c7fed29729f4c3a78/numpy-2.3.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:51c55fe3451421f3a6ef9a9c1439e82101c57a2c9eab9feb196a62b1a10b58ce", size = 18533275, upload-time = "2025-11-16T22:50:37.651Z" }, + { url = "https://files.pythonhosted.org/packages/40/56/2932d75b6f13465239e3b7b7e511be27f1b8161ca2510854f0b6e521c395/numpy-2.3.5-cp313-cp313-win32.whl", hash = "sha256:1978155dd49972084bd6ef388d66ab70f0c323ddee6f693d539376498720fb7e", size = 6277637, upload-time = "2025-11-16T22:50:40.11Z" }, + { url = "https://files.pythonhosted.org/packages/0c/88/e2eaa6cffb115b85ed7c7c87775cb8bcf0816816bc98ca8dbfa2ee33fe6e/numpy-2.3.5-cp313-cp313-win_amd64.whl", hash = "sha256:00dc4e846108a382c5869e77c6ed514394bdeb3403461d25a829711041217d5b", size = 12779090, upload-time = "2025-11-16T22:50:42.503Z" }, + { url = "https://files.pythonhosted.org/packages/8f/88/3f41e13a44ebd4034ee17baa384acac29ba6a4fcc2aca95f6f08ca0447d1/numpy-2.3.5-cp313-cp313-win_arm64.whl", hash = "sha256:0472f11f6ec23a74a906a00b48a4dcf3849209696dff7c189714511268d103ae", size = 10194710, upload-time = "2025-11-16T22:50:44.971Z" }, + { url = "https://files.pythonhosted.org/packages/13/cb/71744144e13389d577f867f745b7df2d8489463654a918eea2eeb166dfc9/numpy-2.3.5-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:414802f3b97f3c1eef41e530aaba3b3c1620649871d8cb38c6eaff034c2e16bd", size = 16827292, upload-time = "2025-11-16T22:50:47.715Z" }, + { url = "https://files.pythonhosted.org/packages/71/80/ba9dc6f2a4398e7f42b708a7fdc841bb638d353be255655498edbf9a15a8/numpy-2.3.5-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5ee6609ac3604fa7780e30a03e5e241a7956f8e2fcfe547d51e3afa5247ac47f", size = 12378897, upload-time = "2025-11-16T22:50:51.327Z" }, + { url = "https://files.pythonhosted.org/packages/2e/6d/db2151b9f64264bcceccd51741aa39b50150de9b602d98ecfe7e0c4bff39/numpy-2.3.5-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:86d835afea1eaa143012a2d7a3f45a3adce2d7adc8b4961f0b362214d800846a", size = 5207391, upload-time = "2025-11-16T22:50:54.542Z" }, + { url = "https://files.pythonhosted.org/packages/80/ae/429bacace5ccad48a14c4ae5332f6aa8ab9f69524193511d60ccdfdc65fa/numpy-2.3.5-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:30bc11310e8153ca664b14c5f1b73e94bd0503681fcf136a163de856f3a50139", size = 6721275, upload-time = "2025-11-16T22:50:56.794Z" }, + { url = "https://files.pythonhosted.org/packages/74/5b/1919abf32d8722646a38cd527bc3771eb229a32724ee6ba340ead9b92249/numpy-2.3.5-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1062fde1dcf469571705945b0f221b73928f34a20c904ffb45db101907c3454e", size = 14306855, upload-time = "2025-11-16T22:50:59.208Z" }, + { url = "https://files.pythonhosted.org/packages/a5/87/6831980559434973bebc30cd9c1f21e541a0f2b0c280d43d3afd909b66d0/numpy-2.3.5-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ce581db493ea1a96c0556360ede6607496e8bf9b3a8efa66e06477267bc831e9", size = 16657359, upload-time = "2025-11-16T22:51:01.991Z" }, + { url = "https://files.pythonhosted.org/packages/dd/91/c797f544491ee99fd00495f12ebb7802c440c1915811d72ac5b4479a3356/numpy-2.3.5-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:cc8920d2ec5fa99875b670bb86ddeb21e295cb07aa331810d9e486e0b969d946", size = 16093374, upload-time = "2025-11-16T22:51:05.291Z" }, + { url = "https://files.pythonhosted.org/packages/74/a6/54da03253afcbe7a72785ec4da9c69fb7a17710141ff9ac5fcb2e32dbe64/numpy-2.3.5-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:9ee2197ef8c4f0dfe405d835f3b6a14f5fee7782b5de51ba06fb65fc9b36e9f1", size = 18594587, upload-time = "2025-11-16T22:51:08.585Z" }, + { url = "https://files.pythonhosted.org/packages/80/e9/aff53abbdd41b0ecca94285f325aff42357c6b5abc482a3fcb4994290b18/numpy-2.3.5-cp313-cp313t-win32.whl", hash = "sha256:70b37199913c1bd300ff6e2693316c6f869c7ee16378faf10e4f5e3275b299c3", size = 6405940, upload-time = "2025-11-16T22:51:11.541Z" }, + { url = "https://files.pythonhosted.org/packages/d5/81/50613fec9d4de5480de18d4f8ef59ad7e344d497edbef3cfd80f24f98461/numpy-2.3.5-cp313-cp313t-win_amd64.whl", hash = "sha256:b501b5fa195cc9e24fe102f21ec0a44dffc231d2af79950b451e0d99cea02234", size = 12920341, upload-time = "2025-11-16T22:51:14.312Z" }, + { url = "https://files.pythonhosted.org/packages/bb/ab/08fd63b9a74303947f34f0bd7c5903b9c5532c2d287bead5bdf4c556c486/numpy-2.3.5-cp313-cp313t-win_arm64.whl", hash = "sha256:a80afd79f45f3c4a7d341f13acbe058d1ca8ac017c165d3fa0d3de6bc1a079d7", size = 10262507, upload-time = "2025-11-16T22:51:16.846Z" }, + { url = "https://files.pythonhosted.org/packages/ba/97/1a914559c19e32d6b2e233cf9a6a114e67c856d35b1d6babca571a3e880f/numpy-2.3.5-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:bf06bc2af43fa8d32d30fae16ad965663e966b1a3202ed407b84c989c3221e82", size = 16735706, upload-time = "2025-11-16T22:51:19.558Z" }, + { url = "https://files.pythonhosted.org/packages/57/d4/51233b1c1b13ecd796311216ae417796b88b0616cfd8a33ae4536330748a/numpy-2.3.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:052e8c42e0c49d2575621c158934920524f6c5da05a1d3b9bab5d8e259e045f0", size = 12264507, upload-time = "2025-11-16T22:51:22.492Z" }, + { url = "https://files.pythonhosted.org/packages/45/98/2fe46c5c2675b8306d0b4a3ec3494273e93e1226a490f766e84298576956/numpy-2.3.5-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:1ed1ec893cff7040a02c8aa1c8611b94d395590d553f6b53629a4461dc7f7b63", size = 5093049, upload-time = "2025-11-16T22:51:25.171Z" }, + { url = "https://files.pythonhosted.org/packages/ce/0e/0698378989bb0ac5f1660c81c78ab1fe5476c1a521ca9ee9d0710ce54099/numpy-2.3.5-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:2dcd0808a421a482a080f89859a18beb0b3d1e905b81e617a188bd80422d62e9", size = 6626603, upload-time = "2025-11-16T22:51:27Z" }, + { url = "https://files.pythonhosted.org/packages/5e/a6/9ca0eecc489640615642a6cbc0ca9e10df70df38c4d43f5a928ff18d8827/numpy-2.3.5-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:727fd05b57df37dc0bcf1a27767a3d9a78cbbc92822445f32cc3436ba797337b", size = 14262696, upload-time = "2025-11-16T22:51:29.402Z" }, + { url = "https://files.pythonhosted.org/packages/c8/f6/07ec185b90ec9d7217a00eeeed7383b73d7e709dae2a9a021b051542a708/numpy-2.3.5-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fffe29a1ef00883599d1dc2c51aa2e5d80afe49523c261a74933df395c15c520", size = 16597350, upload-time = "2025-11-16T22:51:32.167Z" }, + { url = "https://files.pythonhosted.org/packages/75/37/164071d1dde6a1a84c9b8e5b414fa127981bad47adf3a6b7e23917e52190/numpy-2.3.5-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:8f7f0e05112916223d3f438f293abf0727e1181b5983f413dfa2fefc4098245c", size = 16040190, upload-time = "2025-11-16T22:51:35.403Z" }, + { url = "https://files.pythonhosted.org/packages/08/3c/f18b82a406b04859eb026d204e4e1773eb41c5be58410f41ffa511d114ae/numpy-2.3.5-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2e2eb32ddb9ccb817d620ac1d8dae7c3f641c1e5f55f531a33e8ab97960a75b8", size = 18536749, upload-time = "2025-11-16T22:51:39.698Z" }, + { url = "https://files.pythonhosted.org/packages/40/79/f82f572bf44cf0023a2fe8588768e23e1592585020d638999f15158609e1/numpy-2.3.5-cp314-cp314-win32.whl", hash = "sha256:66f85ce62c70b843bab1fb14a05d5737741e74e28c7b8b5a064de10142fad248", size = 6335432, upload-time = "2025-11-16T22:51:42.476Z" }, + { url = "https://files.pythonhosted.org/packages/a3/2e/235b4d96619931192c91660805e5e49242389742a7a82c27665021db690c/numpy-2.3.5-cp314-cp314-win_amd64.whl", hash = "sha256:e6a0bc88393d65807d751a614207b7129a310ca4fe76a74e5c7da5fa5671417e", size = 12919388, upload-time = "2025-11-16T22:51:45.275Z" }, + { url = "https://files.pythonhosted.org/packages/07/2b/29fd75ce45d22a39c61aad74f3d718e7ab67ccf839ca8b60866054eb15f8/numpy-2.3.5-cp314-cp314-win_arm64.whl", hash = "sha256:aeffcab3d4b43712bb7a60b65f6044d444e75e563ff6180af8f98dd4b905dfd2", size = 10476651, upload-time = "2025-11-16T22:51:47.749Z" }, + { url = "https://files.pythonhosted.org/packages/17/e1/f6a721234ebd4d87084cfa68d081bcba2f5cfe1974f7de4e0e8b9b2a2ba1/numpy-2.3.5-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:17531366a2e3a9e30762c000f2c43a9aaa05728712e25c11ce1dbe700c53ad41", size = 16834503, upload-time = "2025-11-16T22:51:50.443Z" }, + { url = "https://files.pythonhosted.org/packages/5c/1c/baf7ffdc3af9c356e1c135e57ab7cf8d247931b9554f55c467efe2c69eff/numpy-2.3.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:d21644de1b609825ede2f48be98dfde4656aefc713654eeee280e37cadc4e0ad", size = 12381612, upload-time = "2025-11-16T22:51:53.609Z" }, + { url = "https://files.pythonhosted.org/packages/74/91/f7f0295151407ddc9ba34e699013c32c3c91944f9b35fcf9281163dc1468/numpy-2.3.5-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:c804e3a5aba5460c73955c955bdbd5c08c354954e9270a2c1565f62e866bdc39", size = 5210042, upload-time = "2025-11-16T22:51:56.213Z" }, + { url = "https://files.pythonhosted.org/packages/2e/3b/78aebf345104ec50dd50a4d06ddeb46a9ff5261c33bcc58b1c4f12f85ec2/numpy-2.3.5-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:cc0a57f895b96ec78969c34f682c602bf8da1a0270b09bc65673df2e7638ec20", size = 6724502, upload-time = "2025-11-16T22:51:58.584Z" }, + { url = "https://files.pythonhosted.org/packages/02/c6/7c34b528740512e57ef1b7c8337ab0b4f0bddf34c723b8996c675bc2bc91/numpy-2.3.5-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:900218e456384ea676e24ea6a0417f030a3b07306d29d7ad843957b40a9d8d52", size = 14308962, upload-time = "2025-11-16T22:52:01.698Z" }, + { url = "https://files.pythonhosted.org/packages/80/35/09d433c5262bc32d725bafc619e095b6a6651caf94027a03da624146f655/numpy-2.3.5-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:09a1bea522b25109bf8e6f3027bd810f7c1085c64a0c7ce050c1676ad0ba010b", size = 16655054, upload-time = "2025-11-16T22:52:04.267Z" }, + { url = "https://files.pythonhosted.org/packages/7a/ab/6a7b259703c09a88804fa2430b43d6457b692378f6b74b356155283566ac/numpy-2.3.5-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:04822c00b5fd0323c8166d66c701dc31b7fbd252c100acd708c48f763968d6a3", size = 16091613, upload-time = "2025-11-16T22:52:08.651Z" }, + { url = "https://files.pythonhosted.org/packages/c2/88/330da2071e8771e60d1038166ff9d73f29da37b01ec3eb43cb1427464e10/numpy-2.3.5-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:d6889ec4ec662a1a37eb4b4fb26b6100841804dac55bd9df579e326cdc146227", size = 18591147, upload-time = "2025-11-16T22:52:11.453Z" }, + { url = "https://files.pythonhosted.org/packages/51/41/851c4b4082402d9ea860c3626db5d5df47164a712cb23b54be028b184c1c/numpy-2.3.5-cp314-cp314t-win32.whl", hash = "sha256:93eebbcf1aafdf7e2ddd44c2923e2672e1010bddc014138b229e49725b4d6be5", size = 6479806, upload-time = "2025-11-16T22:52:14.641Z" }, + { url = "https://files.pythonhosted.org/packages/90/30/d48bde1dfd93332fa557cff1972fbc039e055a52021fbef4c2c4b1eefd17/numpy-2.3.5-cp314-cp314t-win_amd64.whl", hash = "sha256:c8a9958e88b65c3b27e22ca2a076311636850b612d6bbfb76e8d156aacde2aaf", size = 13105760, upload-time = "2025-11-16T22:52:17.975Z" }, + { url = "https://files.pythonhosted.org/packages/2d/fd/4b5eb0b3e888d86aee4d198c23acec7d214baaf17ea93c1adec94c9518b9/numpy-2.3.5-cp314-cp314t-win_arm64.whl", hash = "sha256:6203fdf9f3dc5bdaed7319ad8698e685c7a3be10819f41d32a0723e611733b42", size = 10545459, upload-time = "2025-11-16T22:52:20.55Z" }, + { url = "https://files.pythonhosted.org/packages/c6/65/f9dea8e109371ade9c782b4e4756a82edf9d3366bca495d84d79859a0b79/numpy-2.3.5-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:f0963b55cdd70fad460fa4c1341f12f976bb26cb66021a5580329bd498988310", size = 16910689, upload-time = "2025-11-16T22:52:23.247Z" }, + { url = "https://files.pythonhosted.org/packages/00/4f/edb00032a8fb92ec0a679d3830368355da91a69cab6f3e9c21b64d0bb986/numpy-2.3.5-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:f4255143f5160d0de972d28c8f9665d882b5f61309d8362fdd3e103cf7bf010c", size = 12457053, upload-time = "2025-11-16T22:52:26.367Z" }, + { url = "https://files.pythonhosted.org/packages/16/a4/e8a53b5abd500a63836a29ebe145fc1ab1f2eefe1cfe59276020373ae0aa/numpy-2.3.5-pp311-pypy311_pp73-macosx_14_0_arm64.whl", hash = "sha256:a4b9159734b326535f4dd01d947f919c6eefd2d9827466a696c44ced82dfbc18", size = 5285635, upload-time = "2025-11-16T22:52:29.266Z" }, + { url = "https://files.pythonhosted.org/packages/a3/2f/37eeb9014d9c8b3e9c55bc599c68263ca44fdbc12a93e45a21d1d56df737/numpy-2.3.5-pp311-pypy311_pp73-macosx_14_0_x86_64.whl", hash = "sha256:2feae0d2c91d46e59fcd62784a3a83b3fb677fead592ce51b5a6fbb4f95965ff", size = 6801770, upload-time = "2025-11-16T22:52:31.421Z" }, + { url = "https://files.pythonhosted.org/packages/7d/e4/68d2f474df2cb671b2b6c2986a02e520671295647dad82484cde80ca427b/numpy-2.3.5-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ffac52f28a7849ad7576293c0cb7b9f08304e8f7d738a8cb8a90ec4c55a998eb", size = 14391768, upload-time = "2025-11-16T22:52:33.593Z" }, + { url = "https://files.pythonhosted.org/packages/b8/50/94ccd8a2b141cb50651fddd4f6a48874acb3c91c8f0842b08a6afc4b0b21/numpy-2.3.5-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:63c0e9e7eea69588479ebf4a8a270d5ac22763cc5854e9a7eae952a3908103f7", size = 16729263, upload-time = "2025-11-16T22:52:36.369Z" }, + { url = "https://files.pythonhosted.org/packages/2d/ee/346fa473e666fe14c52fcdd19ec2424157290a032d4c41f98127bfb31ac7/numpy-2.3.5-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:f16417ec91f12f814b10bafe79ef77e70113a2f5f7018640e7425ff979253425", size = 12967213, upload-time = "2025-11-16T22:52:39.38Z" }, +] + [[package]] name = "packaging" version = "25.0" @@ -1173,14 +1387,14 @@ wheels = [ [[package]] name = "tqdm" -version = "4.67.1" +version = "4.66.6" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a8/4b/29b4ef32e036bb34e4ab51796dd745cdba7ed47ad142a9f4a1eb8e0c744d/tqdm-4.67.1.tar.gz", hash = "sha256:f8aef9c52c08c13a65f30ea34f4e5aac3fd1a34959879d7e59e63027286627f2", size = 169737, upload-time = "2024-11-24T20:12:22.481Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e9/34/bef135b27fe1864993a5284ad001157ee9b5538e859ac90f5b0e8cc8c9ec/tqdm-4.66.6.tar.gz", hash = "sha256:4bdd694238bef1485ce839d67967ab50af8f9272aab687c0d7702a01da0be090", size = 169533, upload-time = "2024-10-28T12:49:58.611Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d0/30/dc54f88dd4a2b5dc8a0279bdd7270e735851848b762aeb1c1184ed1f6b14/tqdm-4.67.1-py3-none-any.whl", hash = "sha256:26445eca388f82e72884e0d580d5464cd801a3ea01e63e5601bdff9ba6a48de2", size = 78540, upload-time = "2024-11-24T20:12:19.698Z" }, + { url = "https://files.pythonhosted.org/packages/41/73/02342de9c2d20922115f787e101527b831c0cffd2105c946c4a4826bcfd4/tqdm-4.66.6-py3-none-any.whl", hash = "sha256:223e8b5359c2efc4b30555531f09e9f2f3589bcd7fdd389271191031b49b7a63", size = 78326, upload-time = "2024-10-28T12:49:56.931Z" }, ] [[package]] From c51aab6bc3331a4adaddf54602edbdba85af2315 Mon Sep 17 00:00:00 2001 From: Synchon Mandal Date: Wed, 17 Dec 2025 11:28:59 +0100 Subject: [PATCH 03/32] feat(cli): introduce add command --- julio/_cli.py | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/julio/_cli.py b/julio/_cli.py index 42a922e..d61ac29 100644 --- a/julio/_cli.py +++ b/julio/_cli.py @@ -7,12 +7,14 @@ import logging.config import pathlib import sys +from pathlib import Path import click import datalad import structlog from . import _functions as cli_func +from . import _utils as cli_utils __all__ = ["cli", "create"] @@ -152,3 +154,41 @@ def create( click.echo(f"{err}", err=True) else: click.echo("Success") + + +@cli.command +@click.argument( + "yaml_path", + type=click.Path( + exists=True, + readable=True, + writable=True, + dir_okay=False, + path_type=pathlib.Path, + ), + metavar="", +) +@click.option( + "-r", + "--registry", + default=None, + type=cli_utils.PathOrURL, + metavar="", + help="Path to registry; if not passed, use current directory", +) +@click.option("-v", "--verbose", count=True, type=int) +def add( + yaml_path: click.Path, + registry: click.Path, + verbose: int, +) -> None: + """Add feature(s) registry.""" + _set_log_config(verbose) + try: + cli_func.add( + yaml_path, registry if registry is not None else Path(".") + ) + except RuntimeError as err: + click.echo(f"{err}", err=True) + else: + click.echo("Success") From 32b2e8d999c7a90c5c2ab4b023927a5a46a98bc6 Mon Sep 17 00:00:00 2001 From: Synchon Mandal Date: Wed, 17 Dec 2025 11:30:01 +0100 Subject: [PATCH 04/32] feat(functions): introduce add command --- julio/_functions.py | 56 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 55 insertions(+), 1 deletion(-) diff --git a/julio/_functions.py b/julio/_functions.py index 8f5ce90..7cc4a73 100644 --- a/julio/_functions.py +++ b/julio/_functions.py @@ -3,14 +3,17 @@ # Authors: Synchon Mandal # License: AGPL +import tempfile from pathlib import Path import datalad.api as dl import structlog from datalad.support.exceptions import IncompleteResultsError +from ._utils import is_julio_registry, process_features -__all__ = ["create"] + +__all__ = ["add", "create"] logger = structlog.get_logger() @@ -54,3 +57,54 @@ def create(registry_path: Path): on_failure="stop", result_renderer="disabled", ) + + +def add(yaml_path: Path, registry_path: str | Path) -> None: + """Add feature(s) from `yaml_path` to the registry at `registry_path`. + + Parameters + ---------- + yaml_path : pathlib.Path + Path to the junifer YAML. + registry_path : str or pathlib.Path + Path to the existing julio registry. + + Raises + ------ + RuntimeError + If there is a problem cloning a remote registry or + if the dataset is not a julio registry. + + """ + log = logger.bind( + cmd="create", + path=registry_path if str else str(registry_path.resolve()), + ) + if isinstance(registry_path, str): + log.debug("Cloning remote registry") + with tempfile.TemporaryDirectory() as tmpdir: + log.debug(f"Temporary directory created at {tmpdir}") + try: + ds = dl.clone( + source=registry_path, + path=tmpdir, + on_failure="stop", + result_renderer="disabled", + ) + except IncompleteResultsError as e: + raise RuntimeError( + f"Failed to clone dataset: {e.failed}" + ) from e + else: + log.debug("Remote registry cloned successfully") + if not is_julio_registry(ds): + raise RuntimeError( + f"Dataset at {ds.path} is not a julio registry" + ) + process_features(yaml_path, ds) + # TODO: push changes to remote + else: + ds = dl.Dataset(registry_path) + if not is_julio_registry(ds): + raise RuntimeError(f"Dataset at {ds.path} is not a julio registry") + process_features(yaml_path, ds) From 4c6a294993228849616934521bf0749fb6b701d2 Mon Sep 17 00:00:00 2001 From: Synchon Mandal Date: Wed, 17 Dec 2025 11:30:29 +0100 Subject: [PATCH 05/32] chore(functions): update create --- julio/_functions.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/julio/_functions.py b/julio/_functions.py index 7cc4a73..21dff5e 100644 --- a/julio/_functions.py +++ b/julio/_functions.py @@ -19,7 +19,7 @@ logger = structlog.get_logger() -def create(registry_path: Path): +def create(registry_path: Path) -> None: """Create a registry at `registry_path`. Parameters @@ -49,7 +49,7 @@ def create(registry_path: Path): path=str(registry_path.resolve()), ) # Add config file - conf_path = Path(ds.path) / "registry-config.yml" + conf_path = ds.pathobj / "registry-config.yml" conf_path.touch() ds.save( conf_path, From ff90bd8b9010bf4d90ffa65efcbe2ffb0e775036 Mon Sep 17 00:00:00 2001 From: Synchon Mandal Date: Mon, 2 Mar 2026 16:43:46 +0100 Subject: [PATCH 06/32] update(utils): improve feature yaml generation --- julio/_utils.py | 42 ++++++++++++++++++++---------------------- 1 file changed, 20 insertions(+), 22 deletions(-) diff --git a/julio/_utils.py b/julio/_utils.py index 099b2f1..832d00c 100644 --- a/julio/_utils.py +++ b/julio/_utils.py @@ -245,15 +245,13 @@ def _parse_yaml(yaml_path: Path) -> dict: return contents -def _feature_yaml_from_meta(data: dict, feature: str) -> dict: +def _generate_feature_yaml(meta: dict) -> dict: """Generate a feature YAML from metadata. Parameters ---------- - data : dict - Dictionary containing the HDF5 metadata. - feature : str - MD5 hash of the feature. + meta : dict + Feature metadata as dictionary. Returns ------- @@ -263,36 +261,36 @@ def _feature_yaml_from_meta(data: dict, feature: str) -> dict: """ y: dict[str, Any] = {} y["workdir"] = "" - if "with" in data: - y["with"] = data["with"].copy() + if "with" in meta: + y["with"] = meta["with"].copy() # Set datagrabber - y["datagrabber"] = data["datagrabber"].copy() + y["datagrabber"] = meta["datagrabber"].copy() a = y["datagrabber"].pop("class") y["datagrabber"]["kind"] = a if a not in ("PatternDataGrabber", "PatternDataladDataGrabber"): - _ = y["datagrabber"].pop("uri") - _ = y["datagrabber"].pop("rootdir") - _ = y["datagrabber"].pop("patterns") - _ = y["datagrabber"].pop("replacements") - _ = y["datagrabber"].pop("confounds_format") - _ = y["datagrabber"].pop("partial_pattern_ok") - for k in data[ + y["datagrabber"].pop("uri") + y["datagrabber"].pop("rootdir") + y["datagrabber"].pop("patterns") + y["datagrabber"].pop("replacements") + y["datagrabber"].pop("confounds_format") + y["datagrabber"].pop("partial_pattern_ok") + for k in meta[ "datagrabber" ].keys(): # use data instead of y to avoid .copy() if k.startswith("datalad"): - _ = y["datagrabber"].pop(k) + y["datagrabber"].pop(k) # Set preprocess - if "preprocess" in data: - y["preprocess"] = data["preprocess"].copy() + if "preprocess" in meta: + y["preprocess"] = meta["preprocess"].copy() b = y["preprocess"].pop("class") y["preprocess"]["kind"] = b # Set markers y["markers"] = [] - y["markers"].append(data["marker"].copy()) + y["markers"].append(meta["marker"].copy()) c = y["markers"][0].pop("class") y["markers"][0]["kind"] = c if y["markers"][0]["masks"] is None: - _ = y["markers"][0].pop("masks") + y["markers"][0].pop("masks") # Set storage y["storage"] = { "kind": "HDF5FeatureStorage", @@ -300,7 +298,7 @@ def _feature_yaml_from_meta(data: dict, feature: str) -> dict: } # Set queue y["queue"] = { - "jobname": data["name"], + "jobname": meta["name"], "kind": "", } return y @@ -336,7 +334,7 @@ def _process_hdf5(data: dict, ds: dl.Dataset) -> dl.Dataset: meta_path = feature_dir / f"feature-{k}-meta.yaml" yaml.dump(v, stream=meta_path.open("w")) # YAML - yaml_data = _feature_yaml_from_meta(v, k) + yaml_data = _generate_feature_yaml(v) yaml_path = feature_dir / f"feature-{k}.yml" yaml.dump(yaml_data, stream=yaml_path.open("w")) # Data From 6e06fa0f85d154508aac032161c15127a257f0df Mon Sep 17 00:00:00 2001 From: Synchon Mandal Date: Mon, 2 Mar 2026 16:47:43 +0100 Subject: [PATCH 07/32] update(utils): improve feature meta yaml generation --- julio/_utils.py | 84 +++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 81 insertions(+), 3 deletions(-) diff --git a/julio/_utils.py b/julio/_utils.py index 832d00c..584a3dc 100644 --- a/julio/_utils.py +++ b/julio/_utils.py @@ -5,6 +5,8 @@ import pathlib import re +import sys +from datetime import datetime from pathlib import Path from typing import Any @@ -305,6 +307,74 @@ def _generate_feature_yaml(meta: dict) -> dict: def _process_hdf5(data: dict, ds: dl.Dataset) -> dl.Dataset: +def _generate_meta_yaml( + meta: dict, + data: dict, + md5: str, + mappings: dict, + dataset_display_name: str | None, +) -> dict: + """Generate a feature meta YAML from data. + + Parameters + ---------- + meta : dict + Feature metadata as dictionary. + data : dict + Feature data as dictionary. + md5 : str + Feature MD5. + mappings : dict + Registry mappings as dictionary. + dataset_display_name : str or None + Dataset display name. + + Returns + ------- + dict + Feature meta YAML. + + """ + y: dict[str, Any] = {} + y["md5"] = md5 + y["name"] = meta["name"] + y["added_on"] = datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S") + ndata = data["data"] + y["data"] = { + "shape": ndata.shape, + "size": ndata.size, + "dtype": str(ndata.dtype), + "nbytes": sys.getsizeof(ndata), + } + y["samples"] = len(data["element"]) + mm = {} + for k, v in meta["marker"].items(): + if k not in ("class", "on", "name"): + mm[k] = v + y["marker_meta"] = mm + sm = {} + for k, v in data.items(): + if k not in ("data", "element"): + sm[k] = v + y["storage_meta"] = sm + tags = [] + for i in mappings["datagrabbers"]: + if meta["datagrabber"]["class"] == i["class"]: + tags.extend(i["tags"]) + y["dataset"] = { + "name": i["class"], + "description": i["description"], + "display_name": i["display_name"] + if dataset_display_name is None + else dataset_display_name, + } + for i in mappings["markers"]: + if meta["marker"]["class"] == i["class"]: + tags.extend(i["tags"]) + y["tags"] = list(set(tags)) + return y + + """Read and write HDF5 data. Parameters @@ -330,9 +400,6 @@ def _process_hdf5(data: dict, ds: dl.Dataset) -> dl.Dataset: feature_dir = ds.pathobj / "features" feature_dir.mkdir(exist_ok=True) for k, v in tqdm(metadata.items(), desc="Processing features"): - # Metadata - meta_path = feature_dir / f"feature-{k}-meta.yaml" - yaml.dump(v, stream=meta_path.open("w")) # YAML yaml_data = _generate_feature_yaml(v) yaml_path = feature_dir / f"feature-{k}.yml" @@ -348,6 +415,17 @@ def _process_hdf5(data: dict, ds: dl.Dataset) -> dl.Dataset: slash="error", use_json=False, ) + # Metadata + config_path = ds.pathobj / "registry-config.yml" + meta_data = _generate_meta_yaml( + meta=v, + data=data, + md5=k, + mappings=yaml.load(stream=config_path.open("r"))["mappings"], + dataset_display_name=dataset_display_name, + ) + meta_path = feature_dir / f"feature-{k}-meta.yml" + yaml.dump(meta_data, stream=meta_path.open("w")) log.debug("Processed HDF5 file") return ds From 7b2f8ce3ef494c19b8ea2afc89d5ac8475aafa4e Mon Sep 17 00:00:00 2001 From: Synchon Mandal Date: Mon, 2 Mar 2026 16:49:20 +0100 Subject: [PATCH 08/32] update(utils): improve storage hdf5 processing --- julio/_utils.py | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/julio/_utils.py b/julio/_utils.py index 584a3dc..6cd84b5 100644 --- a/julio/_utils.py +++ b/julio/_utils.py @@ -306,7 +306,6 @@ def _generate_feature_yaml(meta: dict) -> dict: return y -def _process_hdf5(data: dict, ds: dl.Dataset) -> dl.Dataset: def _generate_meta_yaml( meta: dict, data: dict, @@ -375,14 +374,21 @@ def _generate_meta_yaml( return y +def _process_hdf5( + data: dict, + ds: dl.Dataset, + dataset_display_name: str | None, +) -> dl.Dataset: """Read and write HDF5 data. Parameters ---------- data : dict - Dictionary containing the HDF5 data. + Parsed YAML as dictionary. ds : dl.Dataset Dataset to add features to. + dataset_display_name : str or None + Dataset display name. Returns ------- @@ -456,10 +462,11 @@ def process_features(yaml_path: Path, ds: dl.Dataset) -> dl.Dataset: log.debug("Processing features") data = _parse_yaml(yaml_path) if data["storage"]["uri"].endswith(".hdf5"): - ds = _process_hdf5(data, ds) - elif data["storage"]["uri"].endswith(".sqlite"): - # TODO(synchon): add support for SQLite - pass + ds = _process_hdf5( + data=data, + ds=ds, + dataset_display_name=dataset_display_name, + ) else: raise RuntimeError( "Unsupported storage format extension: " From 13f688a9b34d7ba52ad0ca5d6f8f07dd9a3f5619 Mon Sep 17 00:00:00 2001 From: Synchon Mandal Date: Mon, 2 Mar 2026 16:55:12 +0100 Subject: [PATCH 09/32] update(functions): add dataset_display_name to add --- julio/_functions.py | 53 +++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 51 insertions(+), 2 deletions(-) diff --git a/julio/_functions.py b/julio/_functions.py index 21dff5e..7ca3560 100644 --- a/julio/_functions.py +++ b/julio/_functions.py @@ -59,7 +59,11 @@ def create(registry_path: Path) -> None: ) -def add(yaml_path: Path, registry_path: str | Path) -> None: +def add( + yaml_path: Path, + registry_path: str | Path, + dataset_display_name: str | None, +) -> None: """Add feature(s) from `yaml_path` to the registry at `registry_path`. Parameters @@ -68,6 +72,8 @@ def add(yaml_path: Path, registry_path: str | Path) -> None: Path to the junifer YAML. registry_path : str or pathlib.Path Path to the existing julio registry. + dataset_display_name : str or None + Dataset display name. Raises ------ @@ -77,7 +83,50 @@ def add(yaml_path: Path, registry_path: str | Path) -> None: """ log = logger.bind( - cmd="create", + cmd="add", + path=registry_path if str else str(registry_path.resolve()), + ) + if isinstance(registry_path, str): + log.debug("Cloning remote registry") + with tempfile.TemporaryDirectory() as tmpdir: + log.debug(f"Temporary directory created at {tmpdir}") + # Clone the remote registry + try: + ds = dl.clone( + source=registry_path, + path=tmpdir, + on_failure="stop", + result_renderer="disabled", + ) + except IncompleteResultsError as e: + raise RuntimeError( + f"Failed to clone dataset: {e.failed}" + ) from e + else: + log.debug("Remote registry cloned successfully") + if not is_julio_registry(ds): + raise RuntimeError( + f"Dataset at {ds.path} is not a julio registry" + ) + # Add features + process_features(yaml_path, ds, dataset_display_name) + # Push changes to remote registry + try: + ds = dl.push( + on_failure="stop", + result_renderer="disabled", + ) + except IncompleteResultsError as e: + raise RuntimeError( + f"Failed to push dataset: {e.failed}" + ) from e + else: + log.debug("Pushed changes to remote registry successfully") + else: + ds = dl.Dataset(registry_path) + if not is_julio_registry(ds): + raise RuntimeError(f"Dataset at {ds.path} is not a julio registry") + process_features(yaml_path, ds, dataset_display_name) path=registry_path if str else str(registry_path.resolve()), ) if isinstance(registry_path, str): From 8b1b0197eefbd6bb9e7935d49df89fa7724266f2 Mon Sep 17 00:00:00 2001 From: Synchon Mandal Date: Mon, 2 Mar 2026 17:01:53 +0100 Subject: [PATCH 10/32] update(functions): copy default registry-config.yml in create --- julio/_functions.py | 5 +- julio/registry-config.yml | 293 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 297 insertions(+), 1 deletion(-) create mode 100644 julio/registry-config.yml diff --git a/julio/_functions.py b/julio/_functions.py index 7ca3560..2b0b980 100644 --- a/julio/_functions.py +++ b/julio/_functions.py @@ -50,7 +50,10 @@ def create(registry_path: Path) -> None: ) # Add config file conf_path = ds.pathobj / "registry-config.yml" - conf_path.touch() + shutil.copy( + src=Path(__file__).parent / "registry-config.yml", + dst=conf_path, + ) ds.save( conf_path, message="[julio] add registry configuration", diff --git a/julio/registry-config.yml b/julio/registry-config.yml new file mode 100644 index 0000000..bde863f --- /dev/null +++ b/julio/registry-config.yml @@ -0,0 +1,293 @@ +mappings: + + datagrabbers: + + - class: HCP1200 + display_name: WU-Minn HCP1200 + tags: + - HCP1200 + description: HCP1200 3T/7T MR scans from young healthy adults twins and non-twin siblings (ages 22-35) + + - class: DataladHCP1200 + display_name: WU-Minn HCP1200 + tags: + - HCP1200 + description: HCP1200 3T/7T MR scans from young healthy adults twins and non-twin siblings (ages 22-35) + + - class: DataladAOMICID1000 + display_name: AOMIC ID1000 + tags: + - AOMIC + - ID1000 + description: Amsterdam Open MRI Collection (AOMIC) ID1000 3T MR scans from 928 participants + + - class: DataladAOMICPIOP1 + display_name: AOMIC PIOP1 + tags: + - AOMIC + - PIOP1 + description: Amsterdam Open MRI Collection (AOMIC) PIOP1 3T MR scans from 216 participants + + - class: DataladAOMICPIOP2 + display_name: AOMIC PIOP2 + tags: + - AOMIC + - PIOP2 + description: Amsterdam Open MRI Collection (AOMIC) PIOP2 3T MR scans from 226 participants + + - class: DMCC13Benchmark + display_name: DMCC13 Benchmark + tags: + - DMCC + description: Dual Mechanisms of Cognitive Control data for 13 subjects used as a benchmark dataset for comparing preprocessing pipelines + + - class: JuselessDataladAOMICID1000VBM + display_name: AOMIC ID1000 VBM + tags: + - AOMIC + - ID1000 + - CAT + description: AOMIC ID1000 VBM data + + - class: JuselessDataladCamCANVBM + display_name: CamCAN VBM + tags: + - CamCAN + - CAT + description: Cambridge Centre for Ageing Neuroscience (CamCAN) VBM data + + - class: JuselessDataladIXIVBM + display_name: IXI VBM + tags: + - IXI + - CAT + description: IXI VBM data + + - class: JuselessUCLA + display_name: UCLA + tags: + - UCLA + description: UCLA data on juseless.imm7.de + + - class: JuselessDataladUKBVBM + display_name: UK Biobank VBM + tags: + - UK Biobank + - CAT + description: UK Biobank VBM data, computed using CAT12.8 by Felix + + markers: + + - class: ParcelAggregation + display_name: Aggregation + tags: + - fMRI + - Aggregation + - Parcellation + description: Signal extraction from Region of Interests (ROIs) based on deterministic atlas, computed using specific aggregation function + + - class: SphereAggregation + display_name: Aggregation + tags: + - fMRI + - Aggregation + - Coordinates + description: Signal extraction from Region of Interests (ROIs) based on spherical region around coordinates, computed using specific aggregation function + + - class: MapsAggregation + display_name: Aggregation + tags: + - fMRI + - Aggregation + - Maps + description: Signal extraction from Region of Interests (ROIs) based on probabilistic atlas, computed using specific aggregation function + + - class: FunctionalConnectivityParcels + display_name: Functional Connectivity + tags: + - fMRI + - FC + - Parcellation + description: Functional connectivity computed on parcellations + + - class: FunctionalConnectivitySpheres + display_name: Functional Connectivity + tags: + - fMRI + - FC + - Coordinates + description: Functional connectivity computed on coordinates + + - class: FunctionalConnectivityMaps + display_name: Functional Connectivity + tags: + - fMRI + - FC + - Maps + description: Functional connectivity computed on maps + + - class: ALFFParcels + display_name: ALFF / fALFF + tags: + - fMRI + - ALFF/fALFF + - Parcellation + description: Amplitude of low-frequency fluctuations (ALFF) and fractional ALFF (fALFF) computed on parcellations + + - class: ALFFSpheres + display_name: ALFF / fALFF + tags: + - fMRI + - ALFF/fALFF + - Coordinates + description: Amplitude of low-frequency fluctuations (ALFF) and fractional ALFF (fALFF) computed on coordinates + + - class: ALFFMaps + display_name: ALFF / fALFF + tags: + - fMRI + - ALFF/fALFF + - Maps + description: Amplitude of low-frequency fluctuations (ALFF) and fractional ALFF (fALFF) computed on maps + + - class: ReHoParcels + display_name: Regional Homogeneity + tags: + - fMRI + - ReHo + - Parcellation + description: Regional homogeneity computed on parcellations + + - class: ReHoSpheres + display_name: Regional Homogeneity + tags: + - fMRI + - ReHo + - Coordinates + description: Regional homogeneity computed on coordinates + + - class: ReHoMaps + display_name: Regional Homogeneity + tags: + - fMRI + - ReHo + - Maps + description: Regional homogeneity computed on maps + + - class: EdgeCentricFCParcels + display_name: Edge Functional Connectivity + tags: + - fMRI + - Edge FC + - Parcellation + description: Edge-centric functional connectivity computed on parcellation + + - class: EdgeCentricFCSpheres + display_name: Edge Functional Connectivity + tags: + - fMRI + - Edge FC + - Coordinates + description: Edge-centric functional connectivity computed on coordinates + + - class: EdgeCentricFCMaps + display_name: Edge Functional Connectivity + tags: + - fMRI + - Edge FC + - Maps + description: Edge-centric functional connectivity computed on maps + + - class: CrossParcellationFC + display_name: Cross-parcellation functional connectivity + tags: + - fMRI + - FC + - Parcellation + description: Cross-parcellation functional connectivity computed on two parcellations + + - class: TemporalSNRParcels + display_name: Temporal signal-to-noise ratio + tags: + - fMRI + - tSNR + - Parcellation + description: Temporal signal-to-noise (tSNR) ratio computed on parcellation + + - class: TemporalSNRSpheres + display_name: Temporal signal-to-noise ratio + tags: + - fMRI + - tSNR + - Coordinates + description: Temporal signal-to-noise (tSNR) ratio computed on coordinates + + - class: TemporalSNRMaps + display_name: Temporal signal-to-noise ratio + tags: + - fMRI + - tSNR + - Maps + description: Temporal signal-to-noise (tSNR) ratio computed on maps + + - class: RSSETSMarker + display_name: Root sum of squares of edgewise timeseries + tags: + - fMRI + - Parcellation + description: Root sum of squares of edgewise timeseries computed on parcellation + + - class: HurstExponent + display_name: Hurst Exponent of timeseries + tags: + - fMRI + - Entropy + - Parcellation + description: Entropy measure using Hurst Exponent of timeseries + + - class: MultiscaleEntropyAUC + display_name: AUC of Multiscale Entropy of timeseries + tags: + - fMRI + - Entropy + - Parcellation + description: Area under the curve of Multiscale Entropy measure of timeseries + + - class: PermEntropy + display_name: Permutation Entropy of timeseries + tags: + - fMRI + - Entropy + - Parcellation + description: Permutation Entropy measure of timeseries + + - class: RangeEntropy + display_name: Range Entropy of timeseries + tags: + - fMRI + - Entropy + - Parcellation + description: Range Entropy measure of timeseries + + - class: RangeEntropyAUC + display_name: AUC of Range Entropy of timeseries + tags: + - fMRI + - Entropy + - Parcellation + description: Area under the curve of Range Entropy measure of timeseries + + - class: SampleEntropy + display_name: Sample Entropy of timeseries + tags: + - fMRI + - Entropy + - Parcellation + description: Sample Entropy measure of timeseries + + - class: BrainPrint + display_name: BrainPrint measure + tags: + - sMRI + - BrainPrint + description: Computed BrainPrint measure from FreeSurfer-derived surface data From eab51df91091c1a449c571d58698bf2f17b54205 Mon Sep 17 00:00:00 2001 From: Synchon Mandal Date: Mon, 2 Mar 2026 17:04:33 +0100 Subject: [PATCH 11/32] update(cli): add dataset_display_name in create --- julio/_cli.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/julio/_cli.py b/julio/_cli.py index d61ac29..1700ae7 100644 --- a/julio/_cli.py +++ b/julio/_cli.py @@ -176,17 +176,28 @@ def create( metavar="", help="Path to registry; if not passed, use current directory", ) +@click.option( + "-d", + "--dataset-display-name", + default=None, + type=str, + metavar="", + help="Dataset display name; if not passed, will use the name from YAML", +) @click.option("-v", "--verbose", count=True, type=int) def add( yaml_path: click.Path, registry: click.Path, + dataset_display_name: str, verbose: int, ) -> None: """Add feature(s) registry.""" _set_log_config(verbose) try: cli_func.add( - yaml_path, registry if registry is not None else Path(".") + yaml_path=yaml_path, + registry_path=registry if registry is not None else Path("."), + dataset_display_name=dataset_display_name, ) except RuntimeError as err: click.echo(f"{err}", err=True) From 35ef2e4036fe4756fc11baa18f2d02634ee001ab Mon Sep 17 00:00:00 2001 From: Synchon Mandal Date: Mon, 2 Mar 2026 17:07:55 +0100 Subject: [PATCH 12/32] chore(utils): code clean up --- julio/_utils.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/julio/_utils.py b/julio/_utils.py index 6cd84b5..8450fd9 100644 --- a/julio/_utils.py +++ b/julio/_utils.py @@ -139,11 +139,11 @@ def _make_absolute_path(path: str, base_path: Path) -> str: """ log = logger.bind(cmd="make_absolute_path", path=str(path)) log.debug("Making path absolute") - path = Path(path) - if not path.is_absolute(): - path = base_path / path + path_p = Path(path) + if not path_p.is_absolute(): + path_p = base_path / path_p log.debug("Made path absolute") - return str(path.resolve()) + return str(path_p.resolve()) def _adjust_paths(data: dict, yaml_path: Path) -> dict: @@ -242,7 +242,7 @@ def _parse_yaml(yaml_path: Path) -> dict: # Remove elements key if empty if "elements" in contents: if contents["elements"] is None: - _ = contents.pop("elements") + contents.pop("elements") log.debug("Parsed junifer YAML") return contents From decd53eb75f45e86af8f56a7afcf4d017725f8fd Mon Sep 17 00:00:00 2001 From: Synchon Mandal Date: Mon, 2 Mar 2026 17:08:14 +0100 Subject: [PATCH 13/32] update(utils): add dataset_display_name in process_features --- julio/_utils.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/julio/_utils.py b/julio/_utils.py index 8450fd9..0ac61e8 100644 --- a/julio/_utils.py +++ b/julio/_utils.py @@ -436,7 +436,11 @@ def _process_hdf5( return ds -def process_features(yaml_path: Path, ds: dl.Dataset) -> dl.Dataset: +def process_features( + yaml_path: Path, + ds: dl.Dataset, + dataset_display_name: str | None, +) -> dl.Dataset: """Parse the junifer YAML and add features to the dataset. Parameters @@ -445,6 +449,8 @@ def process_features(yaml_path: Path, ds: dl.Dataset) -> dl.Dataset: Path to the junifer YAML. ds : dl.Dataset Dataset to add features to. + dataset_display_name : str or None + Dataset display name. Returns ------- From c67881feecfe1b2900ffee0310ca933b9d0162d9 Mon Sep 17 00:00:00 2001 From: Synchon Mandal Date: Mon, 2 Mar 2026 17:34:54 +0100 Subject: [PATCH 14/32] update(tests): add test for add --- julio/tests/feature.yml | 22 ++++++++++++++++++++++ julio/tests/store.hdf5 | Bin 0 -> 29466 bytes julio/tests/test_functions.py | 23 ++++++++++++++++++++++- 3 files changed, 44 insertions(+), 1 deletion(-) create mode 100644 julio/tests/feature.yml create mode 100644 julio/tests/store.hdf5 diff --git a/julio/tests/feature.yml b/julio/tests/feature.yml new file mode 100644 index 0000000..75b85a9 --- /dev/null +++ b/julio/tests/feature.yml @@ -0,0 +1,22 @@ +datagrabber: + kind: DMCC13Benchmark + types: + - BOLD + sessions: + - ses-wave1bas + tasks: + - Rest + phase_encodings: + - AP + runs: + - "1" + +markers: + - kind: FunctionalConnectivitySpheres + coords: DMNBuckner + radius: 5.0 + name: fc_spheres + +storage: + kind: HDF5FeatureStorage + uri: ./store.hdf5 diff --git a/julio/tests/store.hdf5 b/julio/tests/store.hdf5 new file mode 100644 index 0000000000000000000000000000000000000000..67eea961680f9c61fb753d48f3adbc36acefe7ec GIT binary patch literal 29466 zcmeHQ3sh4_8oo&sBk~dv6)L3E7b-NM$onPo(1IfJP+WmPf&?KTL6NGc)e4AO>+05O zec=j}%C-u}2NiJXab2yomSancT0F4ok<#L_zK_<;WadYV+@#>ebEtDU-2cu$|NJxe z%Rh7P{PRzeA2cn%P~SqIL5EPt^kYoamvDMY4htpbKpOJn$OVtkJVHzO_=3lvKZY4j z_=AalO}X$ee}6_L3HpJOVvM*Sz7hh1($@-sAb;Obbi;GWUAqwaZ(?U8$)w6MCFpvJ ztLtR3SUkx^EKN*wlDWDhI7?i_vMG}jq)rJgiOwJob}Isg2IVmW)b@){$U?Ct^LV(V z74A6TXHOl8PQdwe(BvYoU6(2%UpMcL_ z)*rJ_RamtuZNT`bxjaUXF;nRXpB^5p>PXB8m15Ke&2~UR)SXHU)kTRi5l-kh&zU)E z8js{@Z6D9CQQ>urliZKG|S4i{K$H8X4|5M4-LpJsJ)?HTEbXuEcgz4flmCu<`@ z-rjd%@riwh3Y}_#Vya!n7`%~UFxhbD@gmbhcUPUg`>3UKM{Hr?W%Kj)51aj)j7I8R zZ9IGZ%VYIty<;w|Q6_EMbab{w&KP<6R~PdZe zY3>jDW$Q<@4XujW;@*F8T-Ggz6$AKpYARCA&nO0&=LeR29J*`Gj+ldiPeyh8f1smzgM5<5Rj3ZKm5w@wjj%0u3sAS7NYv$xG zb{sy|XKTJ*q2JHSE#_56oNZbg-Rgh(sQjmZQ@hHe&`8?^XWU6aj@=zlx;g6-Ru z(ck=1uydHx>F+=JZtM05dc`?`@5Jr6d2(LS*AKTA7mQA;E1FhwJ}Tm>S;*{dt?RzZ zpI>pyuJv%zsen3u*?|_%Xy=(1C#?^>{#i}oxjCNyC^#POur{yYS3AcM_S^O^+?c(5 zO+s_zRGW?2A1(CXV`npQ{LihkgQO~k?yZ60-<&c7EJF8!v$?4=LkRq!WTsI zK0-}{@dcZmwa>!&aYUX?Zfo@Oazvbgh#Ae5^_N-sZyJZSBQG*rd-RD+9BGKI{?YD= z1;s?3lGh6X=pUL9I>}_*1T^H*WwFRc5OpBbk2+l|*{T#*_O}w}mXHbw9n|*8`^+Wr)whvt^XHt1W zCaN0($zVk-MXpryAs`1LvX&_1329Q9DpC?3ntT$a)e*r3@_H=tP#!4AgFS2V2NFHd zF1bS8Oojxay-@EUmU<0X_#`N-q}LPyiU37` zB0v$K2v7tl0u%v?07ZZz@P`pVJEPP-8RD4H+`$F&=x0>N4lX|4!PT)di`V3X8v`0} zs;7PvG~WdOEY_&Q0fnpk!)NR-)9dh;HMwXn3iEh3ckOa8U!LKiT~0)@qdm3Dfm4VOLlqw|1oq59l4wXm|VBErJJV z;0_P$v+HYkwDuYv*jL%t@M!BbJg^V2ui??&Yj_CNU5{%#x?ZQRi&0m`#9`9ybEuzR zNN9jxif>?sZ(vGLKt@miEYYM&GBf1MRR1Yqv!-_;=Nsyvlqv19Buixu88V4-NxWU6 ztJukTQF@pREe@>qzNvTJYk}Dt-lrQUdjCCYn%8|fQ;m2iFEXgzYsjZfUhf^;;U1KI z&;!XK9?DCr|J|#3XS@4Jkp&@rRN zSoUOir6HXL@LW5-vEaAQn_SdnnIF-WKnvN zTp{gT4!>AqHp$z64#=xPlCVI3Dp7p6#P=U`ZstB zp3(059R`rk4>aIA?P>q+Y|l6*gxk+gSDa@2Y}|hEo8c5azb*(soS$a=Ur+5BjcV|` zLLp4O-yijRtv})zH*Wi*roTNbRJ*0YxKrfvz%j9X z4Ub{Hh6j!X?rV5h^co&G2AW$ubiF5TSb#@xA=d*r)z>Ew(A=*9?-kMyq{|BpW)*D6zv(%W%pC#@Qu9eeGoV(>0=0J+CA8{BN-3jc935pJxQXJ#mW?_ z4MB3nLY$-aXDm^u{TQm%L#3R2F(2ZqRuGeAc)O80KU1Py1p4qi0eufNJ~sGZ3iY>> zerKxD4kAtoK>)@NO}w}mKhWMHUGLqnj|^z{=OH6~@vVRcoa$-&JNujP9w=@{Hf*=Z z8;lRfSaQKaM1;RV!XiM};|SduuU!tp*Gdz#%fVe&c6cha%fISeU3LZI0MPE+l{K*| z(17od3%kO5=m24Ekkf?+?56|TeLO@25759J9@sbYYVm-5oMbQAJ1N11RtXs$ZT!*MmjY3`Ri2 z3==ZLRuTD*^*{297D7g(&JpnYH6&sgq!RrjH60-Kbi6^m6#UG1Pc3*rRT*6z=smxy yUs%}Eg=|+Wci-o^fDce~Q|RcpF`T4liU37`B0v$K2v7tl0u%v?07ak=BJh7)`V$QR literal 0 HcmV?d00001 diff --git a/julio/tests/test_functions.py b/julio/tests/test_functions.py index 978fb8a..166f4a8 100644 --- a/julio/tests/test_functions.py +++ b/julio/tests/test_functions.py @@ -8,7 +8,7 @@ import pytest -from julio import create +from julio import add, create def test_create(tmp_path: Path) -> None: @@ -27,3 +27,24 @@ def test_create(tmp_path: Path) -> None: with pytest.raises(RuntimeError): create(registry_path) shutil.rmtree(registry_path) + + +def test_add(tmp_path: Path) -> None: + """Test feature addition. + + Parameters + ---------- + tmp_path : Path + Pytest fixture that provides a temporary directory. + + """ + registry_path = tmp_path / "test_registry" + create(registry_path) + add( + yaml_path=Path(__file__).parent / "feature.yml", + registry_path=registry_path, + dataset_display_name=None, + ) + f_dir = registry_path / "features" + assert f_dir.is_dir() + shutil.rmtree(registry_path) From 2043da42fb07baccc25362d541b462f038d7bc12 Mon Sep 17 00:00:00 2001 From: Synchon Mandal Date: Mon, 2 Mar 2026 17:35:55 +0100 Subject: [PATCH 15/32] chore: add workdir section in tests/feature.yml --- julio/tests/feature.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/julio/tests/feature.yml b/julio/tests/feature.yml index 75b85a9..55378f4 100644 --- a/julio/tests/feature.yml +++ b/julio/tests/feature.yml @@ -1,3 +1,5 @@ +workdir: ./junifer-temp + datagrabber: kind: DMCC13Benchmark types: From 5b928fc04556f5760b5610f1d46a258e4b836090 Mon Sep 17 00:00:00 2001 From: Synchon Mandal Date: Tue, 3 Mar 2026 11:41:30 +0100 Subject: [PATCH 16/32] fix(tests): make add test work --- julio/tests/test_functions.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/julio/tests/test_functions.py b/julio/tests/test_functions.py index 166f4a8..d56c0bf 100644 --- a/julio/tests/test_functions.py +++ b/julio/tests/test_functions.py @@ -6,6 +6,7 @@ import shutil from pathlib import Path +import datalad.api as dl import pytest from julio import add, create @@ -47,4 +48,5 @@ def test_add(tmp_path: Path) -> None: ) f_dir = registry_path / "features" assert f_dir.is_dir() + dl.drop(".", reckless="kill", dataset=dl.Dataset(registry_path)) shutil.rmtree(registry_path) From fba578883c73147eafa83bb0ce08e81541c3dca1 Mon Sep 17 00:00:00 2001 From: Synchon Mandal Date: Tue, 3 Mar 2026 11:48:26 +0100 Subject: [PATCH 17/32] chore(functions): fix statements --- julio/_functions.py | 30 ------------------------------ 1 file changed, 30 deletions(-) diff --git a/julio/_functions.py b/julio/_functions.py index 2b0b980..cfcd375 100644 --- a/julio/_functions.py +++ b/julio/_functions.py @@ -130,33 +130,3 @@ def add( if not is_julio_registry(ds): raise RuntimeError(f"Dataset at {ds.path} is not a julio registry") process_features(yaml_path, ds, dataset_display_name) - path=registry_path if str else str(registry_path.resolve()), - ) - if isinstance(registry_path, str): - log.debug("Cloning remote registry") - with tempfile.TemporaryDirectory() as tmpdir: - log.debug(f"Temporary directory created at {tmpdir}") - try: - ds = dl.clone( - source=registry_path, - path=tmpdir, - on_failure="stop", - result_renderer="disabled", - ) - except IncompleteResultsError as e: - raise RuntimeError( - f"Failed to clone dataset: {e.failed}" - ) from e - else: - log.debug("Remote registry cloned successfully") - if not is_julio_registry(ds): - raise RuntimeError( - f"Dataset at {ds.path} is not a julio registry" - ) - process_features(yaml_path, ds) - # TODO: push changes to remote - else: - ds = dl.Dataset(registry_path) - if not is_julio_registry(ds): - raise RuntimeError(f"Dataset at {ds.path} is not a julio registry") - process_features(yaml_path, ds) From d94634fba595d0e314bfba0f43c6fbdf3a930afc Mon Sep 17 00:00:00 2001 From: Synchon Mandal Date: Tue, 3 Mar 2026 11:52:19 +0100 Subject: [PATCH 18/32] chore(functions): add missing shutil import --- julio/_functions.py | 1 + 1 file changed, 1 insertion(+) diff --git a/julio/_functions.py b/julio/_functions.py index cfcd375..b3b26ea 100644 --- a/julio/_functions.py +++ b/julio/_functions.py @@ -3,6 +3,7 @@ # Authors: Synchon Mandal # License: AGPL +import shutil import tempfile from pathlib import Path From e21248257009028f0db65543d454e26ef3ea0156 Mon Sep 17 00:00:00 2001 From: Synchon Mandal Date: Tue, 3 Mar 2026 11:56:08 +0100 Subject: [PATCH 19/32] chore: add missing package import --- julio/__init__.pyi | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/julio/__init__.pyi b/julio/__init__.pyi index 575fb65..d72d57e 100644 --- a/julio/__init__.pyi +++ b/julio/__init__.pyi @@ -1,5 +1,6 @@ __all__ = [ + "add", "create", ] -from ._functions import create +from ._functions import add, create From c99073bd373fed96aee144e97948156f61ffa00b Mon Sep 17 00:00:00 2001 From: Synchon Mandal Date: Tue, 10 Mar 2026 11:54:17 +0100 Subject: [PATCH 20/32] chore(functions): add no cover pragma for add --- julio/_functions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/julio/_functions.py b/julio/_functions.py index b3b26ea..0c26d81 100644 --- a/julio/_functions.py +++ b/julio/_functions.py @@ -90,7 +90,7 @@ def add( cmd="add", path=registry_path if str else str(registry_path.resolve()), ) - if isinstance(registry_path, str): + if isinstance(registry_path, str): # pragma: no cover log.debug("Cloning remote registry") with tempfile.TemporaryDirectory() as tmpdir: log.debug(f"Temporary directory created at {tmpdir}") From 33187c87f4124823e910ffe0624071f62e458d6c Mon Sep 17 00:00:00 2001 From: Synchon Mandal Date: Tue, 10 Mar 2026 12:07:30 +0100 Subject: [PATCH 21/32] tests(functions): improve test for add --- julio/tests/test_functions.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/julio/tests/test_functions.py b/julio/tests/test_functions.py index d56c0bf..bac98b7 100644 --- a/julio/tests/test_functions.py +++ b/julio/tests/test_functions.py @@ -50,3 +50,10 @@ def test_add(tmp_path: Path) -> None: assert f_dir.is_dir() dl.drop(".", reckless="kill", dataset=dl.Dataset(registry_path)) shutil.rmtree(registry_path) + # Check for invalid dataset + with pytest.raises(RuntimeError): + add( + yaml_path=Path(__file__).parent / "feature.yml", + registry_path=tmp_path, + dataset_display_name=None, + ) From 424f5e1b4f762f182297598165b19824d1047d7d Mon Sep 17 00:00:00 2001 From: Synchon Mandal Date: Wed, 11 Mar 2026 13:25:51 +0100 Subject: [PATCH 22/32] update: improve tests for add --- julio/tests/invalid_store.yml | 24 ++++++++++++++++ julio/tests/test_functions.py | 36 ++++++++++++++++-------- julio/tests/{feature.yml => valid.yml} | 0 julio/tests/valid_extra.yml | 38 ++++++++++++++++++++++++++ 4 files changed, 87 insertions(+), 11 deletions(-) create mode 100644 julio/tests/invalid_store.yml rename julio/tests/{feature.yml => valid.yml} (100%) create mode 100644 julio/tests/valid_extra.yml diff --git a/julio/tests/invalid_store.yml b/julio/tests/invalid_store.yml new file mode 100644 index 0000000..f9c1043 --- /dev/null +++ b/julio/tests/invalid_store.yml @@ -0,0 +1,24 @@ +workdir: ./junifer-temp + +datagrabber: + kind: DMCC13Benchmark + types: + - BOLD + sessions: + - ses-wave1bas + tasks: + - Rest + phase_encodings: + - AP + runs: + - "1" + +markers: + - kind: FunctionalConnectivitySpheres + coords: DMNBuckner + radius: 5.0 + name: fc_spheres + +storage: + kind: HDF5FeatureStorage + uri: ./store.sqlite diff --git a/julio/tests/test_functions.py b/julio/tests/test_functions.py index bac98b7..c6ba23c 100644 --- a/julio/tests/test_functions.py +++ b/julio/tests/test_functions.py @@ -4,6 +4,7 @@ # License: AGPL import shutil +from contextlib import AbstractContextManager, nullcontext from pathlib import Path import datalad.api as dl @@ -30,30 +31,43 @@ def test_create(tmp_path: Path) -> None: shutil.rmtree(registry_path) -def test_add(tmp_path: Path) -> None: +@pytest.mark.parametrize( + "yml, expect", + [ + ("valid.yml", nullcontext()), + ("invalid_store.yml", pytest.raises(RuntimeError)), + ("valid_extra.yml", nullcontext()), + ], +) +def test_add(tmp_path: Path, yml: str, expect: AbstractContextManager) -> None: """Test feature addition. Parameters ---------- tmp_path : Path Pytest fixture that provides a temporary directory. + yml : str + The parametrized YAML file name. + expect : typing.ContextManager + The parametrized ContextManager object. """ registry_path = tmp_path / "test_registry" create(registry_path) - add( - yaml_path=Path(__file__).parent / "feature.yml", - registry_path=registry_path, - dataset_display_name=None, - ) - f_dir = registry_path / "features" - assert f_dir.is_dir() - dl.drop(".", reckless="kill", dataset=dl.Dataset(registry_path)) - shutil.rmtree(registry_path) + with expect: + add( + yaml_path=Path(__file__).parent / yml, + registry_path=registry_path, + dataset_display_name=None, + ) + f_dir = registry_path / "features" + assert f_dir.is_dir() + dl.drop(".", reckless="kill", dataset=dl.Dataset(registry_path)) + shutil.rmtree(registry_path) # Check for invalid dataset with pytest.raises(RuntimeError): add( - yaml_path=Path(__file__).parent / "feature.yml", + yaml_path=Path(__file__).parent / yml, registry_path=tmp_path, dataset_display_name=None, ) diff --git a/julio/tests/feature.yml b/julio/tests/valid.yml similarity index 100% rename from julio/tests/feature.yml rename to julio/tests/valid.yml diff --git a/julio/tests/valid_extra.yml b/julio/tests/valid_extra.yml new file mode 100644 index 0000000..3f3c5b6 --- /dev/null +++ b/julio/tests/valid_extra.yml @@ -0,0 +1,38 @@ +workdir: ./junifer-temp + +with: + - math + +datagrabber: + kind: PatternDataladDataGrabber + uri: https://github.com/OpenNeuroDatasets/ds003720.git + types: + - BOLD + patterns: + BOLD: + pattern: "{subject}/func/{subject}_task-{task}_{session}_bold.nii" + space: MNI152NLin2009cAsym + replacements: + - subject + - task + - session + +preprocess: + - kind: Smoothing + using: nilearn + on: + - BOLD + smoothing_params: + fwhm: fast + +markers: + - kind: FunctionalConnectivitySpheres + coords: DMNBuckner + radius: 5.0 + name: fc_spheres + masks: + - compute_epi_mask + +storage: + kind: HDF5FeatureStorage + uri: ./store.hdf5 From 0b2e020eb6b157dcbf1bfbbd219767400b32b2e2 Mon Sep 17 00:00:00 2001 From: Synchon Mandal Date: Wed, 11 Mar 2026 13:28:10 +0100 Subject: [PATCH 23/32] chore: add and update no cover pragma --- julio/_functions.py | 2 +- julio/_utils.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/julio/_functions.py b/julio/_functions.py index 0c26d81..1f63a60 100644 --- a/julio/_functions.py +++ b/julio/_functions.py @@ -90,7 +90,7 @@ def add( cmd="add", path=registry_path if str else str(registry_path.resolve()), ) - if isinstance(registry_path, str): # pragma: no cover + if isinstance(registry_path, str): # pragma: no cover log.debug("Cloning remote registry") with tempfile.TemporaryDirectory() as tmpdir: log.debug(f"Temporary directory created at {tmpdir}") diff --git a/julio/_utils.py b/julio/_utils.py index 0ac61e8..23b8ed5 100644 --- a/julio/_utils.py +++ b/julio/_utils.py @@ -74,7 +74,7 @@ pattern = re.compile(regex) -class PathOrURLParamType(click.ParamType): +class PathOrURLParamType(click.ParamType): # pragma : no cover name = "path_or_url" def convert(self, value, param, ctx): From 97cbc184c6b0dae2036eee3103f833c55641bea4 Mon Sep 17 00:00:00 2001 From: Synchon Mandal Date: Wed, 11 Mar 2026 13:29:22 +0100 Subject: [PATCH 24/32] chore: update tox.ini --- tox.ini | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index f46a1e7..8eea918 100644 --- a/tox.ini +++ b/tox.ini @@ -49,4 +49,8 @@ skip_install = false deps = pytest-cov commands = - pytest --cov={envsitepackagesdir}/julio --cov-report=xml --cov-report=term --cov-config=pyproject.toml {envsitepackagesdir}/julio + pytest --cov={envsitepackagesdir}/julio \ + --cov-report=xml \ + --cov-report=term-missing \ + --cov-config=pyproject.toml \ + {envsitepackagesdir}/julio From bbf525af1bbdfacf1673858595e90fd8a5d28285 Mon Sep 17 00:00:00 2001 From: Synchon Mandal Date: Wed, 11 Mar 2026 13:32:38 +0100 Subject: [PATCH 25/32] chore: fix no cover pragma --- julio/_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/julio/_utils.py b/julio/_utils.py index 23b8ed5..c49d2ac 100644 --- a/julio/_utils.py +++ b/julio/_utils.py @@ -74,7 +74,7 @@ pattern = re.compile(regex) -class PathOrURLParamType(click.ParamType): # pragma : no cover +class PathOrURLParamType(click.ParamType): # pragma: no cover name = "path_or_url" def convert(self, value, param, ctx): From f288050050233a356b1ac65fe9d6e88d5fa495bf Mon Sep 17 00:00:00 2001 From: Synchon Mandal Date: Wed, 11 Mar 2026 13:34:42 +0100 Subject: [PATCH 26/32] chore: update valid_extra.yml --- julio/tests/extra.py | 0 julio/tests/valid_extra.yml | 5 ++++- 2 files changed, 4 insertions(+), 1 deletion(-) create mode 100644 julio/tests/extra.py diff --git a/julio/tests/extra.py b/julio/tests/extra.py new file mode 100644 index 0000000..e69de29 diff --git a/julio/tests/valid_extra.yml b/julio/tests/valid_extra.yml index 3f3c5b6..14e8dd0 100644 --- a/julio/tests/valid_extra.yml +++ b/julio/tests/valid_extra.yml @@ -1,7 +1,10 @@ -workdir: ./junifer-temp +workdir: + path: ./junifer-temp + cleanup: true with: - math + - ./extra.py datagrabber: kind: PatternDataladDataGrabber From 355e1737bddfc852ae4ca46eeb6a48061041f6f9 Mon Sep 17 00:00:00 2001 From: Synchon Mandal Date: Wed, 11 Mar 2026 13:56:17 +0100 Subject: [PATCH 27/32] update: improve tests for add --- julio/tests/invalid_mandatory_section.yml | 22 +++++++++++++++++++ julio/tests/invalid_section.yml | 26 +++++++++++++++++++++++ julio/tests/missing_storage_uri.yml | 23 ++++++++++++++++++++ julio/tests/test_functions.py | 5 ++++- 4 files changed, 75 insertions(+), 1 deletion(-) create mode 100644 julio/tests/invalid_mandatory_section.yml create mode 100644 julio/tests/invalid_section.yml create mode 100644 julio/tests/missing_storage_uri.yml diff --git a/julio/tests/invalid_mandatory_section.yml b/julio/tests/invalid_mandatory_section.yml new file mode 100644 index 0000000..75b85a9 --- /dev/null +++ b/julio/tests/invalid_mandatory_section.yml @@ -0,0 +1,22 @@ +datagrabber: + kind: DMCC13Benchmark + types: + - BOLD + sessions: + - ses-wave1bas + tasks: + - Rest + phase_encodings: + - AP + runs: + - "1" + +markers: + - kind: FunctionalConnectivitySpheres + coords: DMNBuckner + radius: 5.0 + name: fc_spheres + +storage: + kind: HDF5FeatureStorage + uri: ./store.hdf5 diff --git a/julio/tests/invalid_section.yml b/julio/tests/invalid_section.yml new file mode 100644 index 0000000..34b01bb --- /dev/null +++ b/julio/tests/invalid_section.yml @@ -0,0 +1,26 @@ +invalid: + +workdir: ./junifer-temp + +datagrabber: + kind: DMCC13Benchmark + types: + - BOLD + sessions: + - ses-wave1bas + tasks: + - Rest + phase_encodings: + - AP + runs: + - "1" + +markers: + - kind: FunctionalConnectivitySpheres + coords: DMNBuckner + radius: 5.0 + name: fc_spheres + +storage: + kind: HDF5FeatureStorage + uri: ./store.hdf5 diff --git a/julio/tests/missing_storage_uri.yml b/julio/tests/missing_storage_uri.yml new file mode 100644 index 0000000..b2af8ca --- /dev/null +++ b/julio/tests/missing_storage_uri.yml @@ -0,0 +1,23 @@ +workdir: ./junifer-temp + +datagrabber: + kind: DMCC13Benchmark + types: + - BOLD + sessions: + - ses-wave1bas + tasks: + - Rest + phase_encodings: + - AP + runs: + - "1" + +markers: + - kind: FunctionalConnectivitySpheres + coords: DMNBuckner + radius: 5.0 + name: fc_spheres + +storage: + kind: HDF5FeatureStorage diff --git a/julio/tests/test_functions.py b/julio/tests/test_functions.py index c6ba23c..fbbccb6 100644 --- a/julio/tests/test_functions.py +++ b/julio/tests/test_functions.py @@ -35,8 +35,11 @@ def test_create(tmp_path: Path) -> None: "yml, expect", [ ("valid.yml", nullcontext()), - ("invalid_store.yml", pytest.raises(RuntimeError)), ("valid_extra.yml", nullcontext()), + ("invalid_store.yml", pytest.raises(RuntimeError)), + ("invalid_mandatory_section.yml", pytest.raises(RuntimeError)), + ("invalid_section.yml", pytest.raises(RuntimeError)), + ("missing_storage_uri.yml", pytest.raises(RuntimeError)), ], ) def test_add(tmp_path: Path, yml: str, expect: AbstractContextManager) -> None: From a8784cc58785c3c0732953ba9a3b4e37d6121d86 Mon Sep 17 00:00:00 2001 From: Synchon Mandal Date: Wed, 11 Mar 2026 14:22:00 +0100 Subject: [PATCH 28/32] chore: lint --- julio/tests/extra.py | 1 + 1 file changed, 1 insertion(+) diff --git a/julio/tests/extra.py b/julio/tests/extra.py index e69de29..88eba52 100644 --- a/julio/tests/extra.py +++ b/julio/tests/extra.py @@ -0,0 +1 @@ +"""Placeholder module for test.""" From 5d2018c53556a61dbae8ec3635541ec4c09f1fc5 Mon Sep 17 00:00:00 2001 From: Synchon Mandal Date: Wed, 11 Mar 2026 14:28:40 +0100 Subject: [PATCH 29/32] chore: add changelog --- changelog.d/3.added.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/3.added.md diff --git a/changelog.d/3.added.md b/changelog.d/3.added.md new file mode 100644 index 0000000..0e55a70 --- /dev/null +++ b/changelog.d/3.added.md @@ -0,0 +1 @@ +Introduce feature files addition to registry From bf010ab0185fe5351e02a2d4c8fd21ad8a59b3bc Mon Sep 17 00:00:00 2001 From: Synchon Mandal Date: Fri, 29 May 2026 16:28:16 +0200 Subject: [PATCH 30/32] chore: remove ruamel.yaml and use junifer's yaml config --- julio/_utils.py | 3 +-- julio/_yaml.py | 16 ---------------- pyproject.toml | 1 - 3 files changed, 1 insertion(+), 19 deletions(-) delete mode 100644 julio/_yaml.py diff --git a/julio/_utils.py b/julio/_utils.py index c49d2ac..71c05de 100644 --- a/julio/_utils.py +++ b/julio/_utils.py @@ -14,10 +14,9 @@ import datalad.api as dl import structlog from h5io import read_hdf5, write_hdf5 +from junifer.utils import yaml from tqdm import tqdm -from ._yaml import yaml - __all__ = ["PathOrURL", "is_julio_registry", "process_features"] diff --git a/julio/_yaml.py b/julio/_yaml.py deleted file mode 100644 index 732ca75..0000000 --- a/julio/_yaml.py +++ /dev/null @@ -1,16 +0,0 @@ -"""Provide YAML config definition for junifer use.""" - -# Authors: Synchon Mandal -# License: AGPL - -from ruamel.yaml import YAML - - -__all__ = ["yaml"] - - -# Configure YAML class once for further use -yaml = YAML() -yaml.default_flow_style = False -yaml.allow_unicode = True -yaml.indent(mapping=2, sequence=4, offset=2) diff --git a/pyproject.toml b/pyproject.toml index 332697c..236bb36 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -39,7 +39,6 @@ dependencies = [ "datalad>=1.0.0,<1.3.0", "lazy_loader==0.4", "structlog>=25.0.0,<26.0.0", - "ruamel.yaml>=0.17,<0.19", "h5io@git+https://github.com/juaml/h5io@600d9e25c625b11520d86b08f9f0b761f71b0542", "tqdm>=4.66.1,<4.67.0", ] From 3d0930ef183e66e3e2af745574a529370ff98f7d Mon Sep 17 00:00:00 2001 From: Synchon Mandal Date: Fri, 29 May 2026 16:31:19 +0200 Subject: [PATCH 31/32] update: use junifer to parse and generate yaml --- julio/_utils.py | 144 +++--------------------------------------------- 1 file changed, 8 insertions(+), 136 deletions(-) diff --git a/julio/_utils.py b/julio/_utils.py index 71c05de..3e9ccb2 100644 --- a/julio/_utils.py +++ b/julio/_utils.py @@ -14,6 +14,7 @@ import datalad.api as dl import structlog from h5io import read_hdf5, write_hdf5 +from junifer.api import generate_yaml, parse_yaml from junifer.utils import yaml from tqdm import tqdm @@ -120,75 +121,6 @@ def is_julio_registry(ds: dl.Dataset) -> bool: return False -def _make_absolute_path(path: str, base_path: Path) -> str: - """Make a path absolute. - - Parameters - ---------- - path : str - Path to make absolute. - base_path : Path - Base path to use. - - Returns - ------- - str - Absolute path. - - """ - log = logger.bind(cmd="make_absolute_path", path=str(path)) - log.debug("Making path absolute") - path_p = Path(path) - if not path_p.is_absolute(): - path_p = base_path / path_p - log.debug("Made path absolute") - return str(path_p.resolve()) - - -def _adjust_paths(data: dict, yaml_path: Path) -> dict: - """Adjust paths in the data dictionary. - - Parameters - ---------- - data : dict - Data dictionary to adjust. - yaml_path : Path - Path to the YAML file. - - Returns - ------- - dict - Adjusted data dictionary. - - """ - log = logger.bind(cmd="adjust_paths", path=str(yaml_path.resolve())) - log.debug("Adjusting paths") - if "workdir" in data: - if isinstance(data["workdir"], str): - data["workdir"] = _make_absolute_path( - data["workdir"], yaml_path.parent - ) - else: - data["workdir"]["path"] = _make_absolute_path( - data["workdir"]["path"], yaml_path.parent - ) - if "with" in data: - if not isinstance(data["with"], list): - data["with"] = list(data["with"]) - mods = [] - for w in data["with"]: - if w.endswith(".py"): - mods.append(_make_absolute_path(w, yaml_path.parent)) - else: - mods.append(w) - data["with"] = mods - data["storage"]["uri"] = _make_absolute_path( - data["storage"]["uri"], yaml_path.parent - ) - log.debug("Adjusted paths") - return data - - def _parse_yaml(yaml_path: Path) -> dict: """Parse the junifer YAML. @@ -211,7 +143,7 @@ def _parse_yaml(yaml_path: Path) -> dict: """ log = logger.bind(cmd="parse_yaml", path=str(yaml_path.resolve())) log.debug("Parsing junifer YAML") - contents = yaml.load(yaml_path) + contents = parse_yaml(yaml_path) # Validate mandatory sections mandatory = ("workdir", "datagrabber", "markers", "storage") for s in mandatory: @@ -231,80 +163,15 @@ def _parse_yaml(yaml_path: Path) -> dict: raise RuntimeError( f"`uri` missing from `storage` section in {yaml_path.resolve()!s}" ) - # Replace relative file paths with absolute - contents = _adjust_paths(contents, yaml_path) # Validate storage file exists if not Path(contents["storage"]["uri"]).exists(): raise RuntimeError( f"Storage file does not exist: {contents['storage']['uri']}" ) - # Remove elements key if empty - if "elements" in contents: - if contents["elements"] is None: - contents.pop("elements") log.debug("Parsed junifer YAML") return contents -def _generate_feature_yaml(meta: dict) -> dict: - """Generate a feature YAML from metadata. - - Parameters - ---------- - meta : dict - Feature metadata as dictionary. - - Returns - ------- - dict - Feature YAML. - - """ - y: dict[str, Any] = {} - y["workdir"] = "" - if "with" in meta: - y["with"] = meta["with"].copy() - # Set datagrabber - y["datagrabber"] = meta["datagrabber"].copy() - a = y["datagrabber"].pop("class") - y["datagrabber"]["kind"] = a - if a not in ("PatternDataGrabber", "PatternDataladDataGrabber"): - y["datagrabber"].pop("uri") - y["datagrabber"].pop("rootdir") - y["datagrabber"].pop("patterns") - y["datagrabber"].pop("replacements") - y["datagrabber"].pop("confounds_format") - y["datagrabber"].pop("partial_pattern_ok") - for k in meta[ - "datagrabber" - ].keys(): # use data instead of y to avoid .copy() - if k.startswith("datalad"): - y["datagrabber"].pop(k) - # Set preprocess - if "preprocess" in meta: - y["preprocess"] = meta["preprocess"].copy() - b = y["preprocess"].pop("class") - y["preprocess"]["kind"] = b - # Set markers - y["markers"] = [] - y["markers"].append(meta["marker"].copy()) - c = y["markers"][0].pop("class") - y["markers"][0]["kind"] = c - if y["markers"][0]["masks"] is None: - y["markers"][0].pop("masks") - # Set storage - y["storage"] = { - "kind": "HDF5FeatureStorage", - "uri": "", - } - # Set queue - y["queue"] = { - "jobname": meta["name"], - "kind": "", - } - return y - - def _generate_meta_yaml( meta: dict, data: dict, @@ -405,8 +272,13 @@ def _process_hdf5( feature_dir = ds.pathobj / "features" feature_dir.mkdir(exist_ok=True) for k, v in tqdm(metadata.items(), desc="Processing features"): + # Add "with" and "queue" section if present in data + if "with" in data: + v["with"] = data["with"].copy() + if "queue" in data: + v["queue"] = data["queue"].copy() # YAML - yaml_data = _generate_feature_yaml(v) + yaml_data = generate_yaml(v) yaml_path = feature_dir / f"feature-{k}.yml" yaml.dump(yaml_data, stream=yaml_path.open("w")) # Data From 817e050ed9bc8a6dfda7636ee848c438d901222f Mon Sep 17 00:00:00 2001 From: Synchon Mandal Date: Fri, 29 May 2026 16:31:59 +0200 Subject: [PATCH 32/32] chore: improve datetime imports --- julio/_utils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/julio/_utils.py b/julio/_utils.py index 3e9ccb2..a9e8730 100644 --- a/julio/_utils.py +++ b/julio/_utils.py @@ -3,10 +3,10 @@ # Authors: Synchon Mandal # License: AGPL +import datetime as dt import pathlib import re import sys -from datetime import datetime from pathlib import Path from typing import Any @@ -203,7 +203,7 @@ def _generate_meta_yaml( y: dict[str, Any] = {} y["md5"] = md5 y["name"] = meta["name"] - y["added_on"] = datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S") + y["added_on"] = dt.datetime.now(tz=dt.UTC).strftime("%Y-%m-%d %H:%M:%S") ndata = data["data"] y["data"] = { "shape": ndata.shape,