From 70aaf213f0c82082d1456cce8758b2e9ee7f49a1 Mon Sep 17 00:00:00 2001 From: Abid Sikder <41392423+abidsikder@users.noreply.github.com> Date: Tue, 18 Mar 2025 13:48:08 -0400 Subject: [PATCH 01/11] update xarray and zarr versions to zarr v3 compatible ones --- pyproject.toml | 4 +- uv.lock | 190 ++++++++++++++++++++++++++++++++++++++++--------- 2 files changed, 160 insertions(+), 34 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 38a1f2f..b41cac8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,6 +10,7 @@ dependencies = [ "multiformats[full]>=0.3.1.post4", "pycryptodome>=3.21.0", "requests>=2.32.3", + "zarr", ] [build-system] @@ -27,6 +28,5 @@ dev = [ "snakeviz>=2.2.0", "pandas>=2.2.3", "numpy>=2.1.3", - "xarray==2024.11.0", - "zarr==2.18.3", + "xarray>=2025.1.2", ] diff --git a/uv.lock b/uv.lock index ee5cfbc..36c2e69 100644 --- a/uv.lock +++ b/uv.lock @@ -1,12 +1,7 @@ version = 1 +revision = 1 requires-python = ">=3.12" -[[package]] -name = "asciitree" -version = "0.3.3" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/2d/6a/885bc91484e1aa8f618f6f0228d76d0e67000b0fdd6090673b777e311913/asciitree-0.3.3.tar.gz", hash = "sha256:4aa4b9b649f85e3fcb343363d97564aa1fb62e249677f2e18a96765145cc0f6e", size = 3951 } - [[package]] name = "attrs" version = "24.2.0" @@ -144,6 +139,47 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/7e/77/03fc2979d1538884d921c2013075917fc927f41cd8526909852fe4494112/coverage-7.6.4-cp313-cp313t-win_amd64.whl", hash = "sha256:f3ddf056d3ebcf6ce47bdaf56142af51bb7fad09e4af310241e9db7a3a8022e1", size = 211502 }, ] +[[package]] +name = "crc32c" +version = "2.7.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7f/4c/4e40cc26347ac8254d3f25b9f94710b8e8df24ee4dddc1ba41907a88a94d/crc32c-2.7.1.tar.gz", hash = "sha256:f91b144a21eef834d64178e01982bb9179c354b3e9e5f4c803b0e5096384968c", size = 45712 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1d/02/998dc21333413ce63fe4c1ca70eafe61ca26afc7eb353f20cecdb77d614e/crc32c-2.7.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:f7d1c4e761fe42bf856130daf8b2658df33fe0ced3c43dadafdfeaa42b57b950", size = 49568 }, + { url = "https://files.pythonhosted.org/packages/9c/3e/e3656bfa76e50ef87b7136fef2dbf3c46e225629432fc9184fdd7fd187ff/crc32c-2.7.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:73361c79a6e4605204457f19fda18b042a94508a52e53d10a4239da5fb0f6a34", size = 37019 }, + { url = "https://files.pythonhosted.org/packages/0b/7d/5ff9904046ad15a08772515db19df43107bf5e3901a89c36a577b5f40ba0/crc32c-2.7.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:afd778fc8ac0ed2ffbfb122a9aa6a0e409a8019b894a1799cda12c01534493e0", size = 35373 }, + { url = "https://files.pythonhosted.org/packages/4d/41/4aedc961893f26858ab89fc772d0eaba91f9870f19eaa933999dcacb94ec/crc32c-2.7.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:56ef661b34e9f25991fface7f9ad85e81bbc1b3fe3b916fd58c893eabe2fa0b8", size = 54675 }, + { url = "https://files.pythonhosted.org/packages/d6/63/8cabf09b7e39b9fec8f7010646c8b33057fc8d67e6093b3cc15563d23533/crc32c-2.7.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:571aa4429444b5d7f588e4377663592145d2d25eb1635abb530f1281794fc7c9", size = 52386 }, + { url = "https://files.pythonhosted.org/packages/79/13/13576941bf7cf95026abae43d8427c812c0054408212bf8ed490eda846b0/crc32c-2.7.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c02a3bd67dea95cdb25844aaf44ca2e1b0c1fd70b287ad08c874a95ef4bb38db", size = 53495 }, + { url = "https://files.pythonhosted.org/packages/3d/b6/55ffb26d0517d2d6c6f430ce2ad36ae7647c995c5bfd7abce7f32bb2bad1/crc32c-2.7.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:99d17637c4867672cb8adeea007294e3c3df9d43964369516cfe2c1f47ce500a", size = 54456 }, + { url = "https://files.pythonhosted.org/packages/c2/1a/5562e54cb629ecc5543d3604dba86ddfc7c7b7bf31d64005b38a00d31d31/crc32c-2.7.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:f4a400ac3c69a32e180d8753fd7ec7bccb80ade7ab0812855dce8a208e72495f", size = 52647 }, + { url = "https://files.pythonhosted.org/packages/48/ec/ce4138eaf356cd9aae60bbe931755e5e0151b3eca5f491fce6c01b97fd59/crc32c-2.7.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:588587772e55624dd9c7a906ec9e8773ae0b6ac5e270fc0bc84ee2758eba90d5", size = 53332 }, + { url = "https://files.pythonhosted.org/packages/5e/b5/144b42cd838a901175a916078781cb2c3c9f977151c9ba085aebd6d15b22/crc32c-2.7.1-cp312-cp312-win32.whl", hash = "sha256:9f14b60e5a14206e8173dd617fa0c4df35e098a305594082f930dae5488da428", size = 38371 }, + { url = "https://files.pythonhosted.org/packages/ae/c4/7929dcd5d9b57db0cce4fe6f6c191049380fc6d8c9b9f5581967f4ec018e/crc32c-2.7.1-cp312-cp312-win_amd64.whl", hash = "sha256:7c810a246660a24dc818047dc5f89c7ce7b2814e1e08a8e99993f4103f7219e8", size = 39805 }, + { url = "https://files.pythonhosted.org/packages/bf/98/1a6d60d5b3b5edc8382777b64100343cb4aa6a7e172fae4a6cfcb8ebbbd9/crc32c-2.7.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:24949bffb06fc411cc18188d33357923cb935273642164d0bb37a5f375654169", size = 49567 }, + { url = "https://files.pythonhosted.org/packages/4f/56/0dd652d4e950e6348bbf16b964b3325e4ad8220470774128fc0b0dd069cb/crc32c-2.7.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:2d5d326e7e118d4fa60187770d86b66af2fdfc63ce9eeb265f0d3e7d49bebe0b", size = 37018 }, + { url = "https://files.pythonhosted.org/packages/47/02/2bd65fdef10139b6a802d83a7f966b7750fe5ffb1042f7cbe5dbb6403869/crc32c-2.7.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ba110df60c64c8e2d77a9425b982a520ccdb7abe42f06604f4d98a45bb1fff62", size = 35374 }, + { url = "https://files.pythonhosted.org/packages/a9/0d/3e797d1ed92d357a6a4c5b41cea15a538b27a8fdf18c7863747eb50b73ad/crc32c-2.7.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c277f9d16a3283e064d54854af0976b72abaa89824955579b2b3f37444f89aae", size = 54641 }, + { url = "https://files.pythonhosted.org/packages/a7/d3/4ddeef755caaa75680c559562b6c71f5910fee4c4f3a2eb5ea8b57f0e48c/crc32c-2.7.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:881af0478a01331244e27197356929edbdeaef6a9f81b5c6bacfea18d2139289", size = 52338 }, + { url = "https://files.pythonhosted.org/packages/01/cf/32f019be5de9f6e180926a50ee5f08648e686c7d9a59f2c5d0806a77b1c7/crc32c-2.7.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:724d5ff4d29ff093a983ae656be3307093706d850ea2a233bf29fcacc335d945", size = 53447 }, + { url = "https://files.pythonhosted.org/packages/b2/8b/92f3f62f3bafe8f7ab4af7bfb7246dc683fd11ec0d6dfb73f91e09079f69/crc32c-2.7.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b2416c4d88696ac322632555c0f81ab35e15f154bc96055da6cf110d642dbc10", size = 54484 }, + { url = "https://files.pythonhosted.org/packages/98/b2/113a50f8781f76af5ac65ffdb907e72bddbe974de8e02247f0d58bc48040/crc32c-2.7.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:60254251b88ec9b9795215f0f9ec015a6b5eef8b2c5fba1267c672d83c78fc02", size = 52703 }, + { url = "https://files.pythonhosted.org/packages/b4/6c/309229e9acda8cf36a8ff4061d70b54d905f79b7037e16883ce6590a24ab/crc32c-2.7.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:edefc0e46f3c37372183f70338e5bdee42f6789b62fcd36ec53aa933e9dfbeaf", size = 53367 }, + { url = "https://files.pythonhosted.org/packages/b5/2a/6c6324d920396e1bd9f3efbe8753da071be0ca52bd22d6c82d446b8d6975/crc32c-2.7.1-cp313-cp313-win32.whl", hash = "sha256:813af8111218970fe2adb833c5e5239f091b9c9e76f03b4dd91aaba86e99b499", size = 38377 }, + { url = "https://files.pythonhosted.org/packages/db/a0/f01ccfab538db07ef3f6b4ede46357ff147a81dd4f3c59ca6a34c791a549/crc32c-2.7.1-cp313-cp313-win_amd64.whl", hash = "sha256:7d9ede7be8e4ec1c9e90aaf6884decbeef10e3473e6ddac032706d710cab5888", size = 39803 }, + { url = "https://files.pythonhosted.org/packages/1b/80/61dcae7568b33acfde70c9d651c7d891c0c578c39cc049107c1cf61f1367/crc32c-2.7.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:db9ac92294284b22521356715784b91cc9094eee42a5282ab281b872510d1831", size = 49386 }, + { url = "https://files.pythonhosted.org/packages/1e/f1/80f17c089799ab2b4c247443bdd101d6ceda30c46d7f193e16b5ca29c5a0/crc32c-2.7.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:8fcd7f2f29a30dc92af64a9ee3d38bde0c82bd20ad939999427aac94bbd87373", size = 36937 }, + { url = "https://files.pythonhosted.org/packages/63/42/5fcfc71a3de493d920fd2590843762a2749981ea56b802b380e5df82309d/crc32c-2.7.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5c056ef043393085523e149276a7ce0cb534b872e04f3e20d74d9a94a75c0ad7", size = 35292 }, + { url = "https://files.pythonhosted.org/packages/03/de/fef962e898a953558fe1c55141644553e84ef4190693a31244c59a0856c7/crc32c-2.7.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:03a92551a343702629af91f78d205801219692b6909f8fa126b830e332bfb0e0", size = 54223 }, + { url = "https://files.pythonhosted.org/packages/21/14/fceca1a6f45c0a1814fe8602a65657b75c27425162445925ba87438cad6b/crc32c-2.7.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fb9424ec1a8ca54763155a703e763bcede82e6569fe94762614bb2de1412d4e1", size = 51588 }, + { url = "https://files.pythonhosted.org/packages/13/3b/13d40a7dfbf9ef05c84a0da45544ee72080dca4ce090679e5105689984bd/crc32c-2.7.1-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:88732070f6175530db04e0bb36880ac45c33d49f8ac43fa0e50cfb1830049d23", size = 52678 }, + { url = "https://files.pythonhosted.org/packages/36/09/65ffc4fb9fa60ff6714eeb50a92284a4525e5943f0b040b572c0c76368c1/crc32c-2.7.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:57a20dfc27995f568f64775eea2bbb58ae269f1a1144561df5e4a4955f79db32", size = 53847 }, + { url = "https://files.pythonhosted.org/packages/24/71/938e926085b7288da052db7c84416f3ce25e71baf7ab5b63824c7bcb6f22/crc32c-2.7.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:f7186d098bfd2cff25eac6880b7c7ad80431b90610036131c1c7dd0eab42a332", size = 51860 }, + { url = "https://files.pythonhosted.org/packages/3c/d8/4526d5380189d6f2fa27256c204100f30214fe402f47cf6e9fb9a91ab890/crc32c-2.7.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:55a77e29a265418fa34bef15bd0f2c60afae5348988aaf35ed163b4bbf93cf37", size = 52508 }, + { url = "https://files.pythonhosted.org/packages/19/30/15f7e35176488b77e5b88751947d321d603fccac273099ace27c7b2d50a6/crc32c-2.7.1-cp313-cp313t-win32.whl", hash = "sha256:ae38a4b6aa361595d81cab441405fbee905c72273e80a1c010fb878ae77ac769", size = 38319 }, + { url = "https://files.pythonhosted.org/packages/19/c4/0b3eee04dac195f4730d102d7a9fbea894ae7a32ce075f84336df96a385d/crc32c-2.7.1-cp313-cp313t-win_amd64.whl", hash = "sha256:eee2a43b663feb6c79a6c1c6e5eae339c2b72cfac31ee54ec0209fa736cf7ee5", size = 39781 }, +] + [[package]] name = "dag-cbor" version = "0.3.3" @@ -159,12 +195,27 @@ wheels = [ ] [[package]] -name = "fasteners" -version = "0.19" +name = "deprecated" +version = "1.2.18" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "wrapt" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/98/97/06afe62762c9a8a86af0cfb7bfdab22a43ad17138b07af5b1a58442690a2/deprecated-1.2.18.tar.gz", hash = "sha256:422b6f6d859da6f2ef57857761bfb392480502a64c3028ca9bbe86085d72115d", size = 2928744 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6e/c6/ac0b6c1e2d138f1002bcf799d330bd6d85084fece321e662a14223794041/Deprecated-1.2.18-py2.py3-none-any.whl", hash = "sha256:bd5011788200372a32418f888e326a09ff80d0214bd961147cfed01b5c018eec", size = 9998 }, +] + +[[package]] +name = "donfig" +version = "0.8.1.post1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/5f/d4/e834d929be54bfadb1f3e3b931c38e956aaa3b235a46a3c764c26c774902/fasteners-0.19.tar.gz", hash = "sha256:b4f37c3ac52d8a445af3a66bce57b33b5e90b97c696b7b984f530cf8f0ded09c", size = 24832 } +dependencies = [ + { name = "pyyaml" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/25/71/80cc718ff6d7abfbabacb1f57aaa42e9c1552bfdd01e64ddd704e4a03638/donfig-0.8.1.post1.tar.gz", hash = "sha256:3bef3413a4c1c601b585e8d297256d0c1470ea012afa6e8461dc28bfb7c23f52", size = 19506 } wheels = [ - { url = "https://files.pythonhosted.org/packages/61/bf/fd60001b3abc5222d8eaa4a204cd8c0ae78e75adc688f33ce4bf25b7fafa/fasteners-0.19-py3-none-any.whl", hash = "sha256:758819cb5d94cdedf4e836988b74de396ceacb8e2794d21f82d131fd9ee77237", size = 18679 }, + { url = "https://files.pythonhosted.org/packages/0c/d5/c5db1ea3394c6e1732fb3286b3bd878b59507a8f77d32a2cebda7d7b7cd4/donfig-0.8.1.post1-py3-none-any.whl", hash = "sha256:2a3175ce74a06109ff9307d90a230f81215cbac9a751f4d1c6194644b8204f9d", size = 21592 }, ] [[package]] @@ -420,21 +471,27 @@ wheels = [ [[package]] name = "numcodecs" -version = "0.14.1" +version = "0.15.1" source = { registry = "https://pypi.org/simple" } dependencies = [ + { name = "deprecated" }, { name = "numpy" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/00/65/12ab649988ec278e6a80a29cfcda9aea527df0045dd98da47e59ba114917/numcodecs-0.14.1.tar.gz", hash = "sha256:00a364924fd2d600bcce6e2ced96b47c40eb5f9d84bf4b0207aa208d9ce6cd1c", size = 6176906 } +sdist = { url = "https://files.pythonhosted.org/packages/63/fc/bb532969eb8236984ba65e4f0079a7da885b8ac0ce1f0835decbb3938a62/numcodecs-0.15.1.tar.gz", hash = "sha256:eeed77e4d6636641a2cc605fbc6078c7a8f2cc40f3dfa2b3f61e52e6091b04ff", size = 6267275 } wheels = [ - { url = "https://files.pythonhosted.org/packages/9d/01/b78bbf9409a75a72548ee01e6bd76eac2d0f02276d48edcc3acba3e1a258/numcodecs-0.14.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c1a4947b55f307865c28fc1fa41f5b2915448532f3ba4ad30a0c25965263c235", size = 1408695 }, - { url = "https://files.pythonhosted.org/packages/35/4d/374b1b5162ab6a9798684d8e9438b8f50da39333e85e7b504c087b5a0f67/numcodecs-0.14.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9bf83de0a46caab439328034e33f092b2963ca8754baf5260a10e7647e0e25e6", size = 1186487 }, - { url = "https://files.pythonhosted.org/packages/4e/09/e4c7b5a4a6326d95cbe742857d34c978bc9b2256e004c0339539862fa198/numcodecs-0.14.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4837d9a0d63f1181b24c1eb2def3af698a52ed8b9f90b27519e25f17542e9af7", size = 8873511 }, - { url = "https://files.pythonhosted.org/packages/7b/e7/a86bf123a4f3555c1b2a343b36fe5575ed2ed7e3174d7cb278b3b7642c0d/numcodecs-0.14.1-cp312-cp312-win_amd64.whl", hash = "sha256:107b03429e29776973751001f425abae7cd75ece7c3bb5f0578876f6187dad4a", size = 834202 }, - { url = "https://files.pythonhosted.org/packages/5f/21/e3e3af42b31e9d4543923a0ec8bb8b0f12bb9dc1a9f2f00f34d6421bd2d1/numcodecs-0.14.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:801885de34f83f423e5d2ebaccb30ee41e533593e669ca1a18160b338fe3644a", size = 1399876 }, - { url = "https://files.pythonhosted.org/packages/a9/e3/2062537c65215befb3b662c6cc865836557a73fe0237fe754d3b35dec81b/numcodecs-0.14.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a72861bc2a179bc121deac77e823280aed91e8bda7689f8a87fb24efcc99adb6", size = 1177899 }, - { url = "https://files.pythonhosted.org/packages/75/d7/d5f42598dd342c3b3f6cef6b93214a38690acff91436d75350882a6e0d5e/numcodecs-0.14.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b9abfd2d5904578aadae467e5e6a71aabc6faa1050973d5d9452187ba647537d", size = 8828942 }, - { url = "https://files.pythonhosted.org/packages/4b/e2/ac784ac4b6e5841e4bfb7d3e7e38497450df18eebff9465990a8ac9aecfc/numcodecs-0.14.1-cp313-cp313-win_amd64.whl", hash = "sha256:1c74040b99fab8a0fc3d265f6c4489fa0e1df06d5ac57e2d6a5d3320f22a4a4b", size = 829509 }, + { url = "https://files.pythonhosted.org/packages/e7/7e/f12fc32d3beedc6a8f1ec69ea0ba72e93cb99c0350feed2cff5d04679bc3/numcodecs-0.15.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b0a9d9cd29a0088220682dda4a9898321f7813ff7802be2bbb545f6e3d2f10ff", size = 1691889 }, + { url = "https://files.pythonhosted.org/packages/81/38/88e40d40288b73c3b3a390ed5614a34b0661d00255bdd4cfb91c32101364/numcodecs-0.15.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a34f0fe5e5f3b837bbedbeb98794a6d4a12eeeef8d4697b523905837900b5e1c", size = 1189149 }, + { url = "https://files.pythonhosted.org/packages/28/7d/7527d9180bc76011d6163c848c9cf02cd28a623c2c66cf543e1e86de7c5e/numcodecs-0.15.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c3a09e22140f2c691f7df26303ff8fa2dadcf26d7d0828398c0bc09b69e5efa3", size = 8879163 }, + { url = "https://files.pythonhosted.org/packages/ab/bc/b6c3cde91c754860a3467a8c058dcf0b1a5ca14d82b1c5397c700cf8b1eb/numcodecs-0.15.1-cp312-cp312-win_amd64.whl", hash = "sha256:daed6066ffcf40082da847d318b5ab6123d69ceb433ba603cb87c323a541a8bc", size = 836785 }, + { url = "https://files.pythonhosted.org/packages/78/57/acbc54b3419e5be65015e47177c76c0a73e037fd3ae2cde5808169194d4d/numcodecs-0.15.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e3d82b70500cf61e8d115faa0d0a76be6ecdc24a16477ee3279d711699ad85f3", size = 1688220 }, + { url = "https://files.pythonhosted.org/packages/b6/56/9863fa6dc679f40a31bea5e9713ee5507a31dcd3ee82ea4b1a9268ce52e8/numcodecs-0.15.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1d471a1829ce52d3f365053a2bd1379e32e369517557c4027ddf5ac0d99c591e", size = 1180294 }, + { url = "https://files.pythonhosted.org/packages/fa/91/d96999b41e3146b6c0ce6bddc5ad85803cb4d743c95394562c2a4bb8cded/numcodecs-0.15.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1dfdea4a67108205edfce99c1cb6cd621343bc7abb7e16a041c966776920e7de", size = 8834323 }, + { url = "https://files.pythonhosted.org/packages/c3/32/233e5ede6568bdb044e6f99aaa9fa39827ff3109c6487fc137315f733586/numcodecs-0.15.1-cp313-cp313-win_amd64.whl", hash = "sha256:a4f7bdb26f1b34423cb56d48e75821223be38040907c9b5954eeb7463e7eb03c", size = 831955 }, +] + +[package.optional-dependencies] +crc32c = [ + { name = "crc32c" }, ] [[package]] @@ -560,6 +617,7 @@ dependencies = [ { name = "multiformats", extra = ["full"] }, { name = "pycryptodome" }, { name = "requests" }, + { name = "zarr" }, ] [package.dev-dependencies] @@ -574,7 +632,6 @@ dev = [ { name = "ruff" }, { name = "snakeviz" }, { name = "xarray" }, - { name = "zarr" }, ] [package.metadata] @@ -584,6 +641,7 @@ requires-dist = [ { name = "multiformats", extras = ["full"], specifier = ">=0.3.1.post4" }, { name = "pycryptodome", specifier = ">=3.21.0" }, { name = "requests", specifier = ">=2.32.3" }, + { name = "zarr" }, ] [package.metadata.requires-dev] @@ -597,8 +655,7 @@ dev = [ { name = "pytest-cov", specifier = ">=6.0.0" }, { name = "ruff", specifier = ">=0.7.1" }, { name = "snakeviz", specifier = ">=2.2.0" }, - { name = "xarray", specifier = "==2024.11.0" }, - { name = "zarr", specifier = "==2.18.3" }, + { name = "xarray", specifier = ">=2025.1.2" }, ] [[package]] @@ -701,6 +758,32 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/11/c3/005fcca25ce078d2cc29fd559379817424e94885510568bc1bc53d7d5846/pytz-2024.2-py2.py3-none-any.whl", hash = "sha256:31c7c1817eb7fae7ca4b8c7ee50c72f93aa2dd863de768e1ef4245d426aa0725", size = 508002 }, ] +[[package]] +name = "pyyaml" +version = "6.0.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/54/ed/79a089b6be93607fa5cdaedf301d7dfb23af5f25c398d5ead2525b063e17/pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e", size = 130631 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/86/0c/c581167fc46d6d6d7ddcfb8c843a4de25bdd27e4466938109ca68492292c/PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab", size = 183873 }, + { url = "https://files.pythonhosted.org/packages/a8/0c/38374f5bb272c051e2a69281d71cba6fdb983413e6758b84482905e29a5d/PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725", size = 173302 }, + { url = "https://files.pythonhosted.org/packages/c3/93/9916574aa8c00aa06bbac729972eb1071d002b8e158bd0e83a3b9a20a1f7/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5", size = 739154 }, + { url = "https://files.pythonhosted.org/packages/95/0f/b8938f1cbd09739c6da569d172531567dbcc9789e0029aa070856f123984/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425", size = 766223 }, + { url = "https://files.pythonhosted.org/packages/b9/2b/614b4752f2e127db5cc206abc23a8c19678e92b23c3db30fc86ab731d3bd/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476", size = 767542 }, + { url = "https://files.pythonhosted.org/packages/d4/00/dd137d5bcc7efea1836d6264f049359861cf548469d18da90cd8216cf05f/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48", size = 731164 }, + { url = "https://files.pythonhosted.org/packages/c9/1f/4f998c900485e5c0ef43838363ba4a9723ac0ad73a9dc42068b12aaba4e4/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b", size = 756611 }, + { url = "https://files.pythonhosted.org/packages/df/d1/f5a275fdb252768b7a11ec63585bc38d0e87c9e05668a139fea92b80634c/PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4", size = 140591 }, + { url = "https://files.pythonhosted.org/packages/0c/e8/4f648c598b17c3d06e8753d7d13d57542b30d56e6c2dedf9c331ae56312e/PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8", size = 156338 }, + { url = "https://files.pythonhosted.org/packages/ef/e3/3af305b830494fa85d95f6d95ef7fa73f2ee1cc8ef5b495c7c3269fb835f/PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba", size = 181309 }, + { url = "https://files.pythonhosted.org/packages/45/9f/3b1c20a0b7a3200524eb0076cc027a970d320bd3a6592873c85c92a08731/PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1", size = 171679 }, + { url = "https://files.pythonhosted.org/packages/7c/9a/337322f27005c33bcb656c655fa78325b730324c78620e8328ae28b64d0c/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133", size = 733428 }, + { url = "https://files.pythonhosted.org/packages/a3/69/864fbe19e6c18ea3cc196cbe5d392175b4cf3d5d0ac1403ec3f2d237ebb5/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484", size = 763361 }, + { url = "https://files.pythonhosted.org/packages/04/24/b7721e4845c2f162d26f50521b825fb061bc0a5afcf9a386840f23ea19fa/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5", size = 759523 }, + { url = "https://files.pythonhosted.org/packages/2b/b2/e3234f59ba06559c6ff63c4e10baea10e5e7df868092bf9ab40e5b9c56b6/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc", size = 726660 }, + { url = "https://files.pythonhosted.org/packages/fe/0f/25911a9f080464c59fab9027482f822b86bf0608957a5fcc6eaac85aa515/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652", size = 751597 }, + { url = "https://files.pythonhosted.org/packages/14/0d/e2c3b43bbce3cf6bd97c840b46088a3031085179e596d4929729d8d68270/PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183", size = 140527 }, + { url = "https://files.pythonhosted.org/packages/fa/de/02b54f42487e3d3c6efb3f89428677074ca7bf43aae402517bc7cca949f3/PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563", size = 156446 }, +] + [[package]] name = "requests" version = "2.32.3" @@ -862,31 +945,74 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ce/d9/5f4c13cecde62396b0d3fe530a50ccea91e7dfc1ccf0e09c228841bb5ba8/urllib3-2.2.3-py3-none-any.whl", hash = "sha256:ca899ca043dcb1bafa3e262d73aa25c465bfb49e0bd9dd5d59f1d0acba2f8fac", size = 126338 }, ] +[[package]] +name = "wrapt" +version = "1.17.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c3/fc/e91cc220803d7bc4db93fb02facd8461c37364151b8494762cc88b0fbcef/wrapt-1.17.2.tar.gz", hash = "sha256:41388e9d4d1522446fe79d3213196bd9e3b301a336965b9e27ca2788ebd122f3", size = 55531 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a1/bd/ab55f849fd1f9a58ed7ea47f5559ff09741b25f00c191231f9f059c83949/wrapt-1.17.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:d5e2439eecc762cd85e7bd37161d4714aa03a33c5ba884e26c81559817ca0925", size = 53799 }, + { url = "https://files.pythonhosted.org/packages/53/18/75ddc64c3f63988f5a1d7e10fb204ffe5762bc663f8023f18ecaf31a332e/wrapt-1.17.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:3fc7cb4c1c744f8c05cd5f9438a3caa6ab94ce8344e952d7c45a8ed59dd88392", size = 38821 }, + { url = "https://files.pythonhosted.org/packages/48/2a/97928387d6ed1c1ebbfd4efc4133a0633546bec8481a2dd5ec961313a1c7/wrapt-1.17.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8fdbdb757d5390f7c675e558fd3186d590973244fab0c5fe63d373ade3e99d40", size = 38919 }, + { url = "https://files.pythonhosted.org/packages/73/54/3bfe5a1febbbccb7a2f77de47b989c0b85ed3a6a41614b104204a788c20e/wrapt-1.17.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5bb1d0dbf99411f3d871deb6faa9aabb9d4e744d67dcaaa05399af89d847a91d", size = 88721 }, + { url = "https://files.pythonhosted.org/packages/25/cb/7262bc1b0300b4b64af50c2720ef958c2c1917525238d661c3e9a2b71b7b/wrapt-1.17.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d18a4865f46b8579d44e4fe1e2bcbc6472ad83d98e22a26c963d46e4c125ef0b", size = 80899 }, + { url = "https://files.pythonhosted.org/packages/2a/5a/04cde32b07a7431d4ed0553a76fdb7a61270e78c5fd5a603e190ac389f14/wrapt-1.17.2-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc570b5f14a79734437cb7b0500376b6b791153314986074486e0b0fa8d71d98", size = 89222 }, + { url = "https://files.pythonhosted.org/packages/09/28/2e45a4f4771fcfb109e244d5dbe54259e970362a311b67a965555ba65026/wrapt-1.17.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6d9187b01bebc3875bac9b087948a2bccefe464a7d8f627cf6e48b1bbae30f82", size = 86707 }, + { url = "https://files.pythonhosted.org/packages/c6/d2/dcb56bf5f32fcd4bd9aacc77b50a539abdd5b6536872413fd3f428b21bed/wrapt-1.17.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:9e8659775f1adf02eb1e6f109751268e493c73716ca5761f8acb695e52a756ae", size = 79685 }, + { url = "https://files.pythonhosted.org/packages/80/4e/eb8b353e36711347893f502ce91c770b0b0929f8f0bed2670a6856e667a9/wrapt-1.17.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e8b2816ebef96d83657b56306152a93909a83f23994f4b30ad4573b00bd11bb9", size = 87567 }, + { url = "https://files.pythonhosted.org/packages/17/27/4fe749a54e7fae6e7146f1c7d914d28ef599dacd4416566c055564080fe2/wrapt-1.17.2-cp312-cp312-win32.whl", hash = "sha256:468090021f391fe0056ad3e807e3d9034e0fd01adcd3bdfba977b6fdf4213ea9", size = 36672 }, + { url = "https://files.pythonhosted.org/packages/15/06/1dbf478ea45c03e78a6a8c4be4fdc3c3bddea5c8de8a93bc971415e47f0f/wrapt-1.17.2-cp312-cp312-win_amd64.whl", hash = "sha256:ec89ed91f2fa8e3f52ae53cd3cf640d6feff92ba90d62236a81e4e563ac0e991", size = 38865 }, + { url = "https://files.pythonhosted.org/packages/ce/b9/0ffd557a92f3b11d4c5d5e0c5e4ad057bd9eb8586615cdaf901409920b14/wrapt-1.17.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:6ed6ffac43aecfe6d86ec5b74b06a5be33d5bb9243d055141e8cabb12aa08125", size = 53800 }, + { url = "https://files.pythonhosted.org/packages/c0/ef/8be90a0b7e73c32e550c73cfb2fa09db62234227ece47b0e80a05073b375/wrapt-1.17.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:35621ae4c00e056adb0009f8e86e28eb4a41a4bfa8f9bfa9fca7d343fe94f998", size = 38824 }, + { url = "https://files.pythonhosted.org/packages/36/89/0aae34c10fe524cce30fe5fc433210376bce94cf74d05b0d68344c8ba46e/wrapt-1.17.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a604bf7a053f8362d27eb9fefd2097f82600b856d5abe996d623babd067b1ab5", size = 38920 }, + { url = "https://files.pythonhosted.org/packages/3b/24/11c4510de906d77e0cfb5197f1b1445d4fec42c9a39ea853d482698ac681/wrapt-1.17.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5cbabee4f083b6b4cd282f5b817a867cf0b1028c54d445b7ec7cfe6505057cf8", size = 88690 }, + { url = "https://files.pythonhosted.org/packages/71/d7/cfcf842291267bf455b3e266c0c29dcb675b5540ee8b50ba1699abf3af45/wrapt-1.17.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:49703ce2ddc220df165bd2962f8e03b84c89fee2d65e1c24a7defff6f988f4d6", size = 80861 }, + { url = "https://files.pythonhosted.org/packages/d5/66/5d973e9f3e7370fd686fb47a9af3319418ed925c27d72ce16b791231576d/wrapt-1.17.2-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8112e52c5822fc4253f3901b676c55ddf288614dc7011634e2719718eaa187dc", size = 89174 }, + { url = "https://files.pythonhosted.org/packages/a7/d3/8e17bb70f6ae25dabc1aaf990f86824e4fd98ee9cadf197054e068500d27/wrapt-1.17.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:9fee687dce376205d9a494e9c121e27183b2a3df18037f89d69bd7b35bcf59e2", size = 86721 }, + { url = "https://files.pythonhosted.org/packages/6f/54/f170dfb278fe1c30d0ff864513cff526d624ab8de3254b20abb9cffedc24/wrapt-1.17.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:18983c537e04d11cf027fbb60a1e8dfd5190e2b60cc27bc0808e653e7b218d1b", size = 79763 }, + { url = "https://files.pythonhosted.org/packages/4a/98/de07243751f1c4a9b15c76019250210dd3486ce098c3d80d5f729cba029c/wrapt-1.17.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:703919b1633412ab54bcf920ab388735832fdcb9f9a00ae49387f0fe67dad504", size = 87585 }, + { url = "https://files.pythonhosted.org/packages/f9/f0/13925f4bd6548013038cdeb11ee2cbd4e37c30f8bfd5db9e5a2a370d6e20/wrapt-1.17.2-cp313-cp313-win32.whl", hash = "sha256:abbb9e76177c35d4e8568e58650aa6926040d6a9f6f03435b7a522bf1c487f9a", size = 36676 }, + { url = "https://files.pythonhosted.org/packages/bf/ae/743f16ef8c2e3628df3ddfd652b7d4c555d12c84b53f3d8218498f4ade9b/wrapt-1.17.2-cp313-cp313-win_amd64.whl", hash = "sha256:69606d7bb691b50a4240ce6b22ebb319c1cfb164e5f6569835058196e0f3a845", size = 38871 }, + { url = "https://files.pythonhosted.org/packages/3d/bc/30f903f891a82d402ffb5fda27ec1d621cc97cb74c16fea0b6141f1d4e87/wrapt-1.17.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:4a721d3c943dae44f8e243b380cb645a709ba5bd35d3ad27bc2ed947e9c68192", size = 56312 }, + { url = "https://files.pythonhosted.org/packages/8a/04/c97273eb491b5f1c918857cd26f314b74fc9b29224521f5b83f872253725/wrapt-1.17.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:766d8bbefcb9e00c3ac3b000d9acc51f1b399513f44d77dfe0eb026ad7c9a19b", size = 40062 }, + { url = "https://files.pythonhosted.org/packages/4e/ca/3b7afa1eae3a9e7fefe499db9b96813f41828b9fdb016ee836c4c379dadb/wrapt-1.17.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e496a8ce2c256da1eb98bd15803a79bee00fc351f5dfb9ea82594a3f058309e0", size = 40155 }, + { url = "https://files.pythonhosted.org/packages/89/be/7c1baed43290775cb9030c774bc53c860db140397047cc49aedaf0a15477/wrapt-1.17.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40d615e4fe22f4ad3528448c193b218e077656ca9ccb22ce2cb20db730f8d306", size = 113471 }, + { url = "https://files.pythonhosted.org/packages/32/98/4ed894cf012b6d6aae5f5cc974006bdeb92f0241775addad3f8cd6ab71c8/wrapt-1.17.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a5aaeff38654462bc4b09023918b7f21790efb807f54c000a39d41d69cf552cb", size = 101208 }, + { url = "https://files.pythonhosted.org/packages/ea/fd/0c30f2301ca94e655e5e057012e83284ce8c545df7661a78d8bfca2fac7a/wrapt-1.17.2-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9a7d15bbd2bc99e92e39f49a04653062ee6085c0e18b3b7512a4f2fe91f2d681", size = 109339 }, + { url = "https://files.pythonhosted.org/packages/75/56/05d000de894c4cfcb84bcd6b1df6214297b8089a7bd324c21a4765e49b14/wrapt-1.17.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:e3890b508a23299083e065f435a492b5435eba6e304a7114d2f919d400888cc6", size = 110232 }, + { url = "https://files.pythonhosted.org/packages/53/f8/c3f6b2cf9b9277fb0813418e1503e68414cd036b3b099c823379c9575e6d/wrapt-1.17.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:8c8b293cd65ad716d13d8dd3624e42e5a19cc2a2f1acc74b30c2c13f15cb61a6", size = 100476 }, + { url = "https://files.pythonhosted.org/packages/a7/b1/0bb11e29aa5139d90b770ebbfa167267b1fc548d2302c30c8f7572851738/wrapt-1.17.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4c82b8785d98cdd9fed4cac84d765d234ed3251bd6afe34cb7ac523cb93e8b4f", size = 106377 }, + { url = "https://files.pythonhosted.org/packages/6a/e1/0122853035b40b3f333bbb25f1939fc1045e21dd518f7f0922b60c156f7c/wrapt-1.17.2-cp313-cp313t-win32.whl", hash = "sha256:13e6afb7fe71fe7485a4550a8844cc9ffbe263c0f1a1eea569bc7091d4898555", size = 37986 }, + { url = "https://files.pythonhosted.org/packages/09/5e/1655cf481e079c1f22d0cabdd4e51733679932718dc23bf2db175f329b76/wrapt-1.17.2-cp313-cp313t-win_amd64.whl", hash = "sha256:eaf675418ed6b3b31c7a989fd007fa7c3be66ce14e5c3b27336383604c9da85c", size = 40750 }, + { url = "https://files.pythonhosted.org/packages/2d/82/f56956041adef78f849db6b289b282e72b55ab8045a75abad81898c28d19/wrapt-1.17.2-py3-none-any.whl", hash = "sha256:b18f2d1533a71f069c7f82d524a52599053d4c7166e9dd374ae2136b7f40f7c8", size = 23594 }, +] + [[package]] name = "xarray" -version = "2024.11.0" +version = "2025.1.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "numpy" }, { name = "packaging" }, { name = "pandas" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/3f/d6/5ae0a721bd6cac85b30cff6b119dc6b5e73b735aacbfb43d3ed2680504d7/xarray-2024.11.0.tar.gz", hash = "sha256:1ccace44573ddb862e210ad3ec204210654d2c750bec11bbe7d842dfc298591f", size = 3247277 } +sdist = { url = "https://files.pythonhosted.org/packages/89/c0/edb2f6cfafa5369106f927409f6211141c3296c14877cc634d8ee4c970a4/xarray-2025.1.2.tar.gz", hash = "sha256:e7675c79ac69d274dd3b3c5450ce57176928d2792947576251ed1c7df1783224", size = 3271214 } wheels = [ - { url = "https://files.pythonhosted.org/packages/40/ed/1c4631ad5909487ea8907cd326d9855c2207d790e3936e77bda48173b8be/xarray-2024.11.0-py3-none-any.whl", hash = "sha256:6ee94f63ddcbdd0cf3909d1177f78cdac756640279c0e32ae36819a89cdaba37", size = 1231951 }, + { url = "https://files.pythonhosted.org/packages/05/79/4e19100342fe13d69fd6e77b343e2269924fec681258e2ea21b55576aad2/xarray-2025.1.2-py3-none-any.whl", hash = "sha256:a7ad6a36c6e0becd67f8aff6a7808d20e4bdcd344debb5205f0a34b1a4a7f8d6", size = 1247773 }, ] [[package]] name = "zarr" -version = "2.18.3" +version = "3.0.5" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "asciitree" }, - { name = "fasteners", marker = "sys_platform != 'emscripten'" }, - { name = "numcodecs" }, + { name = "donfig" }, + { name = "numcodecs", extra = ["crc32c"] }, { name = "numpy" }, + { name = "packaging" }, + { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/23/c4/187a21ce7cf7c8f00c060dd0e04c2a81139bb7b1ab178bba83f2e1134ce2/zarr-2.18.3.tar.gz", hash = "sha256:2580d8cb6dd84621771a10d31c4d777dca8a27706a1a89b29f42d2d37e2df5ce", size = 3603224 } +sdist = { url = "https://files.pythonhosted.org/packages/0f/27/9969116e77d0ce3996c58630678fa20ff094a1599018ef24006c1df480c0/zarr-3.0.5.tar.gz", hash = "sha256:4ac0a09d66875d398ab53c95fd4bddca2f3d757a04454831fc2d54bfbafcb7e5", size = 240716 } wheels = [ - { url = "https://files.pythonhosted.org/packages/ed/c9/142095e654c2b97133ff71df60979422717b29738b08bc8a1709a5d5e0d0/zarr-2.18.3-py3-none-any.whl", hash = "sha256:b1f7dfd2496f436745cdd4c7bcf8d3b4bc1dceef5fdd0d589c87130d842496dd", size = 210723 }, + { url = "https://files.pythonhosted.org/packages/8e/2c/1ce2f94d062a9457d980831f60062f274ac3286006c7ef007b9720fa562c/zarr-3.0.5-py3-none-any.whl", hash = "sha256:e56f8cf1f8105a118a9cdadb78f3b44363b9c67f326a569d79271a1f1cb338e1", size = 196354 }, ] From 91b661648c2a42e9819ea4cdfbd2ac3ea3c5cabd Mon Sep 17 00:00:00 2001 From: Abid Sikder <41392423+abidsikder@users.noreply.github.com> Date: Thu, 20 Mar 2025 14:32:33 -0400 Subject: [PATCH 02/11] initial version with write/read and encryption --- py_hamt/__init__.py | 2 + py_hamt/ipfszarr3.py | 119 ++++++++++++++++ py_hamt/zarr_encryption_transformers.py | 26 ++-- tests/test_zarr_ipfs.py | 177 ++++++++++++------------ 4 files changed, 219 insertions(+), 105 deletions(-) create mode 100644 py_hamt/ipfszarr3.py diff --git a/py_hamt/__init__.py b/py_hamt/__init__.py index c526626..ac9b60f 100644 --- a/py_hamt/__init__.py +++ b/py_hamt/__init__.py @@ -1,6 +1,7 @@ from .hamt import HAMT, blake3_hashfn from .store import Store, DictStore, IPFSStore from .zarr_encryption_transformers import create_zarr_encryption_transformers +from .ipfszarr3 import IPFSZarr3 __all__ = [ "HAMT", @@ -9,4 +10,5 @@ "DictStore", "IPFSStore", "create_zarr_encryption_transformers", + "IPFSZarr3", ] diff --git a/py_hamt/ipfszarr3.py b/py_hamt/ipfszarr3.py new file mode 100644 index 0000000..47adaf6 --- /dev/null +++ b/py_hamt/ipfszarr3.py @@ -0,0 +1,119 @@ +from collections.abc import AsyncIterator, Iterable +import zarr.abc.store +import zarr.core.buffer +from zarr.core.common import BytesLike + +from py_hamt.hamt import HAMT + + +class IPFSZarr3(zarr.abc.store.Store): + hamt: HAMT + + def __init__(self, hamt: HAMT, read_only: bool = False) -> None: + super().__init__(read_only=read_only) + self.hamt = hamt + if read_only: + self.hamt.make_read_only() + else: + self.hamt.enable_write() + + @property + def read_only(self) -> bool: + return self.hamt.read_only + + def __eq__(self, val: object) -> bool: + if not isinstance(val, IPFSZarr3): + return False + return self.hamt.root_node_id == val.hamt.root_node_id + + async def get( + self, + key: str, + prototype: zarr.core.buffer.BufferPrototype, + byte_range: zarr.abc.store.ByteRequest | None = None, + ) -> zarr.core.buffer.Buffer | None: + if key not in self.hamt: + return + # We know this value will always be bytes since we only store bytes in the HAMT + val: bytes = self.hamt[key] # type: ignore + return prototype.buffer.from_bytes(val) # type: ignore + + subset: bytes + match byte_range: + case None: + subset = val + case zarr.abc.store.RangeByteRequest: + subset = val[byte_range.start : byte_range.end] + case zarr.abc.store.OffsetByteRequest: + subset = val[byte_range.offset :] + case zarr.abc.store.SuffixByteRequest: + subset = val[-byte_range.suffix :] + return prototype.buffer.from_bytes(subset) # type: ignore + + async def get_partial_values( + self, + prototype: zarr.core.buffer.BufferPrototype, + key_ranges: Iterable[tuple[str, zarr.abc.store.ByteRequest | None]], + ) -> list[zarr.core.buffer.Buffer | None]: + results: list[zarr.core.buffer.Buffer | None] = [] + + for key, ran in key_ranges: + results.append(await self.get(key, prototype, ran)) + + return results + + async def exists(self, key: str) -> bool: + return key in self.hamt + + @property + def supports_writes(self) -> bool: + return not self.hamt.read_only + + @property + def supports_partial_writes(self) -> bool: + return False + + async def set(self, key: str, value: zarr.core.buffer.Buffer) -> None: + self.hamt[key] = value.to_bytes() + + async def set_if_not_exists(self, key: str, value: zarr.core.buffer.Buffer) -> None: + if key not in self.hamt: + await self.set(key, value) + + async def set_partial_values( + self, key_start_values: Iterable[tuple[str, int, BytesLike]] + ) -> None: + for key, range_start, val in key_start_values: + subset = val[range_start:] + self.hamt[key] = bytes(subset) + + @property + def supports_deletes(self) -> bool: + return not self.hamt.read_only + + async def delete(self, key: str) -> None: + del self.hamt[key] + + @property + def supports_listing(self) -> bool: + return True + + async def list(self) -> AsyncIterator[str]: + for key in self.hamt: + yield key + + async def list_prefix(self, prefix: str) -> AsyncIterator: + for key in self.hamt: + if key.startswith(prefix): + yield key + + async def list_dir(self, prefix: str) -> AsyncIterator: + for key in self.hamt: + if key.startswith(prefix): + suffix = key[len(prefix) :] + first_slash = suffix.find("/") + if first_slash == -1: + yield suffix + else: + name = suffix[0:first_slash] + yield name diff --git a/py_hamt/zarr_encryption_transformers.py b/py_hamt/zarr_encryption_transformers.py index f4ab938..9e7256e 100644 --- a/py_hamt/zarr_encryption_transformers.py +++ b/py_hamt/zarr_encryption_transformers.py @@ -4,18 +4,6 @@ from Crypto.Cipher import ChaCha20_Poly1305 from Crypto.Random import get_random_bytes -# Metadata files used in zarr v2 -_metadata_files = [ - # top level metadata - ".zattrs", # Also found within folders for variables - ".zgroup", - ".zmetadata", - # Found within folders for variables - ".zarray", - # important for coordinate variables, so that we can read bounds - "0", -] - type TransformerFN = Callable[[str, bytes], bytes] @@ -31,7 +19,9 @@ def create_zarr_encryption_transformers( Note that the encryption key must always be 32 bytes long. A header is required by the underlying encryption algorithm. Every time a zarr chunk is encrypted, a random 24-byte nonce is generated. This is saved with the chunk for use when reading back. - Metadata within a zarr, such as ".zattrs" or ".zgroup" are always ignored, to allow for calculating an encrypted zarr's structure without necessarily having the encryption key. You may also set some variables to be entirely unencrypted with the exclude_vars argument. This allows for partially encrypted zarrs which can be loaded into xarray but the values of encrypted variables cannot be accessed (errors will be thrown). + zarr.json metadata files in a zarr v3 are always ignored, to allow for calculating an encrypted zarr's structure without having the encryption key. + + With `exclude_vars` you may also set some variables to be unencrypted. This allows for partially encrypted zarrs which can be loaded into xarray but the values of encrypted variables cannot be accessed (errors will be thrown). You should generally include your coordinate variables along with your data variables in here. """ if len(encryption_key) != 32: @@ -39,10 +29,16 @@ def create_zarr_encryption_transformers( def _should_transform(key: str) -> bool: p = Path(key) - if p.parent.name in exclude_vars: + + # Find the first directory name in the path since zarr v3 chunks are stored in a nested directory structure + # e.g. for Path("precip/c/0/0/1") it would return "precip" + if p.parts[0] in exclude_vars: return False - if p.name in _metadata_files: + + # Don't transform metadata files + if p.name == "zarr.json": return False + return True def encrypt(key: str, val: bytes) -> bytes: diff --git a/tests/test_zarr_ipfs.py b/tests/test_zarr_ipfs.py index 0ccab52..00b6cb7 100644 --- a/tests/test_zarr_ipfs.py +++ b/tests/test_zarr_ipfs.py @@ -1,15 +1,14 @@ import os import shutil import tempfile +import time -from multiformats import CID import numpy as np import pandas as pd import xarray as xr import pytest -import time -from py_hamt import HAMT, IPFSStore, create_zarr_encryption_transformers +from py_hamt import HAMT, IPFSStore, IPFSZarr3, create_zarr_encryption_transformers @pytest.fixture(scope="module") @@ -64,116 +63,114 @@ def random_zarr_dataset(): shutil.rmtree(temp_dir) -def test_upload_then_read(random_zarr_dataset: tuple[str, xr.Dataset]): - zarr_path, expected_ds = random_zarr_dataset - test_ds = xr.open_zarr(zarr_path) - - print("Writing this xarray Dataset to IPFS") +# This test also collects miscellaneous statistics about performance, run with pytest -s to see these statistics being printed out +def test_write_read(random_zarr_dataset: tuple[str, xr.Dataset]): + _, test_ds = random_zarr_dataset + print("=== Writing this xarray Dataset to a Zarr v3 on IPFS ===") print(test_ds) - ipfs_store_write = IPFSStore(debug=True) - hamt_write = HAMT(store=ipfs_store_write) - start_time = time.time() - test_ds.to_zarr(store=hamt_write, mode="w") - end_time = time.time() - total_time = end_time - start_time - print("=== Write Stats ===") - print(f"Total time in seconds: {total_time:.2f}") - print(f"Sent bytes: {ipfs_store_write.total_sent}") - print(f"Received bytes: {ipfs_store_write.total_received}") - - hamt_write_cid: CID = hamt_write.root_node_id # type: ignore - print(f"Root CID: {hamt_write_cid}") - - ipfs_store_read = IPFSStore(debug=True) - hamt_read = HAMT(store=ipfs_store_read, root_node_id=hamt_write_cid, read_only=True) - start_time = time.time() - loaded_ds = xr.open_zarr(store=hamt_read) - end_time = time.time() - total_time = end_time - start_time - - xr.testing.assert_identical(loaded_ds, expected_ds) - - assert "temp" in loaded_ds - assert "precip" in loaded_ds - assert loaded_ds.temp.attrs["units"] == "celsius" - - assert loaded_ds.temp.shape == expected_ds.temp.shape - - print("=== Read Stats ===") - print(f"Total time in seconds: {total_time:.2f}") - print(f"Sent bytes: {ipfs_store_read.total_sent}") - print(f"Received bytes: {ipfs_store_read.total_received}") + ipfsstore = IPFSStore(debug=True) + hamt = HAMT(store=ipfsstore) + start = time.perf_counter() + ipfszarr3 = IPFSZarr3(hamt) + test_ds.to_zarr(store=ipfszarr3) # type: ignore + from pathlib import Path + end = time.perf_counter() + elapsed = end - start + print("=== Write Stats") + print(f"Total time in seconds: {elapsed:.2f}") + print(f"Sent bytes: {ipfsstore.total_sent}") + print(f"Received bytes: {ipfsstore.total_received}") + print("=== Root CID") + cid = hamt.root_node_id + print(cid) + + print("=== Reading data back in and checking if identical") + ipfsstore = IPFSStore(debug=True) + hamt = HAMT(store=ipfsstore, root_node_id=cid, read_only=True) + start = time.perf_counter() + ipfs_ds: xr.Dataset + ipfszarr3 = IPFSZarr3(hamt) + ipfs_ds = xr.open_zarr(store=ipfszarr3) + xr.testing.assert_identical(test_ds, ipfs_ds) + + end = time.perf_counter() + print("=== Read Stats") + print(f"Total time in seconds: {elapsed:.2f}") + print(f"Sent bytes: {ipfsstore.total_sent}") + print(f"Received bytes: {ipfsstore.total_received}") def test_encryption(random_zarr_dataset: tuple[str, xr.Dataset]): - zarr_path, expected_ds = random_zarr_dataset - test_ds = xr.open_zarr(zarr_path) + _, test_ds = random_zarr_dataset with pytest.raises(ValueError, match="Encryption key is not 32 bytes"): create_zarr_encryption_transformers(bytes(), bytes()) encryption_key = bytes(32) - # Encrypt only precipitation, not temperature + # Encrypt only precipitation, not temperature or the coordinate variables encrypt, decrypt = create_zarr_encryption_transformers( - encryption_key, header="sample-header".encode(), exclude_vars=["temp"] + encryption_key, header="sample-header".encode(), exclude_vars=["lat", "lon", "time", "temp"] ) hamt = HAMT( store=IPFSStore(), transformer_encode=encrypt, transformer_decode=decrypt ) - test_ds.to_zarr(store=hamt, mode="w") + ipfszarr3 = IPFSZarr3(hamt) + test_ds.to_zarr(store=ipfszarr3) # type: ignore - hamt.make_read_only() - loaded_ds = xr.open_zarr(store=hamt) - xr.testing.assert_identical(loaded_ds, expected_ds) + ipfs_ds = xr.open_zarr(store=ipfszarr3) + xr.testing.assert_identical(ipfs_ds, test_ds) # Now trying to load without a decryptor, xarray should be able to read the metadata and still perform operations on the unencrypted variable - print("Attempting to read and print metadata of partially encrypted zarr") + print("=== Attempting to read and print metadata of partially encrypted zarr") + ds = xr.open_zarr( - store=HAMT(store=IPFSStore(), root_node_id=hamt.root_node_id, read_only=True) + store=IPFSZarr3( + HAMT(store=IPFSStore(), root_node_id=hamt.root_node_id, read_only=True) + ) ) print(ds) - assert ds.temp.sum() == expected_ds.temp.sum() + assert ds.temp.sum() == test_ds.temp.sum() # We should be unable to read precipitation values which are still encrypted with pytest.raises(Exception): ds.precip.sum() -# This test assumes the other IPFSStore zarr ipfs tests are working fine, so if other things are breaking check those first -def test_authenticated_gateway(random_zarr_dataset: tuple[str, xr.Dataset]): - zarr_path, test_ds = random_zarr_dataset - - def write_and_check(store: IPFSStore) -> bool: - try: - store.rpc_uri_stem = "http://127.0.0.1:5002" # 5002 is the port configured in the run-checks.yaml actions file for nginx to serve the proxy on - hamt = HAMT(store=store) - test_ds.to_zarr(store=hamt, mode="w") - loaded_ds = xr.open_zarr(store=hamt) - xr.testing.assert_identical(test_ds, loaded_ds) - return True - except Exception as _: - return False - - # Test with API Key - api_key_store = IPFSStore(api_key="test") - assert write_and_check(api_key_store) - - # Test that wrong API Key fails - bad_api_key_store = IPFSStore(api_key="badKey") - assert not write_and_check(bad_api_key_store) - - # Test just bearer token - bearer_ipfs_store = IPFSStore(bearer_token="test") - assert write_and_check(bearer_ipfs_store) - - # Test with wrong bearer - bad_bearer_store = IPFSStore(bearer_token="wrongBearer") - assert not write_and_check(bad_bearer_store) - - # Test with just basic auth - basic_auth_store = IPFSStore(basic_auth=("test", "test")) - assert write_and_check(basic_auth_store) - - # Test with wrong basic auth - bad_basic_auth_store = IPFSStore(basic_auth=("wrong", "wrong")) - assert not write_and_check(bad_basic_auth_store) +# # This test assumes the other zarr ipfs tests are working fine, so if other things are breaking check those first +# def test_authenticated_gateway(random_zarr_dataset: tuple[str, xr.Dataset]): +# _, test_ds = random_zarr_dataset + +# def write_and_check(store: IPFSStore) -> bool: +# try: +# store.rpc_uri_stem = "http://127.0.0.1:5002" # 5002 is the port configured in the run-checks.yaml actions file for nginx to serve the proxy on +# hamt = HAMT(store=store) +# test_ds.to_zarr(store=hamt, mode="w") +# loaded_ds = xr.open_zarr(store=hamt) +# xr.testing.assert_identical(test_ds, loaded_ds) +# return True +# except Exception as _: +# return False + +# # Test with API Key +# api_key_store = IPFSStore(api_key="test") +# assert write_and_check(api_key_store) + +# # Test that wrong API Key fails +# bad_api_key_store = IPFSStore(api_key="badKey") +# assert not write_and_check(bad_api_key_store) + +# # Test just bearer token +# bearer_ipfs_store = IPFSStore(bearer_token="test") +# assert write_and_check(bearer_ipfs_store) + +# # Test with wrong bearer +# bad_bearer_store = IPFSStore(bearer_token="wrongBearer") +# assert not write_and_check(bad_bearer_store) + +# # Test with just basic auth +# basic_auth_store = IPFSStore(basic_auth=("test", "test")) +# assert write_and_check(basic_auth_store) + +# # Test with wrong basic auth +# bad_basic_auth_store = IPFSStore(basic_auth=("wrong", "wrong")) +# assert not write_and_check(bad_basic_auth_store) From 9f2fdc052de37d6e2b24d65f9641a89f27ca3592 Mon Sep 17 00:00:00 2001 From: Abid Sikder <41392423+abidsikder@users.noreply.github.com> Date: Thu, 20 Mar 2025 14:45:26 -0400 Subject: [PATCH 03/11] remove partial gets --- py_hamt/ipfszarr3.py | 34 +++++++++++++++------------------- 1 file changed, 15 insertions(+), 19 deletions(-) diff --git a/py_hamt/ipfszarr3.py b/py_hamt/ipfszarr3.py index 47adaf6..448e978 100644 --- a/py_hamt/ipfszarr3.py +++ b/py_hamt/ipfszarr3.py @@ -36,31 +36,27 @@ async def get( return # We know this value will always be bytes since we only store bytes in the HAMT val: bytes = self.hamt[key] # type: ignore - return prototype.buffer.from_bytes(val) # type: ignore - - subset: bytes - match byte_range: - case None: - subset = val - case zarr.abc.store.RangeByteRequest: - subset = val[byte_range.start : byte_range.end] - case zarr.abc.store.OffsetByteRequest: - subset = val[byte_range.offset :] - case zarr.abc.store.SuffixByteRequest: - subset = val[-byte_range.suffix :] - return prototype.buffer.from_bytes(subset) # type: ignore + return prototype.buffer.from_bytes(val) + + # Hypothetical code for supporting partial writes, but there is not much point since IPFS itself doesn't support partial write and reads + # Untested! If for some reason this is being uncommented and then used in the future, this needs to be tested + # subset: bytes + # match byte_range: + # case None: + # subset = val + # case zarr.abc.store.RangeByteRequest: + # subset = val[byte_range.start : byte_range.end] + # case zarr.abc.store.OffsetByteRequest: + # subset = val[byte_range.offset :] + # case zarr.abc.store.SuffixByteRequest: + # subset = val[-byte_range.suffix :] async def get_partial_values( self, prototype: zarr.core.buffer.BufferPrototype, key_ranges: Iterable[tuple[str, zarr.abc.store.ByteRequest | None]], ) -> list[zarr.core.buffer.Buffer | None]: - results: list[zarr.core.buffer.Buffer | None] = [] - - for key, ran in key_ranges: - results.append(await self.get(key, prototype, ran)) - - return results + raise NotImplementedError async def exists(self, key: str) -> bool: return key in self.hamt From af05c0ea2b57ea0c21ef685730fa097981942779 Mon Sep 17 00:00:00 2001 From: Abid Sikder <41392423+abidsikder@users.noreply.github.com> Date: Thu, 20 Mar 2025 14:45:42 -0400 Subject: [PATCH 04/11] add more code coverage --- pyproject.toml | 1 + tests/test_zarr_ipfs.py | 23 +++++++++++++++++------ uv.lock | 14 ++++++++++++++ 3 files changed, 32 insertions(+), 6 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index b41cac8..64984bd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -29,4 +29,5 @@ dev = [ "pandas>=2.2.3", "numpy>=2.1.3", "xarray>=2025.1.2", + "pytest-asyncio>=0.25.3", ] diff --git a/tests/test_zarr_ipfs.py b/tests/test_zarr_ipfs.py index 00b6cb7..4289857 100644 --- a/tests/test_zarr_ipfs.py +++ b/tests/test_zarr_ipfs.py @@ -64,17 +64,18 @@ def random_zarr_dataset(): # This test also collects miscellaneous statistics about performance, run with pytest -s to see these statistics being printed out -def test_write_read(random_zarr_dataset: tuple[str, xr.Dataset]): +@pytest.mark.asyncio +async def test_write_read(random_zarr_dataset: tuple[str, xr.Dataset]): _, test_ds = random_zarr_dataset print("=== Writing this xarray Dataset to a Zarr v3 on IPFS ===") print(test_ds) ipfsstore = IPFSStore(debug=True) hamt = HAMT(store=ipfsstore) - start = time.perf_counter() ipfszarr3 = IPFSZarr3(hamt) + assert ipfszarr3.supports_writes + start = time.perf_counter() test_ds.to_zarr(store=ipfszarr3) # type: ignore - from pathlib import Path end = time.perf_counter() elapsed = end - start print("=== Write Stats") @@ -87,13 +88,21 @@ def test_write_read(random_zarr_dataset: tuple[str, xr.Dataset]): print("=== Reading data back in and checking if identical") ipfsstore = IPFSStore(debug=True) - hamt = HAMT(store=ipfsstore, root_node_id=cid, read_only=True) + hamt = HAMT(store=ipfsstore, root_node_id=cid) start = time.perf_counter() ipfs_ds: xr.Dataset - ipfszarr3 = IPFSZarr3(hamt) + ipfszarr3 = IPFSZarr3(hamt, read_only=True) ipfs_ds = xr.open_zarr(store=ipfszarr3) + xr.testing.assert_identical(test_ds, ipfs_ds) + # Tests for code coverage's sake + assert await ipfszarr3.exists("zarr.json") + # __eq__ + assert ipfszarr3 == ipfszarr3 + assert ipfszarr3 != hamt + assert not ipfszarr3.supports_writes + end = time.perf_counter() print("=== Read Stats") print(f"Total time in seconds: {elapsed:.2f}") @@ -110,7 +119,9 @@ def test_encryption(random_zarr_dataset: tuple[str, xr.Dataset]): encryption_key = bytes(32) # Encrypt only precipitation, not temperature or the coordinate variables encrypt, decrypt = create_zarr_encryption_transformers( - encryption_key, header="sample-header".encode(), exclude_vars=["lat", "lon", "time", "temp"] + encryption_key, + header="sample-header".encode(), + exclude_vars=["lat", "lon", "time", "temp"], ) hamt = HAMT( store=IPFSStore(), transformer_encode=encrypt, transformer_decode=decrypt diff --git a/uv.lock b/uv.lock index 36c2e69..a8e9328 100644 --- a/uv.lock +++ b/uv.lock @@ -628,6 +628,7 @@ dev = [ { name = "pandas" }, { name = "pdoc" }, { name = "pytest" }, + { name = "pytest-asyncio" }, { name = "pytest-cov" }, { name = "ruff" }, { name = "snakeviz" }, @@ -652,6 +653,7 @@ dev = [ { name = "pandas", specifier = ">=2.2.3" }, { name = "pdoc", specifier = ">=15.0.0" }, { name = "pytest", specifier = ">=8.3.3" }, + { name = "pytest-asyncio", specifier = ">=0.25.3" }, { name = "pytest-cov", specifier = ">=6.0.0" }, { name = "ruff", specifier = ">=0.7.1" }, { name = "snakeviz", specifier = ">=2.2.0" }, @@ -724,6 +726,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/6b/77/7440a06a8ead44c7757a64362dd22df5760f9b12dc5f11b6188cd2fc27a0/pytest-8.3.3-py3-none-any.whl", hash = "sha256:a6853c7375b2663155079443d2e45de913a911a11d669df02a50814944db57b2", size = 342341 }, ] +[[package]] +name = "pytest-asyncio" +version = "0.25.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pytest" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f2/a8/ecbc8ede70921dd2f544ab1cadd3ff3bf842af27f87bbdea774c7baa1d38/pytest_asyncio-0.25.3.tar.gz", hash = "sha256:fc1da2cf9f125ada7e710b4ddad05518d4cee187ae9412e9ac9271003497f07a", size = 54239 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/67/17/3493c5624e48fd97156ebaec380dcaafee9506d7e2c46218ceebbb57d7de/pytest_asyncio-0.25.3-py3-none-any.whl", hash = "sha256:9e89518e0f9bd08928f97a3482fdc4e244df17529460bc038291ccaf8f85c7c3", size = 19467 }, +] + [[package]] name = "pytest-cov" version = "6.0.0" From a29fe09a1b4ae44281df12773db180e9068c757c Mon Sep 17 00:00:00 2001 From: Abid Sikder <41392423+abidsikder@users.noreply.github.com> Date: Mon, 24 Mar 2025 14:41:46 -0400 Subject: [PATCH 05/11] add 100% code coverage --- py_hamt/ipfszarr3.py | 4 +-- tests/test_zarr_ipfs.py | 65 +++++++++++++++++++++++++++++++++++++---- 2 files changed, 61 insertions(+), 8 deletions(-) diff --git a/py_hamt/ipfszarr3.py b/py_hamt/ipfszarr3.py index 448e978..54d953b 100644 --- a/py_hamt/ipfszarr3.py +++ b/py_hamt/ipfszarr3.py @@ -79,9 +79,7 @@ async def set_if_not_exists(self, key: str, value: zarr.core.buffer.Buffer) -> N async def set_partial_values( self, key_start_values: Iterable[tuple[str, int, BytesLike]] ) -> None: - for key, range_start, val in key_start_values: - subset = val[range_start:] - self.hamt[key] = bytes(subset) + raise NotImplementedError @property def supports_deletes(self) -> bool: diff --git a/tests/test_zarr_ipfs.py b/tests/test_zarr_ipfs.py index 4289857..70be95d 100644 --- a/tests/test_zarr_ipfs.py +++ b/tests/test_zarr_ipfs.py @@ -7,6 +7,7 @@ import pandas as pd import xarray as xr import pytest +import zarr.core.buffer from py_hamt import HAMT, IPFSStore, IPFSZarr3, create_zarr_encryption_transformers @@ -96,18 +97,72 @@ async def test_write_read(random_zarr_dataset: tuple[str, xr.Dataset]): xr.testing.assert_identical(test_ds, ipfs_ds) + end = time.perf_counter() + print("=== Read Stats") + print(f"Total time in seconds: {elapsed:.2f}") + print(f"Sent bytes: {ipfsstore.total_sent}") + print(f"Received bytes: {ipfsstore.total_received}") + # Tests for code coverage's sake assert await ipfszarr3.exists("zarr.json") # __eq__ assert ipfszarr3 == ipfszarr3 assert ipfszarr3 != hamt assert not ipfszarr3.supports_writes + assert not ipfszarr3.supports_partial_writes + assert not ipfszarr3.supports_deletes + + hamt_keys = set(ipfszarr3.hamt.keys()) + ipfszarr3_keys: set[str] = set() + async for k in ipfszarr3.list(): + ipfszarr3_keys.add(k) + assert hamt_keys == ipfszarr3_keys + + ipfszarr3_keys: set[str] = set() + async for k in ipfszarr3.list(): + ipfszarr3_keys.add(k) + assert hamt_keys == ipfszarr3_keys + + ipfszarr3_keys: set[str] = set() + async for k in ipfszarr3.list_prefix(""): + ipfszarr3_keys.add(k) + assert hamt_keys == ipfszarr3_keys + + with pytest.raises(NotImplementedError): + await ipfszarr3.set_partial_values([]) + + with pytest.raises(NotImplementedError): + await ipfszarr3.get_partial_values( + zarr.core.buffer.default_buffer_prototype(), [] + ) - end = time.perf_counter() - print("=== Read Stats") - print(f"Total time in seconds: {elapsed:.2f}") - print(f"Sent bytes: {ipfsstore.total_sent}") - print(f"Received bytes: {ipfsstore.total_received}") + previous_zarr_json = await ipfszarr3.get( + "zarr.json", zarr.core.buffer.default_buffer_prototype() + ) + assert previous_zarr_json is not None + # Setting a metadata file that should always exist should not change anything + await ipfszarr3.set_if_not_exists("zarr.json", np.array([b"a"], dtype=np.bytes_)) # type: ignore np.arrays, if dtype is bytes, is usable as a zarr buffer + zarr_json_now = await ipfszarr3.get( + "zarr.json", zarr.core.buffer.default_buffer_prototype() + ) + assert zarr_json_now is not None + assert previous_zarr_json.to_bytes() == zarr_json_now.to_bytes() + + # now remove that metadata file and then add it back + ipfszarr3 = IPFSZarr3(ipfszarr3.hamt, read_only=False) # make a writable version + await ipfszarr3.delete("zarr.json") + ipfszarr3_keys: set[str] = set() + async for k in ipfszarr3.list(): + ipfszarr3_keys.add(k) + assert hamt_keys != ipfszarr3_keys + assert "zarr.json" not in ipfszarr3_keys + + await ipfszarr3.set_if_not_exists("zarr.json", previous_zarr_json) + zarr_json_now = await ipfszarr3.get( + "zarr.json", zarr.core.buffer.default_buffer_prototype() + ) + assert zarr_json_now is not None + assert previous_zarr_json.to_bytes() == zarr_json_now.to_bytes() def test_encryption(random_zarr_dataset: tuple[str, xr.Dataset]): From ff8f3613a8f2586669adae99a1405c6f5ea4c8f4 Mon Sep 17 00:00:00 2001 From: Abid Sikder <41392423+abidsikder@users.noreply.github.com> Date: Mon, 24 Mar 2025 14:50:31 -0400 Subject: [PATCH 06/11] Add appends to testing --- tests/test_zarr_ipfs.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/tests/test_zarr_ipfs.py b/tests/test_zarr_ipfs.py index 70be95d..c72a0db 100644 --- a/tests/test_zarr_ipfs.py +++ b/tests/test_zarr_ipfs.py @@ -76,7 +76,9 @@ async def test_write_read(random_zarr_dataset: tuple[str, xr.Dataset]): ipfszarr3 = IPFSZarr3(hamt) assert ipfszarr3.supports_writes start = time.perf_counter() + # Do an initial write along with an append test_ds.to_zarr(store=ipfszarr3) # type: ignore + test_ds.to_zarr(store=ipfszarr3, mode="a", append_dim="time") # type: ignore end = time.perf_counter() elapsed = end - start print("=== Write Stats") @@ -94,8 +96,15 @@ async def test_write_read(random_zarr_dataset: tuple[str, xr.Dataset]): ipfs_ds: xr.Dataset ipfszarr3 = IPFSZarr3(hamt, read_only=True) ipfs_ds = xr.open_zarr(store=ipfszarr3) + print(ipfs_ds) + + # Check both halves, since each are an identical copy + ds1 = ipfs_ds.isel(time=slice(0, len(ipfs_ds.time) // 2)) + ds2 = ipfs_ds.isel(time=slice(len(ipfs_ds.time) // 2, len(ipfs_ds.time))) + xr.testing.assert_identical(ds1, ds2) + xr.testing.assert_identical(test_ds, ds1) + xr.testing.assert_identical(test_ds, ds2) - xr.testing.assert_identical(test_ds, ipfs_ds) end = time.perf_counter() print("=== Read Stats") From 577e368c7da7ffc635e974623002a9500606aab7 Mon Sep 17 00:00:00 2001 From: Abid Sikder <41392423+abidsikder@users.noreply.github.com> Date: Mon, 24 Mar 2025 14:50:45 -0400 Subject: [PATCH 07/11] uncomment auth tests --- tests/test_zarr_ipfs.py | 76 ++++++++++++++++++++--------------------- 1 file changed, 38 insertions(+), 38 deletions(-) diff --git a/tests/test_zarr_ipfs.py b/tests/test_zarr_ipfs.py index c72a0db..80cc609 100644 --- a/tests/test_zarr_ipfs.py +++ b/tests/test_zarr_ipfs.py @@ -211,41 +211,41 @@ def test_encryption(random_zarr_dataset: tuple[str, xr.Dataset]): ds.precip.sum() -# # This test assumes the other zarr ipfs tests are working fine, so if other things are breaking check those first -# def test_authenticated_gateway(random_zarr_dataset: tuple[str, xr.Dataset]): -# _, test_ds = random_zarr_dataset - -# def write_and_check(store: IPFSStore) -> bool: -# try: -# store.rpc_uri_stem = "http://127.0.0.1:5002" # 5002 is the port configured in the run-checks.yaml actions file for nginx to serve the proxy on -# hamt = HAMT(store=store) -# test_ds.to_zarr(store=hamt, mode="w") -# loaded_ds = xr.open_zarr(store=hamt) -# xr.testing.assert_identical(test_ds, loaded_ds) -# return True -# except Exception as _: -# return False - -# # Test with API Key -# api_key_store = IPFSStore(api_key="test") -# assert write_and_check(api_key_store) - -# # Test that wrong API Key fails -# bad_api_key_store = IPFSStore(api_key="badKey") -# assert not write_and_check(bad_api_key_store) - -# # Test just bearer token -# bearer_ipfs_store = IPFSStore(bearer_token="test") -# assert write_and_check(bearer_ipfs_store) - -# # Test with wrong bearer -# bad_bearer_store = IPFSStore(bearer_token="wrongBearer") -# assert not write_and_check(bad_bearer_store) - -# # Test with just basic auth -# basic_auth_store = IPFSStore(basic_auth=("test", "test")) -# assert write_and_check(basic_auth_store) - -# # Test with wrong basic auth -# bad_basic_auth_store = IPFSStore(basic_auth=("wrong", "wrong")) -# assert not write_and_check(bad_basic_auth_store) +# This test assumes the other zarr ipfs tests are working fine, so if other things are breaking check those first +def test_authenticated_gateway(random_zarr_dataset: tuple[str, xr.Dataset]): + _, test_ds = random_zarr_dataset + + def write_and_check(store: IPFSStore) -> bool: + try: + store.rpc_uri_stem = "http://127.0.0.1:5002" # 5002 is the port configured in the run-checks.yaml actions file for nginx to serve the proxy on + hamt = HAMT(store=store) + test_ds.to_zarr(store=hamt, mode="w") + loaded_ds = xr.open_zarr(store=hamt) + xr.testing.assert_identical(test_ds, loaded_ds) + return True + except Exception as _: + return False + + # Test with API Key + api_key_store = IPFSStore(api_key="test") + assert write_and_check(api_key_store) + + # Test that wrong API Key fails + bad_api_key_store = IPFSStore(api_key="badKey") + assert not write_and_check(bad_api_key_store) + + # Test just bearer token + bearer_ipfs_store = IPFSStore(bearer_token="test") + assert write_and_check(bearer_ipfs_store) + + # Test with wrong bearer + bad_bearer_store = IPFSStore(bearer_token="wrongBearer") + assert not write_and_check(bad_bearer_store) + + # Test with just basic auth + basic_auth_store = IPFSStore(basic_auth=("test", "test")) + assert write_and_check(basic_auth_store) + + # Test with wrong basic auth + bad_basic_auth_store = IPFSStore(basic_auth=("wrong", "wrong")) + assert not write_and_check(bad_basic_auth_store) From 372078f465a63b4381786242ccaefc6567289340 Mon Sep 17 00:00:00 2001 From: Abid Sikder <41392423+abidsikder@users.noreply.github.com> Date: Mon, 24 Mar 2025 14:53:39 -0400 Subject: [PATCH 08/11] add zarrv3 to the auth tests --- tests/test_zarr_ipfs.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/test_zarr_ipfs.py b/tests/test_zarr_ipfs.py index 80cc609..f4bf8da 100644 --- a/tests/test_zarr_ipfs.py +++ b/tests/test_zarr_ipfs.py @@ -219,8 +219,9 @@ def write_and_check(store: IPFSStore) -> bool: try: store.rpc_uri_stem = "http://127.0.0.1:5002" # 5002 is the port configured in the run-checks.yaml actions file for nginx to serve the proxy on hamt = HAMT(store=store) - test_ds.to_zarr(store=hamt, mode="w") - loaded_ds = xr.open_zarr(store=hamt) + ipfszarr3 = IPFSZarr3(hamt) + test_ds.to_zarr(store=ipfszarr3, mode="w") # type: ignore + loaded_ds = xr.open_zarr(store=ipfszarr3) xr.testing.assert_identical(test_ds, loaded_ds) return True except Exception as _: From 3e783e68e72c736a030e890c606d0af5e09ba034 Mon Sep 17 00:00:00 2001 From: Abid Sikder <41392423+abidsikder@users.noreply.github.com> Date: Mon, 24 Mar 2025 14:54:54 -0400 Subject: [PATCH 09/11] ruff format --- tests/test_zarr_ipfs.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tests/test_zarr_ipfs.py b/tests/test_zarr_ipfs.py index f4bf8da..58f4d4a 100644 --- a/tests/test_zarr_ipfs.py +++ b/tests/test_zarr_ipfs.py @@ -78,7 +78,7 @@ async def test_write_read(random_zarr_dataset: tuple[str, xr.Dataset]): start = time.perf_counter() # Do an initial write along with an append test_ds.to_zarr(store=ipfszarr3) # type: ignore - test_ds.to_zarr(store=ipfszarr3, mode="a", append_dim="time") # type: ignore + test_ds.to_zarr(store=ipfszarr3, mode="a", append_dim="time") # type: ignore end = time.perf_counter() elapsed = end - start print("=== Write Stats") @@ -105,7 +105,6 @@ async def test_write_read(random_zarr_dataset: tuple[str, xr.Dataset]): xr.testing.assert_identical(test_ds, ds1) xr.testing.assert_identical(test_ds, ds2) - end = time.perf_counter() print("=== Read Stats") print(f"Total time in seconds: {elapsed:.2f}") @@ -220,7 +219,7 @@ def write_and_check(store: IPFSStore) -> bool: store.rpc_uri_stem = "http://127.0.0.1:5002" # 5002 is the port configured in the run-checks.yaml actions file for nginx to serve the proxy on hamt = HAMT(store=store) ipfszarr3 = IPFSZarr3(hamt) - test_ds.to_zarr(store=ipfszarr3, mode="w") # type: ignore + test_ds.to_zarr(store=ipfszarr3, mode="w") # type: ignore loaded_ds = xr.open_zarr(store=ipfszarr3) xr.testing.assert_identical(test_ds, loaded_ds) return True From 9ce6840614b4a43e413b1d966781cd3c72cd99ca Mon Sep 17 00:00:00 2001 From: Abid Sikder <41392423+abidsikder@users.noreply.github.com> Date: Mon, 24 Mar 2025 14:59:23 -0400 Subject: [PATCH 10/11] fix time reporting in tests --- tests/test_zarr_ipfs.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/test_zarr_ipfs.py b/tests/test_zarr_ipfs.py index 58f4d4a..aa09b76 100644 --- a/tests/test_zarr_ipfs.py +++ b/tests/test_zarr_ipfs.py @@ -106,6 +106,7 @@ async def test_write_read(random_zarr_dataset: tuple[str, xr.Dataset]): xr.testing.assert_identical(test_ds, ds2) end = time.perf_counter() + elapsed = end - start print("=== Read Stats") print(f"Total time in seconds: {elapsed:.2f}") print(f"Sent bytes: {ipfsstore.total_sent}") From 8f02031637d5dc629eaf8e00a60da7bb2d43f2cc Mon Sep 17 00:00:00 2001 From: Abid Sikder <41392423+abidsikder@users.noreply.github.com> Date: Tue, 25 Mar 2025 15:55:26 -0400 Subject: [PATCH 11/11] Documentation update --- README.md | 57 ++++++++----------------- py_hamt/ipfszarr3.py | 29 +++++++++++++ py_hamt/zarr_encryption_transformers.py | 26 +++++++++++ 3 files changed, 73 insertions(+), 39 deletions(-) diff --git a/README.md b/README.md index b4a4272..7c4c05c 100644 --- a/README.md +++ b/README.md @@ -7,27 +7,26 @@ [![codecov](https://codecov.io/gh/dClimate/py-hamt/graph/badge.svg?token=M6Y4D19Y38)](https://codecov.io/gh/dClimate/py-hamt) # py-hamt -This is a python implementation of a HAMT, adapted from [rvagg's IAMap project written in JavaScript](https://github.com/rvagg/iamap). -Like IAMap, py-hamt abstracts over a backing storage layer which lets you store any arbitrary amount of data but returns its own ID, e.g. content-addressed systems. +This is a python implementation of a HAMT, inspired by [rvagg's IAMap project written in JavaScript](https://github.com/rvagg/iamap). +Like IAMap, py-hamt abstracts over a content-addressed storage system, something that can keep arbitrary values but will return its own key, like IPFS. -Key differences from IAMap is that the py-hamt data structure is mutable and not asynchronous. But the key idea of abstracting over a value store is the same. +dClimate primarily created this for storing [zarr](https://zarr.dev/) on IPFS. To see this in action, see our [data ETLs](https://github.com/dClimate/etl-scripts). -dClimate created this library to use IPFS to store [zarr](https://zarr.dev/) files. To see this in action, see our [data ETLs](https://github.com/dClimate/etl-scripts). - -# Usage +# Installation and Usage To install, since we do not publish this package to PyPI, add this library to your project directly from git. ```sh pip install 'git+https://github.com/dClimate/py-hamt' ``` -Below are some examples, but for more information see the [API documentation](https://dclimate.github.io/py-hamt/py_hamt.html). Looking at the test files, namely `test_hamt.py` is also quite helpful. You can also see this library used in notebooks for data analysis here [dClimate Jupyter Notebooks](https://github.com/dClimate/jupyter-notebooks) +Below are some examples, but for more information see the [API documentation](https://dclimate.github.io/py-hamt/py_hamt.html). Each major item has example code. You can also see this library used in [Jupyter notebooks for data analysis](https://github.com/dClimate/jupyter-notebooks). -## Basic Writing/Reading from an in memory store +## Basic Writing/Reading +A HAMT allows for generic key-value storage. ```python -from py_hamt import HAMT, DictStore +from py_hamt import HAMT, IPFSStore -# Setup a HAMT with an in memory store -hamt = HAMT(store=DictStore()) +# Setup a HAMT and connect it to your local ipfs node +hamt = HAMT(store=IPFSStore()) # Set and get one value hamt["foo"] = "bar" @@ -48,6 +47,9 @@ print (list(hamt)) # [foo, foo2], order depends on the hash function used # Delete a value del hamt["foo"] assert len(hamt) == 1 + +# Print CID of the HAMT +print(hamt.root_node_id) ``` ## Reading a CID from IPFS @@ -55,7 +57,7 @@ assert len(hamt) == 1 from py_hamt import HAMT, IPFSStore from multiformats import CID -# Get the CID you wish to read whether from a blog post, a smart contract, or a friend +# A CID for data you wish to read, from a blog post, a smart contract, or a friend dataset_cid = "baf..." # Use the multiformats library to decode the CID into an object @@ -70,32 +72,9 @@ hamt = HAMT(store=IPFSStore(), root_node_id=root_cid) # You can optionally pass ... ``` -## Partially encrypted zarrs -```python -from py_hamt import HAMT, IPFSStore, create_zarr_encryption_transformers - -ds = ... # example ds with precip and temp data variables -encryption_key = bytes(32) # change before using, only for demonstration purposes! -header = "sample-header".encode() -encrypt, decrypt = create_zarr_encryption_transformers( - encryption_key, header, exclude_vars=["temp"] -) -hamt = HAMT( - store=IPFSStore(), transformer_encode=encrypt, transformer_decode=decrypt -) -ds.to_zarr(store=hamt, mode="w") - -print("Attempting to read and print metadata of partially encrypted zarr") -enc_ds = xr.open_zarr( - store=HAMT(store=IPFSStore(), root_node_id=hamt.root_node_id, read_only=True) -) -print(enc_ds) -assert enc_ds.temp.sum() == ds.temp.sum() -try: - enc_ds.precip.sum() -except: - print("Couldn't read encrypted variable") -``` +For an example on how to read and write Zarr v3, check the API documentation and look at the ``IPFSZarr3` class. + +For how to create partially encrypted zarrs, check the API documentation's `create_zarr_encryption_transformers` section. # Development Guide ## Setting Up @@ -141,4 +120,4 @@ uv run pdoc py_hamt ``` ## Managing dependencies -Use `uv add` and `uv remove`, e.g. `uv add numpy` or `uv add --dev pytest`. For more information please see the [uv documentation](https://docs.astral.sh/uv/guides/projects/). +Use `uv add` and `uv remove`, e.g. `uv add numpy` or `uv add pytest --group dev`. For more information please see the [uv documentation](https://docs.astral.sh/uv/guides/projects/). diff --git a/py_hamt/ipfszarr3.py b/py_hamt/ipfszarr3.py index 54d953b..92c198a 100644 --- a/py_hamt/ipfszarr3.py +++ b/py_hamt/ipfszarr3.py @@ -7,7 +7,36 @@ class IPFSZarr3(zarr.abc.store.Store): + """ + While Zarr v2 can use a generic key-value map (MutableMapping) that HAMT already conforms to, Zarr v3s require storage classes to conform to a new abstract class. IPFSZarr3 just wraps over a HAMT to provide this compatibility. + + An example of how to write and read a zarr, using xarray, is provided below. + # Write and get CID + ```python + import xarray as xr + from py_hamt import IPFSStore, HAMT, IPFSZarr3 + + ds = ... # some xarray Dataset + ipfszarr3 = IPFSZarr3(HAMT(store=IPFSStore())) + xr.to_zarr(store=ipfszarr3) + print(ipfszarr3.hamt.root_node_id) # The CID of the root, which is used for reading + ``` + + # Read from CID + ```python + import xarray as xr + from multiformats import CID + from py_hamt import IPFSStore, HAMT, IPFSZarr3 + + cid = CID.decode("...") # the CID for the HAMT root + ipfszarr3 = IPFSZarr3(HAMT(store=IPFSStore(), root_node_id=cid), read_only=True) + ds = xr.open_zarr(store=ipfszarr3) + print(ds) + ``` + """ + hamt: HAMT + """The internal HAMT. Safe to read the CID from, if done doing operations.""" def __init__(self, hamt: HAMT, read_only: bool = False) -> None: super().__init__(read_only=read_only) diff --git a/py_hamt/zarr_encryption_transformers.py b/py_hamt/zarr_encryption_transformers.py index 9e7256e..3014f88 100644 --- a/py_hamt/zarr_encryption_transformers.py +++ b/py_hamt/zarr_encryption_transformers.py @@ -22,6 +22,32 @@ def create_zarr_encryption_transformers( zarr.json metadata files in a zarr v3 are always ignored, to allow for calculating an encrypted zarr's structure without having the encryption key. With `exclude_vars` you may also set some variables to be unencrypted. This allows for partially encrypted zarrs which can be loaded into xarray but the values of encrypted variables cannot be accessed (errors will be thrown). You should generally include your coordinate variables along with your data variables in here. + + # Example code + ```python + from py_hamt import HAMT, IPFSStore, IPFSZarr3 + + ds = ... # example xarray Dataset with precip and temp data variables + encryption_key = bytes(32) # change before using, only for demonstration purposes! + header = "sample-header".encode() + encrypt, decrypt = create_zarr_encryption_transformers( + encryption_key, header, exclude_vars=["temp"] + ) + hamt = HAMT( + store=IPFSStore(), transformer_encode=encrypt, transformer_decode=decrypt + ) + ipfszarr3 = IPFSZarr3(hamt) + ds.to_zarr(store=ipfszarr3, mode="w") + + print("Attempting to read and print metadata of partially encrypted zarr") + enc_ds = xr.open_zarr(store=ipfszarr3, read_only=True) + print(enc_ds) + assert enc_ds.temp.sum() == ds.temp.sum() + try: + enc_ds.precip.sum() + except: + print("Couldn't read encrypted variable") + ``` """ if len(encryption_key) != 32: