diff --git a/.devcontainer/pyproject.toml b/.devcontainer/pyproject.toml new file mode 100644 index 0000000..df0f938 --- /dev/null +++ b/.devcontainer/pyproject.toml @@ -0,0 +1,78 @@ +# Copyright 2024 TGS +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +[project] +name = "mdio-cpp-dev" +version = "0.1.0" +description = "MDIO C++ library development environment" +requires-python = ">=3.12,<3.13" + +# multidimio[cloud] pulls in zarr, xarray, numpy, and cloud storage backends +dependencies = [ + "multidimio[cloud]==1.1.4.dev1781540584", +] + +[project.optional-dependencies] +dev = [ + "pytest>=7.0", + "black>=24.3", + "mypy>=1.5", + "isort>=5.0", + "ruff>=0.8", + "yapf>=0.40", + "cpplint==1.6.1", +] + +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + +[dependency-groups] +dev = [ + "pytest>=7.0", + "black>=24.3", + "mypy>=1.5", + "isort>=5.0", + "ruff>=0.8", + "yapf>=0.40", + "cpplint==1.6.1", +] + +[tool.uv] +package = false +prerelease = "allow" + +[[tool.uv.index]] +name = "testpypi" +url = "https://test.pypi.org/simple/" +explicit = true + +[tool.uv.sources] +multidimio = { index = "testpypi" } + +[tool.isort] +profile = "black" + +[tool.black] +line-length = 79 + +[tool.ruff] +line-length = 79 + +[tool.ruff.lint] +select = ["E", "F", "I", "UP"] + +[tool.mypy] +python_version = "3.12" +plugins = [] diff --git a/.devcontainer/uv.lock b/.devcontainer/uv.lock new file mode 100644 index 0000000..3aeb515 --- /dev/null +++ b/.devcontainer/uv.lock @@ -0,0 +1,1702 @@ +version = 1 +revision = 3 +requires-python = "==3.12.*" + +[options] +prerelease-mode = "allow" + +[[package]] +name = "adlfs" +version = "2025.8.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "aiohttp" }, + { name = "azure-core" }, + { name = "azure-datalake-store" }, + { name = "azure-identity" }, + { name = "azure-storage-blob" }, + { name = "fsspec" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/6b/af/4d74c92254fdeabc19e54df4c9146855c2c1027bd4052477e3a27b05de54/adlfs-2025.8.0.tar.gz", hash = "sha256:6fe5857866c18990f632598273e6a8b15edc6baf8614272ede25624057b83e64", size = 52007, upload-time = "2025-08-30T12:07:40.936Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/59/e0/e98227759d88184406f757d149843fa8f3d8bab71f5a2ebd3f6f3b3970b1/adlfs-2025.8.0-py3-none-any.whl", hash = "sha256:c12a9203a31485cad19599bf2ad30d8efcf4cf0fd2446c60fcdc18604fae07b1", size = 44076, upload-time = "2025-08-30T12:07:39.694Z" }, +] + +[[package]] +name = "aiobotocore" +version = "2.26.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "aiohttp" }, + { name = "aioitertools" }, + { name = "botocore" }, + { name = "jmespath" }, + { name = "multidict" }, + { name = "python-dateutil" }, + { name = "wrapt" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/4d/f8/99fa90d9c25b78292899fd4946fce97b6353838b5ecc139ad8ba1436e70c/aiobotocore-2.26.0.tar.gz", hash = "sha256:50567feaf8dfe2b653570b4491f5bc8c6e7fb9622479d66442462c021db4fadc", size = 122026, upload-time = "2025-11-28T07:54:59.956Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/58/3bf0b7d474607dc7fd67dd1365c4e0f392c8177eaf4054e5ddee3ebd53b5/aiobotocore-2.26.0-py3-none-any.whl", hash = "sha256:a793db51c07930513b74ea7a95bd79aaa42f545bdb0f011779646eafa216abec", size = 87333, upload-time = "2025-11-28T07:54:58.457Z" }, +] + +[[package]] +name = "aiohappyeyeballs" +version = "2.6.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/26/30/f84a107a9c4331c14b2b586036f40965c128aa4fee4dda5d3d51cb14ad54/aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558", size = 22760, upload-time = "2025-03-12T01:42:48.764Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0f/15/5bf3b99495fb160b63f95972b81750f18f7f4e02ad051373b669d17d44f2/aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8", size = 15265, upload-time = "2025-03-12T01:42:47.083Z" }, +] + +[[package]] +name = "aiohttp" +version = "3.13.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "aiohappyeyeballs" }, + { name = "aiosignal" }, + { name = "attrs" }, + { name = "frozenlist" }, + { name = "multidict" }, + { name = "propcache" }, + { name = "yarl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/50/42/32cf8e7704ceb4481406eb87161349abb46a57fee3f008ba9cb610968646/aiohttp-3.13.3.tar.gz", hash = "sha256:a949eee43d3782f2daae4f4a2819b2cb9b0c5d3b7f7a927067cc84dafdbb9f88", size = 7844556, upload-time = "2026-01-03T17:33:05.204Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a0/be/4fc11f202955a69e0db803a12a062b8379c970c7c84f4882b6da17337cc1/aiohttp-3.13.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:b903a4dfee7d347e2d87697d0713be59e0b87925be030c9178c5faa58ea58d5c", size = 739732, upload-time = "2026-01-03T17:30:14.23Z" }, + { url = "https://files.pythonhosted.org/packages/97/2c/621d5b851f94fa0bb7430d6089b3aa970a9d9b75196bc93bb624b0db237a/aiohttp-3.13.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a45530014d7a1e09f4a55f4f43097ba0fd155089372e105e4bff4ca76cb1b168", size = 494293, upload-time = "2026-01-03T17:30:15.96Z" }, + { url = "https://files.pythonhosted.org/packages/5d/43/4be01406b78e1be8320bb8316dc9c42dbab553d281c40364e0f862d5661c/aiohttp-3.13.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:27234ef6d85c914f9efeb77ff616dbf4ad2380be0cda40b4db086ffc7ddd1b7d", size = 493533, upload-time = "2026-01-03T17:30:17.431Z" }, + { url = "https://files.pythonhosted.org/packages/8d/a8/5a35dc56a06a2c90d4742cbf35294396907027f80eea696637945a106f25/aiohttp-3.13.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d32764c6c9aafb7fb55366a224756387cd50bfa720f32b88e0e6fa45b27dcf29", size = 1737839, upload-time = "2026-01-03T17:30:19.422Z" }, + { url = "https://files.pythonhosted.org/packages/bf/62/4b9eeb331da56530bf2e198a297e5303e1c1ebdceeb00fe9b568a65c5a0c/aiohttp-3.13.3-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:b1a6102b4d3ebc07dad44fbf07b45bb600300f15b552ddf1851b5390202ea2e3", size = 1703932, upload-time = "2026-01-03T17:30:21.756Z" }, + { url = "https://files.pythonhosted.org/packages/7c/f6/af16887b5d419e6a367095994c0b1332d154f647e7dc2bd50e61876e8e3d/aiohttp-3.13.3-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c014c7ea7fb775dd015b2d3137378b7be0249a448a1612268b5a90c2d81de04d", size = 1771906, upload-time = "2026-01-03T17:30:23.932Z" }, + { url = "https://files.pythonhosted.org/packages/ce/83/397c634b1bcc24292fa1e0c7822800f9f6569e32934bdeef09dae7992dfb/aiohttp-3.13.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2b8d8ddba8f95ba17582226f80e2de99c7a7948e66490ef8d947e272a93e9463", size = 1871020, upload-time = "2026-01-03T17:30:26Z" }, + { url = "https://files.pythonhosted.org/packages/86/f6/a62cbbf13f0ac80a70f71b1672feba90fdb21fd7abd8dbf25c0105fb6fa3/aiohttp-3.13.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9ae8dd55c8e6c4257eae3a20fd2c8f41edaea5992ed67156642493b8daf3cecc", size = 1755181, upload-time = "2026-01-03T17:30:27.554Z" }, + { url = "https://files.pythonhosted.org/packages/0a/87/20a35ad487efdd3fba93d5843efdfaa62d2f1479eaafa7453398a44faf13/aiohttp-3.13.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:01ad2529d4b5035578f5081606a465f3b814c542882804e2e8cda61adf5c71bf", size = 1561794, upload-time = "2026-01-03T17:30:29.254Z" }, + { url = "https://files.pythonhosted.org/packages/de/95/8fd69a66682012f6716e1bc09ef8a1a2a91922c5725cb904689f112309c4/aiohttp-3.13.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:bb4f7475e359992b580559e008c598091c45b5088f28614e855e42d39c2f1033", size = 1697900, upload-time = "2026-01-03T17:30:31.033Z" }, + { url = "https://files.pythonhosted.org/packages/e5/66/7b94b3b5ba70e955ff597672dad1691333080e37f50280178967aff68657/aiohttp-3.13.3-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:c19b90316ad3b24c69cd78d5c9b4f3aa4497643685901185b65166293d36a00f", size = 1728239, upload-time = "2026-01-03T17:30:32.703Z" }, + { url = "https://files.pythonhosted.org/packages/47/71/6f72f77f9f7d74719692ab65a2a0252584bf8d5f301e2ecb4c0da734530a/aiohttp-3.13.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:96d604498a7c782cb15a51c406acaea70d8c027ee6b90c569baa6e7b93073679", size = 1740527, upload-time = "2026-01-03T17:30:34.695Z" }, + { url = "https://files.pythonhosted.org/packages/fa/b4/75ec16cbbd5c01bdaf4a05b19e103e78d7ce1ef7c80867eb0ace42ff4488/aiohttp-3.13.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:084911a532763e9d3dd95adf78a78f4096cd5f58cdc18e6fdbc1b58417a45423", size = 1554489, upload-time = "2026-01-03T17:30:36.864Z" }, + { url = "https://files.pythonhosted.org/packages/52/8f/bc518c0eea29f8406dcf7ed1f96c9b48e3bc3995a96159b3fc11f9e08321/aiohttp-3.13.3-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:7a4a94eb787e606d0a09404b9c38c113d3b099d508021faa615d70a0131907ce", size = 1767852, upload-time = "2026-01-03T17:30:39.433Z" }, + { url = "https://files.pythonhosted.org/packages/9d/f2/a07a75173124f31f11ea6f863dc44e6f09afe2bca45dd4e64979490deab1/aiohttp-3.13.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:87797e645d9d8e222e04160ee32aa06bc5c163e8499f24db719e7852ec23093a", size = 1722379, upload-time = "2026-01-03T17:30:41.081Z" }, + { url = "https://files.pythonhosted.org/packages/3c/4a/1a3fee7c21350cac78e5c5cef711bac1b94feca07399f3d406972e2d8fcd/aiohttp-3.13.3-cp312-cp312-win32.whl", hash = "sha256:b04be762396457bef43f3597c991e192ee7da460a4953d7e647ee4b1c28e7046", size = 428253, upload-time = "2026-01-03T17:30:42.644Z" }, + { url = "https://files.pythonhosted.org/packages/d9/b7/76175c7cb4eb73d91ad63c34e29fc4f77c9386bba4a65b53ba8e05ee3c39/aiohttp-3.13.3-cp312-cp312-win_amd64.whl", hash = "sha256:e3531d63d3bdfa7e3ac5e9b27b2dd7ec9df3206a98e0b3445fa906f233264c57", size = 455407, upload-time = "2026-01-03T17:30:44.195Z" }, +] + +[[package]] +name = "aioitertools" +version = "0.13.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fd/3c/53c4a17a05fb9ea2313ee1777ff53f5e001aefd5cc85aa2f4c2d982e1e38/aioitertools-0.13.0.tar.gz", hash = "sha256:620bd241acc0bbb9ec819f1ab215866871b4bbd1f73836a55f799200ee86950c", size = 19322, upload-time = "2025-11-06T22:17:07.609Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/10/a1/510b0a7fadc6f43a6ce50152e69dbd86415240835868bb0bd9b5b88b1e06/aioitertools-0.13.0-py3-none-any.whl", hash = "sha256:0be0292b856f08dfac90e31f4739432f4cb6d7520ab9eb73e143f4f2fa5259be", size = 24182, upload-time = "2025-11-06T22:17:06.502Z" }, +] + +[[package]] +name = "aiosignal" +version = "1.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "frozenlist" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/61/62/06741b579156360248d1ec624842ad0edf697050bbaf7c3e46394e106ad1/aiosignal-1.4.0.tar.gz", hash = "sha256:f47eecd9468083c2029cc99945502cb7708b082c232f9aca65da147157b251c7", size = 25007, upload-time = "2025-07-03T22:54:43.528Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl", hash = "sha256:053243f8b92b990551949e63930a839ff0cf0b0ebbe0597b0f3fb19e1a0fe82e", size = 7490, upload-time = "2025-07-03T22:54:42.156Z" }, +] + +[[package]] +name = "annotated-types" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081, upload-time = "2024-05-20T21:33:25.928Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" }, +] + +[[package]] +name = "attrs" +version = "25.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6b/5c/685e6633917e101e5dcb62b9dd76946cbb57c26e133bae9e0cd36033c0a9/attrs-25.4.0.tar.gz", hash = "sha256:16d5969b87f0859ef33a48b35d55ac1be6e42ae49d5e853b597db70c35c57e11", size = 934251, upload-time = "2025-10-06T13:54:44.725Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl", hash = "sha256:adcf7e2a1fb3b36ac48d97835bb6d8ade15b8dcce26aba8bf1d14847b57a3373", size = 67615, upload-time = "2025-10-06T13:54:43.17Z" }, +] + +[[package]] +name = "azure-core" +version = "1.37.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "requests" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ef/83/41c9371c8298999c67b007e308a0a3c4d6a59c6908fa9c62101f031f886f/azure_core-1.37.0.tar.gz", hash = "sha256:7064f2c11e4b97f340e8e8c6d923b822978be3016e46b7bc4aa4b337cfb48aee", size = 357620, upload-time = "2025-12-11T20:05:13.518Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ee/34/a9914e676971a13d6cc671b1ed172f9804b50a3a80a143ff196e52f4c7ee/azure_core-1.37.0-py3-none-any.whl", hash = "sha256:b3abe2c59e7d6bb18b38c275a5029ff80f98990e7c90a5e646249a56630fcc19", size = 214006, upload-time = "2025-12-11T20:05:14.96Z" }, +] + +[[package]] +name = "azure-datalake-store" +version = "0.0.53" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cffi" }, + { name = "msal" }, + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/22/ff/61369d06422b5ac48067215ff404841342651b14a89b46c8d8e1507c8f17/azure-datalake-store-0.0.53.tar.gz", hash = "sha256:05b6de62ee3f2a0a6e6941e6933b792b800c3e7f6ffce2fc324bc19875757393", size = 71430, upload-time = "2023-05-10T21:17:05.665Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/2a/75f56b14f115189155cf12e46b366ad1fe3357af5a1a7c09f7446662d617/azure_datalake_store-0.0.53-py2.py3-none-any.whl", hash = "sha256:a30c902a6e360aa47d7f69f086b426729784e71c536f330b691647a51dc42b2b", size = 55308, upload-time = "2023-05-10T21:17:02.629Z" }, +] + +[[package]] +name = "azure-identity" +version = "1.25.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "azure-core" }, + { name = "cryptography" }, + { name = "msal" }, + { name = "msal-extensions" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/06/8d/1a6c41c28a37eab26dc85ab6c86992c700cd3f4a597d9ed174b0e9c69489/azure_identity-1.25.1.tar.gz", hash = "sha256:87ca8328883de6036443e1c37b40e8dc8fb74898240f61071e09d2e369361456", size = 279826, upload-time = "2025-10-06T20:30:02.194Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/83/7b/5652771e24fff12da9dde4c20ecf4682e606b104f26419d139758cc935a6/azure_identity-1.25.1-py3-none-any.whl", hash = "sha256:e9edd720af03dff020223cd269fa3a61e8f345ea75443858273bcb44844ab651", size = 191317, upload-time = "2025-10-06T20:30:04.251Z" }, +] + +[[package]] +name = "azure-storage-blob" +version = "12.28.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "azure-core" }, + { name = "cryptography" }, + { name = "isodate" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/71/24/072ba8e27b0e2d8fec401e9969b429d4f5fc4c8d4f0f05f4661e11f7234a/azure_storage_blob-12.28.0.tar.gz", hash = "sha256:e7d98ea108258d29aa0efbfd591b2e2075fa1722a2fae8699f0b3c9de11eff41", size = 604225, upload-time = "2026-01-06T23:48:57.282Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d8/3a/6ef2047a072e54e1142718d433d50e9514c999a58f51abfff7902f3a72f8/azure_storage_blob-12.28.0-py3-none-any.whl", hash = "sha256:00fb1db28bf6a7b7ecaa48e3b1d5c83bfadacc5a678b77826081304bd87d6461", size = 431499, upload-time = "2026-01-06T23:48:58.995Z" }, +] + +[[package]] +name = "black" +version = "25.12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "mypy-extensions" }, + { name = "packaging" }, + { name = "pathspec" }, + { name = "platformdirs" }, + { name = "pytokens" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c4/d9/07b458a3f1c525ac392b5edc6b191ff140b596f9d77092429417a54e249d/black-25.12.0.tar.gz", hash = "sha256:8d3dd9cea14bff7ddc0eb243c811cdb1a011ebb4800a5f0335a01a68654796a7", size = 659264, upload-time = "2025-12-08T01:40:52.501Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/bd/26083f805115db17fda9877b3c7321d08c647df39d0df4c4ca8f8450593e/black-25.12.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:31f96b7c98c1ddaeb07dc0f56c652e25bdedaac76d5b68a059d998b57c55594a", size = 1924178, upload-time = "2025-12-08T01:49:51.048Z" }, + { url = "https://files.pythonhosted.org/packages/89/6b/ea00d6651561e2bdd9231c4177f4f2ae19cc13a0b0574f47602a7519b6ca/black-25.12.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:05dd459a19e218078a1f98178c13f861fe6a9a5f88fc969ca4d9b49eb1809783", size = 1742643, upload-time = "2025-12-08T01:49:59.09Z" }, + { url = "https://files.pythonhosted.org/packages/6d/f3/360fa4182e36e9875fabcf3a9717db9d27a8d11870f21cff97725c54f35b/black-25.12.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c1f68c5eff61f226934be6b5b80296cf6939e5d2f0c2f7d543ea08b204bfaf59", size = 1800158, upload-time = "2025-12-08T01:44:27.301Z" }, + { url = "https://files.pythonhosted.org/packages/f8/08/2c64830cb6616278067e040acca21d4f79727b23077633953081c9445d61/black-25.12.0-cp312-cp312-win_amd64.whl", hash = "sha256:274f940c147ddab4442d316b27f9e332ca586d39c85ecf59ebdea82cc9ee8892", size = 1426197, upload-time = "2025-12-08T01:45:51.198Z" }, + { url = "https://files.pythonhosted.org/packages/d4/60/a93f55fd9b9816b7432cf6842f0e3000fdd5b7869492a04b9011a133ee37/black-25.12.0-cp312-cp312-win_arm64.whl", hash = "sha256:169506ba91ef21e2e0591563deda7f00030cb466e747c4b09cb0a9dae5db2f43", size = 1237266, upload-time = "2025-12-08T01:45:10.556Z" }, + { url = "https://files.pythonhosted.org/packages/68/11/21331aed19145a952ad28fca2756a1433ee9308079bd03bd898e903a2e53/black-25.12.0-py3-none-any.whl", hash = "sha256:48ceb36c16dbc84062740049eef990bb2ce07598272e673c17d1a7720c71c828", size = 206191, upload-time = "2025-12-08T01:40:50.963Z" }, +] + +[[package]] +name = "botocore" +version = "1.41.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jmespath" }, + { name = "python-dateutil" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/90/22/7fe08c726a2e3b11a0aef8bf177e83891c9cb2dc1809d35c9ed91a9e60e6/botocore-1.41.5.tar.gz", hash = "sha256:0367622b811597d183bfcaab4a350f0d3ede712031ce792ef183cabdee80d3bf", size = 14668152, upload-time = "2025-11-26T20:27:38.026Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4e/4e/21cd0b8f365449f1576f93de1ec8718ed18a7a3bc086dfbdeb79437bba7a/botocore-1.41.5-py3-none-any.whl", hash = "sha256:3fef7fcda30c82c27202d232cfdbd6782cb27f20f8e7e21b20606483e66ee73a", size = 14337008, upload-time = "2025-11-26T20:27:35.208Z" }, +] + +[[package]] +name = "certifi" +version = "2026.1.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e0/2d/a891ca51311197f6ad14a7ef42e2399f36cf2f9bd44752b3dc4eab60fdc5/certifi-2026.1.4.tar.gz", hash = "sha256:ac726dd470482006e014ad384921ed6438c457018f4b3d204aea4281258b2120", size = 154268, upload-time = "2026-01-04T02:42:41.825Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e6/ad/3cc14f097111b4de0040c83a525973216457bbeeb63739ef1ed275c1c021/certifi-2026.1.4-py3-none-any.whl", hash = "sha256:9943707519e4add1115f44c2bc244f782c0249876bf51b6599fee1ffbedd685c", size = 152900, upload-time = "2026-01-04T02:42:40.15Z" }, +] + +[[package]] +name = "cffi" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pycparser", marker = "implementation_name != 'PyPy'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/eb/56/b1ba7935a17738ae8453301356628e8147c79dbb825bcbc73dc7401f9846/cffi-2.0.0.tar.gz", hash = "sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529", size = 523588, upload-time = "2025-09-08T23:24:04.541Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ea/47/4f61023ea636104d4f16ab488e268b93008c3d0bb76893b1b31db1f96802/cffi-2.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6d02d6655b0e54f54c4ef0b94eb6be0607b70853c45ce98bd278dc7de718be5d", size = 185271, upload-time = "2025-09-08T23:22:44.795Z" }, + { url = "https://files.pythonhosted.org/packages/df/a2/781b623f57358e360d62cdd7a8c681f074a71d445418a776eef0aadb4ab4/cffi-2.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8eca2a813c1cb7ad4fb74d368c2ffbbb4789d377ee5bb8df98373c2cc0dee76c", size = 181048, upload-time = "2025-09-08T23:22:45.938Z" }, + { url = "https://files.pythonhosted.org/packages/ff/df/a4f0fbd47331ceeba3d37c2e51e9dfc9722498becbeec2bd8bc856c9538a/cffi-2.0.0-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:21d1152871b019407d8ac3985f6775c079416c282e431a4da6afe7aefd2bccbe", size = 212529, upload-time = "2025-09-08T23:22:47.349Z" }, + { url = "https://files.pythonhosted.org/packages/d5/72/12b5f8d3865bf0f87cf1404d8c374e7487dcf097a1c91c436e72e6badd83/cffi-2.0.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b21e08af67b8a103c71a250401c78d5e0893beff75e28c53c98f4de42f774062", size = 220097, upload-time = "2025-09-08T23:22:48.677Z" }, + { url = "https://files.pythonhosted.org/packages/c2/95/7a135d52a50dfa7c882ab0ac17e8dc11cec9d55d2c18dda414c051c5e69e/cffi-2.0.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:1e3a615586f05fc4065a8b22b8152f0c1b00cdbc60596d187c2a74f9e3036e4e", size = 207983, upload-time = "2025-09-08T23:22:50.06Z" }, + { url = "https://files.pythonhosted.org/packages/3a/c8/15cb9ada8895957ea171c62dc78ff3e99159ee7adb13c0123c001a2546c1/cffi-2.0.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:81afed14892743bbe14dacb9e36d9e0e504cd204e0b165062c488942b9718037", size = 206519, upload-time = "2025-09-08T23:22:51.364Z" }, + { url = "https://files.pythonhosted.org/packages/78/2d/7fa73dfa841b5ac06c7b8855cfc18622132e365f5b81d02230333ff26e9e/cffi-2.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3e17ed538242334bf70832644a32a7aae3d83b57567f9fd60a26257e992b79ba", size = 219572, upload-time = "2025-09-08T23:22:52.902Z" }, + { url = "https://files.pythonhosted.org/packages/07/e0/267e57e387b4ca276b90f0434ff88b2c2241ad72b16d31836adddfd6031b/cffi-2.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3925dd22fa2b7699ed2617149842d2e6adde22b262fcbfada50e3d195e4b3a94", size = 222963, upload-time = "2025-09-08T23:22:54.518Z" }, + { url = "https://files.pythonhosted.org/packages/b6/75/1f2747525e06f53efbd878f4d03bac5b859cbc11c633d0fb81432d98a795/cffi-2.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2c8f814d84194c9ea681642fd164267891702542f028a15fc97d4674b6206187", size = 221361, upload-time = "2025-09-08T23:22:55.867Z" }, + { url = "https://files.pythonhosted.org/packages/7b/2b/2b6435f76bfeb6bbf055596976da087377ede68df465419d192acf00c437/cffi-2.0.0-cp312-cp312-win32.whl", hash = "sha256:da902562c3e9c550df360bfa53c035b2f241fed6d9aef119048073680ace4a18", size = 172932, upload-time = "2025-09-08T23:22:57.188Z" }, + { url = "https://files.pythonhosted.org/packages/f8/ed/13bd4418627013bec4ed6e54283b1959cf6db888048c7cf4b4c3b5b36002/cffi-2.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:da68248800ad6320861f129cd9c1bf96ca849a2771a59e0344e88681905916f5", size = 183557, upload-time = "2025-09-08T23:22:58.351Z" }, + { url = "https://files.pythonhosted.org/packages/95/31/9f7f93ad2f8eff1dbc1c3656d7ca5bfd8fb52c9d786b4dcf19b2d02217fa/cffi-2.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:4671d9dd5ec934cb9a73e7ee9676f9362aba54f7f34910956b84d727b0d73fb6", size = 177762, upload-time = "2025-09-08T23:22:59.668Z" }, +] + +[[package]] +name = "charset-normalizer" +version = "3.4.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/13/69/33ddede1939fdd074bce5434295f38fae7136463422fe4fd3e0e89b98062/charset_normalizer-3.4.4.tar.gz", hash = "sha256:94537985111c35f28720e43603b8e7b43a6ecfb2ce1d3058bbe955b73404e21a", size = 129418, upload-time = "2025-10-14T04:42:32.879Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f3/85/1637cd4af66fa687396e757dec650f28025f2a2f5a5531a3208dc0ec43f2/charset_normalizer-3.4.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0a98e6759f854bd25a58a73fa88833fba3b7c491169f86ce1180c948ab3fd394", size = 208425, upload-time = "2025-10-14T04:40:53.353Z" }, + { url = "https://files.pythonhosted.org/packages/9d/6a/04130023fef2a0d9c62d0bae2649b69f7b7d8d24ea5536feef50551029df/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b5b290ccc2a263e8d185130284f8501e3e36c5e02750fc6b6bdeb2e9e96f1e25", size = 148162, upload-time = "2025-10-14T04:40:54.558Z" }, + { url = "https://files.pythonhosted.org/packages/78/29/62328d79aa60da22c9e0b9a66539feae06ca0f5a4171ac4f7dc285b83688/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74bb723680f9f7a6234dcf67aea57e708ec1fbdf5699fb91dfd6f511b0a320ef", size = 144558, upload-time = "2025-10-14T04:40:55.677Z" }, + { url = "https://files.pythonhosted.org/packages/86/bb/b32194a4bf15b88403537c2e120b817c61cd4ecffa9b6876e941c3ee38fe/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f1e34719c6ed0b92f418c7c780480b26b5d9c50349e9a9af7d76bf757530350d", size = 161497, upload-time = "2025-10-14T04:40:57.217Z" }, + { url = "https://files.pythonhosted.org/packages/19/89/a54c82b253d5b9b111dc74aca196ba5ccfcca8242d0fb64146d4d3183ff1/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2437418e20515acec67d86e12bf70056a33abdacb5cb1655042f6538d6b085a8", size = 159240, upload-time = "2025-10-14T04:40:58.358Z" }, + { url = "https://files.pythonhosted.org/packages/c0/10/d20b513afe03acc89ec33948320a5544d31f21b05368436d580dec4e234d/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:11d694519d7f29d6cd09f6ac70028dba10f92f6cdd059096db198c283794ac86", size = 153471, upload-time = "2025-10-14T04:40:59.468Z" }, + { url = "https://files.pythonhosted.org/packages/61/fa/fbf177b55bdd727010f9c0a3c49eefa1d10f960e5f09d1d887bf93c2e698/charset_normalizer-3.4.4-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ac1c4a689edcc530fc9d9aa11f5774b9e2f33f9a0c6a57864e90908f5208d30a", size = 150864, upload-time = "2025-10-14T04:41:00.623Z" }, + { url = "https://files.pythonhosted.org/packages/05/12/9fbc6a4d39c0198adeebbde20b619790e9236557ca59fc40e0e3cebe6f40/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:21d142cc6c0ec30d2efee5068ca36c128a30b0f2c53c1c07bd78cb6bc1d3be5f", size = 150647, upload-time = "2025-10-14T04:41:01.754Z" }, + { url = "https://files.pythonhosted.org/packages/ad/1f/6a9a593d52e3e8c5d2b167daf8c6b968808efb57ef4c210acb907c365bc4/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:5dbe56a36425d26d6cfb40ce79c314a2e4dd6211d51d6d2191c00bed34f354cc", size = 145110, upload-time = "2025-10-14T04:41:03.231Z" }, + { url = "https://files.pythonhosted.org/packages/30/42/9a52c609e72471b0fc54386dc63c3781a387bb4fe61c20231a4ebcd58bdd/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:5bfbb1b9acf3334612667b61bd3002196fe2a1eb4dd74d247e0f2a4d50ec9bbf", size = 162839, upload-time = "2025-10-14T04:41:04.715Z" }, + { url = "https://files.pythonhosted.org/packages/c4/5b/c0682bbf9f11597073052628ddd38344a3d673fda35a36773f7d19344b23/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:d055ec1e26e441f6187acf818b73564e6e6282709e9bcb5b63f5b23068356a15", size = 150667, upload-time = "2025-10-14T04:41:05.827Z" }, + { url = "https://files.pythonhosted.org/packages/e4/24/a41afeab6f990cf2daf6cb8c67419b63b48cf518e4f56022230840c9bfb2/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:af2d8c67d8e573d6de5bc30cdb27e9b95e49115cd9baad5ddbd1a6207aaa82a9", size = 160535, upload-time = "2025-10-14T04:41:06.938Z" }, + { url = "https://files.pythonhosted.org/packages/2a/e5/6a4ce77ed243c4a50a1fecca6aaaab419628c818a49434be428fe24c9957/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:780236ac706e66881f3b7f2f32dfe90507a09e67d1d454c762cf642e6e1586e0", size = 154816, upload-time = "2025-10-14T04:41:08.101Z" }, + { url = "https://files.pythonhosted.org/packages/a8/ef/89297262b8092b312d29cdb2517cb1237e51db8ecef2e9af5edbe7b683b1/charset_normalizer-3.4.4-cp312-cp312-win32.whl", hash = "sha256:5833d2c39d8896e4e19b689ffc198f08ea58116bee26dea51e362ecc7cd3ed26", size = 99694, upload-time = "2025-10-14T04:41:09.23Z" }, + { url = "https://files.pythonhosted.org/packages/3d/2d/1e5ed9dd3b3803994c155cd9aacb60c82c331bad84daf75bcb9c91b3295e/charset_normalizer-3.4.4-cp312-cp312-win_amd64.whl", hash = "sha256:a79cfe37875f822425b89a82333404539ae63dbdddf97f84dcbc3d339aae9525", size = 107131, upload-time = "2025-10-14T04:41:10.467Z" }, + { url = "https://files.pythonhosted.org/packages/d0/d9/0ed4c7098a861482a7b6a95603edce4c0d9db2311af23da1fb2b75ec26fc/charset_normalizer-3.4.4-cp312-cp312-win_arm64.whl", hash = "sha256:376bec83a63b8021bb5c8ea75e21c4ccb86e7e45ca4eb81146091b56599b80c3", size = 100390, upload-time = "2025-10-14T04:41:11.915Z" }, + { url = "https://files.pythonhosted.org/packages/0a/4c/925909008ed5a988ccbb72dcc897407e5d6d3bd72410d69e051fc0c14647/charset_normalizer-3.4.4-py3-none-any.whl", hash = "sha256:7a32c560861a02ff789ad905a2fe94e3f840803362c84fecf1851cb4cf3dc37f", size = 53402, upload-time = "2025-10-14T04:42:31.76Z" }, +] + +[[package]] +name = "click" +version = "8.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3d/fa/656b739db8587d7b5dfa22e22ed02566950fbfbcdc20311993483657a5c0/click-8.3.1.tar.gz", hash = "sha256:12ff4785d337a1bb490bb7e9c2b1ee5da3112e94a8622f26a6c77f5d2fc6842a", size = 295065, upload-time = "2025-11-15T20:45:42.706Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/98/78/01c019cdb5d6498122777c1a43056ebb3ebfeef2076d9d026bfe15583b2b/click-8.3.1-py3-none-any.whl", hash = "sha256:981153a64e25f12d547d3426c367a4857371575ee7ad18df2a6183ab0545b2a6", size = 108274, upload-time = "2025-11-15T20:45:41.139Z" }, +] + +[[package]] +name = "click-params" +version = "0.5.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "deprecated" }, + { name = "validators" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0c/49/57e60d9e1b78fd21fbaeda0725ac311595c35d8682dace6b71b274a43b90/click_params-0.5.0.tar.gz", hash = "sha256:5fe97b9459781a3b43b84fe4ec0065193e1b0d5cf6dc77897fe20c31f478d7ff", size = 11097, upload-time = "2023-11-23T11:54:31.315Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ca/c7/a04832e84f1c613194231a657612aee2e377d63a44a5847386c83c38bbd6/click_params-0.5.0-py3-none-any.whl", hash = "sha256:bbb2efe44197ab896bffcb50f42f22240fb077e6756b568fbdab3e1700b859d6", size = 13152, upload-time = "2023-11-23T11:54:29.599Z" }, +] + +[[package]] +name = "cloudpickle" +version = "3.1.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/27/fb/576f067976d320f5f0114a8d9fa1215425441bb35627b1993e5afd8111e5/cloudpickle-3.1.2.tar.gz", hash = "sha256:7fda9eb655c9c230dab534f1983763de5835249750e85fbcef43aaa30a9a2414", size = 22330, upload-time = "2025-11-03T09:25:26.604Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/39/799be3f2f0f38cc727ee3b4f1445fe6d5e4133064ec2e4115069418a5bb6/cloudpickle-3.1.2-py3-none-any.whl", hash = "sha256:9acb47f6afd73f60dc1df93bb801b472f05ff42fa6c84167d25cb206be1fbf4a", size = 22228, upload-time = "2025-11-03T09:25:25.534Z" }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, +] + +[[package]] +name = "cpplint" +version = "1.6.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/18/72/ea0f4035bcf35d8f8df053657d7f3370d56ff4d4e6617021b6544b9958d4/cpplint-1.6.1.tar.gz", hash = "sha256:d430ce8f67afc1839340e60daa89e90de08b874bc27149833077bba726dfc13a", size = 364487, upload-time = "2022-08-20T14:18:12.999Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6c/68/31f71f3946e1b445cc5ffa2bc7143488b1f3a556dce344c5b6ae498bbe2a/cpplint-1.6.1-py3-none-any.whl", hash = "sha256:00ddc86d6e4de2a9dcfa272402dcbe21593363a93b7c475bc391e335062f34b1", size = 77257, upload-time = "2022-08-20T14:18:05.885Z" }, +] + +[[package]] +name = "cryptography" +version = "46.0.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9f/33/c00162f49c0e2fe8064a62cb92b93e50c74a72bc370ab92f86112b33ff62/cryptography-46.0.3.tar.gz", hash = "sha256:a8b17438104fed022ce745b362294d9ce35b4c2e45c1d958ad4a4b019285f4a1", size = 749258, upload-time = "2025-10-15T23:18:31.74Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1d/42/9c391dd801d6cf0d561b5890549d4b27bafcc53b39c31a817e69d87c625b/cryptography-46.0.3-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:109d4ddfadf17e8e7779c39f9b18111a09efb969a301a31e987416a0191ed93a", size = 7225004, upload-time = "2025-10-15T23:16:52.239Z" }, + { url = "https://files.pythonhosted.org/packages/1c/67/38769ca6b65f07461eb200e85fc1639b438bdc667be02cf7f2cd6a64601c/cryptography-46.0.3-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:09859af8466b69bc3c27bdf4f5d84a665e0f7ab5088412e9e2ec49758eca5cbc", size = 4296667, upload-time = "2025-10-15T23:16:54.369Z" }, + { url = "https://files.pythonhosted.org/packages/5c/49/498c86566a1d80e978b42f0d702795f69887005548c041636df6ae1ca64c/cryptography-46.0.3-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:01ca9ff2885f3acc98c29f1860552e37f6d7c7d013d7334ff2a9de43a449315d", size = 4450807, upload-time = "2025-10-15T23:16:56.414Z" }, + { url = "https://files.pythonhosted.org/packages/4b/0a/863a3604112174c8624a2ac3c038662d9e59970c7f926acdcfaed8d61142/cryptography-46.0.3-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:6eae65d4c3d33da080cff9c4ab1f711b15c1d9760809dad6ea763f3812d254cb", size = 4299615, upload-time = "2025-10-15T23:16:58.442Z" }, + { url = "https://files.pythonhosted.org/packages/64/02/b73a533f6b64a69f3cd3872acb6ebc12aef924d8d103133bb3ea750dc703/cryptography-46.0.3-cp311-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e5bf0ed4490068a2e72ac03d786693adeb909981cc596425d09032d372bcc849", size = 4016800, upload-time = "2025-10-15T23:17:00.378Z" }, + { url = "https://files.pythonhosted.org/packages/25/d5/16e41afbfa450cde85a3b7ec599bebefaef16b5c6ba4ec49a3532336ed72/cryptography-46.0.3-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:5ecfccd2329e37e9b7112a888e76d9feca2347f12f37918facbb893d7bb88ee8", size = 4984707, upload-time = "2025-10-15T23:17:01.98Z" }, + { url = "https://files.pythonhosted.org/packages/c9/56/e7e69b427c3878352c2fb9b450bd0e19ed552753491d39d7d0a2f5226d41/cryptography-46.0.3-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:a2c0cd47381a3229c403062f764160d57d4d175e022c1df84e168c6251a22eec", size = 4482541, upload-time = "2025-10-15T23:17:04.078Z" }, + { url = "https://files.pythonhosted.org/packages/78/f6/50736d40d97e8483172f1bb6e698895b92a223dba513b0ca6f06b2365339/cryptography-46.0.3-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:549e234ff32571b1f4076ac269fcce7a808d3bf98b76c8dd560e42dbc66d7d91", size = 4299464, upload-time = "2025-10-15T23:17:05.483Z" }, + { url = "https://files.pythonhosted.org/packages/00/de/d8e26b1a855f19d9994a19c702fa2e93b0456beccbcfe437eda00e0701f2/cryptography-46.0.3-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:c0a7bb1a68a5d3471880e264621346c48665b3bf1c3759d682fc0864c540bd9e", size = 4950838, upload-time = "2025-10-15T23:17:07.425Z" }, + { url = "https://files.pythonhosted.org/packages/8f/29/798fc4ec461a1c9e9f735f2fc58741b0daae30688f41b2497dcbc9ed1355/cryptography-46.0.3-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:10b01676fc208c3e6feeb25a8b83d81767e8059e1fe86e1dc62d10a3018fa926", size = 4481596, upload-time = "2025-10-15T23:17:09.343Z" }, + { url = "https://files.pythonhosted.org/packages/15/8d/03cd48b20a573adfff7652b76271078e3045b9f49387920e7f1f631d125e/cryptography-46.0.3-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:0abf1ffd6e57c67e92af68330d05760b7b7efb243aab8377e583284dbab72c71", size = 4426782, upload-time = "2025-10-15T23:17:11.22Z" }, + { url = "https://files.pythonhosted.org/packages/fa/b1/ebacbfe53317d55cf33165bda24c86523497a6881f339f9aae5c2e13e57b/cryptography-46.0.3-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a04bee9ab6a4da801eb9b51f1b708a1b5b5c9eb48c03f74198464c66f0d344ac", size = 4698381, upload-time = "2025-10-15T23:17:12.829Z" }, + { url = "https://files.pythonhosted.org/packages/96/92/8a6a9525893325fc057a01f654d7efc2c64b9de90413adcf605a85744ff4/cryptography-46.0.3-cp311-abi3-win32.whl", hash = "sha256:f260d0d41e9b4da1ed1e0f1ce571f97fe370b152ab18778e9e8f67d6af432018", size = 3055988, upload-time = "2025-10-15T23:17:14.65Z" }, + { url = "https://files.pythonhosted.org/packages/7e/bf/80fbf45253ea585a1e492a6a17efcb93467701fa79e71550a430c5e60df0/cryptography-46.0.3-cp311-abi3-win_amd64.whl", hash = "sha256:a9a3008438615669153eb86b26b61e09993921ebdd75385ddd748702c5adfddb", size = 3514451, upload-time = "2025-10-15T23:17:16.142Z" }, + { url = "https://files.pythonhosted.org/packages/2e/af/9b302da4c87b0beb9db4e756386a7c6c5b8003cd0e742277888d352ae91d/cryptography-46.0.3-cp311-abi3-win_arm64.whl", hash = "sha256:5d7f93296ee28f68447397bf5198428c9aeeab45705a55d53a6343455dcb2c3c", size = 2928007, upload-time = "2025-10-15T23:17:18.04Z" }, + { url = "https://files.pythonhosted.org/packages/fd/23/45fe7f376a7df8daf6da3556603b36f53475a99ce4faacb6ba2cf3d82021/cryptography-46.0.3-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:cb3d760a6117f621261d662bccc8ef5bc32ca673e037c83fbe565324f5c46936", size = 7218248, upload-time = "2025-10-15T23:17:46.294Z" }, + { url = "https://files.pythonhosted.org/packages/27/32/b68d27471372737054cbd34c84981f9edbc24fe67ca225d389799614e27f/cryptography-46.0.3-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:4b7387121ac7d15e550f5cb4a43aef2559ed759c35df7336c402bb8275ac9683", size = 4294089, upload-time = "2025-10-15T23:17:48.269Z" }, + { url = "https://files.pythonhosted.org/packages/26/42/fa8389d4478368743e24e61eea78846a0006caffaf72ea24a15159215a14/cryptography-46.0.3-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:15ab9b093e8f09daab0f2159bb7e47532596075139dd74365da52ecc9cb46c5d", size = 4440029, upload-time = "2025-10-15T23:17:49.837Z" }, + { url = "https://files.pythonhosted.org/packages/5f/eb/f483db0ec5ac040824f269e93dd2bd8a21ecd1027e77ad7bdf6914f2fd80/cryptography-46.0.3-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:46acf53b40ea38f9c6c229599a4a13f0d46a6c3fa9ef19fc1a124d62e338dfa0", size = 4297222, upload-time = "2025-10-15T23:17:51.357Z" }, + { url = "https://files.pythonhosted.org/packages/fd/cf/da9502c4e1912cb1da3807ea3618a6829bee8207456fbbeebc361ec38ba3/cryptography-46.0.3-cp38-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:10ca84c4668d066a9878890047f03546f3ae0a6b8b39b697457b7757aaf18dbc", size = 4012280, upload-time = "2025-10-15T23:17:52.964Z" }, + { url = "https://files.pythonhosted.org/packages/6b/8f/9adb86b93330e0df8b3dcf03eae67c33ba89958fc2e03862ef1ac2b42465/cryptography-46.0.3-cp38-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:36e627112085bb3b81b19fed209c05ce2a52ee8b15d161b7c643a7d5a88491f3", size = 4978958, upload-time = "2025-10-15T23:17:54.965Z" }, + { url = "https://files.pythonhosted.org/packages/d1/a0/5fa77988289c34bdb9f913f5606ecc9ada1adb5ae870bd0d1054a7021cc4/cryptography-46.0.3-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:1000713389b75c449a6e979ffc7dcc8ac90b437048766cef052d4d30b8220971", size = 4473714, upload-time = "2025-10-15T23:17:56.754Z" }, + { url = "https://files.pythonhosted.org/packages/14/e5/fc82d72a58d41c393697aa18c9abe5ae1214ff6f2a5c18ac470f92777895/cryptography-46.0.3-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:b02cf04496f6576afffef5ddd04a0cb7d49cf6be16a9059d793a30b035f6b6ac", size = 4296970, upload-time = "2025-10-15T23:17:58.588Z" }, + { url = "https://files.pythonhosted.org/packages/78/06/5663ed35438d0b09056973994f1aec467492b33bd31da36e468b01ec1097/cryptography-46.0.3-cp38-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:71e842ec9bc7abf543b47cf86b9a743baa95f4677d22baa4c7d5c69e49e9bc04", size = 4940236, upload-time = "2025-10-15T23:18:00.897Z" }, + { url = "https://files.pythonhosted.org/packages/fc/59/873633f3f2dcd8a053b8dd1d38f783043b5fce589c0f6988bf55ef57e43e/cryptography-46.0.3-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:402b58fc32614f00980b66d6e56a5b4118e6cb362ae8f3fda141ba4689bd4506", size = 4472642, upload-time = "2025-10-15T23:18:02.749Z" }, + { url = "https://files.pythonhosted.org/packages/3d/39/8e71f3930e40f6877737d6f69248cf74d4e34b886a3967d32f919cc50d3b/cryptography-46.0.3-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:ef639cb3372f69ec44915fafcd6698b6cc78fbe0c2ea41be867f6ed612811963", size = 4423126, upload-time = "2025-10-15T23:18:04.85Z" }, + { url = "https://files.pythonhosted.org/packages/cd/c7/f65027c2810e14c3e7268353b1681932b87e5a48e65505d8cc17c99e36ae/cryptography-46.0.3-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:3b51b8ca4f1c6453d8829e1eb7299499ca7f313900dd4d89a24b8b87c0a780d4", size = 4686573, upload-time = "2025-10-15T23:18:06.908Z" }, + { url = "https://files.pythonhosted.org/packages/0a/6e/1c8331ddf91ca4730ab3086a0f1be19c65510a33b5a441cb334e7a2d2560/cryptography-46.0.3-cp38-abi3-win32.whl", hash = "sha256:6276eb85ef938dc035d59b87c8a7dc559a232f954962520137529d77b18ff1df", size = 3036695, upload-time = "2025-10-15T23:18:08.672Z" }, + { url = "https://files.pythonhosted.org/packages/90/45/b0d691df20633eff80955a0fc7695ff9051ffce8b69741444bd9ed7bd0db/cryptography-46.0.3-cp38-abi3-win_amd64.whl", hash = "sha256:416260257577718c05135c55958b674000baef9a1c7d9e8f306ec60d71db850f", size = 3501720, upload-time = "2025-10-15T23:18:10.632Z" }, + { url = "https://files.pythonhosted.org/packages/e8/cb/2da4cc83f5edb9c3257d09e1e7ab7b23f049c7962cae8d842bbef0a9cec9/cryptography-46.0.3-cp38-abi3-win_arm64.whl", hash = "sha256:d89c3468de4cdc4f08a57e214384d0471911a3830fcdaf7a8cc587e42a866372", size = 2918740, upload-time = "2025-10-15T23:18:12.277Z" }, +] + +[[package]] +name = "dask" +version = "2025.12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "cloudpickle" }, + { name = "fsspec" }, + { name = "packaging" }, + { name = "partd" }, + { name = "pyyaml" }, + { name = "toolz" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/49/ae/92fca08ff8fe3e8413842564dd55ee30c9cd9e07629e1bf4d347b005a5bf/dask-2025.12.0.tar.gz", hash = "sha256:8d478f2aabd025e2453cf733ad64559de90cf328c20209e4574e9543707c3e1b", size = 10995316, upload-time = "2025-12-12T14:59:10.885Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6f/3a/2121294941227c548d4b5f897a8a1b5f4c44a58f5437f239e6b86511d78e/dask-2025.12.0-py3-none-any.whl", hash = "sha256:4213ce9c5d51d6d89337cff69de35d902aa0bf6abdb8a25c942a4d0281f3a598", size = 1481293, upload-time = "2025-12-12T14:58:59.32Z" }, +] + +[[package]] +name = "decorator" +version = "5.2.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/43/fa/6d96a0978d19e17b68d634497769987b16c8f4cd0a7a05048bec693caa6b/decorator-5.2.1.tar.gz", hash = "sha256:65f266143752f734b0a7cc83c46f4618af75b8c5911b00ccb61d0ac9b6da0360", size = 56711, upload-time = "2025-02-24T04:41:34.073Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4e/8c/f3147f5c4b73e7550fe5f9352eaa956ae838d5c51eb58e7a25b9f3e2643b/decorator-5.2.1-py3-none-any.whl", hash = "sha256:d316bb415a2d9e2d2b3abcc4084c6502fc09240e292cd76a76afc106a1c8e04a", size = 9190, upload-time = "2025-02-24T04:41:32.565Z" }, +] + +[[package]] +name = "deprecated" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "wrapt" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/49/85/12f0a49a7c4ffb70572b6c2ef13c90c88fd190debda93b23f026b25f9634/deprecated-1.3.1.tar.gz", hash = "sha256:b1b50e0ff0c1fddaa5708a2c6b0a6588bb09b892825ab2b214ac9ea9d92a5223", size = 2932523, upload-time = "2025-10-30T08:19:02.757Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/84/d0/205d54408c08b13550c733c4b85429e7ead111c7f0014309637425520a9a/deprecated-1.3.1-py2.py3-none-any.whl", hash = "sha256:597bfef186b6f60181535a29fbe44865ce137a5079f295b479886c82729d5f3f", size = 11298, upload-time = "2025-10-30T08:19:00.758Z" }, +] + +[[package]] +name = "donfig" +version = "0.8.1.post1" +source = { registry = "https://pypi.org/simple" } +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, upload-time = "2024-05-23T14:14:31.513Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0c/d5/c5db1ea3394c6e1732fb3286b3bd878b59507a8f77d32a2cebda7d7b7cd4/donfig-0.8.1.post1-py3-none-any.whl", hash = "sha256:2a3175ce74a06109ff9307d90a230f81215cbac9a751f4d1c6194644b8204f9d", size = 21592, upload-time = "2024-05-23T14:13:55.283Z" }, +] + +[[package]] +name = "flexcache" +version = "0.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/55/b0/8a21e330561c65653d010ef112bf38f60890051d244ede197ddaa08e50c1/flexcache-0.3.tar.gz", hash = "sha256:18743bd5a0621bfe2cf8d519e4c3bfdf57a269c15d1ced3fb4b64e0ff4600656", size = 15816, upload-time = "2024-03-09T03:21:07.555Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/27/cd/c883e1a7c447479d6e13985565080e3fea88ab5a107c21684c813dba1875/flexcache-0.3-py3-none-any.whl", hash = "sha256:d43c9fea82336af6e0115e308d9d33a185390b8346a017564611f1466dcd2e32", size = 13263, upload-time = "2024-03-09T03:21:05.635Z" }, +] + +[[package]] +name = "flexparser" +version = "0.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/82/99/b4de7e39e8eaf8207ba1a8fa2241dd98b2ba72ae6e16960d8351736d8702/flexparser-0.4.tar.gz", hash = "sha256:266d98905595be2ccc5da964fe0a2c3526fbbffdc45b65b3146d75db992ef6b2", size = 31799, upload-time = "2024-11-07T02:00:56.249Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fe/5e/3be305568fe5f34448807976dc82fc151d76c3e0e03958f34770286278c1/flexparser-0.4-py3-none-any.whl", hash = "sha256:3738b456192dcb3e15620f324c447721023c0293f6af9955b481e91d00179846", size = 27625, upload-time = "2024-11-07T02:00:54.523Z" }, +] + +[[package]] +name = "frozenlist" +version = "1.8.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2d/f5/c831fac6cc817d26fd54c7eaccd04ef7e0288806943f7cc5bbf69f3ac1f0/frozenlist-1.8.0.tar.gz", hash = "sha256:3ede829ed8d842f6cd48fc7081d7a41001a56f1f38603f9d49bf3020d59a31ad", size = 45875, upload-time = "2025-10-06T05:38:17.865Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/69/29/948b9aa87e75820a38650af445d2ef2b6b8a6fab1a23b6bb9e4ef0be2d59/frozenlist-1.8.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:78f7b9e5d6f2fdb88cdde9440dc147259b62b9d3b019924def9f6478be254ac1", size = 87782, upload-time = "2025-10-06T05:36:06.649Z" }, + { url = "https://files.pythonhosted.org/packages/64/80/4f6e318ee2a7c0750ed724fa33a4bdf1eacdc5a39a7a24e818a773cd91af/frozenlist-1.8.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:229bf37d2e4acdaf808fd3f06e854a4a7a3661e871b10dc1f8f1896a3b05f18b", size = 50594, upload-time = "2025-10-06T05:36:07.69Z" }, + { url = "https://files.pythonhosted.org/packages/2b/94/5c8a2b50a496b11dd519f4a24cb5496cf125681dd99e94c604ccdea9419a/frozenlist-1.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f833670942247a14eafbb675458b4e61c82e002a148f49e68257b79296e865c4", size = 50448, upload-time = "2025-10-06T05:36:08.78Z" }, + { url = "https://files.pythonhosted.org/packages/6a/bd/d91c5e39f490a49df14320f4e8c80161cfcce09f1e2cde1edd16a551abb3/frozenlist-1.8.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:494a5952b1c597ba44e0e78113a7266e656b9794eec897b19ead706bd7074383", size = 242411, upload-time = "2025-10-06T05:36:09.801Z" }, + { url = "https://files.pythonhosted.org/packages/8f/83/f61505a05109ef3293dfb1ff594d13d64a2324ac3482be2cedc2be818256/frozenlist-1.8.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:96f423a119f4777a4a056b66ce11527366a8bb92f54e541ade21f2374433f6d4", size = 243014, upload-time = "2025-10-06T05:36:11.394Z" }, + { url = "https://files.pythonhosted.org/packages/d8/cb/cb6c7b0f7d4023ddda30cf56b8b17494eb3a79e3fda666bf735f63118b35/frozenlist-1.8.0-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3462dd9475af2025c31cc61be6652dfa25cbfb56cbbf52f4ccfe029f38decaf8", size = 234909, upload-time = "2025-10-06T05:36:12.598Z" }, + { url = "https://files.pythonhosted.org/packages/31/c5/cd7a1f3b8b34af009fb17d4123c5a778b44ae2804e3ad6b86204255f9ec5/frozenlist-1.8.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c4c800524c9cd9bac5166cd6f55285957fcfc907db323e193f2afcd4d9abd69b", size = 250049, upload-time = "2025-10-06T05:36:14.065Z" }, + { url = "https://files.pythonhosted.org/packages/c0/01/2f95d3b416c584a1e7f0e1d6d31998c4a795f7544069ee2e0962a4b60740/frozenlist-1.8.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d6a5df73acd3399d893dafc71663ad22534b5aa4f94e8a2fabfe856c3c1b6a52", size = 256485, upload-time = "2025-10-06T05:36:15.39Z" }, + { url = "https://files.pythonhosted.org/packages/ce/03/024bf7720b3abaebcff6d0793d73c154237b85bdf67b7ed55e5e9596dc9a/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:405e8fe955c2280ce66428b3ca55e12b3c4e9c336fb2103a4937e891c69a4a29", size = 237619, upload-time = "2025-10-06T05:36:16.558Z" }, + { url = "https://files.pythonhosted.org/packages/69/fa/f8abdfe7d76b731f5d8bd217827cf6764d4f1d9763407e42717b4bed50a0/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:908bd3f6439f2fef9e85031b59fd4f1297af54415fb60e4254a95f75b3cab3f3", size = 250320, upload-time = "2025-10-06T05:36:17.821Z" }, + { url = "https://files.pythonhosted.org/packages/f5/3c/b051329f718b463b22613e269ad72138cc256c540f78a6de89452803a47d/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:294e487f9ec720bd8ffcebc99d575f7eff3568a08a253d1ee1a0378754b74143", size = 246820, upload-time = "2025-10-06T05:36:19.046Z" }, + { url = "https://files.pythonhosted.org/packages/0f/ae/58282e8f98e444b3f4dd42448ff36fa38bef29e40d40f330b22e7108f565/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:74c51543498289c0c43656701be6b077f4b265868fa7f8a8859c197006efb608", size = 250518, upload-time = "2025-10-06T05:36:20.763Z" }, + { url = "https://files.pythonhosted.org/packages/8f/96/007e5944694d66123183845a106547a15944fbbb7154788cbf7272789536/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:776f352e8329135506a1d6bf16ac3f87bc25b28e765949282dcc627af36123aa", size = 239096, upload-time = "2025-10-06T05:36:22.129Z" }, + { url = "https://files.pythonhosted.org/packages/66/bb/852b9d6db2fa40be96f29c0d1205c306288f0684df8fd26ca1951d461a56/frozenlist-1.8.0-cp312-cp312-win32.whl", hash = "sha256:433403ae80709741ce34038da08511d4a77062aa924baf411ef73d1146e74faf", size = 39985, upload-time = "2025-10-06T05:36:23.661Z" }, + { url = "https://files.pythonhosted.org/packages/b8/af/38e51a553dd66eb064cdf193841f16f077585d4d28394c2fa6235cb41765/frozenlist-1.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:34187385b08f866104f0c0617404c8eb08165ab1272e884abc89c112e9c00746", size = 44591, upload-time = "2025-10-06T05:36:24.958Z" }, + { url = "https://files.pythonhosted.org/packages/a7/06/1dc65480ab147339fecc70797e9c2f69d9cea9cf38934ce08df070fdb9cb/frozenlist-1.8.0-cp312-cp312-win_arm64.whl", hash = "sha256:fe3c58d2f5db5fbd18c2987cba06d51b0529f52bc3a6cdc33d3f4eab725104bd", size = 40102, upload-time = "2025-10-06T05:36:26.333Z" }, + { url = "https://files.pythonhosted.org/packages/9a/9a/e35b4a917281c0b8419d4207f4334c8e8c5dbf4f3f5f9ada73958d937dcc/frozenlist-1.8.0-py3-none-any.whl", hash = "sha256:0c18a16eab41e82c295618a77502e17b195883241c563b00f0aa5106fc4eaa0d", size = 13409, upload-time = "2025-10-06T05:38:16.721Z" }, +] + +[[package]] +name = "fsspec" +version = "2025.12.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b6/27/954057b0d1f53f086f681755207dda6de6c660ce133c829158e8e8fe7895/fsspec-2025.12.0.tar.gz", hash = "sha256:c505de011584597b1060ff778bb664c1bc022e87921b0e4f10cc9c44f9635973", size = 309748, upload-time = "2025-12-03T15:23:42.687Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/51/c7/b64cae5dba3a1b138d7123ec36bb5ccd39d39939f18454407e5468f4763f/fsspec-2025.12.0-py3-none-any.whl", hash = "sha256:8bf1fe301b7d8acfa6e8571e3b1c3d158f909666642431cc78a1b7b4dbc5ec5b", size = 201422, upload-time = "2025-12-03T15:23:41.434Z" }, +] + +[[package]] +name = "gcsfs" +version = "2025.12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "aiohttp" }, + { name = "decorator" }, + { name = "fsspec" }, + { name = "google-auth" }, + { name = "google-auth-oauthlib" }, + { name = "google-cloud-storage" }, + { name = "google-cloud-storage-control" }, + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3e/3e/453b42bbcda2177daba97ebc5202faf5c57cc0c2bb4aac7081c12b0471da/gcsfs-2025.12.0.tar.gz", hash = "sha256:9a9c1c32b7899a3967ba30a7e9422cdfda596266f03476cdf5545402b8af4cd5", size = 95148, upload-time = "2025-12-03T15:44:59.703Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b1/bf/e664cbeab8f2a8b097c0604252760410fde191fe6ac7d5081b29e601ac52/gcsfs-2025.12.0-py3-none-any.whl", hash = "sha256:e06aaec53797dc6b83d5cc90c4d3ae7247b4ee0cf8d8b1ce50e8d6b78e3a9aea", size = 41204, upload-time = "2025-12-03T15:44:58.464Z" }, +] + +[[package]] +name = "google-api-core" +version = "2.28.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "google-auth" }, + { name = "googleapis-common-protos" }, + { name = "proto-plus" }, + { name = "protobuf" }, + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/61/da/83d7043169ac2c8c7469f0e375610d78ae2160134bf1b80634c482fa079c/google_api_core-2.28.1.tar.gz", hash = "sha256:2b405df02d68e68ce0fbc138559e6036559e685159d148ae5861013dc201baf8", size = 176759, upload-time = "2025-10-28T21:34:51.529Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ed/d4/90197b416cb61cefd316964fd9e7bd8324bcbafabf40eef14a9f20b81974/google_api_core-2.28.1-py3-none-any.whl", hash = "sha256:4021b0f8ceb77a6fb4de6fde4502cecab45062e66ff4f2895169e0b35bc9466c", size = 173706, upload-time = "2025-10-28T21:34:50.151Z" }, +] + +[package.optional-dependencies] +grpc = [ + { name = "grpcio" }, + { name = "grpcio-status" }, +] + +[[package]] +name = "google-auth" +version = "2.47.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyasn1-modules" }, + { name = "rsa" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/60/3c/ec64b9a275ca22fa1cd3b6e77fefcf837b0732c890aa32d2bd21313d9b33/google_auth-2.47.0.tar.gz", hash = "sha256:833229070a9dfee1a353ae9877dcd2dec069a8281a4e72e72f77d4a70ff945da", size = 323719, upload-time = "2026-01-06T21:55:31.045Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/db/18/79e9008530b79527e0d5f79e7eef08d3b179b7f851cfd3a2f27822fbdfa9/google_auth-2.47.0-py3-none-any.whl", hash = "sha256:c516d68336bfde7cf0da26aab674a36fedcf04b37ac4edd59c597178760c3498", size = 234867, upload-time = "2026-01-06T21:55:28.6Z" }, +] + +[[package]] +name = "google-auth-oauthlib" +version = "1.2.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "google-auth" }, + { name = "requests-oauthlib" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fb/87/e10bf24f7bcffc1421b84d6f9c3377c30ec305d082cd737ddaa6d8f77f7c/google_auth_oauthlib-1.2.2.tar.gz", hash = "sha256:11046fb8d3348b296302dd939ace8af0a724042e8029c1b872d87fabc9f41684", size = 20955, upload-time = "2025-04-22T16:40:29.172Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ac/84/40ee070be95771acd2f4418981edb834979424565c3eec3cd88b6aa09d24/google_auth_oauthlib-1.2.2-py3-none-any.whl", hash = "sha256:fd619506f4b3908b5df17b65f39ca8d66ea56986e5472eb5978fd8f3786f00a2", size = 19072, upload-time = "2025-04-22T16:40:28.174Z" }, +] + +[[package]] +name = "google-cloud-core" +version = "2.5.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "google-api-core" }, + { name = "google-auth" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a6/03/ef0bc99d0e0faf4fdbe67ac445e18cdaa74824fd93cd069e7bb6548cb52d/google_cloud_core-2.5.0.tar.gz", hash = "sha256:7c1b7ef5c92311717bd05301aa1a91ffbc565673d3b0b4163a52d8413a186963", size = 36027, upload-time = "2025-10-29T23:17:39.513Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/89/20/bfa472e327c8edee00f04beecc80baeddd2ab33ee0e86fd7654da49d45e9/google_cloud_core-2.5.0-py3-none-any.whl", hash = "sha256:67d977b41ae6c7211ee830c7912e41003ea8194bff15ae7d72fd6f51e57acabc", size = 29469, upload-time = "2025-10-29T23:17:38.548Z" }, +] + +[[package]] +name = "google-cloud-storage" +version = "3.7.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "google-api-core" }, + { name = "google-auth" }, + { name = "google-cloud-core" }, + { name = "google-crc32c" }, + { name = "google-resumable-media" }, + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d2/8e/fab2de1a0ab7fdbd452eaae5a9a5c933d0911c26b04efa0c76ddfd921259/google_cloud_storage-3.7.0.tar.gz", hash = "sha256:9ce59c65f4d6e372effcecc0456680a8d73cef4f2dc9212a0704799cb3d69237", size = 17258914, upload-time = "2025-12-09T18:24:48.97Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2d/80/6e5c7c83cea15ed4dfc4843b9df9db0716bc551ac938f7b5dd18a72bd5e4/google_cloud_storage-3.7.0-py3-none-any.whl", hash = "sha256:469bc9540936e02f8a4bfd1619e9dca1e42dec48f95e4204d783b36476a15093", size = 303364, upload-time = "2025-12-09T18:24:47.343Z" }, +] + +[[package]] +name = "google-cloud-storage-control" +version = "1.8.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "google-api-core", extra = ["grpc"] }, + { name = "google-auth" }, + { name = "grpc-google-iam-v1" }, + { name = "grpcio" }, + { name = "proto-plus" }, + { name = "protobuf" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/56/24/ea82cba156377fb270d024c2b13e397d6e57663e0db529978736bc444d67/google_cloud_storage_control-1.8.0.tar.gz", hash = "sha256:e77f1365667e9cf8b6fbb0fc0de725a6045ee4b42220c3e6f863adace6c4033f", size = 112056, upload-time = "2025-10-20T14:57:18.086Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bc/88/bbf40bfebbce33c9aa421b6da8ea89d2771edd5142ee3f35f6b4b3efb98c/google_cloud_storage_control-1.8.0-py3-none-any.whl", hash = "sha256:1a3179438f82fc49b2ad3d4674d737164bd6a51e0a9b545a9ec7e7d118b8bee0", size = 86054, upload-time = "2025-10-20T14:54:06.198Z" }, +] + +[[package]] +name = "google-crc32c" +version = "1.8.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/03/41/4b9c02f99e4c5fb477122cd5437403b552873f014616ac1d19ac8221a58d/google_crc32c-1.8.0.tar.gz", hash = "sha256:a428e25fb7691024de47fecfbff7ff957214da51eddded0da0ae0e0f03a2cf79", size = 14192, upload-time = "2025-12-16T00:35:25.142Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e9/5f/7307325b1198b59324c0fa9807cafb551afb65e831699f2ce211ad5c8240/google_crc32c-1.8.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:4b8286b659c1335172e39563ab0a768b8015e88e08329fa5321f774275fc3113", size = 31300, upload-time = "2025-12-16T00:21:56.723Z" }, + { url = "https://files.pythonhosted.org/packages/21/8e/58c0d5d86e2220e6a37befe7e6a94dd2f6006044b1a33edf1ff6d9f7e319/google_crc32c-1.8.0-cp312-cp312-macosx_12_0_x86_64.whl", hash = "sha256:2a3dc3318507de089c5384cc74d54318401410f82aa65b2d9cdde9d297aca7cb", size = 30867, upload-time = "2025-12-16T00:38:31.302Z" }, + { url = "https://files.pythonhosted.org/packages/ce/a9/a780cc66f86335a6019f557a8aaca8fbb970728f0efd2430d15ff1beae0e/google_crc32c-1.8.0-cp312-cp312-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:14f87e04d613dfa218d6135e81b78272c3b904e2a7053b841481b38a7d901411", size = 33364, upload-time = "2025-12-16T00:40:22.96Z" }, + { url = "https://files.pythonhosted.org/packages/21/3f/3457ea803db0198c9aaca2dd373750972ce28a26f00544b6b85088811939/google_crc32c-1.8.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cb5c869c2923d56cb0c8e6bcdd73c009c36ae39b652dbe46a05eb4ef0ad01454", size = 33740, upload-time = "2025-12-16T00:40:23.96Z" }, + { url = "https://files.pythonhosted.org/packages/df/c0/87c2073e0c72515bb8733d4eef7b21548e8d189f094b5dad20b0ecaf64f6/google_crc32c-1.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:3cc0c8912038065eafa603b238abf252e204accab2a704c63b9e14837a854962", size = 34437, upload-time = "2025-12-16T00:35:21.395Z" }, +] + +[[package]] +name = "google-resumable-media" +version = "2.8.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "google-crc32c" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/64/d7/520b62a35b23038ff005e334dba3ffc75fcf583bee26723f1fd8fd4b6919/google_resumable_media-2.8.0.tar.gz", hash = "sha256:f1157ed8b46994d60a1bc432544db62352043113684d4e030ee02e77ebe9a1ae", size = 2163265, upload-time = "2025-11-17T15:38:06.659Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1f/0b/93afde9cfe012260e9fe1522f35c9b72d6ee222f316586b1f23ecf44d518/google_resumable_media-2.8.0-py3-none-any.whl", hash = "sha256:dd14a116af303845a8d932ddae161a26e86cc229645bc98b39f026f9b1717582", size = 81340, upload-time = "2025-11-17T15:38:05.594Z" }, +] + +[[package]] +name = "googleapis-common-protos" +version = "1.72.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "protobuf" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e5/7b/adfd75544c415c487b33061fe7ae526165241c1ea133f9a9125a56b39fd8/googleapis_common_protos-1.72.0.tar.gz", hash = "sha256:e55a601c1b32b52d7a3e65f43563e2aa61bcd737998ee672ac9b951cd49319f5", size = 147433, upload-time = "2025-11-06T18:29:24.087Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c4/ab/09169d5a4612a5f92490806649ac8d41e3ec9129c636754575b3553f4ea4/googleapis_common_protos-1.72.0-py3-none-any.whl", hash = "sha256:4299c5a82d5ae1a9702ada957347726b167f9f8d1fc352477702a1e851ff4038", size = 297515, upload-time = "2025-11-06T18:29:13.14Z" }, +] + +[package.optional-dependencies] +grpc = [ + { name = "grpcio" }, +] + +[[package]] +name = "grpc-google-iam-v1" +version = "0.14.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "googleapis-common-protos", extra = ["grpc"] }, + { name = "grpcio" }, + { name = "protobuf" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/76/1e/1011451679a983f2f5c6771a1682542ecb027776762ad031fd0d7129164b/grpc_google_iam_v1-0.14.3.tar.gz", hash = "sha256:879ac4ef33136c5491a6300e27575a9ec760f6cdf9a2518798c1b8977a5dc389", size = 23745, upload-time = "2025-10-15T21:14:53.318Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4a/bd/330a1bbdb1afe0b96311249e699b6dc9cfc17916394fd4503ac5aca2514b/grpc_google_iam_v1-0.14.3-py3-none-any.whl", hash = "sha256:7a7f697e017a067206a3dfef44e4c634a34d3dee135fe7d7a4613fe3e59217e6", size = 32690, upload-time = "2025-10-15T21:14:51.72Z" }, +] + +[[package]] +name = "grpcio" +version = "1.76.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b6/e0/318c1ce3ae5a17894d5791e87aea147587c9e702f24122cc7a5c8bbaeeb1/grpcio-1.76.0.tar.gz", hash = "sha256:7be78388d6da1a25c0d5ec506523db58b18be22d9c37d8d3a32c08be4987bd73", size = 12785182, upload-time = "2025-10-21T16:23:12.106Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bf/05/8e29121994b8d959ffa0afd28996d452f291b48cfc0875619de0bde2c50c/grpcio-1.76.0-cp312-cp312-linux_armv7l.whl", hash = "sha256:81fd9652b37b36f16138611c7e884eb82e0cec137c40d3ef7c3f9b3ed00f6ed8", size = 5799718, upload-time = "2025-10-21T16:21:17.939Z" }, + { url = "https://files.pythonhosted.org/packages/d9/75/11d0e66b3cdf998c996489581bdad8900db79ebd83513e45c19548f1cba4/grpcio-1.76.0-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:04bbe1bfe3a68bbfd4e52402ab7d4eb59d72d02647ae2042204326cf4bbad280", size = 11825627, upload-time = "2025-10-21T16:21:20.466Z" }, + { url = "https://files.pythonhosted.org/packages/28/50/2f0aa0498bc188048f5d9504dcc5c2c24f2eb1a9337cd0fa09a61a2e75f0/grpcio-1.76.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d388087771c837cdb6515539f43b9d4bf0b0f23593a24054ac16f7a960be16f4", size = 6359167, upload-time = "2025-10-21T16:21:23.122Z" }, + { url = "https://files.pythonhosted.org/packages/66/e5/bbf0bb97d29ede1d59d6588af40018cfc345b17ce979b7b45424628dc8bb/grpcio-1.76.0-cp312-cp312-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:9f8f757bebaaea112c00dba718fc0d3260052ce714e25804a03f93f5d1c6cc11", size = 7044267, upload-time = "2025-10-21T16:21:25.995Z" }, + { url = "https://files.pythonhosted.org/packages/f5/86/f6ec2164f743d9609691115ae8ece098c76b894ebe4f7c94a655c6b03e98/grpcio-1.76.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:980a846182ce88c4f2f7e2c22c56aefd515daeb36149d1c897f83cf57999e0b6", size = 6573963, upload-time = "2025-10-21T16:21:28.631Z" }, + { url = "https://files.pythonhosted.org/packages/60/bc/8d9d0d8505feccfdf38a766d262c71e73639c165b311c9457208b56d92ae/grpcio-1.76.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f92f88e6c033db65a5ae3d97905c8fea9c725b63e28d5a75cb73b49bda5024d8", size = 7164484, upload-time = "2025-10-21T16:21:30.837Z" }, + { url = "https://files.pythonhosted.org/packages/67/e6/5d6c2fc10b95edf6df9b8f19cf10a34263b7fd48493936fffd5085521292/grpcio-1.76.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:4baf3cbe2f0be3289eb68ac8ae771156971848bb8aaff60bad42005539431980", size = 8127777, upload-time = "2025-10-21T16:21:33.577Z" }, + { url = "https://files.pythonhosted.org/packages/3f/c8/dce8ff21c86abe025efe304d9e31fdb0deaaa3b502b6a78141080f206da0/grpcio-1.76.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:615ba64c208aaceb5ec83bfdce7728b80bfeb8be97562944836a7a0a9647d882", size = 7594014, upload-time = "2025-10-21T16:21:41.882Z" }, + { url = "https://files.pythonhosted.org/packages/e0/42/ad28191ebf983a5d0ecef90bab66baa5a6b18f2bfdef9d0a63b1973d9f75/grpcio-1.76.0-cp312-cp312-win32.whl", hash = "sha256:45d59a649a82df5718fd9527ce775fd66d1af35e6d31abdcdc906a49c6822958", size = 3984750, upload-time = "2025-10-21T16:21:44.006Z" }, + { url = "https://files.pythonhosted.org/packages/9e/00/7bd478cbb851c04a48baccaa49b75abaa8e4122f7d86da797500cccdd771/grpcio-1.76.0-cp312-cp312-win_amd64.whl", hash = "sha256:c088e7a90b6017307f423efbb9d1ba97a22aa2170876223f9709e9d1de0b5347", size = 4704003, upload-time = "2025-10-21T16:21:46.244Z" }, +] + +[[package]] +name = "grpcio-status" +version = "1.76.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "googleapis-common-protos" }, + { name = "grpcio" }, + { name = "protobuf" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3f/46/e9f19d5be65e8423f886813a2a9d0056ba94757b0c5007aa59aed1a961fa/grpcio_status-1.76.0.tar.gz", hash = "sha256:25fcbfec74c15d1a1cb5da3fab8ee9672852dc16a5a9eeb5baf7d7a9952943cd", size = 13679, upload-time = "2025-10-21T16:28:52.545Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8c/cc/27ba60ad5a5f2067963e6a858743500df408eb5855e98be778eaef8c9b02/grpcio_status-1.76.0-py3-none-any.whl", hash = "sha256:380568794055a8efbbd8871162df92012e0228a5f6dffaf57f2a00c534103b18", size = 14425, upload-time = "2025-10-21T16:28:40.853Z" }, +] + +[[package]] +name = "idna" +version = "3.11" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/0703ccc57f3a7233505399edb88de3cbd678da106337b9fcde432b65ed60/idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902", size = 194582, upload-time = "2025-10-12T14:55:20.501Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" }, +] + +[[package]] +name = "iniconfig" +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" }, +] + +[[package]] +name = "isodate" +version = "0.7.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/54/4d/e940025e2ce31a8ce1202635910747e5a87cc3a6a6bb2d00973375014749/isodate-0.7.2.tar.gz", hash = "sha256:4cd1aa0f43ca76f4a6c6c0292a85f40b35ec2e43e315b59f06e6d32171a953e6", size = 29705, upload-time = "2024-10-08T23:04:11.5Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/15/aa/0aca39a37d3c7eb941ba736ede56d689e7be91cab5d9ca846bde3999eba6/isodate-0.7.2-py3-none-any.whl", hash = "sha256:28009937d8031054830160fce6d409ed342816b543597cece116d966c6d99e15", size = 22320, upload-time = "2024-10-08T23:04:09.501Z" }, +] + +[[package]] +name = "isort" +version = "7.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/63/53/4f3c058e3bace40282876f9b553343376ee687f3c35a525dc79dbd450f88/isort-7.0.0.tar.gz", hash = "sha256:5513527951aadb3ac4292a41a16cbc50dd1642432f5e8c20057d414bdafb4187", size = 805049, upload-time = "2025-10-11T13:30:59.107Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7f/ed/e3705d6d02b4f7aea715a353c8ce193efd0b5db13e204df895d38734c244/isort-7.0.0-py3-none-any.whl", hash = "sha256:1bcabac8bc3c36c7fb7b98a76c8abb18e0f841a3ba81decac7691008592499c1", size = 94672, upload-time = "2025-10-11T13:30:57.665Z" }, +] + +[[package]] +name = "jmespath" +version = "1.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/00/2a/e867e8531cf3e36b41201936b7fa7ba7b5702dbef42922193f05c8976cd6/jmespath-1.0.1.tar.gz", hash = "sha256:90261b206d6defd58fdd5e85f478bf633a2901798906be2ad389150c5c60edbe", size = 25843, upload-time = "2022-06-17T18:00:12.224Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/31/b4/b9b800c45527aadd64d5b442f9b932b00648617eb5d63d2c7a6587b7cafc/jmespath-1.0.1-py3-none-any.whl", hash = "sha256:02e2e4cc71b5bcab88332eebf907519190dd9e6e82107fa7f83b1003a6252980", size = 20256, upload-time = "2022-06-17T18:00:10.251Z" }, +] + +[[package]] +name = "librt" +version = "0.7.7" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b7/29/47f29026ca17f35cf299290292d5f8331f5077364974b7675a353179afa2/librt-0.7.7.tar.gz", hash = "sha256:81d957b069fed1890953c3b9c3895c7689960f233eea9a1d9607f71ce7f00b2c", size = 145910, upload-time = "2026-01-01T23:52:22.87Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/56/72/1cd9d752070011641e8aee046c851912d5f196ecd726fffa7aed2070f3e0/librt-0.7.7-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2a85a1fc4ed11ea0eb0a632459ce004a2d14afc085a50ae3463cd3dfe1ce43fc", size = 55687, upload-time = "2026-01-01T23:51:16.291Z" }, + { url = "https://files.pythonhosted.org/packages/50/aa/d5a1d4221c4fe7e76ae1459d24d6037783cb83c7645164c07d7daf1576ec/librt-0.7.7-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c87654e29a35938baead1c4559858f346f4a2a7588574a14d784f300ffba0efd", size = 57136, upload-time = "2026-01-01T23:51:17.363Z" }, + { url = "https://files.pythonhosted.org/packages/23/6f/0c86b5cb5e7ef63208c8cc22534df10ecc5278efc0d47fb8815577f3ca2f/librt-0.7.7-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:c9faaebb1c6212c20afd8043cd6ed9de0a47d77f91a6b5b48f4e46ed470703fe", size = 165320, upload-time = "2026-01-01T23:51:18.455Z" }, + { url = "https://files.pythonhosted.org/packages/16/37/df4652690c29f645ffe405b58285a4109e9fe855c5bb56e817e3e75840b3/librt-0.7.7-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1908c3e5a5ef86b23391448b47759298f87f997c3bd153a770828f58c2bb4630", size = 174216, upload-time = "2026-01-01T23:51:19.599Z" }, + { url = "https://files.pythonhosted.org/packages/9a/d6/d3afe071910a43133ec9c0f3e4ce99ee6df0d4e44e4bddf4b9e1c6ed41cc/librt-0.7.7-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dbc4900e95a98fc0729523be9d93a8fedebb026f32ed9ffc08acd82e3e181503", size = 189005, upload-time = "2026-01-01T23:51:21.052Z" }, + { url = "https://files.pythonhosted.org/packages/d5/18/74060a870fe2d9fd9f47824eba6717ce7ce03124a0d1e85498e0e7efc1b2/librt-0.7.7-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a7ea4e1fbd253e5c68ea0fe63d08577f9d288a73f17d82f652ebc61fa48d878d", size = 183961, upload-time = "2026-01-01T23:51:22.493Z" }, + { url = "https://files.pythonhosted.org/packages/7c/5e/918a86c66304af66a3c1d46d54df1b2d0b8894babc42a14fb6f25511497f/librt-0.7.7-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:ef7699b7a5a244b1119f85c5bbc13f152cd38240cbb2baa19b769433bae98e50", size = 177610, upload-time = "2026-01-01T23:51:23.874Z" }, + { url = "https://files.pythonhosted.org/packages/b2/d7/b5e58dc2d570f162e99201b8c0151acf40a03a39c32ab824dd4febf12736/librt-0.7.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:955c62571de0b181d9e9e0a0303c8bc90d47670a5eff54cf71bf5da61d1899cf", size = 199272, upload-time = "2026-01-01T23:51:25.341Z" }, + { url = "https://files.pythonhosted.org/packages/18/87/8202c9bd0968bdddc188ec3811985f47f58ed161b3749299f2c0dd0f63fb/librt-0.7.7-cp312-cp312-win32.whl", hash = "sha256:1bcd79be209313b270b0e1a51c67ae1af28adad0e0c7e84c3ad4b5cb57aaa75b", size = 43189, upload-time = "2026-01-01T23:51:26.799Z" }, + { url = "https://files.pythonhosted.org/packages/61/8d/80244b267b585e7aa79ffdac19f66c4861effc3a24598e77909ecdd0850e/librt-0.7.7-cp312-cp312-win_amd64.whl", hash = "sha256:4353ee891a1834567e0302d4bd5e60f531912179578c36f3d0430f8c5e16b456", size = 49462, upload-time = "2026-01-01T23:51:27.813Z" }, + { url = "https://files.pythonhosted.org/packages/2d/1f/75db802d6a4992d95e8a889682601af9b49d5a13bbfa246d414eede1b56c/librt-0.7.7-cp312-cp312-win_arm64.whl", hash = "sha256:a76f1d679beccccdf8c1958e732a1dfcd6e749f8821ee59d7bec009ac308c029", size = 42828, upload-time = "2026-01-01T23:51:28.804Z" }, +] + +[[package]] +name = "llvmlite" +version = "0.46.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/74/cd/08ae687ba099c7e3d21fe2ea536500563ef1943c5105bf6ab4ee3829f68e/llvmlite-0.46.0.tar.gz", hash = "sha256:227c9fd6d09dce2783c18b754b7cd9d9b3b3515210c46acc2d3c5badd9870ceb", size = 193456, upload-time = "2025-12-08T18:15:36.295Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2b/f8/4db016a5e547d4e054ff2f3b99203d63a497465f81ab78ec8eb2ff7b2304/llvmlite-0.46.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6b9588ad4c63b4f0175a3984b85494f0c927c6b001e3a246a3a7fb3920d9a137", size = 37232767, upload-time = "2025-12-08T18:15:00.737Z" }, + { url = "https://files.pythonhosted.org/packages/aa/85/4890a7c14b4fa54400945cb52ac3cd88545bbdb973c440f98ca41591cdc5/llvmlite-0.46.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3535bd2bb6a2d7ae4012681ac228e5132cdb75fefb1bcb24e33f2f3e0c865ed4", size = 56275176, upload-time = "2025-12-08T18:15:03.936Z" }, + { url = "https://files.pythonhosted.org/packages/6a/07/3d31d39c1a1a08cd5337e78299fca77e6aebc07c059fbd0033e3edfab45c/llvmlite-0.46.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4cbfd366e60ff87ea6cc62f50bc4cd800ebb13ed4c149466f50cf2163a473d1e", size = 55128630, upload-time = "2025-12-08T18:15:07.196Z" }, + { url = "https://files.pythonhosted.org/packages/2a/6b/d139535d7590a1bba1ceb68751bef22fadaa5b815bbdf0e858e3875726b2/llvmlite-0.46.0-cp312-cp312-win_amd64.whl", hash = "sha256:398b39db462c39563a97b912d4f2866cd37cba60537975a09679b28fbbc0fb38", size = 38138940, upload-time = "2025-12-08T18:15:10.162Z" }, +] + +[[package]] +name = "locket" +version = "1.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2f/83/97b29fe05cb6ae28d2dbd30b81e2e402a3eed5f460c26e9eaa5895ceacf5/locket-1.0.0.tar.gz", hash = "sha256:5c0d4c052a8bbbf750e056a8e65ccd309086f4f0f18a2eac306a8dfa4112a632", size = 4350, upload-time = "2022-04-20T22:04:44.312Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/db/bc/83e112abc66cd466c6b83f99118035867cecd41802f8d044638aa78a106e/locket-1.0.0-py2.py3-none-any.whl", hash = "sha256:b6c819a722f7b6bd955b80781788e4a66a55628b858d347536b7e81325a3a5e3", size = 4398, upload-time = "2022-04-20T22:04:42.23Z" }, +] + +[[package]] +name = "markdown-it-py" +version = "4.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mdurl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5b/f5/4ec618ed16cc4f8fb3b701563655a69816155e79e24a17b651541804721d/markdown_it_py-4.0.0.tar.gz", hash = "sha256:cb0a2b4aa34f932c007117b194e945bd74e0ec24133ceb5bac59009cda1cb9f3", size = 73070, upload-time = "2025-08-11T12:57:52.854Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/94/54/e7d793b573f298e1c9013b8c4dade17d481164aa517d1d7148619c2cedbf/markdown_it_py-4.0.0-py3-none-any.whl", hash = "sha256:87327c59b172c5011896038353a81343b6754500a08cd7a4973bb48c6d578147", size = 87321, upload-time = "2025-08-11T12:57:51.923Z" }, +] + +[[package]] +name = "mdio-cpp-dev" +version = "0.1.0" +source = { virtual = "." } +dependencies = [ + { name = "multidimio", extra = ["cloud"] }, +] + +[package.optional-dependencies] +dev = [ + { name = "black" }, + { name = "cpplint" }, + { name = "isort" }, + { name = "mypy" }, + { name = "pytest" }, + { name = "ruff" }, + { name = "yapf" }, +] + +[package.dev-dependencies] +dev = [ + { name = "black" }, + { name = "cpplint" }, + { name = "isort" }, + { name = "mypy" }, + { name = "pytest" }, + { name = "ruff" }, + { name = "yapf" }, +] + +[package.metadata] +requires-dist = [ + { name = "black", marker = "extra == 'dev'", specifier = ">=24.3" }, + { name = "cpplint", marker = "extra == 'dev'", specifier = "==1.6.1" }, + { name = "isort", marker = "extra == 'dev'", specifier = ">=5.0" }, + { name = "multidimio", extras = ["cloud"], specifier = "==1.1.4.dev1781540584", index = "https://test.pypi.org/simple/" }, + { name = "mypy", marker = "extra == 'dev'", specifier = ">=1.5" }, + { name = "pytest", marker = "extra == 'dev'", specifier = ">=7.0" }, + { name = "ruff", marker = "extra == 'dev'", specifier = ">=0.8" }, + { name = "yapf", marker = "extra == 'dev'", specifier = ">=0.40" }, +] +provides-extras = ["dev"] + +[package.metadata.requires-dev] +dev = [ + { name = "black", specifier = ">=24.3" }, + { name = "cpplint", specifier = "==1.6.1" }, + { name = "isort", specifier = ">=5.0" }, + { name = "mypy", specifier = ">=1.5" }, + { name = "pytest", specifier = ">=7.0" }, + { name = "ruff", specifier = ">=0.8" }, + { name = "yapf", specifier = ">=0.40" }, +] + +[[package]] +name = "mdurl" +version = "0.1.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729, upload-time = "2022-08-14T12:40:10.846Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" }, +] + +[[package]] +name = "msal" +version = "1.34.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cryptography" }, + { name = "pyjwt", extra = ["crypto"] }, + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cf/0e/c857c46d653e104019a84f22d4494f2119b4fe9f896c92b4b864b3b045cc/msal-1.34.0.tar.gz", hash = "sha256:76ba83b716ea5a6d75b0279c0ac353a0e05b820ca1f6682c0eb7f45190c43c2f", size = 153961, upload-time = "2025-09-22T23:05:48.989Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c2/dc/18d48843499e278538890dc709e9ee3dea8375f8be8e82682851df1b48b5/msal-1.34.0-py3-none-any.whl", hash = "sha256:f669b1644e4950115da7a176441b0e13ec2975c29528d8b9e81316023676d6e1", size = 116987, upload-time = "2025-09-22T23:05:47.294Z" }, +] + +[[package]] +name = "msal-extensions" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "msal" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/01/99/5d239b6156eddf761a636bded1118414d161bd6b7b37a9335549ed159396/msal_extensions-1.3.1.tar.gz", hash = "sha256:c5b0fd10f65ef62b5f1d62f4251d51cbcaf003fcedae8c91b040a488614be1a4", size = 23315, upload-time = "2025-03-14T23:51:03.902Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5e/75/bd9b7bb966668920f06b200e84454c8f3566b102183bc55c5473d96cb2b9/msal_extensions-1.3.1-py3-none-any.whl", hash = "sha256:96d3de4d034504e969ac5e85bae8106c8373b5c6568e4c8fa7af2eca9dbe6bca", size = 20583, upload-time = "2025-03-14T23:51:03.016Z" }, +] + +[[package]] +name = "multidict" +version = "6.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/80/1e/5492c365f222f907de1039b91f922b93fa4f764c713ee858d235495d8f50/multidict-6.7.0.tar.gz", hash = "sha256:c6e99d9a65ca282e578dfea819cfa9c0a62b2499d8677392e09feaf305e9e6f5", size = 101834, upload-time = "2025-10-06T14:52:30.657Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c2/9e/9f61ac18d9c8b475889f32ccfa91c9f59363480613fc807b6e3023d6f60b/multidict-6.7.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:8a3862568a36d26e650a19bb5cbbba14b71789032aebc0423f8cc5f150730184", size = 76877, upload-time = "2025-10-06T14:49:20.884Z" }, + { url = "https://files.pythonhosted.org/packages/38/6f/614f09a04e6184f8824268fce4bc925e9849edfa654ddd59f0b64508c595/multidict-6.7.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:960c60b5849b9b4f9dcc9bea6e3626143c252c74113df2c1540aebce70209b45", size = 45467, upload-time = "2025-10-06T14:49:22.054Z" }, + { url = "https://files.pythonhosted.org/packages/b3/93/c4f67a436dd026f2e780c433277fff72be79152894d9fc36f44569cab1a6/multidict-6.7.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2049be98fb57a31b4ccf870bf377af2504d4ae35646a19037ec271e4c07998aa", size = 43834, upload-time = "2025-10-06T14:49:23.566Z" }, + { url = "https://files.pythonhosted.org/packages/7f/f5/013798161ca665e4a422afbc5e2d9e4070142a9ff8905e482139cd09e4d0/multidict-6.7.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:0934f3843a1860dd465d38895c17fce1f1cb37295149ab05cd1b9a03afacb2a7", size = 250545, upload-time = "2025-10-06T14:49:24.882Z" }, + { url = "https://files.pythonhosted.org/packages/71/2f/91dbac13e0ba94669ea5119ba267c9a832f0cb65419aca75549fcf09a3dc/multidict-6.7.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b3e34f3a1b8131ba06f1a73adab24f30934d148afcd5f5de9a73565a4404384e", size = 258305, upload-time = "2025-10-06T14:49:26.778Z" }, + { url = "https://files.pythonhosted.org/packages/ef/b0/754038b26f6e04488b48ac621f779c341338d78503fb45403755af2df477/multidict-6.7.0-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:efbb54e98446892590dc2458c19c10344ee9a883a79b5cec4bc34d6656e8d546", size = 242363, upload-time = "2025-10-06T14:49:28.562Z" }, + { url = "https://files.pythonhosted.org/packages/87/15/9da40b9336a7c9fa606c4cf2ed80a649dffeb42b905d4f63a1d7eb17d746/multidict-6.7.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a35c5fc61d4f51eb045061e7967cfe3123d622cd500e8868e7c0c592a09fedc4", size = 268375, upload-time = "2025-10-06T14:49:29.96Z" }, + { url = "https://files.pythonhosted.org/packages/82/72/c53fcade0cc94dfaad583105fd92b3a783af2091eddcb41a6d5a52474000/multidict-6.7.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:29fe6740ebccba4175af1b9b87bf553e9c15cd5868ee967e010efcf94e4fd0f1", size = 269346, upload-time = "2025-10-06T14:49:31.404Z" }, + { url = "https://files.pythonhosted.org/packages/0d/e2/9baffdae21a76f77ef8447f1a05a96ec4bc0a24dae08767abc0a2fe680b8/multidict-6.7.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:123e2a72e20537add2f33a79e605f6191fba2afda4cbb876e35c1a7074298a7d", size = 256107, upload-time = "2025-10-06T14:49:32.974Z" }, + { url = "https://files.pythonhosted.org/packages/3c/06/3f06f611087dc60d65ef775f1fb5aca7c6d61c6db4990e7cda0cef9b1651/multidict-6.7.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b284e319754366c1aee2267a2036248b24eeb17ecd5dc16022095e747f2f4304", size = 253592, upload-time = "2025-10-06T14:49:34.52Z" }, + { url = "https://files.pythonhosted.org/packages/20/24/54e804ec7945b6023b340c412ce9c3f81e91b3bf5fa5ce65558740141bee/multidict-6.7.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:803d685de7be4303b5a657b76e2f6d1240e7e0a8aa2968ad5811fa2285553a12", size = 251024, upload-time = "2025-10-06T14:49:35.956Z" }, + { url = "https://files.pythonhosted.org/packages/14/48/011cba467ea0b17ceb938315d219391d3e421dfd35928e5dbdc3f4ae76ef/multidict-6.7.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:c04a328260dfd5db8c39538f999f02779012268f54614902d0afc775d44e0a62", size = 251484, upload-time = "2025-10-06T14:49:37.631Z" }, + { url = "https://files.pythonhosted.org/packages/0d/2f/919258b43bb35b99fa127435cfb2d91798eb3a943396631ef43e3720dcf4/multidict-6.7.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:8a19cdb57cd3df4cd865849d93ee14920fb97224300c88501f16ecfa2604b4e0", size = 263579, upload-time = "2025-10-06T14:49:39.502Z" }, + { url = "https://files.pythonhosted.org/packages/31/22/a0e884d86b5242b5a74cf08e876bdf299e413016b66e55511f7a804a366e/multidict-6.7.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:9b2fd74c52accced7e75de26023b7dccee62511a600e62311b918ec5c168fc2a", size = 259654, upload-time = "2025-10-06T14:49:41.32Z" }, + { url = "https://files.pythonhosted.org/packages/b2/e5/17e10e1b5c5f5a40f2fcbb45953c9b215f8a4098003915e46a93f5fcaa8f/multidict-6.7.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3e8bfdd0e487acf992407a140d2589fe598238eaeffa3da8448d63a63cd363f8", size = 251511, upload-time = "2025-10-06T14:49:46.021Z" }, + { url = "https://files.pythonhosted.org/packages/e3/9a/201bb1e17e7af53139597069c375e7b0dcbd47594604f65c2d5359508566/multidict-6.7.0-cp312-cp312-win32.whl", hash = "sha256:dd32a49400a2c3d52088e120ee00c1e3576cbff7e10b98467962c74fdb762ed4", size = 41895, upload-time = "2025-10-06T14:49:48.718Z" }, + { url = "https://files.pythonhosted.org/packages/46/e2/348cd32faad84eaf1d20cce80e2bb0ef8d312c55bca1f7fa9865e7770aaf/multidict-6.7.0-cp312-cp312-win_amd64.whl", hash = "sha256:92abb658ef2d7ef22ac9f8bb88e8b6c3e571671534e029359b6d9e845923eb1b", size = 46073, upload-time = "2025-10-06T14:49:50.28Z" }, + { url = "https://files.pythonhosted.org/packages/25/ec/aad2613c1910dce907480e0c3aa306905830f25df2e54ccc9dea450cb5aa/multidict-6.7.0-cp312-cp312-win_arm64.whl", hash = "sha256:490dab541a6a642ce1a9d61a4781656b346a55c13038f0b1244653828e3a83ec", size = 43226, upload-time = "2025-10-06T14:49:52.304Z" }, + { url = "https://files.pythonhosted.org/packages/b7/da/7d22601b625e241d4f23ef1ebff8acfc60da633c9e7e7922e24d10f592b3/multidict-6.7.0-py3-none-any.whl", hash = "sha256:394fc5c42a333c9ffc3e421a4c85e08580d990e08b99f6bf35b4132114c5dcb3", size = 12317, upload-time = "2025-10-06T14:52:29.272Z" }, +] + +[[package]] +name = "multidimio" +version = "1.1.4.dev1781540584" +source = { registry = "https://test.pypi.org/simple/" } +dependencies = [ + { name = "click" }, + { name = "click-params" }, + { name = "dask" }, + { name = "fsspec" }, + { name = "pint" }, + { name = "psutil" }, + { name = "pydantic" }, + { name = "pydantic-settings" }, + { name = "rich" }, + { name = "segy" }, + { name = "tqdm" }, + { name = "universal-pathlib" }, + { name = "xarray" }, + { name = "zarr" }, +] +sdist = { url = "https://test-files.pythonhosted.org/packages/d1/00/ab6949b581b2f1198872e9afbba56e8e43c48b3c1e8519dc3447fd24ef21/multidimio-1.1.4.dev1781540584.tar.gz", hash = "sha256:0649c10c3e2b8252df5138f347e339122623272f116363fba92fc89b08c29c0c", size = 93962, upload-time = "2026-06-15T16:23:14.889Z" } +wheels = [ + { url = "https://test-files.pythonhosted.org/packages/21/08/7ef145b21cbf41d85391dd612cc13a9eae0b16d69ff75c7a7a5227a0f2e8/multidimio-1.1.4.dev1781540584-py3-none-any.whl", hash = "sha256:e6668ebfc4df97b85a47102897d048d5d33380d851adbfbad431e47fe4183e5f", size = 135750, upload-time = "2026-06-15T16:23:12.867Z" }, +] + +[package.optional-dependencies] +cloud = [ + { name = "adlfs" }, + { name = "gcsfs" }, + { name = "s3fs" }, +] + +[[package]] +name = "mypy" +version = "1.19.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "librt", marker = "platform_python_implementation != 'PyPy'" }, + { name = "mypy-extensions" }, + { name = "pathspec" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f5/db/4efed9504bc01309ab9c2da7e352cc223569f05478012b5d9ece38fd44d2/mypy-1.19.1.tar.gz", hash = "sha256:19d88bb05303fe63f71dd2c6270daca27cb9401c4ca8255fe50d1d920e0eb9ba", size = 3582404, upload-time = "2025-12-15T05:03:48.42Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/06/8a/19bfae96f6615aa8a0604915512e0289b1fad33d5909bf7244f02935d33a/mypy-1.19.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a8174a03289288c1f6c46d55cef02379b478bfbc8e358e02047487cad44c6ca1", size = 13206053, upload-time = "2025-12-15T05:03:46.622Z" }, + { url = "https://files.pythonhosted.org/packages/a5/34/3e63879ab041602154ba2a9f99817bb0c85c4df19a23a1443c8986e4d565/mypy-1.19.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ffcebe56eb09ff0c0885e750036a095e23793ba6c2e894e7e63f6d89ad51f22e", size = 12219134, upload-time = "2025-12-15T05:03:24.367Z" }, + { url = "https://files.pythonhosted.org/packages/89/cc/2db6f0e95366b630364e09845672dbee0cbf0bbe753a204b29a944967cd9/mypy-1.19.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b64d987153888790bcdb03a6473d321820597ab8dd9243b27a92153c4fa50fd2", size = 12731616, upload-time = "2025-12-15T05:02:44.725Z" }, + { url = "https://files.pythonhosted.org/packages/00/be/dd56c1fd4807bc1eba1cf18b2a850d0de7bacb55e158755eb79f77c41f8e/mypy-1.19.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c35d298c2c4bba75feb2195655dfea8124d855dfd7343bf8b8c055421eaf0cf8", size = 13620847, upload-time = "2025-12-15T05:03:39.633Z" }, + { url = "https://files.pythonhosted.org/packages/6d/42/332951aae42b79329f743bf1da088cd75d8d4d9acc18fbcbd84f26c1af4e/mypy-1.19.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:34c81968774648ab5ac09c29a375fdede03ba253f8f8287847bd480782f73a6a", size = 13834976, upload-time = "2025-12-15T05:03:08.786Z" }, + { url = "https://files.pythonhosted.org/packages/6f/63/e7493e5f90e1e085c562bb06e2eb32cae27c5057b9653348d38b47daaecc/mypy-1.19.1-cp312-cp312-win_amd64.whl", hash = "sha256:b10e7c2cd7870ba4ad9b2d8a6102eb5ffc1f16ca35e3de6bfa390c1113029d13", size = 10118104, upload-time = "2025-12-15T05:03:10.834Z" }, + { url = "https://files.pythonhosted.org/packages/8d/f4/4ce9a05ce5ded1de3ec1c1d96cf9f9504a04e54ce0ed55cfa38619a32b8d/mypy-1.19.1-py3-none-any.whl", hash = "sha256:f1235f5ea01b7db5468d53ece6aaddf1ad0b88d9e7462b86ef96fe04995d7247", size = 2471239, upload-time = "2025-12-15T05:03:07.248Z" }, +] + +[[package]] +name = "mypy-extensions" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/6e/371856a3fb9d31ca8dac321cda606860fa4548858c0cc45d9d1d4ca2628b/mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558", size = 6343, upload-time = "2025-04-22T14:54:24.164Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963, upload-time = "2025-04-22T14:54:22.983Z" }, +] + +[[package]] +name = "numba" +version = "0.63.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "llvmlite" }, + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/dc/60/0145d479b2209bd8fdae5f44201eceb8ce5a23e0ed54c71f57db24618665/numba-0.63.1.tar.gz", hash = "sha256:b320aa675d0e3b17b40364935ea52a7b1c670c9037c39cf92c49502a75902f4b", size = 2761666, upload-time = "2025-12-10T02:57:39.002Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/14/9c/c0974cd3d00ff70d30e8ff90522ba5fbb2bcee168a867d2321d8d0457676/numba-0.63.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2819cd52afa5d8d04e057bdfd54367575105f8829350d8fb5e4066fb7591cc71", size = 2680981, upload-time = "2025-12-10T02:57:17.579Z" }, + { url = "https://files.pythonhosted.org/packages/cb/70/ea2bc45205f206b7a24ee68a159f5097c9ca7e6466806e7c213587e0c2b1/numba-0.63.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5cfd45dbd3d409e713b1ccfdc2ee72ca82006860254429f4ef01867fdba5845f", size = 3801656, upload-time = "2025-12-10T02:57:19.106Z" }, + { url = "https://files.pythonhosted.org/packages/0d/82/4f4ba4fd0f99825cbf3cdefd682ca3678be1702b63362011de6e5f71f831/numba-0.63.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:69a599df6976c03b7ecf15d05302696f79f7e6d10d620367407517943355bcb0", size = 3501857, upload-time = "2025-12-10T02:57:20.721Z" }, + { url = "https://files.pythonhosted.org/packages/af/fd/6540456efa90b5f6604a86ff50dabefb187e43557e9081adcad3be44f048/numba-0.63.1-cp312-cp312-win_amd64.whl", hash = "sha256:bbad8c63e4fc7eb3cdb2c2da52178e180419f7969f9a685f283b313a70b92af3", size = 2750282, upload-time = "2025-12-10T02:57:22.474Z" }, +] + +[[package]] +name = "numcodecs" +version = "0.16.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/44/bd/8a391e7c356366224734efd24da929cc4796fff468bfb179fe1af6548535/numcodecs-0.16.5.tar.gz", hash = "sha256:0d0fb60852f84c0bd9543cc4d2ab9eefd37fc8efcc410acd4777e62a1d300318", size = 6276387, upload-time = "2025-11-21T02:49:48.986Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/75/cc/55420f3641a67f78392dc0bc5d02cb9eb0a9dcebf2848d1ac77253ca61fa/numcodecs-0.16.5-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:24e675dc8d1550cd976a99479b87d872cb142632c75cc402fea04c08c4898523", size = 1656287, upload-time = "2025-11-21T02:49:25.755Z" }, + { url = "https://files.pythonhosted.org/packages/f5/6c/86644987505dcb90ba6d627d6989c27bafb0699f9fd00187e06d05ea8594/numcodecs-0.16.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:94ddfa4341d1a3ab99989d13b01b5134abb687d3dab2ead54b450aefe4ad5bd6", size = 1148899, upload-time = "2025-11-21T02:49:26.87Z" }, + { url = "https://files.pythonhosted.org/packages/97/1e/98aaddf272552d9fef1f0296a9939d1487914a239e98678f6b20f8b0a5c8/numcodecs-0.16.5-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b554ab9ecf69de7ca2b6b5e8bc696bd9747559cb4dd5127bd08d7a28bec59c3a", size = 8534814, upload-time = "2025-11-21T02:49:28.547Z" }, + { url = "https://files.pythonhosted.org/packages/fb/53/78c98ef5c8b2b784453487f3e4d6c017b20747c58b470393e230c78d18e8/numcodecs-0.16.5-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ad1a379a45bd3491deab8ae6548313946744f868c21d5340116977ea3be5b1d6", size = 9173471, upload-time = "2025-11-21T02:49:30.444Z" }, + { url = "https://files.pythonhosted.org/packages/1c/20/2fdec87fc7f8cec950d2b0bea603c12dc9f05b4966dc5924ba5a36a61bf6/numcodecs-0.16.5-cp312-cp312-win_amd64.whl", hash = "sha256:845a9857886ffe4a3172ba1c537ae5bcc01e65068c31cf1fce1a844bd1da050f", size = 801412, upload-time = "2025-11-21T02:49:32.123Z" }, +] + +[[package]] +name = "numpy" +version = "2.3.5" +source = { registry = "https://pypi.org/simple" } +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/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" }, +] + +[[package]] +name = "oauthlib" +version = "3.3.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0b/5f/19930f824ffeb0ad4372da4812c50edbd1434f678c90c2733e1188edfc63/oauthlib-3.3.1.tar.gz", hash = "sha256:0f0f8aa759826a193cf66c12ea1af1637f87b9b4622d46e866952bb022e538c9", size = 185918, upload-time = "2025-06-19T22:48:08.269Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/be/9c/92789c596b8df838baa98fa71844d84283302f7604ed565dafe5a6b5041a/oauthlib-3.3.1-py3-none-any.whl", hash = "sha256:88119c938d2b8fb88561af5f6ee0eec8cc8d552b7bb1f712743136eb7523b7a1", size = 160065, upload-time = "2025-06-19T22:48:06.508Z" }, +] + +[[package]] +name = "packaging" +version = "25.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727, upload-time = "2025-04-19T11:48:59.673Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" }, +] + +[[package]] +name = "pandas" +version = "2.3.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, + { name = "python-dateutil" }, + { name = "pytz" }, + { name = "tzdata" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/33/01/d40b85317f86cf08d853a4f495195c73815fdf205eef3993821720274518/pandas-2.3.3.tar.gz", hash = "sha256:e05e1af93b977f7eafa636d043f9f94c7ee3ac81af99c13508215942e64c993b", size = 4495223, upload-time = "2025-09-29T23:34:51.853Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9c/fb/231d89e8637c808b997d172b18e9d4a4bc7bf31296196c260526055d1ea0/pandas-2.3.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6d21f6d74eb1725c2efaa71a2bfc661a0689579b58e9c0ca58a739ff0b002b53", size = 11597846, upload-time = "2025-09-29T23:19:48.856Z" }, + { url = "https://files.pythonhosted.org/packages/5c/bd/bf8064d9cfa214294356c2d6702b716d3cf3bb24be59287a6a21e24cae6b/pandas-2.3.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3fd2f887589c7aa868e02632612ba39acb0b8948faf5cc58f0850e165bd46f35", size = 10729618, upload-time = "2025-09-29T23:39:08.659Z" }, + { url = "https://files.pythonhosted.org/packages/57/56/cf2dbe1a3f5271370669475ead12ce77c61726ffd19a35546e31aa8edf4e/pandas-2.3.3-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ecaf1e12bdc03c86ad4a7ea848d66c685cb6851d807a26aa245ca3d2017a1908", size = 11737212, upload-time = "2025-09-29T23:19:59.765Z" }, + { url = "https://files.pythonhosted.org/packages/e5/63/cd7d615331b328e287d8233ba9fdf191a9c2d11b6af0c7a59cfcec23de68/pandas-2.3.3-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b3d11d2fda7eb164ef27ffc14b4fcab16a80e1ce67e9f57e19ec0afaf715ba89", size = 12362693, upload-time = "2025-09-29T23:20:14.098Z" }, + { url = "https://files.pythonhosted.org/packages/a6/de/8b1895b107277d52f2b42d3a6806e69cfef0d5cf1d0ba343470b9d8e0a04/pandas-2.3.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a68e15f780eddf2b07d242e17a04aa187a7ee12b40b930bfdd78070556550e98", size = 12771002, upload-time = "2025-09-29T23:20:26.76Z" }, + { url = "https://files.pythonhosted.org/packages/87/21/84072af3187a677c5893b170ba2c8fbe450a6ff911234916da889b698220/pandas-2.3.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:371a4ab48e950033bcf52b6527eccb564f52dc826c02afd9a1bc0ab731bba084", size = 13450971, upload-time = "2025-09-29T23:20:41.344Z" }, + { url = "https://files.pythonhosted.org/packages/86/41/585a168330ff063014880a80d744219dbf1dd7a1c706e75ab3425a987384/pandas-2.3.3-cp312-cp312-win_amd64.whl", hash = "sha256:a16dcec078a01eeef8ee61bf64074b4e524a2a3f4b3be9326420cabe59c4778b", size = 10992722, upload-time = "2025-09-29T23:20:54.139Z" }, +] + +[[package]] +name = "partd" +version = "1.4.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "locket" }, + { name = "toolz" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b2/3a/3f06f34820a31257ddcabdfafc2672c5816be79c7e353b02c1f318daa7d4/partd-1.4.2.tar.gz", hash = "sha256:d022c33afbdc8405c226621b015e8067888173d85f7f5ecebb3cafed9a20f02c", size = 21029, upload-time = "2024-05-06T19:51:41.945Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/71/e7/40fb618334dcdf7c5a316c0e7343c5cd82d3d866edc100d98e29bc945ecd/partd-1.4.2-py3-none-any.whl", hash = "sha256:978e4ac767ec4ba5b86c6eaa52e5a2a3bc748a2ca839e8cc798f1cc6ce6efb0f", size = 18905, upload-time = "2024-05-06T19:51:39.271Z" }, +] + +[[package]] +name = "pathlib-abc" +version = "0.5.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d6/cb/448649d7f25d228bf0be3a04590ab7afa77f15e056f8fa976ed05ec9a78f/pathlib_abc-0.5.2.tar.gz", hash = "sha256:fcd56f147234645e2c59c7ae22808b34c364bb231f685ddd9f96885aed78a94c", size = 33342, upload-time = "2025-10-10T18:37:20.524Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b1/29/c028a0731e202035f0e2e0bfbf1a3e46ad6c628cbb17f6f1cc9eea5d9ff1/pathlib_abc-0.5.2-py3-none-any.whl", hash = "sha256:4c9d94cf1b23af417ce7c0417b43333b06a106c01000b286c99de230d95eefbb", size = 19070, upload-time = "2025-10-10T18:37:19.437Z" }, +] + +[[package]] +name = "pathspec" +version = "1.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/28/2e/83722ece0f6ee24387d6cb830dd562ddbcd6ce0b9d76072c6849670c31b4/pathspec-1.0.1.tar.gz", hash = "sha256:e2769b508d0dd47b09af6ee2c75b2744a2cb1f474ae4b1494fd6a1b7a841613c", size = 129791, upload-time = "2026-01-06T13:02:55.15Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d2/fe/2257c71721aeab6a6e8aa1f00d01f2a20f58547d249a6c8fef5791f559fc/pathspec-1.0.1-py3-none-any.whl", hash = "sha256:8870061f22c58e6d83463cfce9a7dd6eca0512c772c1001fb09ac64091816721", size = 54584, upload-time = "2026-01-06T13:02:53.601Z" }, +] + +[[package]] +name = "pint" +version = "0.25.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "flexcache" }, + { name = "flexparser" }, + { name = "platformdirs" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5f/74/bc3f671997158aef171194c3c4041e549946f4784b8690baa0626a0a164b/pint-0.25.2.tar.gz", hash = "sha256:85a45d1da8fe9c9f7477fed8aef59ad2b939af3d6611507e1a9cbdacdcd3450a", size = 254467, upload-time = "2025-11-06T22:08:09.184Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ab/88/550d41e81e6d43335603a960cd9c75c1d88f9cf01bc9d4ee8e86290aba7d/pint-0.25.2-py3-none-any.whl", hash = "sha256:ca35ab1d8eeeb6f7d9942b3cb5f34ca42b61cdd5fb3eae79531553dcca04dda7", size = 306762, upload-time = "2025-11-06T22:08:07.745Z" }, +] + +[[package]] +name = "platformdirs" +version = "4.5.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/cf/86/0248f086a84f01b37aaec0fa567b397df1a119f73c16f6c7a9aac73ea309/platformdirs-4.5.1.tar.gz", hash = "sha256:61d5cdcc6065745cdd94f0f878977f8de9437be93de97c1c12f853c9c0cdcbda", size = 21715, upload-time = "2025-12-05T13:52:58.638Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/28/3bfe2fa5a7b9c46fe7e13c97bda14c895fb10fa2ebf1d0abb90e0cea7ee1/platformdirs-4.5.1-py3-none-any.whl", hash = "sha256:d03afa3963c806a9bed9d5125c8f4cb2fdaf74a55ab60e5d59b3fde758104d31", size = 18731, upload-time = "2025-12-05T13:52:56.823Z" }, +] + +[[package]] +name = "pluggy" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, +] + +[[package]] +name = "propcache" +version = "0.4.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9e/da/e9fc233cf63743258bff22b3dfa7ea5baef7b5bc324af47a0ad89b8ffc6f/propcache-0.4.1.tar.gz", hash = "sha256:f48107a8c637e80362555f37ecf49abe20370e557cc4ab374f04ec4423c97c3d", size = 46442, upload-time = "2025-10-08T19:49:02.291Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a2/0f/f17b1b2b221d5ca28b4b876e8bb046ac40466513960646bda8e1853cdfa2/propcache-0.4.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e153e9cd40cc8945138822807139367f256f89c6810c2634a4f6902b52d3b4e2", size = 80061, upload-time = "2025-10-08T19:46:46.075Z" }, + { url = "https://files.pythonhosted.org/packages/76/47/8ccf75935f51448ba9a16a71b783eb7ef6b9ee60f5d14c7f8a8a79fbeed7/propcache-0.4.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:cd547953428f7abb73c5ad82cbb32109566204260d98e41e5dfdc682eb7f8403", size = 46037, upload-time = "2025-10-08T19:46:47.23Z" }, + { url = "https://files.pythonhosted.org/packages/0a/b6/5c9a0e42df4d00bfb4a3cbbe5cf9f54260300c88a0e9af1f47ca5ce17ac0/propcache-0.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f048da1b4f243fc44f205dfd320933a951b8d89e0afd4c7cacc762a8b9165207", size = 47324, upload-time = "2025-10-08T19:46:48.384Z" }, + { url = "https://files.pythonhosted.org/packages/9e/d3/6c7ee328b39a81ee877c962469f1e795f9db87f925251efeb0545e0020d0/propcache-0.4.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ec17c65562a827bba85e3872ead335f95405ea1674860d96483a02f5c698fa72", size = 225505, upload-time = "2025-10-08T19:46:50.055Z" }, + { url = "https://files.pythonhosted.org/packages/01/5d/1c53f4563490b1d06a684742cc6076ef944bc6457df6051b7d1a877c057b/propcache-0.4.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:405aac25c6394ef275dee4c709be43745d36674b223ba4eb7144bf4d691b7367", size = 230242, upload-time = "2025-10-08T19:46:51.815Z" }, + { url = "https://files.pythonhosted.org/packages/20/e1/ce4620633b0e2422207c3cb774a0ee61cac13abc6217763a7b9e2e3f4a12/propcache-0.4.1-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0013cb6f8dde4b2a2f66903b8ba740bdfe378c943c4377a200551ceb27f379e4", size = 238474, upload-time = "2025-10-08T19:46:53.208Z" }, + { url = "https://files.pythonhosted.org/packages/46/4b/3aae6835b8e5f44ea6a68348ad90f78134047b503765087be2f9912140ea/propcache-0.4.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:15932ab57837c3368b024473a525e25d316d8353016e7cc0e5ba9eb343fbb1cf", size = 221575, upload-time = "2025-10-08T19:46:54.511Z" }, + { url = "https://files.pythonhosted.org/packages/6e/a5/8a5e8678bcc9d3a1a15b9a29165640d64762d424a16af543f00629c87338/propcache-0.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:031dce78b9dc099f4c29785d9cf5577a3faf9ebf74ecbd3c856a7b92768c3df3", size = 216736, upload-time = "2025-10-08T19:46:56.212Z" }, + { url = "https://files.pythonhosted.org/packages/f1/63/b7b215eddeac83ca1c6b934f89d09a625aa9ee4ba158338854c87210cc36/propcache-0.4.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:ab08df6c9a035bee56e31af99be621526bd237bea9f32def431c656b29e41778", size = 213019, upload-time = "2025-10-08T19:46:57.595Z" }, + { url = "https://files.pythonhosted.org/packages/57/74/f580099a58c8af587cac7ba19ee7cb418506342fbbe2d4a4401661cca886/propcache-0.4.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:4d7af63f9f93fe593afbf104c21b3b15868efb2c21d07d8732c0c4287e66b6a6", size = 220376, upload-time = "2025-10-08T19:46:59.067Z" }, + { url = "https://files.pythonhosted.org/packages/c4/ee/542f1313aff7eaf19c2bb758c5d0560d2683dac001a1c96d0774af799843/propcache-0.4.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:cfc27c945f422e8b5071b6e93169679e4eb5bf73bbcbf1ba3ae3a83d2f78ebd9", size = 226988, upload-time = "2025-10-08T19:47:00.544Z" }, + { url = "https://files.pythonhosted.org/packages/8f/18/9c6b015dd9c6930f6ce2229e1f02fb35298b847f2087ea2b436a5bfa7287/propcache-0.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:35c3277624a080cc6ec6f847cbbbb5b49affa3598c4535a0a4682a697aaa5c75", size = 215615, upload-time = "2025-10-08T19:47:01.968Z" }, + { url = "https://files.pythonhosted.org/packages/80/9e/e7b85720b98c45a45e1fca6a177024934dc9bc5f4d5dd04207f216fc33ed/propcache-0.4.1-cp312-cp312-win32.whl", hash = "sha256:671538c2262dadb5ba6395e26c1731e1d52534bfe9ae56d0b5573ce539266aa8", size = 38066, upload-time = "2025-10-08T19:47:03.503Z" }, + { url = "https://files.pythonhosted.org/packages/54/09/d19cff2a5aaac632ec8fc03737b223597b1e347416934c1b3a7df079784c/propcache-0.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:cb2d222e72399fcf5890d1d5cc1060857b9b236adff2792ff48ca2dfd46c81db", size = 41655, upload-time = "2025-10-08T19:47:04.973Z" }, + { url = "https://files.pythonhosted.org/packages/68/ab/6b5c191bb5de08036a8c697b265d4ca76148efb10fa162f14af14fb5f076/propcache-0.4.1-cp312-cp312-win_arm64.whl", hash = "sha256:204483131fb222bdaaeeea9f9e6c6ed0cac32731f75dfc1d4a567fc1926477c1", size = 37789, upload-time = "2025-10-08T19:47:06.077Z" }, + { url = "https://files.pythonhosted.org/packages/5b/5a/bc7b4a4ef808fa59a816c17b20c4bef6884daebbdf627ff2a161da67da19/propcache-0.4.1-py3-none-any.whl", hash = "sha256:af2a6052aeb6cf17d3e46ee169099044fd8224cbaf75c76a2ef596e8163e2237", size = 13305, upload-time = "2025-10-08T19:49:00.792Z" }, +] + +[[package]] +name = "proto-plus" +version = "1.27.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "protobuf" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/01/89/9cbe2f4bba860e149108b683bc2efec21f14d5f7ed6e25562ad86acbc373/proto_plus-1.27.0.tar.gz", hash = "sha256:873af56dd0d7e91836aee871e5799e1c6f1bda86ac9a983e0bb9f0c266a568c4", size = 56158, upload-time = "2025-12-16T13:46:25.729Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cd/24/3b7a0818484df9c28172857af32c2397b6d8fcd99d9468bd4684f98ebf0a/proto_plus-1.27.0-py3-none-any.whl", hash = "sha256:1baa7f81cf0f8acb8bc1f6d085008ba4171eaf669629d1b6d1673b21ed1c0a82", size = 50205, upload-time = "2025-12-16T13:46:24.76Z" }, +] + +[[package]] +name = "protobuf" +version = "6.33.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/34/44/e49ecff446afeec9d1a66d6bbf9adc21e3c7cea7803a920ca3773379d4f6/protobuf-6.33.2.tar.gz", hash = "sha256:56dc370c91fbb8ac85bc13582c9e373569668a290aa2e66a590c2a0d35ddb9e4", size = 444296, upload-time = "2025-12-06T00:17:53.311Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bc/91/1e3a34881a88697a7354ffd177e8746e97a722e5e8db101544b47e84afb1/protobuf-6.33.2-cp310-abi3-win32.whl", hash = "sha256:87eb388bd2d0f78febd8f4c8779c79247b26a5befad525008e49a6955787ff3d", size = 425603, upload-time = "2025-12-06T00:17:41.114Z" }, + { url = "https://files.pythonhosted.org/packages/64/20/4d50191997e917ae13ad0a235c8b42d8c1ab9c3e6fd455ca16d416944355/protobuf-6.33.2-cp310-abi3-win_amd64.whl", hash = "sha256:fc2a0e8b05b180e5fc0dd1559fe8ebdae21a27e81ac77728fb6c42b12c7419b4", size = 436930, upload-time = "2025-12-06T00:17:43.278Z" }, + { url = "https://files.pythonhosted.org/packages/b2/ca/7e485da88ba45c920fb3f50ae78de29ab925d9e54ef0de678306abfbb497/protobuf-6.33.2-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:d9b19771ca75935b3a4422957bc518b0cecb978b31d1dd12037b088f6bcc0e43", size = 427621, upload-time = "2025-12-06T00:17:44.445Z" }, + { url = "https://files.pythonhosted.org/packages/7d/4f/f743761e41d3b2b2566748eb76bbff2b43e14d5fcab694f494a16458b05f/protobuf-6.33.2-cp39-abi3-manylinux2014_aarch64.whl", hash = "sha256:b5d3b5625192214066d99b2b605f5783483575656784de223f00a8d00754fc0e", size = 324460, upload-time = "2025-12-06T00:17:45.678Z" }, + { url = "https://files.pythonhosted.org/packages/b1/fa/26468d00a92824020f6f2090d827078c09c9c587e34cbfd2d0c7911221f8/protobuf-6.33.2-cp39-abi3-manylinux2014_s390x.whl", hash = "sha256:8cd7640aee0b7828b6d03ae518b5b4806fdfc1afe8de82f79c3454f8aef29872", size = 339168, upload-time = "2025-12-06T00:17:46.813Z" }, + { url = "https://files.pythonhosted.org/packages/56/13/333b8f421738f149d4fe5e49553bc2a2ab75235486259f689b4b91f96cec/protobuf-6.33.2-cp39-abi3-manylinux2014_x86_64.whl", hash = "sha256:1f8017c48c07ec5859106533b682260ba3d7c5567b1ca1f24297ce03384d1b4f", size = 323270, upload-time = "2025-12-06T00:17:48.253Z" }, + { url = "https://files.pythonhosted.org/packages/0e/15/4f02896cc3df04fc465010a4c6a0cd89810f54617a32a70ef531ed75d61c/protobuf-6.33.2-py3-none-any.whl", hash = "sha256:7636aad9bb01768870266de5dc009de2d1b936771b38a793f73cbbf279c91c5c", size = 170501, upload-time = "2025-12-06T00:17:52.211Z" }, +] + +[[package]] +name = "psutil" +version = "7.2.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/73/cb/09e5184fb5fc0358d110fc3ca7f6b1d033800734d34cac10f4136cfac10e/psutil-7.2.1.tar.gz", hash = "sha256:f7583aec590485b43ca601dd9cea0dcd65bd7bb21d30ef4ddbf4ea6b5ed1bdd3", size = 490253, upload-time = "2025-12-29T08:26:00.169Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c5/cf/5180eb8c8bdf6a503c6919f1da28328bd1e6b3b1b5b9d5b01ae64f019616/psutil-7.2.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:b2e953fcfaedcfbc952b44744f22d16575d3aa78eb4f51ae74165b4e96e55f42", size = 128137, upload-time = "2025-12-29T08:26:27.759Z" }, + { url = "https://files.pythonhosted.org/packages/c5/2c/78e4a789306a92ade5000da4f5de3255202c534acdadc3aac7b5458fadef/psutil-7.2.1-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:05cc68dbb8c174828624062e73078e7e35406f4ca2d0866c272c2410d8ef06d1", size = 128947, upload-time = "2025-12-29T08:26:29.548Z" }, + { url = "https://files.pythonhosted.org/packages/29/f8/40e01c350ad9a2b3cb4e6adbcc8a83b17ee50dd5792102b6142385937db5/psutil-7.2.1-cp36-abi3-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5e38404ca2bb30ed7267a46c02f06ff842e92da3bb8c5bfdadbd35a5722314d8", size = 154694, upload-time = "2025-12-29T08:26:32.147Z" }, + { url = "https://files.pythonhosted.org/packages/06/e4/b751cdf839c011a9714a783f120e6a86b7494eb70044d7d81a25a5cd295f/psutil-7.2.1-cp36-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ab2b98c9fc19f13f59628d94df5cc4cc4844bc572467d113a8b517d634e362c6", size = 156136, upload-time = "2025-12-29T08:26:34.079Z" }, + { url = "https://files.pythonhosted.org/packages/44/ad/bbf6595a8134ee1e94a4487af3f132cef7fce43aef4a93b49912a48c3af7/psutil-7.2.1-cp36-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:f78baafb38436d5a128f837fab2d92c276dfb48af01a240b861ae02b2413ada8", size = 148108, upload-time = "2025-12-29T08:26:36.225Z" }, + { url = "https://files.pythonhosted.org/packages/1c/15/dd6fd869753ce82ff64dcbc18356093471a5a5adf4f77ed1f805d473d859/psutil-7.2.1-cp36-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:99a4cd17a5fdd1f3d014396502daa70b5ec21bf4ffe38393e152f8e449757d67", size = 147402, upload-time = "2025-12-29T08:26:39.21Z" }, + { url = "https://files.pythonhosted.org/packages/34/68/d9317542e3f2b180c4306e3f45d3c922d7e86d8ce39f941bb9e2e9d8599e/psutil-7.2.1-cp37-abi3-win_amd64.whl", hash = "sha256:b1b0671619343aa71c20ff9767eced0483e4fc9e1f489d50923738caf6a03c17", size = 136938, upload-time = "2025-12-29T08:26:41.036Z" }, + { url = "https://files.pythonhosted.org/packages/3e/73/2ce007f4198c80fcf2cb24c169884f833fe93fbc03d55d302627b094ee91/psutil-7.2.1-cp37-abi3-win_arm64.whl", hash = "sha256:0d67c1822c355aa6f7314d92018fb4268a76668a536f133599b91edd48759442", size = 133836, upload-time = "2025-12-29T08:26:43.086Z" }, +] + +[[package]] +name = "pyasn1" +version = "0.6.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ba/e9/01f1a64245b89f039897cb0130016d79f77d52669aae6ee7b159a6c4c018/pyasn1-0.6.1.tar.gz", hash = "sha256:6f580d2bdd84365380830acf45550f2511469f673cb4a5ae3857a3170128b034", size = 145322, upload-time = "2024-09-10T22:41:42.55Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c8/f1/d6a797abb14f6283c0ddff96bbdd46937f64122b8c925cab503dd37f8214/pyasn1-0.6.1-py3-none-any.whl", hash = "sha256:0d632f46f2ba09143da3a8afe9e33fb6f92fa2320ab7e886e2d0f7672af84629", size = 83135, upload-time = "2024-09-11T16:00:36.122Z" }, +] + +[[package]] +name = "pyasn1-modules" +version = "0.4.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyasn1" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e9/e6/78ebbb10a8c8e4b61a59249394a4a594c1a7af95593dc933a349c8d00964/pyasn1_modules-0.4.2.tar.gz", hash = "sha256:677091de870a80aae844b1ca6134f54652fa2c8c5a52aa396440ac3106e941e6", size = 307892, upload-time = "2025-03-28T02:41:22.17Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/47/8d/d529b5d697919ba8c11ad626e835d4039be708a35b0d22de83a269a6682c/pyasn1_modules-0.4.2-py3-none-any.whl", hash = "sha256:29253a9207ce32b64c3ac6600edc75368f98473906e8fd1043bd6b5b1de2c14a", size = 181259, upload-time = "2025-03-28T02:41:19.028Z" }, +] + +[[package]] +name = "pycparser" +version = "2.23" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fe/cf/d2d3b9f5699fb1e4615c8e32ff220203e43b248e1dfcc6736ad9057731ca/pycparser-2.23.tar.gz", hash = "sha256:78816d4f24add8f10a06d6f05b4d424ad9e96cfebf68a4ddc99c65c0720d00c2", size = 173734, upload-time = "2025-09-09T13:23:47.91Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a0/e3/59cd50310fc9b59512193629e1984c1f95e5c8ae6e5d8c69532ccc65a7fe/pycparser-2.23-py3-none-any.whl", hash = "sha256:e5c6e8d3fbad53479cab09ac03729e0a9faf2bee3db8208a550daf5af81a5934", size = 118140, upload-time = "2025-09-09T13:23:46.651Z" }, +] + +[[package]] +name = "pydantic" +version = "2.12.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "annotated-types" }, + { name = "pydantic-core" }, + { name = "typing-extensions" }, + { name = "typing-inspection" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/69/44/36f1a6e523abc58ae5f928898e4aca2e0ea509b5aa6f6f392a5d882be928/pydantic-2.12.5.tar.gz", hash = "sha256:4d351024c75c0f085a9febbb665ce8c0c6ec5d30e903bdb6394b7ede26aebb49", size = 821591, upload-time = "2025-11-26T15:11:46.471Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5a/87/b70ad306ebb6f9b585f114d0ac2137d792b48be34d732d60e597c2f8465a/pydantic-2.12.5-py3-none-any.whl", hash = "sha256:e561593fccf61e8a20fc46dfc2dfe075b8be7d0188df33f221ad1f0139180f9d", size = 463580, upload-time = "2025-11-26T15:11:44.605Z" }, +] + +[[package]] +name = "pydantic-core" +version = "2.41.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/71/70/23b021c950c2addd24ec408e9ab05d59b035b39d97cdc1130e1bce647bb6/pydantic_core-2.41.5.tar.gz", hash = "sha256:08daa51ea16ad373ffd5e7606252cc32f07bc72b28284b6bc9c6df804816476e", size = 460952, upload-time = "2025-11-04T13:43:49.098Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5f/5d/5f6c63eebb5afee93bcaae4ce9a898f3373ca23df3ccaef086d0233a35a7/pydantic_core-2.41.5-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:f41a7489d32336dbf2199c8c0a215390a751c5b014c2c1c5366e817202e9cdf7", size = 2110990, upload-time = "2025-11-04T13:39:58.079Z" }, + { url = "https://files.pythonhosted.org/packages/aa/32/9c2e8ccb57c01111e0fd091f236c7b371c1bccea0fa85247ac55b1e2b6b6/pydantic_core-2.41.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:070259a8818988b9a84a449a2a7337c7f430a22acc0859c6b110aa7212a6d9c0", size = 1896003, upload-time = "2025-11-04T13:39:59.956Z" }, + { url = "https://files.pythonhosted.org/packages/68/b8/a01b53cb0e59139fbc9e4fda3e9724ede8de279097179be4ff31f1abb65a/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e96cea19e34778f8d59fe40775a7a574d95816eb150850a85a7a4c8f4b94ac69", size = 1919200, upload-time = "2025-11-04T13:40:02.241Z" }, + { url = "https://files.pythonhosted.org/packages/38/de/8c36b5198a29bdaade07b5985e80a233a5ac27137846f3bc2d3b40a47360/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ed2e99c456e3fadd05c991f8f437ef902e00eedf34320ba2b0842bd1c3ca3a75", size = 2052578, upload-time = "2025-11-04T13:40:04.401Z" }, + { url = "https://files.pythonhosted.org/packages/00/b5/0e8e4b5b081eac6cb3dbb7e60a65907549a1ce035a724368c330112adfdd/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:65840751b72fbfd82c3c640cff9284545342a4f1eb1586ad0636955b261b0b05", size = 2208504, upload-time = "2025-11-04T13:40:06.072Z" }, + { url = "https://files.pythonhosted.org/packages/77/56/87a61aad59c7c5b9dc8caad5a41a5545cba3810c3e828708b3d7404f6cef/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e536c98a7626a98feb2d3eaf75944ef6f3dbee447e1f841eae16f2f0a72d8ddc", size = 2335816, upload-time = "2025-11-04T13:40:07.835Z" }, + { url = "https://files.pythonhosted.org/packages/0d/76/941cc9f73529988688a665a5c0ecff1112b3d95ab48f81db5f7606f522d3/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eceb81a8d74f9267ef4081e246ffd6d129da5d87e37a77c9bde550cb04870c1c", size = 2075366, upload-time = "2025-11-04T13:40:09.804Z" }, + { url = "https://files.pythonhosted.org/packages/d3/43/ebef01f69baa07a482844faaa0a591bad1ef129253ffd0cdaa9d8a7f72d3/pydantic_core-2.41.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d38548150c39b74aeeb0ce8ee1d8e82696f4a4e16ddc6de7b1d8823f7de4b9b5", size = 2171698, upload-time = "2025-11-04T13:40:12.004Z" }, + { url = "https://files.pythonhosted.org/packages/b1/87/41f3202e4193e3bacfc2c065fab7706ebe81af46a83d3e27605029c1f5a6/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c23e27686783f60290e36827f9c626e63154b82b116d7fe9adba1fda36da706c", size = 2132603, upload-time = "2025-11-04T13:40:13.868Z" }, + { url = "https://files.pythonhosted.org/packages/49/7d/4c00df99cb12070b6bccdef4a195255e6020a550d572768d92cc54dba91a/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:482c982f814460eabe1d3bb0adfdc583387bd4691ef00b90575ca0d2b6fe2294", size = 2329591, upload-time = "2025-11-04T13:40:15.672Z" }, + { url = "https://files.pythonhosted.org/packages/cc/6a/ebf4b1d65d458f3cda6a7335d141305dfa19bdc61140a884d165a8a1bbc7/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:bfea2a5f0b4d8d43adf9d7b8bf019fb46fdd10a2e5cde477fbcb9d1fa08c68e1", size = 2319068, upload-time = "2025-11-04T13:40:17.532Z" }, + { url = "https://files.pythonhosted.org/packages/49/3b/774f2b5cd4192d5ab75870ce4381fd89cf218af999515baf07e7206753f0/pydantic_core-2.41.5-cp312-cp312-win32.whl", hash = "sha256:b74557b16e390ec12dca509bce9264c3bbd128f8a2c376eaa68003d7f327276d", size = 1985908, upload-time = "2025-11-04T13:40:19.309Z" }, + { url = "https://files.pythonhosted.org/packages/86/45/00173a033c801cacf67c190fef088789394feaf88a98a7035b0e40d53dc9/pydantic_core-2.41.5-cp312-cp312-win_amd64.whl", hash = "sha256:1962293292865bca8e54702b08a4f26da73adc83dd1fcf26fbc875b35d81c815", size = 2020145, upload-time = "2025-11-04T13:40:21.548Z" }, + { url = "https://files.pythonhosted.org/packages/f9/22/91fbc821fa6d261b376a3f73809f907cec5ca6025642c463d3488aad22fb/pydantic_core-2.41.5-cp312-cp312-win_arm64.whl", hash = "sha256:1746d4a3d9a794cacae06a5eaaccb4b8643a131d45fbc9af23e353dc0a5ba5c3", size = 1976179, upload-time = "2025-11-04T13:40:23.393Z" }, + { url = "https://files.pythonhosted.org/packages/09/32/59b0c7e63e277fa7911c2fc70ccfb45ce4b98991e7ef37110663437005af/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:7da7087d756b19037bc2c06edc6c170eeef3c3bafcb8f532ff17d64dc427adfd", size = 2110495, upload-time = "2025-11-04T13:42:49.689Z" }, + { url = "https://files.pythonhosted.org/packages/aa/81/05e400037eaf55ad400bcd318c05bb345b57e708887f07ddb2d20e3f0e98/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:aabf5777b5c8ca26f7824cb4a120a740c9588ed58df9b2d196ce92fba42ff8dc", size = 1915388, upload-time = "2025-11-04T13:42:52.215Z" }, + { url = "https://files.pythonhosted.org/packages/6e/0d/e3549b2399f71d56476b77dbf3cf8937cec5cd70536bdc0e374a421d0599/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c007fe8a43d43b3969e8469004e9845944f1a80e6acd47c150856bb87f230c56", size = 1942879, upload-time = "2025-11-04T13:42:56.483Z" }, + { url = "https://files.pythonhosted.org/packages/f7/07/34573da085946b6a313d7c42f82f16e8920bfd730665de2d11c0c37a74b5/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:76d0819de158cd855d1cbb8fcafdf6f5cf1eb8e470abe056d5d161106e38062b", size = 2139017, upload-time = "2025-11-04T13:42:59.471Z" }, +] + +[[package]] +name = "pydantic-settings" +version = "2.12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pydantic" }, + { name = "python-dotenv" }, + { name = "typing-inspection" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/43/4b/ac7e0aae12027748076d72a8764ff1c9d82ca75a7a52622e67ed3f765c54/pydantic_settings-2.12.0.tar.gz", hash = "sha256:005538ef951e3c2a68e1c08b292b5f2e71490def8589d4221b95dab00dafcfd0", size = 194184, upload-time = "2025-11-10T14:25:47.013Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c1/60/5d4751ba3f4a40a6891f24eec885f51afd78d208498268c734e256fb13c4/pydantic_settings-2.12.0-py3-none-any.whl", hash = "sha256:fddb9fd99a5b18da837b29710391e945b1e30c135477f484084ee513adb93809", size = 51880, upload-time = "2025-11-10T14:25:45.546Z" }, +] + +[[package]] +name = "pygments" +version = "2.19.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" }, +] + +[[package]] +name = "pyjwt" +version = "2.10.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e7/46/bd74733ff231675599650d3e47f361794b22ef3e3770998dda30d3b63726/pyjwt-2.10.1.tar.gz", hash = "sha256:3cc5772eb20009233caf06e9d8a0577824723b44e6648ee0a2aedb6cf9381953", size = 87785, upload-time = "2024-11-28T03:43:29.933Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/61/ad/689f02752eeec26aed679477e80e632ef1b682313be70793d798c1d5fc8f/PyJWT-2.10.1-py3-none-any.whl", hash = "sha256:dcdd193e30abefd5debf142f9adfcdd2b58004e644f25406ffaebd50bd98dacb", size = 22997, upload-time = "2024-11-28T03:43:27.893Z" }, +] + +[package.optional-dependencies] +crypto = [ + { name = "cryptography" }, +] + +[[package]] +name = "pytest" +version = "9.0.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "iniconfig" }, + { name = "packaging" }, + { name = "pluggy" }, + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d1/db/7ef3487e0fb0049ddb5ce41d3a49c235bf9ad299b6a25d5780a89f19230f/pytest-9.0.2.tar.gz", hash = "sha256:75186651a92bd89611d1d9fc20f0b4345fd827c41ccd5c299a868a05d70edf11", size = 1568901, upload-time = "2025-12-06T21:30:51.014Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl", hash = "sha256:711ffd45bf766d5264d487b917733b453d917afd2b0ad65223959f59089f875b", size = 374801, upload-time = "2025-12-06T21:30:49.154Z" }, +] + +[[package]] +name = "python-dateutil" +version = "2.9.0.post0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432, upload-time = "2024-03-01T18:36:20.211Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" }, +] + +[[package]] +name = "python-dotenv" +version = "1.2.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f0/26/19cadc79a718c5edbec86fd4919a6b6d3f681039a2f6d66d14be94e75fb9/python_dotenv-1.2.1.tar.gz", hash = "sha256:42667e897e16ab0d66954af0e60a9caa94f0fd4ecf3aaf6d2d260eec1aa36ad6", size = 44221, upload-time = "2025-10-26T15:12:10.434Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/14/1b/a298b06749107c305e1fe0f814c6c74aea7b2f1e10989cb30f544a1b3253/python_dotenv-1.2.1-py3-none-any.whl", hash = "sha256:b81ee9561e9ca4004139c6cbba3a238c32b03e4894671e181b671e8cb8425d61", size = 21230, upload-time = "2025-10-26T15:12:09.109Z" }, +] + +[[package]] +name = "pytokens" +version = "0.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4e/8d/a762be14dae1c3bf280202ba3172020b2b0b4c537f94427435f19c413b72/pytokens-0.3.0.tar.gz", hash = "sha256:2f932b14ed08de5fcf0b391ace2642f858f1394c0857202959000b68ed7a458a", size = 17644, upload-time = "2025-11-05T13:36:35.34Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/84/25/d9db8be44e205a124f6c98bc0324b2bb149b7431c53877fc6d1038dddaf5/pytokens-0.3.0-py3-none-any.whl", hash = "sha256:95b2b5eaf832e469d141a378872480ede3f251a5a5041b8ec6e581d3ac71bbf3", size = 12195, upload-time = "2025-11-05T13:36:33.183Z" }, +] + +[[package]] +name = "pytz" +version = "2025.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f8/bf/abbd3cdfb8fbc7fb3d4d38d320f2441b1e7cbe29be4f23797b4a2b5d8aac/pytz-2025.2.tar.gz", hash = "sha256:360b9e3dbb49a209c21ad61809c7fb453643e048b38924c765813546746e81c3", size = 320884, upload-time = "2025-03-25T02:25:00.538Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/81/c4/34e93fe5f5429d7570ec1fa436f1986fb1f00c3e0f43a589fe2bbcd22c3f/pytz-2025.2-py2.py3-none-any.whl", hash = "sha256:5ddf76296dd8c44c26eb8f4b6f35488f3ccbf6fbbd7adee0b7262d43f0ec2f00", size = 509225, upload-time = "2025-03-25T02:24:58.468Z" }, +] + +[[package]] +name = "pyyaml" +version = "6.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960, upload-time = "2025-09-25T21:33:16.546Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/33/422b98d2195232ca1826284a76852ad5a86fe23e31b009c9886b2d0fb8b2/pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196", size = 182063, upload-time = "2025-09-25T21:32:11.445Z" }, + { url = "https://files.pythonhosted.org/packages/89/a0/6cf41a19a1f2f3feab0e9c0b74134aa2ce6849093d5517a0c550fe37a648/pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0", size = 173973, upload-time = "2025-09-25T21:32:12.492Z" }, + { url = "https://files.pythonhosted.org/packages/ed/23/7a778b6bd0b9a8039df8b1b1d80e2e2ad78aa04171592c8a5c43a56a6af4/pyyaml-6.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28", size = 775116, upload-time = "2025-09-25T21:32:13.652Z" }, + { url = "https://files.pythonhosted.org/packages/65/30/d7353c338e12baef4ecc1b09e877c1970bd3382789c159b4f89d6a70dc09/pyyaml-6.0.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c", size = 844011, upload-time = "2025-09-25T21:32:15.21Z" }, + { url = "https://files.pythonhosted.org/packages/8b/9d/b3589d3877982d4f2329302ef98a8026e7f4443c765c46cfecc8858c6b4b/pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc", size = 807870, upload-time = "2025-09-25T21:32:16.431Z" }, + { url = "https://files.pythonhosted.org/packages/05/c0/b3be26a015601b822b97d9149ff8cb5ead58c66f981e04fedf4e762f4bd4/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e", size = 761089, upload-time = "2025-09-25T21:32:17.56Z" }, + { url = "https://files.pythonhosted.org/packages/be/8e/98435a21d1d4b46590d5459a22d88128103f8da4c2d4cb8f14f2a96504e1/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea", size = 790181, upload-time = "2025-09-25T21:32:18.834Z" }, + { url = "https://files.pythonhosted.org/packages/74/93/7baea19427dcfbe1e5a372d81473250b379f04b1bd3c4c5ff825e2327202/pyyaml-6.0.3-cp312-cp312-win32.whl", hash = "sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5", size = 137658, upload-time = "2025-09-25T21:32:20.209Z" }, + { url = "https://files.pythonhosted.org/packages/86/bf/899e81e4cce32febab4fb42bb97dcdf66bc135272882d1987881a4b519e9/pyyaml-6.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b", size = 154003, upload-time = "2025-09-25T21:32:21.167Z" }, + { url = "https://files.pythonhosted.org/packages/1a/08/67bd04656199bbb51dbed1439b7f27601dfb576fb864099c7ef0c3e55531/pyyaml-6.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd", size = 140344, upload-time = "2025-09-25T21:32:22.617Z" }, +] + +[[package]] +name = "rapidfuzz" +version = "3.14.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d3/28/9d808fe62375b9aab5ba92fa9b29371297b067c2790b2d7cda648b1e2f8d/rapidfuzz-3.14.3.tar.gz", hash = "sha256:2491937177868bc4b1e469087601d53f925e8d270ccc21e07404b4b5814b7b5f", size = 57863900, upload-time = "2025-11-01T11:54:52.321Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fa/8e/3c215e860b458cfbedb3ed73bc72e98eb7e0ed72f6b48099604a7a3260c2/rapidfuzz-3.14.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:685c93ea961d135893b5984a5a9851637d23767feabe414ec974f43babbd8226", size = 1945306, upload-time = "2025-11-01T11:53:06.452Z" }, + { url = "https://files.pythonhosted.org/packages/36/d9/31b33512015c899f4a6e6af64df8dfe8acddf4c8b40a4b3e0e6e1bcd00e5/rapidfuzz-3.14.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fa7c8f26f009f8c673fbfb443792f0cf8cf50c4e18121ff1e285b5e08a94fbdb", size = 1390788, upload-time = "2025-11-01T11:53:08.721Z" }, + { url = "https://files.pythonhosted.org/packages/a9/67/2ee6f8de6e2081ccd560a571d9c9063184fe467f484a17fa90311a7f4a2e/rapidfuzz-3.14.3-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:57f878330c8d361b2ce76cebb8e3e1dc827293b6abf404e67d53260d27b5d941", size = 1374580, upload-time = "2025-11-01T11:53:10.164Z" }, + { url = "https://files.pythonhosted.org/packages/30/83/80d22997acd928eda7deadc19ccd15883904622396d6571e935993e0453a/rapidfuzz-3.14.3-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6c5f545f454871e6af05753a0172849c82feaf0f521c5ca62ba09e1b382d6382", size = 3154947, upload-time = "2025-11-01T11:53:12.093Z" }, + { url = "https://files.pythonhosted.org/packages/5b/cf/9f49831085a16384695f9fb096b99662f589e30b89b4a589a1ebc1a19d34/rapidfuzz-3.14.3-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:07aa0b5d8863e3151e05026a28e0d924accf0a7a3b605da978f0359bb804df43", size = 1223872, upload-time = "2025-11-01T11:53:13.664Z" }, + { url = "https://files.pythonhosted.org/packages/c8/0f/41ee8034e744b871c2e071ef0d360686f5ccfe5659f4fd96c3ec406b3c8b/rapidfuzz-3.14.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:73b07566bc7e010e7b5bd490fb04bb312e820970180df6b5655e9e6224c137db", size = 2392512, upload-time = "2025-11-01T11:53:15.109Z" }, + { url = "https://files.pythonhosted.org/packages/da/86/280038b6b0c2ccec54fb957c732ad6b41cc1fd03b288d76545b9cf98343f/rapidfuzz-3.14.3-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:6de00eb84c71476af7d3110cf25d8fe7c792d7f5fa86764ef0b4ca97e78ca3ed", size = 2521398, upload-time = "2025-11-01T11:53:17.146Z" }, + { url = "https://files.pythonhosted.org/packages/fa/7b/05c26f939607dca0006505e3216248ae2de631e39ef94dd63dbbf0860021/rapidfuzz-3.14.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:d7843a1abf0091773a530636fdd2a49a41bcae22f9910b86b4f903e76ddc82dc", size = 4259416, upload-time = "2025-11-01T11:53:19.34Z" }, + { url = "https://files.pythonhosted.org/packages/40/eb/9e3af4103d91788f81111af1b54a28de347cdbed8eaa6c91d5e98a889aab/rapidfuzz-3.14.3-cp312-cp312-win32.whl", hash = "sha256:dea97ac3ca18cd3ba8f3d04b5c1fe4aa60e58e8d9b7793d3bd595fdb04128d7a", size = 1709527, upload-time = "2025-11-01T11:53:20.949Z" }, + { url = "https://files.pythonhosted.org/packages/b8/63/d06ecce90e2cf1747e29aeab9f823d21e5877a4c51b79720b2d3be7848f8/rapidfuzz-3.14.3-cp312-cp312-win_amd64.whl", hash = "sha256:b5100fd6bcee4d27f28f4e0a1c6b5127bc8ba7c2a9959cad9eab0bf4a7ab3329", size = 1538989, upload-time = "2025-11-01T11:53:22.428Z" }, + { url = "https://files.pythonhosted.org/packages/fc/6d/beee32dcda64af8128aab3ace2ccb33d797ed58c434c6419eea015fec779/rapidfuzz-3.14.3-cp312-cp312-win_arm64.whl", hash = "sha256:4e49c9e992bc5fc873bd0fff7ef16a4405130ec42f2ce3d2b735ba5d3d4eb70f", size = 811161, upload-time = "2025-11-01T11:53:23.811Z" }, +] + +[[package]] +name = "requests" +version = "2.32.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "charset-normalizer" }, + { name = "idna" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c9/74/b3ff8e6c8446842c3f5c837e9c3dfcfe2018ea6ecef224c710c85ef728f4/requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf", size = 134517, upload-time = "2025-08-18T20:46:02.573Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", size = 64738, upload-time = "2025-08-18T20:46:00.542Z" }, +] + +[[package]] +name = "requests-oauthlib" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "oauthlib" }, + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/42/f2/05f29bc3913aea15eb670be136045bf5c5bbf4b99ecb839da9b422bb2c85/requests-oauthlib-2.0.0.tar.gz", hash = "sha256:b3dffaebd884d8cd778494369603a9e7b58d29111bf6b41bdc2dcd87203af4e9", size = 55650, upload-time = "2024-03-22T20:32:29.939Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3b/5d/63d4ae3b9daea098d5d6f5da83984853c1bbacd5dc826764b249fe119d24/requests_oauthlib-2.0.0-py2.py3-none-any.whl", hash = "sha256:7dd8a5c40426b779b0868c404bdef9768deccf22749cde15852df527e6269b36", size = 24179, upload-time = "2024-03-22T20:32:28.055Z" }, +] + +[[package]] +name = "rich" +version = "14.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markdown-it-py" }, + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fb/d2/8920e102050a0de7bfabeb4c4614a49248cf8d5d7a8d01885fbb24dc767a/rich-14.2.0.tar.gz", hash = "sha256:73ff50c7c0c1c77c8243079283f4edb376f0f6442433aecb8ce7e6d0b92d1fe4", size = 219990, upload-time = "2025-10-09T14:16:53.064Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/25/7a/b0178788f8dc6cafce37a212c99565fa1fe7872c70c6c9c1e1a372d9d88f/rich-14.2.0-py3-none-any.whl", hash = "sha256:76bc51fe2e57d2b1be1f96c524b890b816e334ab4c1e45888799bfaab0021edd", size = 243393, upload-time = "2025-10-09T14:16:51.245Z" }, +] + +[[package]] +name = "rsa" +version = "4.9.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyasn1" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/da/8a/22b7beea3ee0d44b1916c0c1cb0ee3af23b700b6da9f04991899d0c555d4/rsa-4.9.1.tar.gz", hash = "sha256:e7bdbfdb5497da4c07dfd35530e1a902659db6ff241e39d9953cad06ebd0ae75", size = 29034, upload-time = "2025-04-16T09:51:18.218Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/64/8d/0133e4eb4beed9e425d9a98ed6e081a55d195481b7632472be1af08d2f6b/rsa-4.9.1-py3-none-any.whl", hash = "sha256:68635866661c6836b8d39430f97a996acbd61bfa49406748ea243539fe239762", size = 34696, upload-time = "2025-04-16T09:51:17.142Z" }, +] + +[[package]] +name = "ruff" +version = "0.14.10" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/57/08/52232a877978dd8f9cf2aeddce3e611b40a63287dfca29b6b8da791f5e8d/ruff-0.14.10.tar.gz", hash = "sha256:9a2e830f075d1a42cd28420d7809ace390832a490ed0966fe373ba288e77aaf4", size = 5859763, upload-time = "2025-12-18T19:28:57.98Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/60/01/933704d69f3f05ee16ef11406b78881733c186fe14b6a46b05cfcaf6d3b2/ruff-0.14.10-py3-none-linux_armv6l.whl", hash = "sha256:7a3ce585f2ade3e1f29ec1b92df13e3da262178df8c8bdf876f48fa0e8316c49", size = 13527080, upload-time = "2025-12-18T19:29:25.642Z" }, + { url = "https://files.pythonhosted.org/packages/df/58/a0349197a7dfa603ffb7f5b0470391efa79ddc327c1e29c4851e85b09cc5/ruff-0.14.10-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:674f9be9372907f7257c51f1d4fc902cb7cf014b9980152b802794317941f08f", size = 13797320, upload-time = "2025-12-18T19:29:02.571Z" }, + { url = "https://files.pythonhosted.org/packages/7b/82/36be59f00a6082e38c23536df4e71cdbc6af8d7c707eade97fcad5c98235/ruff-0.14.10-py3-none-macosx_11_0_arm64.whl", hash = "sha256:d85713d522348837ef9df8efca33ccb8bd6fcfc86a2cde3ccb4bc9d28a18003d", size = 12918434, upload-time = "2025-12-18T19:28:51.202Z" }, + { url = "https://files.pythonhosted.org/packages/a6/00/45c62a7f7e34da92a25804f813ebe05c88aa9e0c25e5cb5a7d23dd7450e3/ruff-0.14.10-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6987ebe0501ae4f4308d7d24e2d0fe3d7a98430f5adfd0f1fead050a740a3a77", size = 13371961, upload-time = "2025-12-18T19:29:04.991Z" }, + { url = "https://files.pythonhosted.org/packages/40/31/a5906d60f0405f7e57045a70f2d57084a93ca7425f22e1d66904769d1628/ruff-0.14.10-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:16a01dfb7b9e4eee556fbfd5392806b1b8550c9b4a9f6acd3dbe6812b193c70a", size = 13275629, upload-time = "2025-12-18T19:29:21.381Z" }, + { url = "https://files.pythonhosted.org/packages/3e/60/61c0087df21894cf9d928dc04bcd4fb10e8b2e8dca7b1a276ba2155b2002/ruff-0.14.10-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7165d31a925b7a294465fa81be8c12a0e9b60fb02bf177e79067c867e71f8b1f", size = 14029234, upload-time = "2025-12-18T19:29:00.132Z" }, + { url = "https://files.pythonhosted.org/packages/44/84/77d911bee3b92348b6e5dab5a0c898d87084ea03ac5dc708f46d88407def/ruff-0.14.10-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:c561695675b972effb0c0a45db233f2c816ff3da8dcfbe7dfc7eed625f218935", size = 15449890, upload-time = "2025-12-18T19:28:53.573Z" }, + { url = "https://files.pythonhosted.org/packages/e9/36/480206eaefa24a7ec321582dda580443a8f0671fdbf6b1c80e9c3e93a16a/ruff-0.14.10-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4bb98fcbbc61725968893682fd4df8966a34611239c9fd07a1f6a07e7103d08e", size = 15123172, upload-time = "2025-12-18T19:29:23.453Z" }, + { url = "https://files.pythonhosted.org/packages/5c/38/68e414156015ba80cef5473d57919d27dfb62ec804b96180bafdeaf0e090/ruff-0.14.10-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f24b47993a9d8cb858429e97bdf8544c78029f09b520af615c1d261bf827001d", size = 14460260, upload-time = "2025-12-18T19:29:27.808Z" }, + { url = "https://files.pythonhosted.org/packages/b3/19/9e050c0dca8aba824d67cc0db69fb459c28d8cd3f6855b1405b3f29cc91d/ruff-0.14.10-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:59aabd2e2c4fd614d2862e7939c34a532c04f1084476d6833dddef4afab87e9f", size = 14229978, upload-time = "2025-12-18T19:29:11.32Z" }, + { url = "https://files.pythonhosted.org/packages/51/eb/e8dd1dd6e05b9e695aa9dd420f4577debdd0f87a5ff2fedda33c09e9be8c/ruff-0.14.10-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:213db2b2e44be8625002dbea33bb9c60c66ea2c07c084a00d55732689d697a7f", size = 14338036, upload-time = "2025-12-18T19:29:09.184Z" }, + { url = "https://files.pythonhosted.org/packages/6a/12/f3e3a505db7c19303b70af370d137795fcfec136d670d5de5391e295c134/ruff-0.14.10-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:b914c40ab64865a17a9a5b67911d14df72346a634527240039eb3bd650e5979d", size = 13264051, upload-time = "2025-12-18T19:29:13.431Z" }, + { url = "https://files.pythonhosted.org/packages/08/64/8c3a47eaccfef8ac20e0484e68e0772013eb85802f8a9f7603ca751eb166/ruff-0.14.10-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:1484983559f026788e3a5c07c81ef7d1e97c1c78ed03041a18f75df104c45405", size = 13283998, upload-time = "2025-12-18T19:29:06.994Z" }, + { url = "https://files.pythonhosted.org/packages/12/84/534a5506f4074e5cc0529e5cd96cfc01bb480e460c7edf5af70d2bcae55e/ruff-0.14.10-py3-none-musllinux_1_2_i686.whl", hash = "sha256:c70427132db492d25f982fffc8d6c7535cc2fd2c83fc8888f05caaa248521e60", size = 13601891, upload-time = "2025-12-18T19:28:55.811Z" }, + { url = "https://files.pythonhosted.org/packages/0d/1e/14c916087d8598917dbad9b2921d340f7884824ad6e9c55de948a93b106d/ruff-0.14.10-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:5bcf45b681e9f1ee6445d317ce1fa9d6cba9a6049542d1c3d5b5958986be8830", size = 14336660, upload-time = "2025-12-18T19:29:16.531Z" }, + { url = "https://files.pythonhosted.org/packages/f2/1c/d7b67ab43f30013b47c12b42d1acd354c195351a3f7a1d67f59e54227ede/ruff-0.14.10-py3-none-win32.whl", hash = "sha256:104c49fc7ab73f3f3a758039adea978869a918f31b73280db175b43a2d9b51d6", size = 13196187, upload-time = "2025-12-18T19:29:19.006Z" }, + { url = "https://files.pythonhosted.org/packages/fb/9c/896c862e13886fae2af961bef3e6312db9ebc6adc2b156fe95e615dee8c1/ruff-0.14.10-py3-none-win_amd64.whl", hash = "sha256:466297bd73638c6bdf06485683e812db1c00c7ac96d4ddd0294a338c62fdc154", size = 14661283, upload-time = "2025-12-18T19:29:30.16Z" }, + { url = "https://files.pythonhosted.org/packages/74/31/b0e29d572670dca3674eeee78e418f20bdf97fa8aa9ea71380885e175ca0/ruff-0.14.10-py3-none-win_arm64.whl", hash = "sha256:e51d046cf6dda98a4633b8a8a771451107413b0f07183b2bef03f075599e44e6", size = 13729839, upload-time = "2025-12-18T19:28:48.636Z" }, +] + +[[package]] +name = "s3fs" +version = "2025.12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "aiobotocore" }, + { name = "aiohttp" }, + { name = "fsspec" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cf/26/fff848df6a76d6fec20208e61548244639c46a741e296244c3404d6e7df0/s3fs-2025.12.0.tar.gz", hash = "sha256:8612885105ce14d609c5b807553f9f9956b45541576a17ff337d9435ed3eb01f", size = 81217, upload-time = "2025-12-03T15:34:04.754Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/44/8c/04797ebb53748b4d594d4c334b2d9a99f2d2e06e19ad505f1313ca5d56eb/s3fs-2025.12.0-py3-none-any.whl", hash = "sha256:89d51e0744256baad7ae5410304a368ca195affd93a07795bc8ba9c00c9effbb", size = 30726, upload-time = "2025-12-03T15:34:03.576Z" }, +] + +[[package]] +name = "segy" +version = "0.6.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "fsspec" }, + { name = "numba" }, + { name = "numpy" }, + { name = "pandas" }, + { name = "pydantic" }, + { name = "pydantic-settings" }, + { name = "rapidfuzz" }, + { name = "typer" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e5/be/39961f0053378ecd2ab42fdffc7d76f8e79ac00f79bbf6ebf31183e9e0f0/segy-0.6.0.tar.gz", hash = "sha256:1ec623361e99ddbf952be286403912e4c50218f49bdddf73a0c907a9a06a74f7", size = 46333, upload-time = "2026-06-16T15:25:23.091Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/ab/c15b6ca03f5e3e8765dbcc477e8963263cf79ea171ed78a193ca5fc8bc4d/segy-0.6.0-py3-none-any.whl", hash = "sha256:82a3c772434ff7a12e0ef371a288362411d90798377c17946e66737a1791f473", size = 58689, upload-time = "2026-06-16T15:25:22.132Z" }, +] + +[[package]] +name = "shellingham" +version = "1.5.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/58/15/8b3609fd3830ef7b27b655beb4b4e9c62313a4e8da8c676e142cc210d58e/shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de", size = 10310, upload-time = "2023-10-24T04:13:40.426Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686", size = 9755, upload-time = "2023-10-24T04:13:38.866Z" }, +] + +[[package]] +name = "six" +version = "1.17.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031, upload-time = "2024-12-04T17:35:28.174Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" }, +] + +[[package]] +name = "toolz" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/11/d6/114b492226588d6ff54579d95847662fc69196bdeec318eb45393b24c192/toolz-1.1.0.tar.gz", hash = "sha256:27a5c770d068c110d9ed9323f24f1543e83b2f300a687b7891c1a6d56b697b5b", size = 52613, upload-time = "2025-10-17T04:03:21.661Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fb/12/5911ae3eeec47800503a238d971e51722ccea5feb8569b735184d5fcdbc0/toolz-1.1.0-py3-none-any.whl", hash = "sha256:15ccc861ac51c53696de0a5d6d4607f99c210739caf987b5d2054f3efed429d8", size = 58093, upload-time = "2025-10-17T04:03:20.435Z" }, +] + +[[package]] +name = "tqdm" +version = "4.67.1" +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" } +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" }, +] + +[[package]] +name = "typer" +version = "0.21.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "rich" }, + { name = "shellingham" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/36/bf/8825b5929afd84d0dabd606c67cd57b8388cb3ec385f7ef19c5cc2202069/typer-0.21.1.tar.gz", hash = "sha256:ea835607cd752343b6b2b7ce676893e5a0324082268b48f27aa058bdb7d2145d", size = 110371, upload-time = "2026-01-06T11:21:10.989Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a0/1d/d9257dd49ff2ca23ea5f132edf1281a0c4f9de8a762b9ae399b670a59235/typer-0.21.1-py3-none-any.whl", hash = "sha256:7985e89081c636b88d172c2ee0cfe33c253160994d47bdfdc302defd7d1f1d01", size = 47381, upload-time = "2026-01-06T11:21:09.824Z" }, +] + +[[package]] +name = "typing-extensions" +version = "4.15.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, +] + +[[package]] +name = "typing-inspection" +version = "0.4.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/55/e3/70399cb7dd41c10ac53367ae42139cf4b1ca5f36bb3dc6c9d33acdb43655/typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464", size = 75949, upload-time = "2025-10-01T02:14:41.687Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7", size = 14611, upload-time = "2025-10-01T02:14:40.154Z" }, +] + +[[package]] +name = "tzdata" +version = "2025.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5e/a7/c202b344c5ca7daf398f3b8a477eeb205cf3b6f32e7ec3a6bac0629ca975/tzdata-2025.3.tar.gz", hash = "sha256:de39c2ca5dc7b0344f2eba86f49d614019d29f060fc4ebc8a417896a620b56a7", size = 196772, upload-time = "2025-12-13T17:45:35.667Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/b0/003792df09decd6849a5e39c28b513c06e84436a54440380862b5aeff25d/tzdata-2025.3-py2.py3-none-any.whl", hash = "sha256:06a47e5700f3081aab02b2e513160914ff0694bce9947d6b76ebd6bf57cfc5d1", size = 348521, upload-time = "2025-12-13T17:45:33.889Z" }, +] + +[[package]] +name = "universal-pathlib" +version = "0.3.7" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "fsspec" }, + { name = "pathlib-abc" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d5/96/b58b00ce27cbc37fd3c79944438dd8630d2c39f9467c9e73e1a4a3eec1ef/universal_pathlib-0.3.7.tar.gz", hash = "sha256:36331056fa59a7d7cd3b61b4045f3a3418f446f23ec1a01d281c4510814b4b05", size = 253466, upload-time = "2025-12-03T00:06:43.859Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/79/77/53c2d3a0413bc55b4c91067a0c41e55451be9b0784d204e4e46ce5abf668/universal_pathlib-0.3.7-py3-none-any.whl", hash = "sha256:fb95117b20b5981f86ef9d887fddbf9c61d3596634ba42cccea444931d87c201", size = 80738, upload-time = "2025-12-03T00:06:41.997Z" }, +] + +[[package]] +name = "urllib3" +version = "2.6.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c7/24/5f1b3bdffd70275f6661c76461e25f024d5a38a46f04aaca912426a2b1d3/urllib3-2.6.3.tar.gz", hash = "sha256:1b62b6884944a57dbe321509ab94fd4d3b307075e0c2eae991ac71ee15ad38ed", size = 435556, upload-time = "2026-01-07T16:24:43.925Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl", hash = "sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4", size = 131584, upload-time = "2026-01-07T16:24:42.685Z" }, +] + +[[package]] +name = "validators" +version = "0.22.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9b/21/40a249498eee5a244a017582c06c0af01851179e2617928063a3d628bc8f/validators-0.22.0.tar.gz", hash = "sha256:77b2689b172eeeb600d9605ab86194641670cdb73b60afd577142a9397873370", size = 41479, upload-time = "2023-09-02T09:17:59.054Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3a/0c/785d317eea99c3739821718f118c70537639aa43f96bfa1d83a71f68eaf6/validators-0.22.0-py3-none-any.whl", hash = "sha256:61cf7d4a62bbae559f2e54aed3b000cea9ff3e2fdbe463f51179b92c58c9585a", size = 26195, upload-time = "2023-09-02T09:17:56.595Z" }, +] + +[[package]] +name = "wrapt" +version = "1.17.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/95/8f/aeb76c5b46e273670962298c23e7ddde79916cb74db802131d49a85e4b7d/wrapt-1.17.3.tar.gz", hash = "sha256:f66eb08feaa410fe4eebd17f2a2c8e2e46d3476e9f8c783daa8e09e0faa666d0", size = 55547, upload-time = "2025-08-12T05:53:21.714Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9f/41/cad1aba93e752f1f9268c77270da3c469883d56e2798e7df6240dcb2287b/wrapt-1.17.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ab232e7fdb44cdfbf55fc3afa31bcdb0d8980b9b95c38b6405df2acb672af0e0", size = 53998, upload-time = "2025-08-12T05:51:47.138Z" }, + { url = "https://files.pythonhosted.org/packages/60/f8/096a7cc13097a1869fe44efe68dace40d2a16ecb853141394047f0780b96/wrapt-1.17.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:9baa544e6acc91130e926e8c802a17f3b16fbea0fd441b5a60f5cf2cc5c3deba", size = 39020, upload-time = "2025-08-12T05:51:35.906Z" }, + { url = "https://files.pythonhosted.org/packages/33/df/bdf864b8997aab4febb96a9ae5c124f700a5abd9b5e13d2a3214ec4be705/wrapt-1.17.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6b538e31eca1a7ea4605e44f81a48aa24c4632a277431a6ed3f328835901f4fd", size = 39098, upload-time = "2025-08-12T05:51:57.474Z" }, + { url = "https://files.pythonhosted.org/packages/9f/81/5d931d78d0eb732b95dc3ddaeeb71c8bb572fb01356e9133916cd729ecdd/wrapt-1.17.3-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:042ec3bb8f319c147b1301f2393bc19dba6e176b7da446853406d041c36c7828", size = 88036, upload-time = "2025-08-12T05:52:34.784Z" }, + { url = "https://files.pythonhosted.org/packages/ca/38/2e1785df03b3d72d34fc6252d91d9d12dc27a5c89caef3335a1bbb8908ca/wrapt-1.17.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3af60380ba0b7b5aeb329bc4e402acd25bd877e98b3727b0135cb5c2efdaefe9", size = 88156, upload-time = "2025-08-12T05:52:13.599Z" }, + { url = "https://files.pythonhosted.org/packages/b3/8b/48cdb60fe0603e34e05cffda0b2a4adab81fd43718e11111a4b0100fd7c1/wrapt-1.17.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0b02e424deef65c9f7326d8c19220a2c9040c51dc165cddb732f16198c168396", size = 87102, upload-time = "2025-08-12T05:52:14.56Z" }, + { url = "https://files.pythonhosted.org/packages/3c/51/d81abca783b58f40a154f1b2c56db1d2d9e0d04fa2d4224e357529f57a57/wrapt-1.17.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:74afa28374a3c3a11b3b5e5fca0ae03bef8450d6aa3ab3a1e2c30e3a75d023dc", size = 87732, upload-time = "2025-08-12T05:52:36.165Z" }, + { url = "https://files.pythonhosted.org/packages/9e/b1/43b286ca1392a006d5336412d41663eeef1ad57485f3e52c767376ba7e5a/wrapt-1.17.3-cp312-cp312-win32.whl", hash = "sha256:4da9f45279fff3543c371d5ababc57a0384f70be244de7759c85a7f989cb4ebe", size = 36705, upload-time = "2025-08-12T05:53:07.123Z" }, + { url = "https://files.pythonhosted.org/packages/28/de/49493f962bd3c586ab4b88066e967aa2e0703d6ef2c43aa28cb83bf7b507/wrapt-1.17.3-cp312-cp312-win_amd64.whl", hash = "sha256:e71d5c6ebac14875668a1e90baf2ea0ef5b7ac7918355850c0908ae82bcb297c", size = 38877, upload-time = "2025-08-12T05:53:05.436Z" }, + { url = "https://files.pythonhosted.org/packages/f1/48/0f7102fe9cb1e8a5a77f80d4f0956d62d97034bbe88d33e94699f99d181d/wrapt-1.17.3-cp312-cp312-win_arm64.whl", hash = "sha256:604d076c55e2fdd4c1c03d06dc1a31b95130010517b5019db15365ec4a405fc6", size = 36885, upload-time = "2025-08-12T05:52:54.367Z" }, + { url = "https://files.pythonhosted.org/packages/1f/f6/a933bd70f98e9cf3e08167fc5cd7aaaca49147e48411c0bd5ae701bb2194/wrapt-1.17.3-py3-none-any.whl", hash = "sha256:7171ae35d2c33d326ac19dd8facb1e82e5fd04ef8c6c0e394d7af55a55051c22", size = 23591, upload-time = "2025-08-12T05:53:20.674Z" }, +] + +[[package]] +name = "xarray" +version = "2025.12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, + { name = "packaging" }, + { name = "pandas" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d3/af/7b945f331ba8911fdfff2fdfa092763156119f124be1ba4144615c540222/xarray-2025.12.0.tar.gz", hash = "sha256:73f6a6fadccc69c4d45bdd70821a47c72de078a8a0313ff8b1e97cd54ac59fed", size = 3082244, upload-time = "2025-12-05T21:51:22.432Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d5/e4/62a677feefde05b12a70a4fc9bdc8558010182a801fbcab68cb56c2b0986/xarray-2025.12.0-py3-none-any.whl", hash = "sha256:9e77e820474dbbe4c6c2954d0da6342aa484e33adaa96ab916b15a786181e970", size = 1381742, upload-time = "2025-12-05T21:51:20.841Z" }, +] + +[[package]] +name = "yapf" +version = "0.43.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "platformdirs" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/23/97/b6f296d1e9cc1ec25c7604178b48532fa5901f721bcf1b8d8148b13e5588/yapf-0.43.0.tar.gz", hash = "sha256:00d3aa24bfedff9420b2e0d5d9f5ab6d9d4268e72afbf59bb3fa542781d5218e", size = 254907, upload-time = "2024-11-14T00:11:41.584Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/37/81/6acd6601f61e31cfb8729d3da6d5df966f80f374b78eff83760714487338/yapf-0.43.0-py3-none-any.whl", hash = "sha256:224faffbc39c428cb095818cf6ef5511fdab6f7430a10783fdfb292ccf2852ca", size = 256158, upload-time = "2024-11-14T00:11:39.37Z" }, +] + +[[package]] +name = "yarl" +version = "1.22.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "idna" }, + { name = "multidict" }, + { name = "propcache" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/57/63/0c6ebca57330cd313f6102b16dd57ffaf3ec4c83403dcb45dbd15c6f3ea1/yarl-1.22.0.tar.gz", hash = "sha256:bebf8557577d4401ba8bd9ff33906f1376c877aa78d1fe216ad01b4d6745af71", size = 187169, upload-time = "2025-10-06T14:12:55.963Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/75/ff/46736024fee3429b80a165a732e38e5d5a238721e634ab41b040d49f8738/yarl-1.22.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e340382d1afa5d32b892b3ff062436d592ec3d692aeea3bef3a5cfe11bbf8c6f", size = 142000, upload-time = "2025-10-06T14:09:44.631Z" }, + { url = "https://files.pythonhosted.org/packages/5a/9a/b312ed670df903145598914770eb12de1bac44599549b3360acc96878df8/yarl-1.22.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f1e09112a2c31ffe8d80be1b0988fa6a18c5d5cad92a9ffbb1c04c91bfe52ad2", size = 94338, upload-time = "2025-10-06T14:09:46.372Z" }, + { url = "https://files.pythonhosted.org/packages/ba/f5/0601483296f09c3c65e303d60c070a5c19fcdbc72daa061e96170785bc7d/yarl-1.22.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:939fe60db294c786f6b7c2d2e121576628468f65453d86b0fe36cb52f987bd74", size = 94909, upload-time = "2025-10-06T14:09:48.648Z" }, + { url = "https://files.pythonhosted.org/packages/60/41/9a1fe0b73dbcefce72e46cf149b0e0a67612d60bfc90fb59c2b2efdfbd86/yarl-1.22.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e1651bf8e0398574646744c1885a41198eba53dc8a9312b954073f845c90a8df", size = 372940, upload-time = "2025-10-06T14:09:50.089Z" }, + { url = "https://files.pythonhosted.org/packages/17/7a/795cb6dfee561961c30b800f0ed616b923a2ec6258b5def2a00bf8231334/yarl-1.22.0-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:b8a0588521a26bf92a57a1705b77b8b59044cdceccac7151bd8d229e66b8dedb", size = 345825, upload-time = "2025-10-06T14:09:52.142Z" }, + { url = "https://files.pythonhosted.org/packages/d7/93/a58f4d596d2be2ae7bab1a5846c4d270b894958845753b2c606d666744d3/yarl-1.22.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:42188e6a615c1a75bcaa6e150c3fe8f3e8680471a6b10150c5f7e83f47cc34d2", size = 386705, upload-time = "2025-10-06T14:09:54.128Z" }, + { url = "https://files.pythonhosted.org/packages/61/92/682279d0e099d0e14d7fd2e176bd04f48de1484f56546a3e1313cd6c8e7c/yarl-1.22.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f6d2cb59377d99718913ad9a151030d6f83ef420a2b8f521d94609ecc106ee82", size = 396518, upload-time = "2025-10-06T14:09:55.762Z" }, + { url = "https://files.pythonhosted.org/packages/db/0f/0d52c98b8a885aeda831224b78f3be7ec2e1aa4a62091f9f9188c3c65b56/yarl-1.22.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:50678a3b71c751d58d7908edc96d332af328839eea883bb554a43f539101277a", size = 377267, upload-time = "2025-10-06T14:09:57.958Z" }, + { url = "https://files.pythonhosted.org/packages/22/42/d2685e35908cbeaa6532c1fc73e89e7f2efb5d8a7df3959ea8e37177c5a3/yarl-1.22.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1e8fbaa7cec507aa24ea27a01456e8dd4b6fab829059b69844bd348f2d467124", size = 365797, upload-time = "2025-10-06T14:09:59.527Z" }, + { url = "https://files.pythonhosted.org/packages/a2/83/cf8c7bcc6355631762f7d8bdab920ad09b82efa6b722999dfb05afa6cfac/yarl-1.22.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:433885ab5431bc3d3d4f2f9bd15bfa1614c522b0f1405d62c4f926ccd69d04fa", size = 365535, upload-time = "2025-10-06T14:10:01.139Z" }, + { url = "https://files.pythonhosted.org/packages/25/e1/5302ff9b28f0c59cac913b91fe3f16c59a033887e57ce9ca5d41a3a94737/yarl-1.22.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:b790b39c7e9a4192dc2e201a282109ed2985a1ddbd5ac08dc56d0e121400a8f7", size = 382324, upload-time = "2025-10-06T14:10:02.756Z" }, + { url = "https://files.pythonhosted.org/packages/bf/cd/4617eb60f032f19ae3a688dc990d8f0d89ee0ea378b61cac81ede3e52fae/yarl-1.22.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:31f0b53913220599446872d757257be5898019c85e7971599065bc55065dc99d", size = 383803, upload-time = "2025-10-06T14:10:04.552Z" }, + { url = "https://files.pythonhosted.org/packages/59/65/afc6e62bb506a319ea67b694551dab4a7e6fb7bf604e9bd9f3e11d575fec/yarl-1.22.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a49370e8f711daec68d09b821a34e1167792ee2d24d405cbc2387be4f158b520", size = 374220, upload-time = "2025-10-06T14:10:06.489Z" }, + { url = "https://files.pythonhosted.org/packages/e7/3d/68bf18d50dc674b942daec86a9ba922d3113d8399b0e52b9897530442da2/yarl-1.22.0-cp312-cp312-win32.whl", hash = "sha256:70dfd4f241c04bd9239d53b17f11e6ab672b9f1420364af63e8531198e3f5fe8", size = 81589, upload-time = "2025-10-06T14:10:09.254Z" }, + { url = "https://files.pythonhosted.org/packages/c8/9a/6ad1a9b37c2f72874f93e691b2e7ecb6137fb2b899983125db4204e47575/yarl-1.22.0-cp312-cp312-win_amd64.whl", hash = "sha256:8884d8b332a5e9b88e23f60bb166890009429391864c685e17bd73a9eda9105c", size = 87213, upload-time = "2025-10-06T14:10:11.369Z" }, + { url = "https://files.pythonhosted.org/packages/44/c5/c21b562d1680a77634d748e30c653c3ca918beb35555cff24986fff54598/yarl-1.22.0-cp312-cp312-win_arm64.whl", hash = "sha256:ea70f61a47f3cc93bdf8b2f368ed359ef02a01ca6393916bc8ff877427181e74", size = 81330, upload-time = "2025-10-06T14:10:13.112Z" }, + { url = "https://files.pythonhosted.org/packages/73/ae/b48f95715333080afb75a4504487cbe142cae1268afc482d06692d605ae6/yarl-1.22.0-py3-none-any.whl", hash = "sha256:1380560bdba02b6b6c90de54133c81c9f2a453dee9912fe58c1dcced1edb7cff", size = 46814, upload-time = "2025-10-06T14:12:53.872Z" }, +] + +[[package]] +name = "zarr" +version = "3.2.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "donfig" }, + { name = "google-crc32c" }, + { name = "numcodecs" }, + { name = "numpy" }, + { name = "packaging" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/93/8d/aeb164004f87543b06ef54f885d02c342c31ceb274e2bbec470a98927621/zarr-3.2.1.tar.gz", hash = "sha256:71565b738a0e7e8ed226f0516eba8c6bb53440ad7669a8c48ebb3534a161d035", size = 675161, upload-time = "2026-05-05T12:37:22.383Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/0a/469e2bd01be1490336e6c8707386845655d59261543315778a3ccc7e8019/zarr-3.2.1-py3-none-any.whl", hash = "sha256:f78cdd3d9687ad0e9f9cba2c5683b64f0c52589c19f685eeabe872e93cc0d2c7", size = 319617, upload-time = "2026-05-05T12:37:20.66Z" }, +] diff --git a/.github/workflows/cmake_build.yaml b/.github/workflows/cmake_build.yaml index e7c65e9..27b5116 100644 --- a/.github/workflows/cmake_build.yaml +++ b/.github/workflows/cmake_build.yaml @@ -30,16 +30,15 @@ jobs: cxx-compiler: ${{ matrix.compiler }} - name: Build tests run: cd build && pwd && make -j - - name: Install Python 3.10 + - name: Install UV run: | - sudo add-apt-repository -y ppa:deadsnakes/ppa - sudo apt update -y - sudo apt install -y python3.10 python3.10-venv python3.10-dev - sudo update-alternatives --install /usr/bin/python3 python3 /usr/bin/python3.10 1 + curl -LsSf https://astral.sh/uv/install.sh | sh + echo "$HOME/.local/bin" >> $GITHUB_PATH - name: Install test dependencies run: | - python3.10 -m pip install --upgrade pip setuptools wheel --no-input - python3.10 -m pip install yapf cpplint zarr xarray --no-input + uv sync --project .devcontainer --frozen + env: + UV_PROJECT_ENVIRONMENT: /usr - name: Run tests run: | cd build/mdio/ \ @@ -52,4 +51,8 @@ jobs: && ./mdio_utils_trim_test \ && ./mdio_utils_delete_test \ && ./mdio_variable_collection_test \ - && ./mdio_coordinate_selector_test \ No newline at end of file + && ./mdio_coordinate_selector_test \ + && ./mdio_header_variable_test \ + && ./mdio_zarr_test \ + && ./mdio_gcs_test \ + && ./mdio_s3_test \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index 82fa673..ce31c4a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -75,6 +75,8 @@ include(CMakeDependentOption) # provides a method to download dependencies: include(FetchContent) include(CMakeHelpers/MdioHelpers) +# optional code coverage support: +include(CodeCoverage) list(APPEND mdio_DEFAULT_COPTS "-Wno-deprecated-declarations" @@ -108,6 +110,7 @@ if(NOT CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR) set(mdio_INTERNAL_DEPS tensorstore::driver_array tensorstore::driver_zarr + tensorstore::driver_zarr3 tensorstore::driver_json tensorstore::kvstore_file tensorstore::stack diff --git a/cmake/CMakeHelpers/MdioHelpers.cmake b/cmake/CMakeHelpers/MdioHelpers.cmake index c2162d7..e51b49f 100644 --- a/cmake/CMakeHelpers/MdioHelpers.cmake +++ b/cmake/CMakeHelpers/MdioHelpers.cmake @@ -297,6 +297,12 @@ function(mdio_cc_test) PRIVATE ${mdio_CC_TEST_COPTS} ) + # Add coverage flags only to mdio test targets (not dependencies) + if(MDIO_ENABLE_COVERAGE) + target_compile_options(${_NAME} PRIVATE ${MDIO_COVERAGE_COMPILE_FLAGS}) + target_link_options(${_NAME} PRIVATE ${MDIO_COVERAGE_LINK_FLAGS}) + endif() + target_link_libraries(${_NAME} PUBLIC ${mdio_CC_TEST_DEPS} PRIVATE ${mdio_CC_TEST_LINKOPTS} diff --git a/cmake/CodeCoverage.cmake b/cmake/CodeCoverage.cmake new file mode 100644 index 0000000..120da7f --- /dev/null +++ b/cmake/CodeCoverage.cmake @@ -0,0 +1,166 @@ +# Copyright 2024 TGS +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# ============================================================================== +# Code Coverage Support (gcov + lcov) +# ============================================================================== +# +# Coverage instrumentation is applied ONLY to mdio test targets, not to +# dependencies like Tensorstore. This keeps build times and test execution fast. +# +# REQUIRED CMAKE FLAGS: +# -DMDIO_ENABLE_COVERAGE=ON Enable coverage instrumentation +# -DCMAKE_BUILD_TYPE=Debug Recommended for meaningful line coverage +# +# REQUIRED SYSTEM TOOLS: +# - gcov (usually bundled with GCC) +# - lcov (install via: apt install lcov / brew install lcov) +# - genhtml (included with lcov) +# +# USAGE: +# 1. Configure and build with coverage enabled: +# cd build +# cmake .. -DMDIO_ENABLE_COVERAGE=ON -DCMAKE_BUILD_TYPE=Debug +# make +# +# 2. Run tests to generate coverage data (coverage accumulates across runs): +# ./mdio/mdio_variable_test # single test +# ./mdio/mdio_variable_test && ./mdio/mdio_dataset_test # multiple tests +# ctest # all registered tests +# +# 3. Generate HTML coverage report: +# make coverage-capture +# +# 4. View the report: +# Open build/coverage_report/index.html in a browser +# +# AVAILABLE TARGETS: +# make coverage - Reset counters, capture data, generate report +# make coverage-capture - Capture current data and generate report (no reset) +# make coverage-reset - Zero out all coverage counters +# +# ============================================================================== + +option(MDIO_ENABLE_COVERAGE "Enable code coverage instrumentation (requires GCC or Clang)" OFF) + +if(MDIO_ENABLE_COVERAGE) + message(STATUS "Code coverage enabled") + + # Check for supported compiler + if(NOT CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang") + message(FATAL_ERROR "Code coverage requires GCC or Clang compiler") + endif() + + # Coverage compiler/linker flags - exported for use in MdioHelpers.cmake + # These are applied only to mdio targets, not to dependencies like Tensorstore + set(MDIO_COVERAGE_COMPILE_FLAGS "-fprofile-arcs;-ftest-coverage" CACHE INTERNAL "Coverage compile flags") + set(MDIO_COVERAGE_LINK_FLAGS "--coverage" CACHE INTERNAL "Coverage link flags") + + # Find required tools + find_program(LCOV_PATH lcov) + find_program(GENHTML_PATH genhtml) + + if(NOT LCOV_PATH) + message(WARNING "lcov not found - coverage report generation will not be available") + endif() + + if(NOT GENHTML_PATH) + message(WARNING "genhtml not found - coverage report generation will not be available") + endif() + + # Create coverage report target if tools are available + if(LCOV_PATH AND GENHTML_PATH) + # Custom target to generate coverage report + add_custom_target(coverage + COMMENT "Generating code coverage report..." + + # Clear previous coverage data + COMMAND ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --zerocounters + + # Run all tests (ctest must be run separately or you can uncomment below) + # COMMAND ${CMAKE_CTEST_COMMAND} --output-on-failure + + # Capture coverage data + COMMAND ${LCOV_PATH} + --directory ${CMAKE_BINARY_DIR} + --capture + --output-file ${CMAKE_BINARY_DIR}/coverage.info + --ignore-errors mismatch,negative + + # Remove coverage data for external dependencies + COMMAND ${LCOV_PATH} + --remove ${CMAKE_BINARY_DIR}/coverage.info + '/usr/*' + '${CMAKE_BINARY_DIR}/_deps/*' + '*/googletest/*' + '*/gtest/*' + '*/gmock/*' + '*_test.cc' + --output-file ${CMAKE_BINARY_DIR}/coverage.info + --ignore-errors unused,negative + + # Generate HTML report + COMMAND ${GENHTML_PATH} + ${CMAKE_BINARY_DIR}/coverage.info + --output-directory ${CMAKE_BINARY_DIR}/coverage_report + --title "MDIO Code Coverage" + --legend + --show-details + + WORKING_DIRECTORY ${CMAKE_BINARY_DIR} + ) + + # Target to just capture coverage (without zeroing first) + add_custom_target(coverage-capture + COMMENT "Capturing coverage data..." + + COMMAND ${LCOV_PATH} + --directory ${CMAKE_BINARY_DIR} + --capture + --output-file ${CMAKE_BINARY_DIR}/coverage.info + --ignore-errors mismatch,negative + + COMMAND ${LCOV_PATH} + --remove ${CMAKE_BINARY_DIR}/coverage.info + '/usr/*' + '${CMAKE_BINARY_DIR}/_deps/*' + '*/googletest/*' + '*/gtest/*' + '*/gmock/*' + '*_test.cc' + --output-file ${CMAKE_BINARY_DIR}/coverage.info + --ignore-errors unused,negative + + COMMAND ${GENHTML_PATH} + ${CMAKE_BINARY_DIR}/coverage.info + --output-directory ${CMAKE_BINARY_DIR}/coverage_report + --title "MDIO Code Coverage" + --legend + --show-details + + WORKING_DIRECTORY ${CMAKE_BINARY_DIR} + ) + + # Target to reset coverage counters + add_custom_target(coverage-reset + COMMENT "Resetting coverage counters..." + COMMAND ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --zerocounters + WORKING_DIRECTORY ${CMAKE_BINARY_DIR} + ) + + message(STATUS "Coverage targets available: 'coverage', 'coverage-capture', 'coverage-reset'") + message(STATUS "Coverage report will be generated at: ${CMAKE_BINARY_DIR}/coverage_report/index.html") + endif() +endif() + diff --git a/cmake/FindEXT_TENSORSTORE.cmake b/cmake/FindEXT_TENSORSTORE.cmake index 250f4fc..355a475 100644 --- a/cmake/FindEXT_TENSORSTORE.cmake +++ b/cmake/FindEXT_TENSORSTORE.cmake @@ -3,11 +3,11 @@ IF ( NOT TARGET tensorstore ) include(FetchContent) - FetchContent_Declare( - tensorstore - GIT_REPOSITORY - https://github.com/brian-michell/tensorstore.git - GIT_TAG v0.1.63_latest +FetchContent_Declare( + tensorstore + GIT_REPOSITORY + https://github.com/google/tensorstore.git + GIT_TAG 917edaf341217f750b7bd3b8db6e75e6db64eab8 ) FetchContent_MakeAvailable(tensorstore) diff --git a/cmake/Findnlohmann_json_schema_validator.cmake b/cmake/Findnlohmann_json_schema_validator.cmake index 6e82872..141d61e 100644 --- a/cmake/Findnlohmann_json_schema_validator.cmake +++ b/cmake/Findnlohmann_json_schema_validator.cmake @@ -10,7 +10,7 @@ if (NOT TARGET nlohmann_json_schema_validator) FetchContent_Declare( nlohmann_json_schema_validator GIT_REPOSITORY https://github.com/pboettch/json-schema-validator.git - GIT_TAG 2.2.0 + GIT_TAG 2.4.0 ) if(NOT BUILD_VALIDATOR) diff --git a/examples/dataset_example/src/dataset_example.cc b/examples/dataset_example/src/dataset_example.cc index 460b351..e7dc726 100644 --- a/examples/dataset_example/src/dataset_example.cc +++ b/examples/dataset_example/src/dataset_example.cc @@ -79,7 +79,7 @@ absl::Status Run() { /// handled by returning a "Result" object; the result can be tested for /// "ok()", there is also a convenient Tensorstore macro for doing assigment /// while handling errors. - MDIO_ASSIGN_OR_RETURN(auto dataset, dataset_result) + MDIO_ASSIGN_OR_RETURN(auto dataset, dataset_result); /// The dataset represent data on disk, the object holds only minimal state. /// Its memory footprint is small. @@ -113,7 +113,7 @@ absl::Status Run() { mdio::SliceDescriptor desc1 = {"inline", 20, 120, 1}; mdio::SliceDescriptor desc2 = {"crossline", 100, 200, 1}; - MDIO_ASSIGN_OR_RETURN(auto slice, dataset.isel(desc1, desc2)) + MDIO_ASSIGN_OR_RETURN(auto slice, dataset.isel(desc1, desc2)); /// The slice represents a subset of the MDIO data on disk, I/O operations can /// be made using the variables contained in this slice. std::cout << slice << "\n\n" << std::endl; @@ -123,7 +123,7 @@ absl::Status Run() { /// describing data, this means that the datatype is discovered at runtime. /// Here we request a Variable given we know its dtype of unit32: MDIO_ASSIGN_OR_RETURN(auto variableObject, - slice.variables.get("inline")) + slice.variables.get("inline")); /// The Variable object contains a collection of metadata associated with the /// seismic, including names, and units. std::cout << variableObject << std::endl; @@ -143,7 +143,7 @@ absl::Status Run() { // MDIO_ASSIGN_OR_RETURN( // auto data, tensorstore::Read(variableObject.get_store()).result() // ) - MDIO_ASSIGN_OR_RETURN(auto data, variableObject.Read().result()) + MDIO_ASSIGN_OR_RETURN(auto data, variableObject.Read().result()); auto d1 = data.get_data_accessor(); /// The read returns a SharedArray, which we can addrss like and array, in 3-d /// we might do something like this data({0, 0, 0}), here the inline label is @@ -163,7 +163,7 @@ absl::Status Run() { /// Optionally, the variable can handle the I/O. In this scenario data can be /// read into a VariableData object that retains it's dimension names and /// other metadata. - MDIO_ASSIGN_OR_RETURN(auto variableData, variableObject.Read().result()) + MDIO_ASSIGN_OR_RETURN(auto variableData, variableObject.Read().result()); /// The accessor method provides access to an underlying tensorstore /// SharedArray. @@ -185,7 +185,7 @@ absl::Status Run() { auto existing_dataset = mdio::Dataset::Open(dataset_path, mdio::constants::kOpen).result(); - MDIO_ASSIGN_OR_RETURN(dataset, existing_dataset) + MDIO_ASSIGN_OR_RETURN(dataset, existing_dataset); /// In the previous scenario used a slice to operate over the range of data /// [20, 120), this dataset is defined over the entire range, so we should see @@ -196,14 +196,14 @@ absl::Status Run() { /// to uint32_t (in this case), then the get method will return a result that /// is not OK. MDIO_ASSIGN_OR_RETURN(variableObject, - dataset.variables.get("inline")) + dataset.variables.get("inline")); inclusive_min = variableObject.get_store().domain()[0].interval().inclusive_min(); exclusive_max = variableObject.get_store().domain()[0].interval().exclusive_max(); /// Here we read all of the variable from disk into memory - MDIO_ASSIGN_OR_RETURN(variableData, variableObject.Read().result()) + MDIO_ASSIGN_OR_RETURN(variableData, variableObject.Read().result()); tick_labels = variableData.get_data_accessor(); for (Index i = inclusive_min; i < exclusive_max; i += 20) { std::cout << "dataset domain, " << variableData.variableName @@ -219,10 +219,10 @@ absl::Status Run() { } MDIO_ASSIGN_OR_RETURN( /// in this example, all the dims labels are unint32 - variableObject, dataset.variables.get(label)) + variableObject, dataset.variables.get(label)); /// Suppose we want to read existing values ... - MDIO_ASSIGN_OR_RETURN(variableData, variableObject.Read().result()) + MDIO_ASSIGN_OR_RETURN(variableData, variableObject.Read().result()); inclusive_min = variableObject.get_store().domain()[0].interval().inclusive_min(); @@ -242,7 +242,7 @@ absl::Status Run() { /// For the purposes of information hiding, you can also extract a single /// variable and its coordinates, with the other metadata. This acts like a /// regular Dataset, but has only a single variable. - MDIO_ASSIGN_OR_RETURN(auto inline_labels, dataset["cdp-x"]) + MDIO_ASSIGN_OR_RETURN(auto inline_labels, dataset["cdp-x"]); return absl::OkStatus(); } diff --git a/examples/real_data_example/src/real_data_example.cc b/examples/real_data_example/src/real_data_example.cc index d144f50..85196f9 100644 --- a/examples/real_data_example/src/real_data_example.cc +++ b/examples/real_data_example/src/real_data_example.cc @@ -52,7 +52,7 @@ absl::Status Run(const Descriptors... descriptors) { auto dataset, mdio::Dataset::Open(std::string(absl::GetFlag(FLAGS_dataset_path)), mdio::constants::kOpen) - .result()) + .result()); if (absl::GetFlag(FLAGS_print_dataset)) { std::cout << dataset << std::endl; @@ -70,7 +70,7 @@ absl::Status Run(const Descriptors... descriptors) { return absl::InvalidArgumentError("Seismic data must be 3D"); } - MDIO_ASSIGN_OR_RETURN(auto seismic_data, ReadWithProgress(variable).result()) + MDIO_ASSIGN_OR_RETURN(auto seismic_data, ReadWithProgress(variable).result()); auto seismic_accessor = seismic_data.get_data_accessor(); diff --git a/examples/xarray_integration/src/mdio_from_xarray.cc b/examples/xarray_integration/src/mdio_from_xarray.cc index cdcba54..b818459 100644 --- a/examples/xarray_integration/src/mdio_from_xarray.cc +++ b/examples/xarray_integration/src/mdio_from_xarray.cc @@ -27,12 +27,12 @@ absl::Status Run(std::string dataset_path) { MDIO_ASSIGN_OR_RETURN( auto dataset, - mdio::Dataset::Open(dataset_path, mdio::constants::kOpen).result()) + mdio::Dataset::Open(dataset_path, mdio::constants::kOpen).result()); MDIO_ASSIGN_OR_RETURN(auto variable, - dataset.variables.get("image")) + dataset.variables.get("image")); - MDIO_ASSIGN_OR_RETURN(auto variable_data, variable.Read().result()) + MDIO_ASSIGN_OR_RETURN(auto variable_data, variable.Read().result()); auto image = variable_data.get_data_accessor(); diff --git a/examples/xarray_integration/src/xarray_integration.cc b/examples/xarray_integration/src/xarray_integration.cc index 8e8f509..2daeffa 100644 --- a/examples/xarray_integration/src/xarray_integration.cc +++ b/examples/xarray_integration/src/xarray_integration.cc @@ -42,7 +42,7 @@ absl::Status Run(std::string dataset_path) { MDIO_ASSIGN_OR_RETURN(auto dataset, mdio::Dataset::from_json(json_spec, dataset_path, mdio::constants::kCreateClean) - .result()) + .result()); auto populate_inline = [](SharedArray& data) { for (auto i = data.domain()[0].inclusive_min(); diff --git a/examples/xarray_integration/src/xarray_integration.h b/examples/xarray_integration/src/xarray_integration.h index 70a72fe..7793b1e 100644 --- a/examples/xarray_integration/src/xarray_integration.h +++ b/examples/xarray_integration/src/xarray_integration.h @@ -49,7 +49,7 @@ using SharedArray = mdio::SharedArray; template mdio::Result> from_dataset( const mdio::Dataset& dataset, const std::string& variable_name) { - MDIO_ASSIGN_OR_RETURN(auto variable, dataset.variables.get(variable_name)) + MDIO_ASSIGN_OR_RETURN(auto variable, dataset.variables.get(variable_name)); return mdio::from_variable(variable); } diff --git a/mdio/CMakeLists.txt b/mdio/CMakeLists.txt index 47fc89f..208a929 100644 --- a/mdio/CMakeLists.txt +++ b/mdio/CMakeLists.txt @@ -54,6 +54,15 @@ endif() if (TARGET nlohmann_json_json) install(TARGETS nlohmann_json_json EXPORT mdioTargets) endif() +# Tensorstore's bazel_to_cmake (post-bzlmod rename) exposes nlohmann_json as +# `nlohmann_json_nlohmann_json` plus a companion `.alwayslink` static lib that +# the INTERFACE target links against. Both must be in the export set. +if (TARGET nlohmann_json_nlohmann_json) + install(TARGETS nlohmann_json_nlohmann_json EXPORT mdioTargets) +endif() +if (TARGET nlohmann_json_nlohmann_json.alwayslink) + install(TARGETS nlohmann_json_nlohmann_json.alwayslink EXPORT mdioTargets) +endif() # Export the targets to be used with FetchContent export(EXPORT mdioTargets FILE "${CMAKE_CURRENT_BINARY_DIR}/mdioTargets.cmake" NAMESPACE mdio::) @@ -101,6 +110,7 @@ mdio_cc_test( GTest::gmock_main tensorstore::driver_array tensorstore::driver_zarr + tensorstore::driver_zarr3 tensorstore::driver_json tensorstore::kvstore_file tensorstore::stack @@ -125,6 +135,7 @@ mdio_cc_test( GTest::gmock_main tensorstore::driver_array tensorstore::driver_zarr + tensorstore::driver_zarr3 tensorstore::driver_json tensorstore::kvstore_file tensorstore::stack @@ -134,6 +145,8 @@ mdio_cc_test( tensorstore::util_status_testutil nlohmann_json_schema_validator tensorstore::kvstore_s3 + absl::log_initialize + absl::log_globals ) mdio_cc_test( @@ -186,6 +199,7 @@ mdio_cc_test( GTest::gmock_main tensorstore::driver_array tensorstore::driver_zarr + tensorstore::driver_zarr3 tensorstore::driver_json tensorstore::kvstore_file tensorstore::stack @@ -209,6 +223,7 @@ mdio_cc_test( GTest::gmock_main tensorstore::driver_array tensorstore::driver_zarr + tensorstore::driver_zarr3 tensorstore::driver_json tensorstore::kvstore_file tensorstore::stack @@ -232,6 +247,7 @@ mdio_cc_test( GTest::gmock_main tensorstore::driver_array tensorstore::driver_zarr + tensorstore::driver_zarr3 tensorstore::driver_json tensorstore::kvstore_file tensorstore::tensorstore @@ -255,6 +271,7 @@ mdio_cc_test( GTest::gmock_main tensorstore::driver_array tensorstore::driver_zarr + tensorstore::driver_zarr3 tensorstore::driver_json tensorstore::kvstore_file tensorstore::stack @@ -277,6 +294,7 @@ mdio_cc_test( GTest::gmock_main tensorstore::driver_array tensorstore::driver_zarr + tensorstore::driver_zarr3 tensorstore::driver_json tensorstore::kvstore_file tensorstore::stack @@ -301,6 +319,7 @@ mdio_cc_test( GTest::gmock_main tensorstore::driver_array tensorstore::driver_zarr + tensorstore::driver_zarr3 tensorstore::driver_json tensorstore::kvstore_file tensorstore::stack @@ -321,14 +340,55 @@ mdio_cc_test( ${mdio_DEFAULT_COPTS} LINKOPTS ${mdio_DEFAULT_LINKOPTS} + DEPS + GTest::gmock_main + tensorstore::driver_json + tensorstore::tensorstore + nlohmann_json_schema_validator +) + +mdio_cc_test( + NAME + coordinate_selector_test + SRCS + coordinate_selector_test.cc + COPTS + ${mdio_DEFAULT_COPTS} + LINKOPTS + ${mdio_DEFAULT_LINKOPTS} DEPS GTest::gmock_main tensorstore::driver_array tensorstore::driver_zarr + tensorstore::driver_zarr3 tensorstore::driver_json tensorstore::kvstore_file + tensorstore::tensorstore tensorstore::stack + tensorstore::index_space_dim_expression + tensorstore::index_space_index_transform + tensorstore::util_status_testutil + nlohmann_json_schema_validator +) + +mdio_cc_test( + NAME + header_variable_test + SRCS + header_variable_test.cc + COPTS + ${mdio_DEFAULT_COPTS} + LINKOPTS + ${mdio_DEFAULT_LINKOPTS} + DEPS + GTest::gmock_main + tensorstore::driver_array + tensorstore::driver_zarr + tensorstore::driver_zarr3 + tensorstore::driver_json + tensorstore::kvstore_file tensorstore::tensorstore + tensorstore::stack tensorstore::index_space_dim_expression tensorstore::index_space_index_transform tensorstore::util_status_testutil @@ -337,9 +397,9 @@ mdio_cc_test( mdio_cc_test( NAME - coordinate_selector_test + zarr_test SRCS - coordinate_selector_test.cc + zarr/zarr_test.cc COPTS ${mdio_DEFAULT_COPTS} LINKOPTS @@ -348,6 +408,7 @@ mdio_cc_test( GTest::gmock_main tensorstore::driver_array tensorstore::driver_zarr + tensorstore::driver_zarr3 tensorstore::driver_json tensorstore::kvstore_file tensorstore::tensorstore @@ -357,3 +418,4 @@ mdio_cc_test( tensorstore::util_status_testutil nlohmann_json_schema_validator ) + diff --git a/mdio/acceptance_test.cc b/mdio/acceptance_test.cc index c464291..2510165 100644 --- a/mdio/acceptance_test.cc +++ b/mdio/acceptance_test.cc @@ -12,6 +12,15 @@ // See the License for the specific language governing permissions and // limitations under the License. +/** + * @file acceptance_test.cc + * @brief Unified acceptance tests for both Zarr V2 and V3 driver support. + * + * This file contains parameterized tests that verify the MDIO library works + * correctly with both Zarr V2 and Zarr V3 formats, including variable creation, + * reading, writing, and dataset operations. + */ + #include #include #include @@ -19,41 +28,336 @@ #include #include +#include #include // NOLINT #include "mdio/dataset.h" #include "mdio/dataset_factory.h" +#include "mdio/zarr/zarr.h" namespace { -constexpr char PYTHON_EXECUTABLE[] = "python3"; // Don't specify absolute path +constexpr char PYTHON_EXECUTABLE[] = "python3"; constexpr char PROJECT_BASE_PATH_ENV[] = "PROJECT_BASE_PATH"; constexpr char DEFAULT_BASE_PATH[] = "../.."; -constexpr char ZARR_SCRIPT_RELATIVE_PATH[] = - "/mdio/regression_tests/zarr_compatibility.py"; constexpr char XARRAY_SCRIPT_RELATIVE_PATH[] = "/mdio/regression_tests/xarray_compatibility_test.py"; -constexpr char FILE_PATH_BASE[] = "./zarrs/acceptance/"; +constexpr char MULTIDIMIO_SCRIPT_RELATIVE_PATH[] = + "/mdio/regression_tests/multidimio_compatibility_test.py"; constexpr int ERROR_CODE = EXIT_FAILURE; constexpr int SUCCESS_CODE = EXIT_SUCCESS; +using float16_t = mdio::dtypes::float_16_t; + +/** + * @brief Test variable definition with dtype info for V2 and V3. + */ +struct TestVariableDef { + std::string name; + std::string dtype_v2; + std::string dtype_v3; + std::string long_name; + mdio::DataType expected_dtype; +}; + +// Common test variable definitions used across multiple tests +const std::vector kTestVariables = { + {"i2", "> OpenTestVariable(const std::string& name, + mdio::zarr::ZarrVersion version, + const std::string& base_path) { + return mdio::Variable<>::Open(CreateBaseSpec(version, name, base_path), + mdio::constants::kOpen); +} + +/** + * @brief Gets the Python script base path from environment. + */ +const char* GetPythonBasePath() { + const char* basePath = std::getenv(PROJECT_BASE_PATH_ENV); + if (!basePath) { + std::cout << "PROJECT_BASE_PATH environment variable not set. Expecting to " + "be in the 'build/mdio' directory." + << std::endl; + basePath = DEFAULT_BASE_PATH; + } + return basePath; +} + +/** + * @brief Returns the dataset manifest JSON for the given version. + * Both versions use the same manifest structure including struct arrays. + */ +std::string GetDatasetManifest(mdio::zarr::ZarrVersion version) { + std::string name = + version == mdio::zarr::ZarrVersion::kV3 ? "campos_3d_v3" : "campos_3d"; + + // clang-format off + std::string manifest = R"( +{ + "metadata": { + "name": ")" + name + R"(", + "apiVersion": "1.0.0", + "createdOn": "2023-12-12T15:02:06.413469-06:00", + "attributes": { + "textHeader": [ + "C01 .......................... ", + "C02 .......................... ", + "C03 .......................... " + ], + "foo": "bar" + } + }, + "variables": [ + { + "name": "image", + "dataType": "float32", + "dimensions": [ + {"name": "inline", "size": 256}, + {"name": "crossline", "size": 512}, + {"name": "depth", "size": 384} + ], + "metadata": { + "chunkGrid": { + "name": "regular", + "configuration": { "chunkShape": [128, 128, 128] } + }, + "statsV1": { + "count": 100, + "sum": 1215.1, + "sumSquares": 125.12, + "min": 5.61, + "max": 10.84, + "histogram": {"binCenters": [1, 2], "counts": [10, 15]} + }, + "attributes": { + "fizz": "buzz" + } + }, + "coordinates": ["inline", "crossline", "depth", "cdp-x", "cdp-y"], + "compressor": {"name": "blosc", "algorithm": "zstd"} + }, + { + "name": "velocity", + "dataType": "float64", + "dimensions": ["inline", "crossline", "depth"], + "metadata": { + "chunkGrid": { + "name": "regular", + "configuration": { "chunkShape": [128, 128, 128] } + }, + "unitsV1": {"speed": "m/s"} + }, + "coordinates": ["inline", "crossline", "depth", "cdp-x", "cdp-y"] + }, + { + "name": "image_inline", + "dataType": "int16", + "dimensions": ["inline", "crossline", "depth"], + "longName": "inline optimized version of 3d_stack", + "compressor": {"name": "blosc", "algorithm": "zstd"}, + "metadata": { + "chunkGrid": { + "name": "regular", + "configuration": { "chunkShape": [128, 128, 128] } + } + }, + "coordinates": ["inline", "crossline", "depth", "cdp-x", "cdp-y"] + }, + { + "name": "image_headers", + "dataType": { + "fields": [ + {"name": "cdp-x", "format": "int32"}, + {"name": "cdp-y", "format": "int32"}, + {"name": "elevation", "format": "float16"}, + {"name": "some_scalar", "format": "float16"} + ] + }, + "dimensions": ["inline", "crossline"], + "metadata": { + "chunkGrid": { + "name": "regular", + "configuration": { "chunkShape": [128, 128] } + } + }, + "coordinates": ["inline", "crossline", "cdp-x", "cdp-y"] + }, + { + "name": "inline", + "dataType": "uint32", + "dimensions": [{"name": "inline", "size": 256}] + }, + { + "name": "crossline", + "dataType": "uint32", + "dimensions": [{"name": "crossline", "size": 512}] + }, + { + "name": "depth", + "dataType": "uint32", + "dimensions": [{"name": "depth", "size": 384}], + "metadata": { + "unitsV1": { "length": "m" } + } + }, + { + "name": "cdp-x", + "dataType": "float32", + "dimensions": [ + {"name": "inline", "size": 256}, + {"name": "crossline", "size": 512} + ], + "metadata": { + "unitsV1": { "length": "m" } + } + }, + { + "name": "cdp-y", + "dataType": "float32", + "dimensions": [ + {"name": "inline", "size": 256}, + {"name": "crossline", "size": 512} + ], + "metadata": { + "unitsV1": { "length": "m" } + } + } + ] +} + )"; + // clang-format on + return manifest; +} + /** * @brief Executes the Python regression tests - * - * @param scriptPath Which script to run, should be either - * ZARR_SCRIPT_RELATIVE_PATH or XARRAY_SCRIPT_RELATIVE_PATH - * @param arg The argument to pass to the script - * @return int The status code of the script execution */ int executePythonScript(const std::string& scriptPath, const std::vector& args) { - // Ensure scriptPath and args are sanitized if (scriptPath.empty() || args.empty()) { std::cerr << "Error: scriptPath or args are empty." << std::endl; return ERROR_CODE; } - // Ensure the scriptPath is an absolute path or default base path if (scriptPath[0] != '/' && scriptPath.find(DEFAULT_BASE_PATH) != 0) { std::cerr << "Error: PROJECT_BASE_PATH must be an absolute path or not be set." @@ -61,1018 +365,279 @@ int executePythonScript(const std::string& scriptPath, return ERROR_CODE; } - // Prepare arguments for execvp std::vector execArgs = {PYTHON_EXECUTABLE, scriptPath.c_str()}; for (const auto& arg : args) { execArgs.push_back(arg.c_str()); } execArgs.push_back(nullptr); - // Execute the Python script if (execvp(execArgs[0], const_cast(execArgs.data())) == -1) { perror("execvp failed"); return ERROR_CODE; } - return SUCCESS_CODE; // This line will never be reached if execvp is - // successful + return SUCCESS_CODE; } -namespace VariableTesting { - -using float16_t = mdio::dtypes::float_16_t; - -// TODO(BrianMichell): Extend test coverage to include uint -nlohmann::json i2Base = R"( - { - "driver": "zarr", - "kvstore": { - "driver": "file", - "path": "zarrs/acceptance/i2" - } - } -)"_json; - -nlohmann::json i4Base = R"( - { - "driver": "zarr", - "kvstore": { - "driver": "file", - "path": "zarrs/acceptance/i4" - } - } -)"_json; - -nlohmann::json i8Base = R"( - { - "driver": "zarr", - "kvstore": { - "driver": "file", - "path": "zarrs/acceptance/i8" - } - } -)"_json; - -nlohmann::json f2Base = R"( - { - "driver": "zarr", - "kvstore": { - "driver": "file", - "path": "zarrs/acceptance/f2" - } - } -)"_json; +/** + * @brief Runs Python scripts with fork/wait and checks results. + * @return true if all scripts passed, false otherwise. + */ +bool RunPythonScripts(const std::string& script_path, + const std::vector>& arg_sets, + const std::string& skip_message) { + std::vector pids; -nlohmann::json f4Base = R"( - { - "driver": "zarr", - "kvstore": { - "driver": "file", - "path": "zarrs/acceptance/f4" - } + for (const auto& args : arg_sets) { + pid_t pid = fork(); + if (pid == 0) { + int result = executePythonScript(script_path, args); + if (result == 0xfd00) { + exit(SUCCESS_CODE); + } + exit(result); + } else if (pid > 0) { + pids.push_back(pid); + } else { + perror("fork failed"); + return false; } -)"_json; + } -nlohmann::json f8Base = R"( - { - "driver": "zarr", - "kvstore": { - "driver": "file", - "path": "zarrs/acceptance/f8" - } + bool all_passed = true; + for (pid_t pid : pids) { + int status; + if (waitpid(pid, &status, 0) == -1) { + perror("waitpid failed"); + return false; } -)"_json; - -nlohmann::json voidedBase = R"( - { - "driver": "zarr", - "kvstore": { - "driver": "file", - "path": "zarrs/acceptance/voided" - } + if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) { + if (WIFEXITED(status) && WEXITSTATUS(status) == 0xfd00) { + // Import error - will be handled by caller + continue; + } + all_passed = false; } -)"_json; + } + return all_passed; +} -nlohmann::json u2Base = R"( - { - "driver": "zarr", - "kvstore": { - "driver": "file", - "path": "zarrs/acceptance/u2" - } - } -)"_json; +// ============================================================================ +// Parameterized Variable Tests +// ============================================================================ -nlohmann::json u4Base = R"( - { - "driver": "zarr", - "kvstore": { - "driver": "file", - "path": "zarrs/acceptance/u4" - } - } -)"_json; +class VariableTest : public ::testing::TestWithParam { + protected: + void SetUp() override { + version_ = GetParam(); + base_path_ = GetBasePath(version_); + driver_ = GetTestDriverName(version_); + } -// Test to set up some pre-existing elements for testing -TEST(Variable, SETUP) { - mdio::TransactionalOpenOptions options; - auto opt = options.Set(std::move(mdio::constants::kCreateClean)); - nlohmann::json i2Spec = R"( - { - "driver": "zarr", - "kvstore": { - "driver": "file", - "path": "zarrs/acceptance/i2" - }, - "metadata": { - "dtype": "::Open(i2Base, mdio::constants::kOpen).status().ok()); - EXPECT_TRUE( - mdio::Variable<>::Open(i4Base, mdio::constants::kOpen).status().ok()); - EXPECT_TRUE( - mdio::Variable<>::Open(i8Base, mdio::constants::kOpen).status().ok()); - EXPECT_TRUE( - mdio::Variable<>::Open(f2Base, mdio::constants::kOpen).status().ok()); - EXPECT_TRUE( - mdio::Variable<>::Open(f4Base, mdio::constants::kOpen).status().ok()); - EXPECT_TRUE( - mdio::Variable<>::Open(f8Base, mdio::constants::kOpen).status().ok()); - EXPECT_TRUE( - mdio::Variable<>::Open(voidedBase, mdio::constants::kOpen).status().ok()); +TEST_P(VariableTest, open) { + for (const auto& def : kTestVariables) { + auto var = OpenTestVariable(def.name, version_, base_path_); + EXPECT_TRUE(var.status().ok()) + << "Failed to open " << def.name << ": " << var.status(); + } } -TEST(Variable, name) { - auto i2 = mdio::Variable<>::Open(i2Base, mdio::constants::kOpen); - auto i4 = mdio::Variable<>::Open(i4Base, mdio::constants::kOpen); - auto i8 = mdio::Variable<>::Open(i8Base, mdio::constants::kOpen); - auto f2 = mdio::Variable<>::Open(f2Base, mdio::constants::kOpen); - auto f4 = mdio::Variable<>::Open(f4Base, mdio::constants::kOpen); - auto f8 = mdio::Variable<>::Open(f8Base, mdio::constants::kOpen); - auto voided = mdio::Variable<>::Open(voidedBase, mdio::constants::kOpen); - ASSERT_TRUE(i2.status().ok()) << i2.status(); - ASSERT_TRUE(i4.status().ok()) << i4.status(); - ASSERT_TRUE(i8.status().ok()) << i8.status(); - ASSERT_TRUE(f2.status().ok()) << f2.status(); - ASSERT_TRUE(f4.status().ok()) << f4.status(); - ASSERT_TRUE(f8.status().ok()) << f8.status(); - ASSERT_TRUE(voided.status().ok()) << voided.status(); - - EXPECT_EQ(i2.value().get_variable_name(), "i2") - << i2.value().get_variable_name(); - EXPECT_EQ(i4.value().get_variable_name(), "i4") - << i4.value().get_variable_name(); - EXPECT_EQ(i8.value().get_variable_name(), "i8") - << i8.value().get_variable_name(); - EXPECT_EQ(f2.value().get_variable_name(), "f2") - << f2.value().get_variable_name(); - EXPECT_EQ(f4.value().get_variable_name(), "f4") - << f4.value().get_variable_name(); - EXPECT_EQ(f8.value().get_variable_name(), "f8") - << f8.value().get_variable_name(); - EXPECT_EQ(voided.value().get_variable_name(), "voided") - << voided.value().get_variable_name(); +TEST_P(VariableTest, name) { + for (const auto& def : kTestVariables) { + auto var = OpenTestVariable(def.name, version_, base_path_); + ASSERT_TRUE(var.status().ok()) << var.status(); + EXPECT_EQ(var.value().get_variable_name(), def.name); + } } -TEST(Variable, longName) { - auto i2 = mdio::Variable<>::Open(i2Base, mdio::constants::kOpen); - auto i4 = mdio::Variable<>::Open(i4Base, mdio::constants::kOpen); - auto i8 = mdio::Variable<>::Open(i8Base, mdio::constants::kOpen); - auto f2 = mdio::Variable<>::Open(f2Base, mdio::constants::kOpen); - auto f4 = mdio::Variable<>::Open(f4Base, mdio::constants::kOpen); - auto f8 = mdio::Variable<>::Open(f8Base, mdio::constants::kOpen); - auto voided = mdio::Variable<>::Open(voidedBase, mdio::constants::kOpen); - ASSERT_TRUE(i2.status().ok()) << i2.status(); - ASSERT_TRUE(i4.status().ok()) << i4.status(); - ASSERT_TRUE(i8.status().ok()) << i8.status(); - ASSERT_TRUE(f2.status().ok()) << f2.status(); - ASSERT_TRUE(f4.status().ok()) << f4.status(); - ASSERT_TRUE(f8.status().ok()) << f8.status(); - ASSERT_TRUE(voided.status().ok()) << voided.status(); - - EXPECT_EQ(i2.value().get_long_name(), "2-byte integer test") - << i2.value().get_long_name(); - EXPECT_EQ(i4.value().get_long_name(), "4-byte integer test") - << i4.value().get_long_name(); - EXPECT_EQ(i8.value().get_long_name(), "8-byte integer test") - << i8.value().get_long_name(); - EXPECT_EQ(f2.value().get_long_name(), "2-byte float test") - << f2.value().get_long_name(); - EXPECT_EQ(f4.value().get_long_name(), "4-byte float test") - << f4.value().get_long_name(); - EXPECT_EQ(f8.value().get_long_name(), "8-byte float test") - << f8.value().get_long_name(); - EXPECT_EQ(voided.value().get_long_name(), "struct array test") - << voided.value().get_long_name(); +TEST_P(VariableTest, longName) { + for (const auto& def : kTestVariables) { + auto var = OpenTestVariable(def.name, version_, base_path_); + ASSERT_TRUE(var.status().ok()) << var.status(); + EXPECT_EQ(var.value().get_long_name(), def.long_name); + } } -TEST(Variable, optionalAttrs) { - auto i2 = mdio::Variable<>::Open(i2Base, mdio::constants::kOpen); - auto i4 = mdio::Variable<>::Open(i4Base, mdio::constants::kOpen); - auto i8 = mdio::Variable<>::Open(i8Base, mdio::constants::kOpen); - auto f2 = mdio::Variable<>::Open(f2Base, mdio::constants::kOpen); - auto f4 = mdio::Variable<>::Open(f4Base, mdio::constants::kOpen); - auto f8 = mdio::Variable<>::Open(f8Base, mdio::constants::kOpen); - auto voided = mdio::Variable<>::Open(voidedBase, mdio::constants::kOpen); +TEST_P(VariableTest, optionalAttrs) { + auto i2 = OpenTestVariable("i2", version_, base_path_); ASSERT_TRUE(i2.status().ok()) << i2.status(); - ASSERT_TRUE(i4.status().ok()) << i4.status(); - ASSERT_TRUE(i8.status().ok()) << i8.status(); - ASSERT_TRUE(f2.status().ok()) << f2.status(); - ASSERT_TRUE(f4.status().ok()) << f4.status(); - ASSERT_TRUE(f8.status().ok()) << f8.status(); - ASSERT_TRUE(voided.status().ok()) << voided.status(); - - EXPECT_EQ(i2.value().GetAttributes()["attributes"]["foo"], "bar") - << i2.value().GetAttributes(); - EXPECT_EQ(i4.value().GetAttributes()["attributes"]["foo"], "bar") - << i4.value().GetAttributes(); - EXPECT_EQ(i8.value().GetAttributes()["attributes"]["foo"], "bar") - << i8.value().GetAttributes(); - EXPECT_EQ(f2.value().GetAttributes()["attributes"]["foo"], "bar") - << f2.value().GetAttributes(); - EXPECT_EQ(f4.value().GetAttributes()["attributes"]["foo"], "bar") - << f4.value().GetAttributes(); - EXPECT_EQ(f8.value().GetAttributes()["attributes"]["foo"], "bar") - << f8.value().GetAttributes(); - EXPECT_EQ(voided.value().GetAttributes()["attributes"]["foo"], "bar") - << voided.value().GetAttributes(); + EXPECT_EQ(i2.value().GetAttributes()["attributes"]["foo"], "bar"); } -TEST(Variable, namedDimensions) { - auto i2 = mdio::Variable<>::Open(i2Base, mdio::constants::kOpen); - auto i4 = mdio::Variable<>::Open(i4Base, mdio::constants::kOpen); - auto i8 = mdio::Variable<>::Open(i8Base, mdio::constants::kOpen); - auto f2 = mdio::Variable<>::Open(f2Base, mdio::constants::kOpen); - auto f4 = mdio::Variable<>::Open(f4Base, mdio::constants::kOpen); - auto f8 = mdio::Variable<>::Open(f8Base, mdio::constants::kOpen); - auto voided = mdio::Variable<>::Open(voidedBase, mdio::constants::kOpen); +TEST_P(VariableTest, namedDimensions) { + auto i2 = OpenTestVariable("i2", version_, base_path_); ASSERT_TRUE(i2.status().ok()) << i2.status(); - ASSERT_TRUE(i4.status().ok()) << i4.status(); - ASSERT_TRUE(i8.status().ok()) << i8.status(); - ASSERT_TRUE(f2.status().ok()) << f2.status(); - ASSERT_TRUE(f4.status().ok()) << f4.status(); - ASSERT_TRUE(f8.status().ok()) << f8.status(); - ASSERT_TRUE(voided.status().ok()) << voided.status(); - - EXPECT_EQ(i2.value().getMetadata()["dimension_names"].size(), 2) - << i2.value().getMetadata(); - EXPECT_EQ(i4.value().getMetadata()["dimension_names"].size(), 2) - << i4.value().getMetadata(); - EXPECT_EQ(i8.value().getMetadata()["dimension_names"].size(), 2) - << i8.value().getMetadata(); - EXPECT_EQ(f2.value().getMetadata()["dimension_names"].size(), 2) - << f2.value().getMetadata(); - EXPECT_EQ(f4.value().getMetadata()["dimension_names"].size(), 2) - << f4.value().getMetadata(); - EXPECT_EQ(f8.value().getMetadata()["dimension_names"].size(), 2) - << f8.value().getMetadata(); - EXPECT_EQ(voided.value().getMetadata()["dimension_names"].size(), 2) - << voided.value().getMetadata(); + EXPECT_EQ(i2.value().getMetadata()["dimension_names"].size(), 2); } -TEST(Variable, sliceByDimIdx) { - auto i2 = mdio::Variable<>::Open(i2Base, mdio::constants::kOpen); - auto i4 = mdio::Variable<>::Open(i4Base, mdio::constants::kOpen); - auto i8 = mdio::Variable<>::Open(i8Base, mdio::constants::kOpen); - auto f2 = mdio::Variable<>::Open(f2Base, mdio::constants::kOpen); - auto f4 = mdio::Variable<>::Open(f4Base, mdio::constants::kOpen); - auto f8 = mdio::Variable<>::Open(f8Base, mdio::constants::kOpen); - auto voided = mdio::Variable<>::Open(voidedBase, mdio::constants::kOpen); +TEST_P(VariableTest, sliceByDimIdx) { + auto i2 = OpenTestVariable("i2", version_, base_path_); + auto f4 = OpenTestVariable("f4", version_, base_path_); ASSERT_TRUE(i2.status().ok()) << i2.status(); - ASSERT_TRUE(i4.status().ok()) << i4.status(); - ASSERT_TRUE(i8.status().ok()) << i8.status(); - ASSERT_TRUE(f2.status().ok()) << f2.status(); ASSERT_TRUE(f4.status().ok()) << f4.status(); - ASSERT_TRUE(f8.status().ok()) << f8.status(); - ASSERT_TRUE(voided.status().ok()) << voided.status(); mdio::RangeDescriptor zeroIdxSlice = {0, 0, 5, 1}; mdio::RangeDescriptor oneIdxSlice = {1, 0, 5, 1}; auto i2Slice = i2.value().slice(zeroIdxSlice, oneIdxSlice); - auto i4Slice = i4.value().slice(zeroIdxSlice, oneIdxSlice); - auto i8Slice = i8.value().slice(zeroIdxSlice, oneIdxSlice); - auto f2Slice = f2.value().slice(zeroIdxSlice, oneIdxSlice); auto f4Slice = f4.value().slice(zeroIdxSlice, oneIdxSlice); - auto f8Slice = f8.value().slice(zeroIdxSlice, oneIdxSlice); - auto voidedSlice = voided.value().slice(zeroIdxSlice, oneIdxSlice); EXPECT_TRUE(i2Slice.status().ok()) << i2Slice.status(); - EXPECT_TRUE(i4Slice.status().ok()) << i4Slice.status(); - EXPECT_TRUE(i8Slice.status().ok()) << i8Slice.status(); - EXPECT_TRUE(f2Slice.status().ok()) << f2Slice.status(); EXPECT_TRUE(f4Slice.status().ok()) << f4Slice.status(); - EXPECT_TRUE(f8Slice.status().ok()) << f8Slice.status(); - EXPECT_TRUE(voidedSlice.status().ok()) << voidedSlice.status(); EXPECT_THAT(i2Slice.value().dimensions().shape(), - ::testing::ElementsAre(5, 5)) - << i2Slice.value().dimensions(); - EXPECT_THAT(i4Slice.value().dimensions().shape(), - ::testing::ElementsAre(5, 5)) - << i4Slice.value().dimensions(); - EXPECT_THAT(i8Slice.value().dimensions().shape(), - ::testing::ElementsAre(5, 5)) - << i8Slice.value().dimensions(); - EXPECT_THAT(f2Slice.value().dimensions().shape(), - ::testing::ElementsAre(5, 5)) - << f2Slice.value().dimensions(); + ::testing::ElementsAre(5, 5)); EXPECT_THAT(f4Slice.value().dimensions().shape(), - ::testing::ElementsAre(5, 5)) - << f4Slice.value().dimensions(); - EXPECT_THAT(f8Slice.value().dimensions().shape(), - ::testing::ElementsAre(5, 5)) - << f8Slice.value().dimensions(); - EXPECT_THAT(voidedSlice.value().dimensions().shape(), - ::testing::ElementsAre(5, 5, 14)) - << voidedSlice.value().dimensions(); + ::testing::ElementsAre(5, 5)); } -TEST(Variable, zarrCompatibility) { - const char* basePath = std::getenv(PROJECT_BASE_PATH_ENV); - if (!basePath) { - std::cout << "PROJECT_BASE_PATH environment variable not set. Expecting to " - "be in the 'build/mdio' directory." - << std::endl; - basePath = DEFAULT_BASE_PATH; - } - - // Resolve the absolute path for the script - std::string srcPath = std::string(basePath) + ZARR_SCRIPT_RELATIVE_PATH; - - // Ensure that srcPath is valid and points to an existing file - if (access(srcPath.c_str(), F_OK) == -1) { - std::cerr << "Error: Python script not found at " << srcPath << std::endl; - FAIL() << "Script not found: " << srcPath; - } - - std::vector args = {"i2", "i4", "i8", "f2", - "f4", "f8", "voided"}; - std::vector pids; - - for (const auto& arg : args) { - pid_t pid = fork(); - if (pid == 0) { - // Child process - int result = executePythonScript(srcPath, {FILE_PATH_BASE + arg}); - if (result == 0xfd00) { // 0xfd from Python is 0xfd00 in C++ - GTEST_SKIP() << "Zarr compatibility skipped due to import error for " - "required library"; - exit(SUCCESS_CODE); - } - exit(result); - } else if (pid > 0) { - // Parent process - pids.push_back(pid); - } else { - // Fork failed - perror("fork failed"); - FAIL() << "fork failed"; - } - } +TEST_P(VariableTest, sliceByDimName) { + auto i2 = OpenTestVariable("i2", version_, base_path_); + ASSERT_TRUE(i2.status().ok()) << i2.status(); - // Wait for all child processes - for (pid_t pid : pids) { - int status; - if (waitpid(pid, &status, 0) == -1) { - perror("waitpid failed"); - FAIL() << "waitpid failed"; - } - if (WIFEXITED(status) && WEXITSTATUS(status) == 0xfd00) { - GTEST_SKIP() << "Zarr compatibility skipped due to import error for " - "required library"; - } - EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0) - << "Failed to read one of the arguments\n\tMore detailed error " - "expected above..."; - } -} + mdio::RangeDescriptor inlineSlice = {"inline", 0, 5, 1}; + mdio::RangeDescriptor crosslineSpec = {"crossline", 0, 5, 1}; + auto i2Slice = i2.value().slice(inlineSlice, crosslineSpec); -TEST(Variable, dimensionUnits) { - auto i2 = mdio::Variable<>::Open(i2Base, mdio::constants::kOpen); - auto i4 = mdio::Variable<>::Open(i4Base, mdio::constants::kOpen); - auto i8 = mdio::Variable<>::Open(i8Base, mdio::constants::kOpen); - auto f2 = mdio::Variable<>::Open(f2Base, mdio::constants::kOpen); - auto f4 = mdio::Variable<>::Open(f4Base, mdio::constants::kOpen); - auto f8 = mdio::Variable<>::Open(f8Base, mdio::constants::kOpen); - auto voided = mdio::Variable<>::Open(voidedBase, mdio::constants::kOpen); - ASSERT_TRUE(i2.status().ok()) << i2.status(); - ASSERT_TRUE(i4.status().ok()) << i4.status(); - ASSERT_TRUE(i8.status().ok()) << i8.status(); - ASSERT_TRUE(f2.status().ok()) << f2.status(); - ASSERT_TRUE(f4.status().ok()) << f4.status(); - ASSERT_TRUE(f8.status().ok()) << f8.status(); - ASSERT_TRUE(voided.status().ok()) << voided.status(); - - EXPECT_TRUE(i2.value().getMetadata().contains("dimension_units")) - << i2.value().getMetadata(); - EXPECT_TRUE(i4.value().getMetadata().contains("dimension_units")) - << i4.value().getMetadata(); - EXPECT_TRUE(i8.value().getMetadata().contains("dimension_units")) - << i8.value().getMetadata(); - EXPECT_TRUE(f2.value().getMetadata().contains("dimension_units")) - << f2.value().getMetadata(); - EXPECT_TRUE(f4.value().getMetadata().contains("dimension_units")) - << f4.value().getMetadata(); - EXPECT_TRUE(f8.value().getMetadata().contains("dimension_units")) - << f8.value().getMetadata(); - EXPECT_TRUE(voided.value().getMetadata().contains("dimension_units")) - << voided.value().getMetadata(); + EXPECT_TRUE(i2Slice.status().ok()) << i2Slice.status(); + EXPECT_THAT(i2Slice.value().dimensions().shape(), + ::testing::ElementsAre(5, 5)); } -TEST(Variable, chunkSize) { - auto i2 = mdio::Variable<>::Open(i2Base, mdio::constants::kOpen); - auto i4 = mdio::Variable<>::Open(i4Base, mdio::constants::kOpen); - auto i8 = mdio::Variable<>::Open(i8Base, mdio::constants::kOpen); - auto f2 = mdio::Variable<>::Open(f2Base, mdio::constants::kOpen); - auto f4 = mdio::Variable<>::Open(f4Base, mdio::constants::kOpen); - auto f8 = mdio::Variable<>::Open(f8Base, mdio::constants::kOpen); - auto voided = mdio::Variable<>::Open(voidedBase, mdio::constants::kOpen); +TEST_P(VariableTest, dimensionUnits) { + auto i2 = OpenTestVariable("i2", version_, base_path_); ASSERT_TRUE(i2.status().ok()) << i2.status(); - ASSERT_TRUE(i4.status().ok()) << i4.status(); - ASSERT_TRUE(i8.status().ok()) << i8.status(); - ASSERT_TRUE(f2.status().ok()) << f2.status(); - ASSERT_TRUE(f4.status().ok()) << f4.status(); - ASSERT_TRUE(f8.status().ok()) << f8.status(); - ASSERT_TRUE(voided.status().ok()) << voided.status(); - - EXPECT_TRUE(i2.value().get_chunk_shape().status().ok()) - << i2.value().get_chunk_shape().status(); - EXPECT_TRUE(i4.value().get_chunk_shape().status().ok()) - << i4.value().get_chunk_shape().status(); - EXPECT_TRUE(i8.value().get_chunk_shape().status().ok()) - << i8.value().get_chunk_shape().status(); - EXPECT_TRUE(f2.value().get_chunk_shape().status().ok()) - << f2.value().get_chunk_shape().status(); - EXPECT_TRUE(f4.value().get_chunk_shape().status().ok()) - << f4.value().get_chunk_shape().status(); - EXPECT_TRUE(f8.value().get_chunk_shape().status().ok()) - << f8.value().get_chunk_shape().status(); - EXPECT_TRUE(voided.value().get_chunk_shape().status().ok()) - << voided.value().get_chunk_shape().status(); + EXPECT_TRUE(i2.value().getMetadata().contains("dimension_units")); } -TEST(Variable, getCompressor) { - auto i2 = mdio::Variable<>::Open(i2Base, mdio::constants::kOpen); - auto i4 = mdio::Variable<>::Open(i4Base, mdio::constants::kOpen); - auto i8 = mdio::Variable<>::Open(i8Base, mdio::constants::kOpen); - auto f2 = mdio::Variable<>::Open(f2Base, mdio::constants::kOpen); - auto f4 = mdio::Variable<>::Open(f4Base, mdio::constants::kOpen); - auto f8 = mdio::Variable<>::Open(f8Base, mdio::constants::kOpen); - auto voided = mdio::Variable<>::Open(voidedBase, mdio::constants::kOpen); +TEST_P(VariableTest, chunkSize) { + auto i2 = OpenTestVariable("i2", version_, base_path_); ASSERT_TRUE(i2.status().ok()) << i2.status(); - ASSERT_TRUE(i4.status().ok()) << i4.status(); - ASSERT_TRUE(i8.status().ok()) << i8.status(); - ASSERT_TRUE(f2.status().ok()) << f2.status(); - ASSERT_TRUE(f4.status().ok()) << f4.status(); - ASSERT_TRUE(f8.status().ok()) << f8.status(); - ASSERT_TRUE(voided.status().ok()) << voided.status(); - - auto i2Json = i2.value().get_spec(); - auto i4Json = i4.value().get_spec(); - auto i8Json = i8.value().get_spec(); - auto f2Json = f2.value().get_spec(); - auto f4Json = f4.value().get_spec(); - auto f8Json = f8.value().get_spec(); - auto voidedJson = voided.value().get_spec(); - - ASSERT_TRUE(i2Json.status().ok()) << i2Json.status(); - ASSERT_TRUE(i4Json.status().ok()) << i4Json.status(); - ASSERT_TRUE(i8Json.status().ok()) << i8Json.status(); - ASSERT_TRUE(f2Json.status().ok()) << f2Json.status(); - ASSERT_TRUE(f4Json.status().ok()) << f4Json.status(); - ASSERT_TRUE(f8Json.status().ok()) << f8Json.status(); - ASSERT_TRUE(voidedJson.status().ok()) << voidedJson.status(); - - EXPECT_TRUE(i2Json.value()["metadata"].contains("compressor")) - << i2Json.value(); - EXPECT_TRUE(i4Json.value()["metadata"].contains("compressor")) - << i4Json.value(); - EXPECT_TRUE(i8Json.value()["metadata"].contains("compressor")) - << i8Json.value(); - EXPECT_TRUE(f2Json.value()["metadata"].contains("compressor")) - << f2Json.value(); - EXPECT_TRUE(f4Json.value()["metadata"].contains("compressor")) - << f4Json.value(); - EXPECT_TRUE(f8Json.value()["metadata"].contains("compressor")) - << f8Json.value(); - EXPECT_TRUE(voidedJson.value()["metadata"].contains("compressor")) - << voidedJson.value(); + EXPECT_TRUE(i2.value().get_chunk_shape().status().ok()); } -TEST(Variable, shape) { - auto i2 = mdio::Variable<>::Open(i2Base, mdio::constants::kOpen); - auto i4 = mdio::Variable<>::Open(i4Base, mdio::constants::kOpen); - auto i8 = mdio::Variable<>::Open(i8Base, mdio::constants::kOpen); - auto f2 = mdio::Variable<>::Open(f2Base, mdio::constants::kOpen); - auto f4 = mdio::Variable<>::Open(f4Base, mdio::constants::kOpen); - auto f8 = mdio::Variable<>::Open(f8Base, mdio::constants::kOpen); - auto voided = mdio::Variable<>::Open(voidedBase, mdio::constants::kOpen); +TEST_P(VariableTest, shape) { + auto i2 = OpenTestVariable("i2", version_, base_path_); ASSERT_TRUE(i2.status().ok()) << i2.status(); - ASSERT_TRUE(i4.status().ok()) << i4.status(); - ASSERT_TRUE(i8.status().ok()) << i8.status(); - ASSERT_TRUE(f2.status().ok()) << f2.status(); - ASSERT_TRUE(f4.status().ok()) << f4.status(); - ASSERT_TRUE(f8.status().ok()) << f8.status(); - ASSERT_TRUE(voided.status().ok()) << voided.status(); - - EXPECT_THAT(i2.value().dimensions().shape(), ::testing::ElementsAre(10, 10)) - << i2.value().dimensions(); - EXPECT_THAT(i4.value().dimensions().shape(), ::testing::ElementsAre(10, 10)) - << i4.value().dimensions(); - EXPECT_THAT(i8.value().dimensions().shape(), ::testing::ElementsAre(10, 10)) - << i8.value().dimensions(); - EXPECT_THAT(f2.value().dimensions().shape(), ::testing::ElementsAre(10, 10)) - << f2.value().dimensions(); - EXPECT_THAT(f4.value().dimensions().shape(), ::testing::ElementsAre(10, 10)) - << f4.value().dimensions(); - EXPECT_THAT(f8.value().dimensions().shape(), ::testing::ElementsAre(10, 10)) - << f8.value().dimensions(); - EXPECT_THAT(voided.value().dimensions().shape(), - ::testing::ElementsAre(10, 10, 14)) - << voided.value().dimensions(); + EXPECT_THAT(i2.value().dimensions().shape(), ::testing::ElementsAre(10, 10)); } -TEST(Variable, dtype) { - auto i2 = mdio::Variable<>::Open(i2Base, mdio::constants::kOpen); - auto i4 = mdio::Variable<>::Open(i4Base, mdio::constants::kOpen); - auto i8 = mdio::Variable<>::Open(i8Base, mdio::constants::kOpen); - auto f2 = mdio::Variable<>::Open(f2Base, mdio::constants::kOpen); - auto f4 = mdio::Variable<>::Open(f4Base, mdio::constants::kOpen); - auto f8 = mdio::Variable<>::Open(f8Base, mdio::constants::kOpen); - auto voided = mdio::Variable<>::Open(voidedBase, mdio::constants::kOpen); - ASSERT_TRUE(i2.status().ok()) << i2.status(); - ASSERT_TRUE(i4.status().ok()) << i4.status(); - ASSERT_TRUE(i8.status().ok()) << i8.status(); - ASSERT_TRUE(f2.status().ok()) << f2.status(); - ASSERT_TRUE(f4.status().ok()) << f4.status(); - ASSERT_TRUE(f8.status().ok()) << f8.status(); - ASSERT_TRUE(voided.status().ok()) << voided.status(); - - EXPECT_EQ(i2.value().dtype(), mdio::constants::kInt16) << i2.value().dtype(); - EXPECT_EQ(i4.value().dtype(), mdio::constants::kInt32) << i4.value().dtype(); - EXPECT_EQ(i8.value().dtype(), mdio::constants::kInt64) << i8.value().dtype(); - EXPECT_EQ(f2.value().dtype(), mdio::constants::kFloat16) - << f2.value().dtype(); - EXPECT_EQ(f4.value().dtype(), mdio::constants::kFloat32) - << f4.value().dtype(); - EXPECT_EQ(f8.value().dtype(), mdio::constants::kFloat64) - << f8.value().dtype(); - EXPECT_EQ(voided.value().dtype(), mdio::constants::kByte) - << voided.value().dtype(); +TEST_P(VariableTest, dtype) { + for (const auto& def : kTestVariables) { + auto var = OpenTestVariable(def.name, version_, base_path_); + ASSERT_TRUE(var.status().ok()) << var.status(); + EXPECT_EQ(var.value().dtype(), def.expected_dtype) + << "dtype mismatch for " << def.name; + } } -TEST(Variable, domain) { - auto i2 = mdio::Variable<>::Open(i2Base, mdio::constants::kOpen); - auto i4 = mdio::Variable<>::Open(i4Base, mdio::constants::kOpen); - auto i8 = mdio::Variable<>::Open(i8Base, mdio::constants::kOpen); - auto f2 = mdio::Variable<>::Open(f2Base, mdio::constants::kOpen); - auto f4 = mdio::Variable<>::Open(f4Base, mdio::constants::kOpen); - auto f8 = mdio::Variable<>::Open(f8Base, mdio::constants::kOpen); - auto voided = mdio::Variable<>::Open(voidedBase, mdio::constants::kOpen); +TEST_P(VariableTest, domain) { + auto i2 = OpenTestVariable("i2", version_, base_path_); ASSERT_TRUE(i2.status().ok()) << i2.status(); - ASSERT_TRUE(i4.status().ok()) << i4.status(); - ASSERT_TRUE(i8.status().ok()) << i8.status(); - ASSERT_TRUE(f2.status().ok()) << f2.status(); - ASSERT_TRUE(f4.status().ok()) << f4.status(); - ASSERT_TRUE(f8.status().ok()) << f8.status(); - ASSERT_TRUE(voided.status().ok()) << voided.status(); const mdio::Index EXPECTED_SHAPE = 10; - EXPECT_EQ(i2.value().dimensions().shape()[0], EXPECTED_SHAPE) - << i2.value().dimensions(); - EXPECT_EQ(i4.value().dimensions().shape()[0], EXPECTED_SHAPE) - << i4.value().dimensions(); - EXPECT_EQ(i8.value().dimensions().shape()[0], EXPECTED_SHAPE) - << i8.value().dimensions(); - EXPECT_EQ(f2.value().dimensions().shape()[0], EXPECTED_SHAPE) - << f2.value().dimensions(); - EXPECT_EQ(f4.value().dimensions().shape()[0], EXPECTED_SHAPE) - << f4.value().dimensions(); - EXPECT_EQ(f8.value().dimensions().shape()[0], EXPECTED_SHAPE) - << f8.value().dimensions(); - EXPECT_EQ(voided.value().dimensions().shape()[0], EXPECTED_SHAPE) - << voided.value().dimensions(); - - EXPECT_EQ(i2.value().dimensions().rank(), 2) << i2.value().dimensions(); - EXPECT_EQ(i4.value().dimensions().rank(), 2) << i4.value().dimensions(); - EXPECT_EQ(i8.value().dimensions().rank(), 2) << i8.value().dimensions(); - EXPECT_EQ(f2.value().dimensions().rank(), 2) << f2.value().dimensions(); - EXPECT_EQ(f4.value().dimensions().rank(), 2) << f4.value().dimensions(); - EXPECT_EQ(f8.value().dimensions().rank(), 2) << f8.value().dimensions(); - EXPECT_EQ(voided.value().dimensions().rank(), 3) - << voided.value().dimensions(); -} - -TEST(Variable, sliceByDimName) { - auto i2 = mdio::Variable<>::Open(i2Base, mdio::constants::kOpen); - auto i4 = mdio::Variable<>::Open(i4Base, mdio::constants::kOpen); - auto i8 = mdio::Variable<>::Open(i8Base, mdio::constants::kOpen); - auto f2 = mdio::Variable<>::Open(f2Base, mdio::constants::kOpen); - auto f4 = mdio::Variable<>::Open(f4Base, mdio::constants::kOpen); - auto f8 = mdio::Variable<>::Open(f8Base, mdio::constants::kOpen); - auto voided = mdio::Variable<>::Open(voidedBase, mdio::constants::kOpen); - ASSERT_TRUE(i2.status().ok()) << i2.status(); - ASSERT_TRUE(i4.status().ok()) << i4.status(); - ASSERT_TRUE(i8.status().ok()) << i8.status(); - ASSERT_TRUE(f2.status().ok()) << f2.status(); - ASSERT_TRUE(f4.status().ok()) << f4.status(); - ASSERT_TRUE(f8.status().ok()) << f8.status(); - ASSERT_TRUE(voided.status().ok()) << voided.status(); - - mdio::RangeDescriptor inlineSlice = {"inline", 0, 5, 1}; - mdio::RangeDescriptor crosslineSpec = {"crossline", 0, 5, 1}; - auto i2Slice = i2.value().slice(inlineSlice, crosslineSpec); - auto i4Slice = i4.value().slice(inlineSlice, crosslineSpec); - auto i8Slice = i8.value().slice(inlineSlice, crosslineSpec); - auto f2Slice = f2.value().slice(inlineSlice, crosslineSpec); - auto f4Slice = f4.value().slice(inlineSlice, crosslineSpec); - auto f8Slice = f8.value().slice(inlineSlice, crosslineSpec); - auto voidedSlice = voided.value().slice(inlineSlice, crosslineSpec); - - EXPECT_TRUE(i2Slice.status().ok()) << i2.status(); - EXPECT_TRUE(i4Slice.status().ok()) << i4Slice.status(); - EXPECT_TRUE(i8Slice.status().ok()) << i8Slice.status(); - EXPECT_TRUE(f2Slice.status().ok()) << f2Slice.status(); - EXPECT_TRUE(f4Slice.status().ok()) << f4Slice.status(); - EXPECT_TRUE(f8Slice.status().ok()) << f8Slice.status(); - EXPECT_TRUE(voidedSlice.status().ok()) << voidedSlice.status(); - EXPECT_THAT(i2Slice.value().dimensions().shape(), - ::testing::ElementsAre(5, 5)) - << i2Slice.value().dimensions(); - EXPECT_THAT(i4Slice.value().dimensions().shape(), - ::testing::ElementsAre(5, 5)) - << i4Slice.value().dimensions(); - EXPECT_THAT(i8Slice.value().dimensions().shape(), - ::testing::ElementsAre(5, 5)) - << i8Slice.value().dimensions(); - EXPECT_THAT(f2Slice.value().dimensions().shape(), - ::testing::ElementsAre(5, 5)) - << f2Slice.value().dimensions(); - EXPECT_THAT(f4Slice.value().dimensions().shape(), - ::testing::ElementsAre(5, 5)) - << f4Slice.value().dimensions(); - EXPECT_THAT(f8Slice.value().dimensions().shape(), - ::testing::ElementsAre(5, 5)) - << f8Slice.value().dimensions(); - EXPECT_THAT(voidedSlice.value().dimensions().shape(), - ::testing::ElementsAre(5, 5, 14)) - << voidedSlice.value().dimensions(); + EXPECT_EQ(i2.value().dimensions().shape()[0], EXPECTED_SHAPE); + EXPECT_EQ(i2.value().dimensions().rank(), 2); } -// A test to clean up after the test suite -TEST(Variable, TEARDOWN) { - std::filesystem::remove_all("zarrs/acceptance/i2"); - std::filesystem::remove_all("zarrs/acceptance/i4"); - std::filesystem::remove_all("zarrs/acceptance/i8"); - std::filesystem::remove_all("zarrs/acceptance/f2"); - std::filesystem::remove_all("zarrs/acceptance/f4"); - std::filesystem::remove_all("zarrs/acceptance/f8"); - std::filesystem::remove_all("zarrs/acceptance/voided"); - std::filesystem::remove_all("zarrs/acceptance"); - ASSERT_TRUE(true); +TEST_P(VariableTest, TEARDOWN) { + for (const auto& def : kTestVariables) { + std::filesystem::remove_all(base_path_ + "/" + def.name); + } } -} // namespace VariableTesting - -namespace VariableDataTest { +INSTANTIATE_TEST_SUITE_P( + ZarrVersions, VariableTest, + ::testing::Values(mdio::zarr::ZarrVersion::kV2, + mdio::zarr::ZarrVersion::kV3), + [](const ::testing::TestParamInfo& info) { + return ZarrVersionToString(info.param); + }); + +// ============================================================================ +// Parameterized VariableData Tests +// ============================================================================ + +class VariableDataTest + : public ::testing::TestWithParam { + protected: + void SetUp() override { + version_ = GetParam(); + base_path_ = GetBasePath(version_); + } -template -mdio::Variable getVariable() { - nlohmann::json i2Spec = R"( - { - "driver": "zarr", - "kvstore": { - "driver": "file", - "path": "zarrs/acceptance/i2" - } - } - )"_json; - auto i2 = mdio::Variable<>::Open(i2Spec, (mdio::constants::kOpen)); - if (!i2.status().ok()) { - std::cout << "Error opening i2: " << i2.status() << std::endl; - return mdio::Variable(); + mdio::Variable<> getVariable() { + auto var = OpenTestVariable("i2", version_, base_path_); + if (!var.status().ok()) { + std::cout << "Error opening i2: " << var.status() << std::endl; + return mdio::Variable<>(); + } + return var.value(); } - return i2.value(); -} -TEST(VariableData, SETUP) { - mdio::TransactionalOpenOptions options; - auto opt = options.Set(std::move(mdio::constants::kCreateClean)); - nlohmann::json i2Spec = R"( - { - "driver": "zarr", - "kvstore": { - "driver": "file", - "path": "zarrs/acceptance/i2" - }, - "metadata": { - "dtype": " zeroIdxSlice = {0, 0, 5, 1}; mdio::RangeDescriptor oneIdxSlice = {1, 0, 5, 1}; auto slicedVariableData = variableData.slice(zeroIdxSlice, oneIdxSlice); ASSERT_TRUE(slicedVariableData.status().ok()) << slicedVariableData.status(); EXPECT_THAT(slicedVariableData.value().domain().shape(), - ::testing::ElementsAre(5, 5)) - << slicedVariableData.value().domain(); + ::testing::ElementsAre(5, 5)); } -TEST(VariableData, sliceByDimName) { +TEST_P(VariableDataTest, sliceByDimName) { auto variableData = getVariable().Read().value(); mdio::RangeDescriptor inlineSlice = {"inline", 0, 5, 1}; mdio::RangeDescriptor crosslineSpec = {"crossline", 0, 5, 1}; auto slicedVariableData = variableData.slice(inlineSlice, crosslineSpec); ASSERT_TRUE(slicedVariableData.status().ok()) << slicedVariableData.status(); EXPECT_THAT(slicedVariableData.value().domain().shape(), - ::testing::ElementsAre(5, 5)) - << slicedVariableData.value().domain(); + ::testing::ElementsAre(5, 5)); } -TEST(VariableData, writeToStore) { +TEST_P(VariableDataTest, writeToStore) { auto variable = getVariable(); auto variableData = variable.Read().value(); auto data = @@ -1085,374 +650,104 @@ TEST(VariableData, writeToStore) { auto variableCheck = getVariable().Read().value(); auto dataCheck = reinterpret_cast(variableCheck.get_data_accessor().data()); - EXPECT_EQ(dataCheck[0], 0xff) << dataCheck[0]; + EXPECT_EQ(dataCheck[0], 0xff); } -TEST(VariableData, dimensionUnits) { +TEST_P(VariableDataTest, dimensionUnits) { auto variableData = getVariable().Read().value(); - EXPECT_EQ(variableData.metadata["dimension_units"].size(), 2) - << variableData.metadata; + EXPECT_EQ(variableData.metadata["dimension_units"].size(), 2); } -TEST(VariableData, TEARDOWN) { - std::filesystem::remove_all("zarrs/acceptance/i2"); +TEST_P(VariableDataTest, TEARDOWN) { + std::filesystem::remove_all(base_path_ + "/i2"); ASSERT_TRUE(true); } -} // namespace VariableDataTest - -namespace DatasetTest { +INSTANTIATE_TEST_SUITE_P( + ZarrVersions, VariableDataTest, + ::testing::Values(mdio::zarr::ZarrVersion::kV2, + mdio::zarr::ZarrVersion::kV3), + [](const ::testing::TestParamInfo& info) { + return ZarrVersionToString(info.param); + }); + +// ============================================================================ +// Parameterized Dataset Tests +// ============================================================================ + +class DatasetTest : public ::testing::TestWithParam { + protected: + void SetUp() override { + version_ = GetParam(); + base_path_ = GetBasePath(version_); + dataset_manifest_ = GetDatasetManifest(version_); + expected_var_count_ = 9; // Both versions support struct arrays + } -// clang-format off -/*NOLINT*/ std::string datasetManifest = R"( -{ - "metadata": { - "name": "campos_3d", - "apiVersion": "1.0.0", - "createdOn": "2023-12-12T15:02:06.413469-06:00", - "attributes": { - "textHeader": [ - "C01 .......................... ", - "C02 .......................... ", - "C03 .......................... " - ], - "foo": "bar" - } - }, - "variables": [ - { - "name": "image", - "dataType": "float32", - "dimensions": [ - {"name": "inline", "size": 256}, - {"name": "crossline", "size": 512}, - {"name": "depth", "size": 384} - ], - "metadata": { - "chunkGrid": { - "name": "regular", - "configuration": { "chunkShape": [128, 128, 128] } - }, - "statsV1": { - "count": 100, - "sum": 1215.1, - "sumSquares": 125.12, - "min": 5.61, - "max": 10.84, - "histogram": {"binCenters": [1, 2], "counts": [10, 15]} - }, - "attributes": { - "fizz": "buzz" - } - }, - "coordinates": ["inline", "crossline", "depth", "cdp-x", "cdp-y"], - "compressor": {"name": "blosc", "algorithm": "zstd"} - }, - { - "name": "velocity", - "dataType": "float64", - "dimensions": ["inline", "crossline", "depth"], - "metadata": { - "chunkGrid": { - "name": "regular", - "configuration": { "chunkShape": [128, 128, 128] } - }, - "unitsV1": {"speed": "m/s"} - }, - "coordinates": ["inline", "crossline", "depth", "cdp-x", "cdp-y"] - }, - { - "name": "image_inline", - "dataType": "int16", - "dimensions": ["inline", "crossline", "depth"], - "longName": "inline optimized version of 3d_stack", - "compressor": {"name": "blosc", "algorithm": "zstd"}, - "metadata": { - "chunkGrid": { - "name": "regular", - "configuration": { "chunkShape": [128, 128, 128] } - } - }, - "coordinates": ["inline", "crossline", "depth", "cdp-x", "cdp-y"] - }, - { - "name": "image_headers", - "dataType": { - "fields": [ - {"name": "cdp-x", "format": "int32"}, - {"name": "cdp-y", "format": "int32"}, - {"name": "elevation", "format": "float16"}, - {"name": "some_scalar", "format": "float16"} - ] - }, - "dimensions": ["inline", "crossline"], - "metadata": { - "chunkGrid": { - "name": "regular", - "configuration": { "chunkShape": [128, 128] } - } - }, - "coordinates": ["inline", "crossline", "cdp-x", "cdp-y"] - }, - { - "name": "inline", - "dataType": "uint32", - "dimensions": [{"name": "inline", "size": 256}] - }, - { - "name": "crossline", - "dataType": "uint32", - "dimensions": [{"name": "crossline", "size": 512}] - }, - { - "name": "depth", - "dataType": "uint32", - "dimensions": [{"name": "depth", "size": 384}], - "metadata": { - "unitsV1": { "length": "m" } - } - }, - { - "name": "cdp-x", - "dataType": "float32", - "dimensions": [ - {"name": "inline", "size": 256}, - {"name": "crossline", "size": 512} - ], - "metadata": { - "unitsV1": { "length": "m" } - } - }, - { - "name": "cdp-y", - "dataType": "float32", - "dimensions": [ - {"name": "inline", "size": 256}, - {"name": "crossline", "size": 512} - ], - "metadata": { - "unitsV1": { "length": "m" } - } - } - ] -} - )"; -// clang-format on + mdio::zarr::ZarrVersion version_; + std::string base_path_; + std::string dataset_manifest_; + size_t expected_var_count_; +}; -TEST(DatasetSpec, valid) { - nlohmann::json j = nlohmann::json::parse(datasetManifest); - auto res = Construct(j, "zarrs/acceptance"); +TEST_P(DatasetTest, specValid) { + nlohmann::json j = nlohmann::json::parse(dataset_manifest_); + auto res = Construct(j, base_path_, version_); ASSERT_TRUE(res.status().ok()) << res.status(); std::tuple> parsed = res.value(); std::vector variables = std::get<1>(parsed); - EXPECT_EQ(variables.size(), 9) << variables.size(); + EXPECT_EQ(variables.size(), expected_var_count_); + + // Verify driver name + std::string expected_driver = GetTestDriverName(version_); + for (const auto& var : variables) { + EXPECT_EQ(var["driver"], expected_driver); + } } -TEST(DatasetSpec, invalid) { - // manifest["variables"][0] missing "name" field - // Compressor for image_inline is also invalid - std::string manifest = R"( -{ - "metadata": { - "name": "campos_3d", - "apiVersion": "1.0.0", - "createdOn": "2023-12-12T15:02:06.413469-06:00", - "attributes": { - "textHeader": [ - "C01 .......................... ", - "C02 .......................... ", - "C03 .......................... " - ], - "foo": "bar" - } - }, - "variables": [ - { - "dataType": "float32", - "dimensions": [ - {"name": "inline", "size": 256}, - {"name": "crossline", "size": 512}, - {"name": "depth", "size": 384} - ], - "metadata": { - "chunkGrid": { - "name": "regular", - "configuration": { "chunkShape": [128, 128, 128] } - }, - "statsV1": { - "count": 100, - "sum": 1215.1, - "sumSquares": 125.12, - "min": 5.61, - "max": 10.84, - "histogram": {"binCenters": [1, 2], "counts": [10, 15]} - }, - "attributes": { - "fizz": "buzz" - } - }, - "coordinates": ["inline", "crossline", "depth", "cdp-x", "cdp-y"], - "compressor": {"name": "blosc", "algorithm": "zstd"} - }, - { - "name": "velocity", - "dataType": "float16", - "dimensions": ["inline", "crossline", "depth"], - "metadata": { - "chunkGrid": { - "name": "regular", - "configuration": { "chunkShape": [128, 128, 128] } - }, - "unitsV1": {"speed": "m/s"} - }, - "coordinates": ["inline", "crossline", "depth", "cdp-x", "cdp-y"] - }, - { - "name": "image_inline", - "dataType": "float32", - "dimensions": ["inline", "crossline", "depth"], - "longName": "inline optimized version of 3d_stack", - "compressor": {"name": "zfp", "mode": "fixed_accuracy", "tolerance": 0.05}, - "metadata": { - "chunkGrid": { - "name": "regular", - "configuration": { "chunkShape": [4, 512, 512] } - } - }, - "coordinates": ["inline", "crossline", "depth", "cdp-x", "cdp-y"] - }, - { - "name": "image_headers", - "dataType": { - "fields": [ - {"name": "cdp-x", "format": "int32"}, - {"name": "cdp-y", "format": "int32"}, - {"name": "elevation", "format": "float16"}, - {"name": "some_scalar", "format": "float16"} - ] - }, - "dimensions": ["inline", "crossline"], - "metadata": { - "chunkGrid": { - "name": "regular", - "configuration": { "chunkShape": [128, 128] } - } - }, - "coordinates": ["inline", "crossline", "cdp-x", "cdp-y"] - }, - { - "name": "inline", - "dataType": "uint32", - "dimensions": [{"name": "inline", "size": 256}] - }, - { - "name": "crossline", - "dataType": "uint32", - "dimensions": [{"name": "crossline", "size": 512}] - }, - { - "name": "depth", - "dataType": "uint32", - "dimensions": [{"name": "depth", "size": 384}], - "metadata": { - "unitsV1": { "length": "m" } - } - }, - { - "name": "cdp-x", - "dataType": "float32", - "dimensions": [ - {"name": "inline", "size": 256}, - {"name": "crossline", "size": 512} - ], - "metadata": { - "unitsV1": { "length": "m" } - } - }, - { - "name": "cdp-y", - "dataType": "float32", - "dimensions": [ - {"name": "inline", "size": 256}, - {"name": "crossline", "size": 512} - ], - "metadata": { - "unitsV1": { "length": "m" } - } - } - ] -} - )"; - - nlohmann::json j = nlohmann::json::parse(manifest); - auto res = Construct(j, "zarrs/acceptance"); - ASSERT_FALSE(res.status().ok()) << res.status(); -} - -TEST(Dataset, fillValue) { - nlohmann::json j = nlohmann::json::parse(datasetManifest); - auto ds = mdio::Dataset::from_json(j, "zarrs/acceptance", - mdio::constants::kCreateClean); - ASSERT_TRUE(ds.status().ok()) << ds.status(); - - std::string key = "image_headers"; - auto var = ds.value().get_variable(key); - ASSERT_TRUE(var.status().ok()) << var.status(); - auto vdf = var.value().Read(); - ASSERT_TRUE(vdf.status().ok()) << vdf.status(); - auto vd = vdf.value(); - - auto data = - reinterpret_cast(vd.get_data_accessor().data()); - std::byte zero = std::byte(0); - for (int i = 0; i < 1000000; i++) { - ASSERT_EQ(data[i], zero) << "Expected 0 at byte " << i << " but got " - << static_cast(data[i]); - } - - // This still doesn't work. We end up with the same empty fill values {, , , , - // , , , , , , , } auto dataRes = - // tensorstore::StaticDataTypeCast(vd.get_data_accessor()); - // ASSERT_TRUE(dataRes.status().ok()) << dataRes.status(); - // auto d = dataRes.value(); - // std::cout << d[0][0] << std::endl; -} - -TEST(Dataset, open) { - nlohmann::json j = nlohmann::json::parse(datasetManifest); - auto construct = Construct(j, "zarrs/acceptance"); +TEST_P(DatasetTest, open) { + nlohmann::json j = nlohmann::json::parse(dataset_manifest_); + auto construct = Construct(j, base_path_, version_); ASSERT_TRUE(construct.status().ok()) << construct.status(); - std::tuple> parsed = - construct.value(); - nlohmann::json metadata = std::get<0>(parsed); - std::vector variables = std::get<1>(parsed); - + auto [metadata, variables] = construct.value(); auto dataset = mdio::Dataset::Open(metadata, variables, mdio::constants::kCreateClean); ASSERT_TRUE(dataset.status().ok()) << dataset.status(); } -TEST(Dataset, condensed) { - std::string path = "zarrs/acceptance/"; - auto ds = mdio::Dataset::Open(path, mdio::constants::kOpen); - ASSERT_TRUE(ds.status().ok()) << ds.status(); +TEST_P(DatasetTest, condensed) { + // First create the dataset + nlohmann::json j = nlohmann::json::parse(dataset_manifest_); + auto construct = Construct(j, base_path_, version_); + ASSERT_TRUE(construct.status().ok()) << construct.status(); - std::string missingSlashPath = "zarrs/acceptance"; - auto ds2 = mdio::Dataset::Open(missingSlashPath, mdio::constants::kOpen); - ASSERT_TRUE(ds2.status().ok()) << ds2.status(); + auto [metadata, variables] = construct.value(); + auto createDs = + mdio::Dataset::Open(metadata, variables, mdio::constants::kCreateClean); + ASSERT_TRUE(createDs.status().ok()) << createDs.status(); + + // Now test opening with just the path + auto ds = mdio::Dataset::Open(base_path_, mdio::constants::kOpen); + ASSERT_TRUE(ds.status().ok()) + << "Failed to open with trailing slash: " << ds.status(); + + // Test without trailing slash + std::string path_no_slash = base_path_; + if (!path_no_slash.empty() && path_no_slash.back() == '/') { + path_no_slash.pop_back(); + } + auto ds2 = mdio::Dataset::Open(path_no_slash, mdio::constants::kOpen); + ASSERT_TRUE(ds2.status().ok()) + << "Failed to open without trailing slash: " << ds2.status(); } -TEST(Dataset, read) { - nlohmann::json j = nlohmann::json::parse(datasetManifest); - auto construct = Construct(j, "zarrs/acceptance"); +TEST_P(DatasetTest, read) { + nlohmann::json j = nlohmann::json::parse(dataset_manifest_); + auto construct = Construct(j, base_path_, version_); ASSERT_TRUE(construct.status().ok()) << construct.status(); - std::tuple> parsed = - construct.value(); - nlohmann::json metadata = std::get<0>(parsed); - std::vector variables = std::get<1>(parsed); - + auto [metadata, variables] = construct.value(); auto dataset = mdio::Dataset::Open(metadata, variables, mdio::constants::kOpen); ASSERT_TRUE(dataset.status().ok()) << dataset.status(); @@ -1465,17 +760,15 @@ TEST(Dataset, read) { } } -TEST(Dataset, write) { - nlohmann::json j = nlohmann::json::parse(datasetManifest); - auto construct = Construct(j, "zarrs/acceptance"); - - std::tuple> parsed = - construct.value(); - nlohmann::json metadata = std::get<0>(parsed); - std::vector variables = std::get<1>(parsed); +TEST_P(DatasetTest, write) { + nlohmann::json j = nlohmann::json::parse(dataset_manifest_); + auto construct = Construct(j, base_path_, version_); + ASSERT_TRUE(construct.status().ok()) << construct.status(); + auto [metadata, variables] = construct.value(); auto dataset = mdio::Dataset::Open(metadata, variables, mdio::constants::kOpen); + ASSERT_TRUE(dataset.status().ok()) << dataset.status(); auto ds = dataset.value(); std::vector names = ds.variables.get_keys(); @@ -1568,50 +861,32 @@ TEST(Dataset, write) { ASSERT_TRUE(w.status().ok()) << w.status(); } + // Test SelectField and negative case for struct arrays std::string fielded = "image_headers"; ASSERT_TRUE(ds.SelectField(fielded, "cdp-x").status().ok()); auto wf = ds.get_variable(fielded).value().Write(readVariables[4]); ASSERT_FALSE(wf.status().ok()) << wf.status(); - nlohmann::json imageJson = R"( - { - "driver": "zarr", - "kvstore": { - "driver": "file", - "path": "zarrs/acceptance/image" - } - } - )"_json; - - nlohmann::json velocityJson = R"( - { - "driver": "zarr", - "kvstore": { - "driver": "file", - "path": "zarrs/acceptance/velocity" - } - } - )"_json; - - nlohmann::json imageInlineJson = R"( - { - "driver": "zarr", - "kvstore": { - "driver": "file", - "path": "zarrs/acceptance/image_inline" - } - } - )"_json; - - nlohmann::json imageHeadersJson = R"( - { - "driver": "zarr", - "kvstore": { - "driver": "file", - "path": "zarrs/acceptance/image_headers" - } - } - )"_json; + std::string driver = GetTestDriverName(version_); + nlohmann::json imageJson; + imageJson["driver"] = driver; + imageJson["kvstore"]["driver"] = "file"; + imageJson["kvstore"]["path"] = base_path_ + "/image"; + + nlohmann::json velocityJson; + velocityJson["driver"] = driver; + velocityJson["kvstore"]["driver"] = "file"; + velocityJson["kvstore"]["path"] = base_path_ + "/velocity"; + + nlohmann::json imageInlineJson; + imageInlineJson["driver"] = driver; + imageInlineJson["kvstore"]["driver"] = "file"; + imageInlineJson["kvstore"]["path"] = base_path_ + "/image_inline"; + + nlohmann::json imageHeadersJson; + imageHeadersJson["driver"] = driver; + imageHeadersJson["kvstore"]["driver"] = "file"; + imageHeadersJson["kvstore"]["path"] = base_path_ + "/image_headers"; auto image = mdio::Variable<>::Open(imageJson, mdio::constants::kOpen); auto velocity = mdio::Variable<>::Open(velocityJson, mdio::constants::kOpen); @@ -1641,174 +916,154 @@ TEST(Dataset, write) { velocityData.value().get_data_accessor().data()); auto castedImageInline = reinterpret_cast( imageInlineData.value().get_data_accessor().data()); - auto castedImageHeaders = reinterpret_cast( - imageHeadersData.value().get_data_accessor().data()); EXPECT_EQ(castedImage[0], 3.14f) << castedImage[0]; EXPECT_EQ(castedVelociy[0], 2.71828) << castedVelociy[0]; EXPECT_EQ(castedImageInline[0], 0xff) << castedImageInline[0]; - // EXPECT_EQ(castedImageHeaders[0], std::byte(0xffffffffffff)) << "Struct - // array was not correct value"; } -TEST(Dataset, name) { - nlohmann::json j = nlohmann::json::parse(datasetManifest); - auto construct = Construct(j, "zarrs/acceptance"); +TEST_P(DatasetTest, name) { + nlohmann::json j = nlohmann::json::parse(dataset_manifest_); + auto construct = Construct(j, base_path_, version_); ASSERT_TRUE(construct.status().ok()) << construct.status(); - std::tuple> parsed = - construct.value(); - nlohmann::json metadata = std::get<0>(parsed); - std::vector variables = std::get<1>(parsed); - + auto [metadata, variables] = construct.value(); auto dataset = mdio::Dataset::Open(metadata, variables, mdio::constants::kOpen); ASSERT_TRUE(dataset.status().ok()) << dataset.status(); - auto ds = dataset.value(); - - EXPECT_EQ(ds.getMetadata()["name"], "campos_3d") << ds.getMetadata(); + std::string expected_name = + version_ == mdio::zarr::ZarrVersion::kV3 ? "campos_3d_v3" : "campos_3d"; + EXPECT_EQ(dataset.value().getMetadata()["name"], expected_name); } -TEST(Dataset, optionalAttrs) { - nlohmann::json j = nlohmann::json::parse(datasetManifest); - auto construct = Construct(j, "zarrs/acceptance"); +TEST_P(DatasetTest, optionalAttrs) { + nlohmann::json j = nlohmann::json::parse(dataset_manifest_); + auto construct = Construct(j, base_path_, version_); ASSERT_TRUE(construct.status().ok()) << construct.status(); - std::tuple> parsed = - construct.value(); - nlohmann::json metadata = std::get<0>(parsed); - std::vector variables = std::get<1>(parsed); - + auto [metadata, variables] = construct.value(); auto dataset = mdio::Dataset::Open(metadata, variables, mdio::constants::kOpen); ASSERT_TRUE(dataset.status().ok()) << dataset.status(); - auto ds = dataset.value(); - - EXPECT_TRUE(ds.getMetadata().contains("name")) << ds.getMetadata(); + EXPECT_TRUE(dataset.value().getMetadata().contains("name")); } -TEST(Dataset, isel) { - std::string path = "zarrs/acceptance"; - auto dataset = mdio::Dataset::Open(path, mdio::constants::kOpen); +TEST_P(DatasetTest, isel) { + nlohmann::json j = nlohmann::json::parse(dataset_manifest_); + auto construct = Construct(j, base_path_, version_); + ASSERT_TRUE(construct.status().ok()) << construct.status(); + auto [metadata, variables] = construct.value(); + auto dataset = + mdio::Dataset::Open(metadata, variables, mdio::constants::kOpen); ASSERT_TRUE(dataset.status().ok()) << dataset.status(); auto ds = dataset.value(); mdio::RangeDescriptor desc1 = {"inline", 0, 5, 1}; auto slice = ds.isel(desc1); - ASSERT_TRUE(slice.status().ok()); + ASSERT_TRUE(slice.status().ok()) << slice.status(); auto domain = slice->domain; ASSERT_EQ(domain.rank(), 3) << "This should have a rank of 3..."; - // Check depth range auto depthRange = domain[1]; - EXPECT_EQ(depthRange.interval().inclusive_min(), 0) - << "Depth range should start at 0"; - EXPECT_EQ(depthRange.interval().exclusive_max(), 384) - << "Depth range should end at 384"; + EXPECT_EQ(depthRange.interval().inclusive_min(), 0); + EXPECT_EQ(depthRange.interval().exclusive_max(), 384); - // Check crossline range auto crosslineRange = domain[0]; - EXPECT_EQ(crosslineRange.interval().inclusive_min(), 0) - << "Crossline range should start at 0"; - EXPECT_EQ(crosslineRange.interval().exclusive_max(), 512) - << "Crossline range should end at 512"; + EXPECT_EQ(crosslineRange.interval().inclusive_min(), 0); + EXPECT_EQ(crosslineRange.interval().exclusive_max(), 512); - // Check inline range auto inlineRange = domain[2]; - EXPECT_EQ(inlineRange.interval().inclusive_min(), 0) - << "Inline range should start at 0"; - EXPECT_EQ(inlineRange.interval().exclusive_max(), 5) - << "Inline range should end at 5"; + EXPECT_EQ(inlineRange.interval().inclusive_min(), 0); + EXPECT_EQ(inlineRange.interval().exclusive_max(), 5); } -TEST(Dataset, xarrayCompatible) { - const char* basePath = std::getenv(PROJECT_BASE_PATH_ENV); - if (!basePath) { - std::cout << "PROJECT_BASE_PATH environment variable not set. Expecting to " - "be in the 'build/mdio' directory." - << std::endl; - basePath = DEFAULT_BASE_PATH; - } - - // Resolve the absolute path for the script - std::string srcPath = std::string(basePath) + XARRAY_SCRIPT_RELATIVE_PATH; - - // Ensure that srcPath is valid and points to an existing file - if (access(srcPath.c_str(), F_OK) == -1) { - std::cerr << "Error: Python script not found at " << srcPath << std::endl; - FAIL() << "Script not found: " << srcPath; - } - - std::vector metadataOptions = {"False", "True"}; - std::vector pids; +TEST_P(DatasetTest, listVars) { + nlohmann::json j = nlohmann::json::parse(dataset_manifest_); + auto construct = Construct(j, base_path_, version_); + ASSERT_TRUE(construct.status().ok()) << construct.status(); - for (const auto& option : metadataOptions) { - pid_t pid = fork(); - if (pid == 0) { - // Child process - int result = executePythonScript(srcPath, {FILE_PATH_BASE, option}); - if (result == 0xfd00) { // 0xfd from Python is 0xfd00 in C++ - GTEST_SKIP() - << "Xarray compatibility skipped due to import error for xarray"; - exit(SUCCESS_CODE); - } - exit(result); - } else if (pid > 0) { - // Parent process - pids.push_back(pid); - } else { - // Fork failed - perror("fork failed"); - FAIL() << "fork failed"; - } - } + auto [metadata, variables] = construct.value(); + auto dataset = + mdio::Dataset::Open(metadata, variables, mdio::constants::kOpen); + ASSERT_TRUE(dataset.status().ok()) << dataset.status(); - // Wait for all child processes - for (pid_t pid : pids) { - int status; - if (waitpid(pid, &status, 0) == -1) { - perror("waitpid failed"); - FAIL() << "waitpid failed"; - } - if (WIFEXITED(status) && WEXITSTATUS(status) == 0xfd00) { - GTEST_SKIP() - << "Xarray compatibility skipped due to import error for xarray"; - } - ASSERT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0) - << "xarray compatibility test failed with one of the metadata " - "options\n\tThere was some expected output above..."; - } + std::vector varList = dataset.value().variables.get_keys(); + EXPECT_EQ(varList.size(), expected_var_count_); } -TEST(Dataset, listVars) { - nlohmann::json j = nlohmann::json::parse(datasetManifest); - auto construct = Construct(j, "zarrs/acceptance"); - ASSERT_TRUE(construct.status().ok()) << construct.status(); - - std::tuple> parsed = - construct.value(); - nlohmann::json metadata = std::get<0>(parsed); - std::vector variables = std::get<1>(parsed); +TEST_P(DatasetTest, fromJson) { + std::filesystem::remove_all(base_path_ + "/from_json_test"); + nlohmann::json j = nlohmann::json::parse(dataset_manifest_); auto dataset = - mdio::Dataset::Open(metadata, variables, mdio::constants::kOpen); + mdio::Dataset::from_json(j, base_path_ + "/from_json_test", version_, + mdio::constants::kCreateClean); ASSERT_TRUE(dataset.status().ok()) << dataset.status(); std::vector varList = dataset.value().variables.get_keys(); - EXPECT_TRUE(varList.size() == 9) << "No variables found in dataset"; + EXPECT_EQ(varList.size(), expected_var_count_); + + std::filesystem::remove_all(base_path_ + "/from_json_test"); } -TEST(Dataset, selectField) { - std::string path = "zarrs/acceptance"; - auto dataset = mdio::Dataset::Open(path, mdio::constants::kOpen); - std::string name = "image_headers"; +TEST_P(DatasetTest, selectField) { + std::string manifest = R"( +{ + "metadata": { + "name": "select_field_test", + "apiVersion": "1.0.0", + "createdOn": "2023-12-12T15:02:06.413469-06:00" + }, + "variables": [ + { + "name": "image_headers", + "dataType": { + "fields": [ + {"name": "cdp-x", "format": "int32"}, + {"name": "cdp-y", "format": "int32"}, + {"name": "elevation", "format": "float16"}, + {"name": "some_scalar", "format": "float16"} + ] + }, + "dimensions": [ + {"name": "inline", "size": 128}, + {"name": "crossline", "size": 128} + ], + "metadata": { + "chunkGrid": { + "name": "regular", + "configuration": { "chunkShape": [64, 64] } + } + }, + "coordinates": ["inline", "crossline"] + }, + { + "name": "inline", + "dataType": "uint32", + "dimensions": [{"name": "inline", "size": 128}] + }, + { + "name": "crossline", + "dataType": "uint32", + "dimensions": [{"name": "crossline", "size": 128}] + } + ] +} + )"; + std::string test_path = base_path_ + "/select_field_test"; + std::filesystem::remove_all(test_path); + nlohmann::json j = nlohmann::json::parse(manifest); + auto dataset = mdio::Dataset::from_json(j, test_path, version_, + mdio::constants::kCreateClean); ASSERT_TRUE(dataset.status().ok()) << dataset.status(); + auto ds = dataset.value(); + std::string name = "image_headers"; EXPECT_TRUE(ds.get_variable(name).value().dtype() == mdio::constants::kByte) << "Failed to pull byte array from image_headers"; @@ -1845,13 +1100,482 @@ TEST(Dataset, selectField) { EXPECT_FALSE(ds.SelectField("image_headers", "NotAField").status().ok()) << "Somehow pulled NotAField from image_headers"; + + std::filesystem::remove_all(test_path); } -TEST(Dataset, TEARDOWN) { - std::filesystem::remove_all("zarrs/acceptance"); +TEST_P(DatasetTest, fillValue) { + std::string manifest = R"( +{ + "metadata": { + "name": "fill_value_test", + "apiVersion": "1.0.0", + "createdOn": "2023-12-12T15:02:06.413469-06:00" + }, + "variables": [ + { + "name": "image_headers", + "dataType": { + "fields": [ + {"name": "cdp-x", "format": "int32"}, + {"name": "cdp-y", "format": "int32"}, + {"name": "elevation", "format": "float16"}, + {"name": "some_scalar", "format": "float16"} + ] + }, + "dimensions": [ + {"name": "inline", "size": 256}, + {"name": "crossline", "size": 512} + ], + "metadata": { + "chunkGrid": { + "name": "regular", + "configuration": { "chunkShape": [128, 128] } + } + } + }, + { + "name": "inline", + "dataType": "uint32", + "dimensions": [{"name": "inline", "size": 256}] + }, + { + "name": "crossline", + "dataType": "uint32", + "dimensions": [{"name": "crossline", "size": 512}] + } + ] +} + )"; + + std::string test_path = base_path_ + "/fill_value_test"; + std::filesystem::remove_all(test_path); + nlohmann::json j = nlohmann::json::parse(manifest); + auto ds = mdio::Dataset::from_json(j, test_path, version_, + mdio::constants::kCreateClean); + ASSERT_TRUE(ds.status().ok()) << ds.status(); + + std::string key = "image_headers"; + auto var = ds.value().get_variable(key); + ASSERT_TRUE(var.status().ok()) << var.status(); + auto vdf = var.value().Read(); + ASSERT_TRUE(vdf.status().ok()) << vdf.status(); + auto vd = vdf.value(); + + auto data = + reinterpret_cast(vd.get_data_accessor().data()); + std::byte zero = std::byte(0); + for (int i = 0; i < 1000; i++) { + ASSERT_EQ(data[i], zero) << "Expected 0 at byte " << i << " but got " + << static_cast(data[i]); + } + + std::filesystem::remove_all(test_path); +} + +TEST_P(DatasetTest, TEARDOWN) { + std::filesystem::remove_all(base_path_); ASSERT_TRUE(true); } -} // namespace DatasetTest +INSTANTIATE_TEST_SUITE_P( + ZarrVersions, DatasetTest, + ::testing::Values(mdio::zarr::ZarrVersion::kV2, + mdio::zarr::ZarrVersion::kV3), + [](const ::testing::TestParamInfo& info) { + return ZarrVersionToString(info.param); + }); + +// ============================================================================ +// Parameterized Python/Xarray Dataset Compatibility Tests +// ============================================================================ + +class XarrayCompatibilityTest + : public ::testing::TestWithParam { + protected: + void SetUp() override { + version_ = GetParam(); + base_path_ = GetBasePath(version_); + } + + mdio::zarr::ZarrVersion version_; + std::string base_path_; +}; + +TEST_P(XarrayCompatibilityTest, datasetCompatible) { + // This test verifies that a Dataset created by MDIO can be opened by xarray. + // The dataset is created fresh for this test to ensure isolation. + std::string manifest = R"( +{ + "metadata": { + "name": "xarray_compat_test", + "apiVersion": "1.0.0", + "createdOn": "2023-12-12T15:02:06.413469-06:00" + }, + "variables": [ + { + "name": "data", + "dataType": "float32", + "dimensions": [ + {"name": "x", "size": 10}, + {"name": "y", "size": 10} + ], + "metadata": { + "chunkGrid": { + "name": "regular", + "configuration": { "chunkShape": [5, 5] } + } + } + }, + { + "name": "x", + "dataType": "int32", + "dimensions": [{"name": "x", "size": 10}] + }, + { + "name": "y", + "dataType": "int32", + "dimensions": [{"name": "y", "size": 10}] + } + ] +} + )"; + + std::string test_path = base_path_ + "/xarray_compat"; + std::filesystem::remove_all(test_path); + + nlohmann::json j = nlohmann::json::parse(manifest); + auto ds = mdio::Dataset::from_json(j, test_path, version_, + mdio::constants::kCreateClean); + ASSERT_TRUE(ds.status().ok()) << ds.status(); + + std::string srcPath = + std::string(GetPythonBasePath()) + XARRAY_SCRIPT_RELATIVE_PATH; + + if (access(srcPath.c_str(), F_OK) == -1) { + std::cerr << "Error: Python script not found at " << srcPath << std::endl; + std::filesystem::remove_all(test_path); + FAIL() << "Script not found: " << srcPath; + } + + // Test without consolidated metadata (both versions) + // Note: Consolidated metadata is only supported for V2 + std::vector> arg_sets = { + {test_path + "/", "False"}, + }; + if (version_ == mdio::zarr::ZarrVersion::kV2) { + // Also test with consolidated metadata for V2 + arg_sets.push_back({test_path + "/", "True"}); + } + + std::string version_name = ZarrVersionToString(version_); + EXPECT_TRUE(RunPythonScripts( + srcPath, arg_sets, "Xarray compatibility skipped due to import error")) + << "xarray " << version_name << " compatibility test failed"; + + // std::filesystem::remove_all(test_path); +} + +INSTANTIATE_TEST_SUITE_P( + ZarrVersions, XarrayCompatibilityTest, + ::testing::Values(mdio::zarr::ZarrVersion::kV2, + mdio::zarr::ZarrVersion::kV3), + [](const ::testing::TestParamInfo& info) { + return ZarrVersionToString(info.param); + }); + +// ============================================================================ +// Parameterized Python/Multidimio Dataset Compatibility Tests +// ============================================================================ + +class MultidimioCompatibilityTest + : public ::testing::TestWithParam { + protected: + void SetUp() override { + version_ = GetParam(); + base_path_ = GetBasePath(version_); + } + + mdio::zarr::ZarrVersion version_; + std::string base_path_; +}; + +TEST_P(MultidimioCompatibilityTest, datasetCompatible) { + // This test verifies that a Dataset created by multidimio can be opened by + // C++, for both Zarr V2 and V3. It runs the python script to ingest a real + // SEG-Y dataset to an MDIO dataset. The python ingestion honors the requested + // Zarr format via the ZARR_DEFAULT_ZARR_FORMAT environment variable, which + // the forked interpreter inherits. + std::string test_path = base_path_ + "/multidimio_compat"; + std::filesystem::remove_all(test_path); + + std::string srcPath = + std::string(GetPythonBasePath()) + MULTIDIMIO_SCRIPT_RELATIVE_PATH; + + if (access(srcPath.c_str(), F_OK) == -1) { + std::cerr << "Error: Python script not found at " << srcPath << std::endl; + std::filesystem::remove_all(test_path); + FAIL() << "Script not found: " << srcPath; + } + + const std::string zarr_format = + version_ == mdio::zarr::ZarrVersion::kV3 ? "3" : "2"; + setenv("ZARR_DEFAULT_ZARR_FORMAT", zarr_format.c_str(), /*overwrite=*/1); + + std::vector> arg_sets = { + {test_path + "/"}, + }; + + bool scripts_passed = RunPythonScripts( + srcPath, arg_sets, + "Multidimio compatibility skipped due to import error"); + unsetenv("ZARR_DEFAULT_ZARR_FORMAT"); + + EXPECT_TRUE(scripts_passed) << "multidimio compatibility test failed"; + + // Now try to open the ingested dataset with C++ + std::cout << "Attempting to open the ingested dataset with C++..." + << std::endl; + auto ds = mdio::Dataset::Open(test_path, mdio::constants::kOpen); + + EXPECT_TRUE(ds.status().ok()) << ds.status(); + + auto dataset = ds.value(); + EXPECT_TRUE(dataset.header_variables.contains_key("segy_file_header")); + + auto header_var = dataset.get_header_variable("segy_file_header"); + ASSERT_TRUE(header_var.status().ok()) << header_var.status(); + EXPECT_EQ(header_var.value().get_variable_name(), "segy_file_header"); + EXPECT_EQ(header_var.value().rank(), 0); + + auto read_future = header_var.value().Read(); + EXPECT_FALSE(read_future.status().ok()); + + std::stringstream printed; + printed << dataset; + EXPECT_THAT(printed.str(), ::testing::HasSubstr("Header Variable: segy_file_header")); + + auto attrs = header_var.value().GetAttributes(); + ASSERT_TRUE(attrs.contains("attributes")); + ASSERT_TRUE(attrs["attributes"].contains("textHeader")); + nlohmann::json updated_attrs = attrs; + updated_attrs["attributes"]["testMarker"] = "cpp-mdio"; + ASSERT_TRUE(header_var.value().UpdateAttributes(updated_attrs).ok()); + + // Persist through the Dataset commit path, which rewrites the consolidated + // metadata and republishes the variable attributes together, keeping them in + // sync. This is the canonical way to persist metadata changes. + auto commit_future = dataset.CommitMetadata(); + ASSERT_TRUE(commit_future.status().ok()) << commit_future.status(); + + auto reopened = mdio::Dataset::Open(test_path, mdio::constants::kOpen); + ASSERT_TRUE(reopened.status().ok()) << reopened.status(); + auto reopened_header = + reopened.value().get_header_variable("segy_file_header"); + ASSERT_TRUE(reopened_header.status().ok()) << reopened_header.status(); + EXPECT_EQ(reopened_header.value().GetAttributes()["attributes"]["testMarker"], + "cpp-mdio"); + + // Clean up + std::filesystem::remove_all(test_path); +} + +INSTANTIATE_TEST_SUITE_P( + ZarrVersions, MultidimioCompatibilityTest, + ::testing::Values(mdio::zarr::ZarrVersion::kV2, + mdio::zarr::ZarrVersion::kV3), + [](const ::testing::TestParamInfo& info) { + return ZarrVersionToString(info.param); + }); + +// ============================================================================ +// Dataset::from_json with Version Parameter Tests +// ============================================================================ + +class DatasetFromJsonTest + : public ::testing::TestWithParam { + protected: + void SetUp() override { + version_ = GetParam(); + base_path_ = GetBasePath(version_) + "/from_json"; + std::filesystem::remove_all(base_path_); + } + + void TearDown() override { std::filesystem::remove_all(base_path_); } + + std::string GetSimpleManifest() { + return R"( +{ + "metadata": { + "name": "from_json_test", + "apiVersion": "1.0.0", + "createdOn": "2023-12-12T15:02:06.413469-06:00" + }, + "variables": [ + { + "name": "data", + "dataType": "float32", + "dimensions": [ + {"name": "x", "size": 32}, + {"name": "y", "size": 32} + ], + "metadata": { + "chunkGrid": { + "name": "regular", + "configuration": { "chunkShape": [16, 16] } + } + } + }, + { + "name": "x", + "dataType": "int32", + "dimensions": [{"name": "x", "size": 32}] + }, + { + "name": "y", + "dataType": "int32", + "dimensions": [{"name": "y", "size": 32}] + } + ] +} + )"; + } + + mdio::zarr::ZarrVersion version_; + std::string base_path_; +}; + +TEST_P(DatasetFromJsonTest, createWithExplicitVersion) { + nlohmann::json j = nlohmann::json::parse(GetSimpleManifest()); + auto dataset = mdio::Dataset::from_json(j, base_path_, version_, + mdio::constants::kCreateClean); + ASSERT_TRUE(dataset.status().ok()) << dataset.status(); + + auto ds = dataset.value(); + EXPECT_EQ(ds.getMetadata()["name"], "from_json_test"); + + std::vector varList = ds.variables.get_keys(); + EXPECT_EQ(varList.size(), 3); +} + +TEST_P(DatasetFromJsonTest, createWithOptionalVersion) { + nlohmann::json j = nlohmann::json::parse(GetSimpleManifest()); + auto dataset = mdio::Dataset::from_json( + j, base_path_, std::optional(version_), + mdio::constants::kCreateClean); + ASSERT_TRUE(dataset.status().ok()) << dataset.status(); + + auto ds = dataset.value(); + EXPECT_EQ(ds.getMetadata()["name"], "from_json_test"); +} + +TEST_P(DatasetFromJsonTest, readWrite) { + nlohmann::json j = nlohmann::json::parse(GetSimpleManifest()); + auto datasetRes = mdio::Dataset::from_json(j, base_path_, version_, + mdio::constants::kCreateClean); + ASSERT_TRUE(datasetRes.status().ok()) << datasetRes.status(); + auto ds = datasetRes.value(); + + auto dataVarRes = ds.variables.get("data"); + ASSERT_TRUE(dataVarRes.status().ok()) << dataVarRes.status(); + auto dataVar = dataVarRes.value(); + + auto dataRes = dataVar.Read(); + ASSERT_TRUE(dataRes.status().ok()) << dataRes.status(); + auto data = dataRes.value(); + + auto accessor = data.get_data_accessor().data(); + accessor[0] = 42.0f; + accessor[1] = 43.0f; + + auto writeFut = dataVar.Write(data); + ASSERT_TRUE(writeFut.status().ok()) << writeFut.status(); + + auto rereadFut = dataVar.Read(); + ASSERT_TRUE(rereadFut.status().ok()) << rereadFut.status(); + auto rereadData = rereadFut.value(); + auto rereadAccessor = rereadData.get_data_accessor().data(); + + EXPECT_FLOAT_EQ(rereadAccessor[0], 42.0f); + EXPECT_FLOAT_EQ(rereadAccessor[1], 43.0f); +} + +TEST_P(DatasetFromJsonTest, isel) { + nlohmann::json j = nlohmann::json::parse(GetSimpleManifest()); + auto datasetRes = mdio::Dataset::from_json(j, base_path_, version_, + mdio::constants::kCreateClean); + ASSERT_TRUE(datasetRes.status().ok()) << datasetRes.status(); + auto ds = datasetRes.value(); + + mdio::RangeDescriptor desc1 = {"x", 0, 10, 1}; + mdio::RangeDescriptor desc2 = {"y", 5, 15, 1}; + auto sliceRes = ds.isel(desc1, desc2); + + ASSERT_TRUE(sliceRes.status().ok()) << sliceRes.status(); + auto slice = sliceRes.value(); + + auto domain = slice.domain; + ASSERT_EQ(domain.rank(), 2); +} + +TEST_P(DatasetFromJsonTest, intervals) { + nlohmann::json j = nlohmann::json::parse(GetSimpleManifest()); + auto datasetRes = mdio::Dataset::from_json(j, base_path_, version_, + mdio::constants::kCreateClean); + ASSERT_TRUE(datasetRes.status().ok()) << datasetRes.status(); + auto ds = datasetRes.value(); + + auto intervalRes = ds.get_intervals(); + ASSERT_TRUE(intervalRes.ok()) << intervalRes.status(); + auto intervals = intervalRes.value(); + + EXPECT_GE(intervals.size(), 2); +} + +INSTANTIATE_TEST_SUITE_P( + ZarrVersions, DatasetFromJsonTest, + ::testing::Values(mdio::zarr::ZarrVersion::kV2, + mdio::zarr::ZarrVersion::kV3), + [](const ::testing::TestParamInfo& info) { + return ZarrVersionToString(info.param); + }); + +// Test nullopt version (should default to V2) +TEST(DatasetFromJsonNullopt, createWithNulloptVersion) { + std::filesystem::remove_all("zarrs/from_json_nullopt"); + + std::string manifest = R"( +{ + "metadata": { + "name": "nullopt_test", + "apiVersion": "1.0.0", + "createdOn": "2023-12-12T15:02:06.413469-06:00" + }, + "variables": [ + { + "name": "data", + "dataType": "float32", + "dimensions": [{"name": "x", "size": 10}] + }, + { + "name": "x", + "dataType": "int32", + "dimensions": [{"name": "x", "size": 10}] + } + ] +} + )"; + + nlohmann::json j = nlohmann::json::parse(manifest); + std::optional version = std::nullopt; + auto dataset = mdio::Dataset::from_json(j, "zarrs/from_json_nullopt", version, + mdio::constants::kCreateClean); + ASSERT_TRUE(dataset.status().ok()) << dataset.status(); + + auto ds = dataset.value(); + EXPECT_EQ(ds.getMetadata()["name"], "nullopt_test"); + + std::filesystem::remove_all("zarrs/from_json_nullopt"); +} } // namespace diff --git a/mdio/coordinate_selector_test.cc b/mdio/coordinate_selector_test.cc index 93eaec9..7652daa 100644 --- a/mdio/coordinate_selector_test.cc +++ b/mdio/coordinate_selector_test.cc @@ -26,6 +26,7 @@ #include "mdio/dataset.h" #include "mdio/dataset_factory.h" +#include "mdio/zarr/zarr.h" #include "tensorstore/driver/driver.h" #include "tensorstore/driver/registry.h" #include "tensorstore/index_space/dim_expression.h" @@ -43,8 +44,24 @@ namespace { -mdio::Result SetupDataset() { - std::string ds_path = "generic_with_coords.mdio"; +/** + * @brief Returns a string representation of the Zarr version for naming. + */ +std::string ZarrVersionToString(mdio::zarr::ZarrVersion version) { + return version == mdio::zarr::ZarrVersion::kV3 ? "V3" : "V2"; +} + +/** + * @brief Returns the base path for test data based on Zarr version. + */ +std::string GetBasePath(mdio::zarr::ZarrVersion version) { + return version == mdio::zarr::ZarrVersion::kV3 ? "generic_with_coords_v3.mdio" + : "generic_with_coords.mdio"; +} + +mdio::Result SetupDataset( + mdio::zarr::ZarrVersion version = mdio::zarr::ZarrVersion::kV2) { + std::string ds_path = GetBasePath(version); std::string schema_str = R"( { "metadata": { @@ -141,8 +158,8 @@ mdio::Result SetupDataset() { })"; auto schema = ::nlohmann::json::parse(schema_str); - auto dsFut = - mdio::Dataset::from_json(schema, ds_path, mdio::constants::kCreate); + auto dsFut = mdio::Dataset::from_json(schema, ds_path, version, + mdio::constants::kCreateClean); if (!dsFut.status().ok()) { return ds_path; } @@ -210,13 +227,31 @@ mdio::Result SetupDataset() { return ds_path; } -TEST(Intersection, SETUP) { - auto pathResult = SetupDataset(); +// ============================================================================ +// Parameterized Coordinate Selector Tests +// ============================================================================ + +class CoordinateSelectorTest + : public ::testing::TestWithParam { + protected: + void SetUp() override { + version_ = GetParam(); + base_path_ = GetBasePath(version_); + } + + void TearDown() override { std::filesystem::remove_all(base_path_); } + + mdio::zarr::ZarrVersion version_; + std::string base_path_; +}; + +TEST_P(CoordinateSelectorTest, SETUP) { + auto pathResult = SetupDataset(version_); ASSERT_TRUE(pathResult.status().ok()) << pathResult.status(); } -TEST(Intersection, constructor) { - auto pathResult = SetupDataset(); +TEST_P(CoordinateSelectorTest, constructor) { + auto pathResult = SetupDataset(version_); ASSERT_TRUE(pathResult.status().ok()) << pathResult.status(); auto path = pathResult.value(); @@ -227,8 +262,8 @@ TEST(Intersection, constructor) { mdio::CoordinateSelector cs(ds); } -TEST(Intersection, add_selection) { - auto pathResult = SetupDataset(); +TEST_P(CoordinateSelectorTest, add_selection) { + auto pathResult = SetupDataset(version_); ASSERT_TRUE(pathResult.status().ok()) << pathResult.status(); auto path = pathResult.value(); @@ -248,8 +283,8 @@ TEST(Intersection, add_selection) { // map but got " << selections.size(); } -TEST(Intersection, range_descriptors) { - auto pathResult = SetupDataset(); +TEST_P(CoordinateSelectorTest, range_descriptors) { + auto pathResult = SetupDataset(version_); ASSERT_TRUE(pathResult.status().ok()) << pathResult.status(); auto path = pathResult.value(); @@ -285,8 +320,8 @@ TEST(Intersection, range_descriptors) { // to have a step of 1"; } -TEST(Intersection, get_inline_range) { - auto pathResult = SetupDataset(); +TEST_P(CoordinateSelectorTest, get_inline_range) { + auto pathResult = SetupDataset(version_); ASSERT_TRUE(pathResult.status().ok()) << pathResult.status(); auto path = pathResult.value(); @@ -311,8 +346,8 @@ TEST(Intersection, get_inline_range) { // } } -TEST(Intersection, get_inline_range_dead) { - auto pathResult = SetupDataset(); +TEST_P(CoordinateSelectorTest, get_inline_range_dead) { + auto pathResult = SetupDataset(version_); ASSERT_TRUE(pathResult.status().ok()) << pathResult.status(); auto path = pathResult.value(); @@ -338,4 +373,12 @@ TEST(Intersection, get_inline_range_dead) { // } } +INSTANTIATE_TEST_SUITE_P( + ZarrVersions, CoordinateSelectorTest, + ::testing::Values(mdio::zarr::ZarrVersion::kV2, + mdio::zarr::ZarrVersion::kV3), + [](const ::testing::TestParamInfo& info) { + return ZarrVersionToString(info.param); + }); + } // namespace diff --git a/mdio/dataset.h b/mdio/dataset.h index 150940b..c4673a2 100644 --- a/mdio/dataset.h +++ b/mdio/dataset.h @@ -22,6 +22,7 @@ #include #include #include +#include #include #include #include @@ -32,10 +33,14 @@ #include #include "mdio/dataset_factory.h" +#include "mdio/header_variable.h" #include "mdio/variable.h" #include "mdio/variable_collection.h" +#include "mdio/zarr/zarr.h" #include "tensorstore/driver/zarr/metadata.h" #include "tensorstore/util/future.h" +#include "tensorstore/util/option.h" +#include "tensorstore/util/status.h" // clang-format off #include // NOLINT @@ -48,201 +53,41 @@ namespace internal { * @brief Retrieves the .zarray JSON metadata from the given `metadata`. * * This function derives the .zarray JSON metadata without actually reading it. + * This is a compatibility wrapper that delegates to the zarr V2 implementation. * * @param metadata The input JSON metadata. * @return An `mdio::Result` containing the .zarray JSON metadata on success, or * an error on failure. + * @deprecated Use zarr::v2::GetZarray directly for V2 stores. */ inline Result get_zarray(const ::nlohmann::json metadata) { - // derive .zarray json metadata (without reading it). - auto json = - metadata; // Why am I doing this? It's an extra copy that does nothing! - nlohmann::json zarray; - if (!json.contains("metadata")) { - json["metadata"] = nlohmann::json::object(); // Just add an empty object - json["metadata"]["attributes"] = - nlohmann::json::object(); // We need attributes as well - } - - // these fields can have defaults: - if (!json["metadata"].contains("order")) { - zarray["order"] = "C"; - } else { - zarray["order"] = json["metadata"]["order"]; - } - if (!json["metadata"].contains("filters")) { - zarray["filters"] = nullptr; - } else { - zarray["filters"] = json["metadata"]["filters"]; - } - - if (!json["metadata"].contains("fill_value")) { - zarray["fill_value"] = nullptr; - } else { - zarray["fill_value"] = json["metadata"]["fill_value"]; - } - - if (!json["metadata"].contains("zarr_format")) { - zarray["zarr_format"] = 2; - } else { - zarray["zarr_format"] = json["metadata"]["zarr_format"]; - } - - if (!json["metadata"].contains("chunks") && - json["metadata"].contains("shape")) { - zarray["chunks"] = json["metadata"]["shape"]; - } else { - zarray["chunks"] = json["metadata"]["chunks"]; - } - - if (!json["metadata"].contains("compressor")) { - zarray["compressor"] = nullptr; - } else { - zarray["compressor"] = json["metadata"]["compressor"]; - } - - if (!json["metadata"].contains("dimension_separator")) { - zarray["dimension_separator"] = "/"; - } else { - zarray["dimension_separator"] = json["metadata"]["dimension_separator"]; - } - - zarray["shape"] = json["metadata"]["shape"]; - zarray["dtype"] = json["metadata"]["dtype"]; - - // fixme chunks must be configured ... - MDIO_ASSIGN_OR_RETURN( - auto zarr_metadata, - tensorstore::internal_zarr::ZarrMetadata::FromJson(zarray)) - - return ::nlohmann::json(zarr_metadata); + return zarr::v2::GetZarray(metadata); } /** - * @brief Writes the zmetadata for the dataset. + * @brief Writes the metadata for the dataset. + * + * For Zarr V2, writes consolidated metadata (.zmetadata, .zgroup, .zattrs). + * For Zarr V3, writes only root zarr.json (no consolidated metadata support). + * + * This overload auto-detects the Zarr version from the first variable's + * driver specification. * * @param dataset_metadata The metadata for the dataset. * @param json_variables The JSON variables. + * @param context Optional TensorStore context for credentials/configuration. * @return An `mdio::Future` representing the asynchronous write. */ inline Future write_zmetadata( const ::nlohmann::json& dataset_metadata, - const std::vector<::nlohmann::json>& json_variables) { - // header material at the root of the dataset ... - // Configure a kvstore (we can't deduce if it's in memory etc). - // { - // "kvstore", - // { - // {"driver", "file"}, - // {"path", "name"} - // } - //} - auto zattrs = dataset_metadata; - - // FIXME - generalize for zarr v3 - ::nlohmann::json zgroup; - zgroup["zarr_format"] = 2; - - // The consolidated metadata for the datset - ::nlohmann::json zmetadata; - - // FIXME - don't hard code here ... - zmetadata["zarr_consolidated_format"] = 1; - - zmetadata["metadata"][".zattrs"] = zattrs; - zmetadata["metadata"][".zgroup"] = zgroup; - - std::string zarray_key; - std::string zattrs_key; - std::string driver = - json_variables[0]["kvstore"]["driver"].get(); - - for (const auto& json : json_variables) { - zarray_key = - std::filesystem::path(json["kvstore"]["path"]).stem() / ".zarray"; - zattrs_key = - std::filesystem::path(json["kvstore"]["path"]).stem() / ".zattrs"; - - MDIO_ASSIGN_OR_RETURN(zmetadata["metadata"][zarray_key], get_zarray(json)) - - nlohmann::json fixedJson = json["attributes"]; - fixedJson["_ARRAY_DIMENSIONS"] = fixedJson["dimension_names"]; - fixedJson.erase("dimension_names"); - // We do not want to be seralizing the variable_name. It should be - // self-describing - if (fixedJson.contains("variable_name")) { - fixedJson.erase("variable_name"); - } - if (fixedJson.contains("long_name") && - fixedJson["long_name"].get() == "") { - fixedJson.erase("long_name"); - } - if (fixedJson.contains("metadata")) { - if (fixedJson["metadata"].contains("chunkGrid")) { - fixedJson["metadata"].erase("chunkGrid"); - } - for (auto& item : fixedJson["metadata"].items()) { - fixedJson[item.key()] = std::move(item.value()); - } - fixedJson.erase("metadata"); - } - // Case where an empty array of coordinates were provided - if (fixedJson.contains("coordinates")) { - auto coords = fixedJson["coordinates"]; - if (coords.empty() || - (coords.is_string() && coords.get() == "")) { - fixedJson.erase("coordinates"); - } - } - zmetadata["metadata"][zattrs_key] = fixedJson; + const std::vector<::nlohmann::json>& json_variables, + tensorstore::Context context = tensorstore::Context::Default()) { + zarr::ZarrVersion version = zarr::ZarrVersion::kV2; + if (!json_variables.empty()) { + version = zarr::GetVersionFromSpec(json_variables[0]); } - - nlohmann::json kvstore = nlohmann::json::object(); - kvstore["driver"] = driver; - std::vector file_parts = absl::StrSplit( - json_variables[0]["kvstore"]["path"].get(), '/'); - size_t toRemove = file_parts.back().size(); - std::string strippedPath = - json_variables[0]["kvstore"]["path"].get().substr( - 0, json_variables[0]["kvstore"]["path"].get().size() - - toRemove - 1); - kvstore["path"] = strippedPath; - - if (driver == "gcs" || driver == "s3") { - kvstore["bucket"] = - json_variables[0]["kvstore"]["bucket"].get(); - std::string cloudPath = kvstore["path"].get(); - kvstore["path"] = cloudPath; - } - - auto kvs_future = tensorstore::kvstore::Open(kvstore); - - auto zattrs_future = tensorstore::MapFutureValue( - tensorstore::InlineExecutor{}, - [zattrs = std::move(zattrs)](const tensorstore::KvStore& kvstore) { - return tensorstore::kvstore::Write(kvstore, "/.zattrs", - absl::Cord(zattrs.dump(4))); - }, - kvs_future); - - auto zmetadata_future = tensorstore::MapFutureValue( - tensorstore::InlineExecutor{}, - [zmetadata = std::move(zmetadata)](const tensorstore::KvStore& kvstore) { - return tensorstore::kvstore::Write(kvstore, "/.zmetadata", - absl::Cord(zmetadata.dump(4))); - }, - kvs_future); - - auto zgroup_future = tensorstore::MapFutureValue( - tensorstore::InlineExecutor{}, - [zgroup = std::move(zgroup)](const tensorstore::KvStore& kvstore) { - return tensorstore::kvstore::Write(kvstore, "/.zgroup", - absl::Cord(zgroup.dump(4))); - }, - kvs_future); - - return tensorstore::WaitAllFuture(zattrs_future, zmetadata_future, - zgroup_future); + return zarr::WriteDatasetMetadata(version, dataset_metadata, json_variables, + context); } /** @@ -251,143 +96,84 @@ inline Future write_zmetadata( * It will also attempt to infer the driver based on the prefix of the path. * It will default to the "file" driver if no prefix is found. * @param dataset_path The path to the dataset. + * @param context Optional TensorStore context for credentials/configuration. */ inline Future dataset_kvs_store( - const std::string& dataset_path) { - // the tensorstore driver needs a bucket field + const std::string& dataset_path, + tensorstore::Context context = tensorstore::Context::Default()) { ::nlohmann::json kvstore; - absl::string_view output_file = dataset_path; - - if (absl::StartsWith(output_file, "gs://")) { - absl::ConsumePrefix(&output_file, "gs://"); - kvstore["driver"] = "gcs"; - } else if (absl::StartsWith(output_file, "s3://")) { - absl::ConsumePrefix(&output_file, "s3://"); - kvstore["driver"] = "s3"; - } else { - kvstore["driver"] = "file"; - kvstore["path"] = output_file; - return tensorstore::kvstore::Open(kvstore); - } // FIXME - we need azure support ... - - std::vector file_parts = absl::StrSplit(output_file, '/'); - if (file_parts.size() < 2) { + // Use shared utility to infer driver from path prefix + std::string driver = zarr::InferDriverFromPath(dataset_path); + kvstore["driver"] = driver; + + if (driver == "file") { + // Local file system - just normalize with trailing slash + kvstore["path"] = zarr::NormalizePathWithSlash(dataset_path); + return tensorstore::kvstore::Open(kvstore, context); + } + + // Cloud storage (GCS or S3) - extract bucket and path + auto [bucket, path] = zarr::ExtractCloudPath(dataset_path); + if (bucket.empty()) { return absl::InvalidArgumentError( "gcs/s3 drivers requires [s3/gs]://[bucket]/[path_to_file]"); } - std::string bucket = file_parts[0]; - std::string filepath(file_parts[1]); - for (std::size_t i = 2; i < file_parts.size(); ++i) { - filepath += "/" + file_parts[i]; - } - // update the bucket and path ... kvstore["bucket"] = bucket; - kvstore["path"] = filepath; + kvstore["path"] = zarr::NormalizePathWithSlash(path); - return tensorstore::kvstore::Open(kvstore); + return tensorstore::kvstore::Open(kvstore, context); } /** - * @brief Retrieves the .zmetadata for the dataset. - * This is for executing a read on the dataset's consolidated metadata. - * It will also attempt to infer the driver based on the prefix of the path. - * It will default to the "file" driver if no prefix is found. + * @brief Retrieves the metadata for the dataset with auto-detection. + * This version auto-detects the Zarr version by checking for V3 markers first. * @param dataset_path The path to the dataset. - * @return An `mdio::Future` containing the .zmetadata JSON on success, or an + * @param context Optional TensorStore context for credentials/configuration. + * @return An `mdio::Future` containing the metadata JSON on success, or an * error on failure. */ inline Future>> -from_zmetadata(const std::string& dataset_path) { - // e.g. dataset_path = "zarrs/acceptance/"; - // FIXME - enable async - auto kvs_future = mdio::internal::dataset_kvs_store(dataset_path).result(); - - if (!kvs_future.ok()) { - return internal::CheckMissingDriverStatus(kvs_future.status()); - } - auto kvs_read_result = - tensorstore::kvstore::Read(kvs_future.value(), ".zmetadata").result(); - if (!kvs_read_result.ok()) { - return internal::CheckMissingDriverStatus(kvs_read_result.status()); - } - - ::nlohmann::json zmetadata; - try { - zmetadata = - ::nlohmann::json::parse(std::string(kvs_read_result.value().value)); - } catch (const nlohmann::json::parse_error& e) { - // It's a common error to not have a trailing slash on the dataset path. - if (!dataset_path.empty() && dataset_path.back() != '/') { - std::string fixPath = dataset_path + "/"; - return mdio::internal::from_zmetadata(fixPath); - } - return absl::Status(absl::StatusCode::kInvalidArgument, e.what()); - } - - if (!zmetadata.contains("metadata")) { - return absl::Status(absl::StatusCode::kInvalidArgument, - "zmetadata does not contain metadata."); - } - - if (!zmetadata["metadata"].contains(".zattrs")) { - return absl::Status(absl::StatusCode::kInvalidArgument, - "zmetadata does not contain dataset metadata."); - } - - auto dataset_metadata = zmetadata["metadata"][".zattrs"]; - - std::string driver = "file"; - // TODO(BrianMichell): Make this more robust. May be invalid if the stored - // path gets mangled somehow. Infer the driver - if (dataset_path.length() > 5) { - if (dataset_path.substr(0, 5) == "gs://") { - driver = "gcs"; - } else if (dataset_path.substr(0, 5) == "s3://") { - driver = "s3"; - } - } - - std::string bucket; - std::string cloudPath; - if (driver != "file") { - std::string providedPath = - dataset_path.substr(5); // Strip the gs:// or s3:// - size_t bucketLen = providedPath.find_first_of('/'); - bucket = providedPath.substr(0, bucketLen); // Extract the bucket name - cloudPath = providedPath.substr( - bucketLen + 1, providedPath.length() - 2); // Extract the path - } - - // Remove .zattrs from metadata - zmetadata["metadata"].erase(".zattrs"); - std::vector json_vars_from_zmeta; - // Assemble a list of json for opening the variables in the dataset. - for (auto& element : zmetadata["metadata"].items()) { - // FIXME - remove hard code .zarray - if (element.key().substr(element.key().find_last_of(".") + 1) == "zarray") { - std::string variable_name = - element.key().substr(0, element.key().find("/")); - nlohmann::json new_dict = { - {"driver", "zarr"}, - {"kvstore", - {{"driver", driver}, {"path", dataset_path + "/" + variable_name}}}}; - if (driver != "file") { - new_dict["kvstore"]["bucket"] = bucket; - new_dict["kvstore"]["path"] = cloudPath + variable_name; - } - json_vars_from_zmeta.push_back(new_dict); - } - } - if (!json_vars_from_zmeta.size()) { - return absl::Status(absl::StatusCode::kInvalidArgument, - "Not variables found in zmetadata."); - } +from_zmetadata(const std::string& dataset_path, + tensorstore::Context context = tensorstore::Context::Default()) { + auto kvs_future = mdio::internal::dataset_kvs_store(dataset_path, context); + + auto pair = tensorstore::PromiseFuturePair< + std::tuple<::nlohmann::json, std::vector<::nlohmann::json>>>::Make(); + + kvs_future.ExecuteWhenReady( + [promise = std::move(pair.promise), + dataset_path](tensorstore::ReadyFuture ready_kvs) { + if (!ready_kvs.result().ok()) { + promise.SetResult( + internal::CheckMissingDriverStatus(ready_kvs.result().status())); + return; + } - return tensorstore::ReadyFuture< - std::tuple<::nlohmann::json, std::vector<::nlohmann::json>>>( - std::make_tuple(dataset_metadata, json_vars_from_zmeta)); + // Auto-detect version + auto version_future = zarr::DetectVersion(ready_kvs.value()); + version_future.ExecuteWhenReady( + [promise = std::move(promise), dataset_path, + kvs = ready_kvs.value()]( + tensorstore::ReadyFuture version_ready) { + zarr::ZarrVersion version = zarr::ZarrVersion::kV2; + if (version_ready.result().ok()) { + version = version_ready.value(); + } + auto result_future = zarr::ReadDatasetMetadata( + version, dataset_path, + tensorstore::MakeReadyFuture(kvs)); + + result_future.ExecuteWhenReady( + [promise = std::move(promise)]( + tensorstore::ReadyFuture>> + result) { promise.SetResult(result.result()); }); + }); + }); + + return pair.future; } } // namespace internal @@ -402,11 +188,15 @@ class Dataset { public: Dataset(const nlohmann::json& metadata, const VariableCollection& variables, const coordinate_map& coordinates, - const tensorstore::IndexDomain<>& domain) + const tensorstore::IndexDomain<>& domain, + tensorstore::Context context = tensorstore::Context::Default(), + HeaderVariableCollection header_variables = {}) : metadata(metadata), variables(variables), coordinates(coordinates), - domain(domain) {} + domain(domain), + context_(context), + header_variables(std::move(header_variables)) {} friend std::ostream& operator<<(std::ostream& os, const Dataset& dataset) { // Output metadata @@ -420,6 +210,12 @@ class Dataset { << "\n"; } + const auto header_keys = dataset.header_variables.get_iterable_accessor(); + for (const auto& key : header_keys) { + os << "Header Variable: " << key << " - Dimensions: " + << dataset.header_variables.at(key).value().dimensions() << "\n"; + } + // Output coordinates for (const auto& [key, coord_list] : dataset.coordinates) { os << "Variable: " << key << " - Coordinates: "; @@ -438,6 +234,13 @@ class Dataset { return os; } + template + Result> get_header_variable( + const std::string& variable_name) { + return header_variables.get(variable_name); + } + /** * @brief Retrieves a variable from within the dataset. * @param variable_name The name of the variable to retrieve. @@ -483,13 +286,99 @@ class Dataset { return intervals; } + /** + * @brief Constructs a Dataset from a JSON schema with explicit Zarr version. + * This method will validate the JSON schema against the MDIO Dataset schema + * and use the specified Zarr format version. + * @param json_schema The JSON schema to validate. + * @param path The path to create/open the dataset. + * @param zarr_version The Zarr format version to use (kV2 or kV3). + * @param options Variadic options for dataset creation/opening. + * @details \b Usage + * + * Create a Zarr V3 dataset given a schema and a path: + * @code + * auto dataset_future = mdio::Dataset::from_json( + * json_spec, + * dataset_path, + * mdio::zarr::ZarrVersion::kV3, + * mdio::constants::kCreate + * ); + * @endcode + * + * @return An `mdio::Future` resolves to a Dataset if successful, or an error + * if the schema is invalid. + */ + template + static Future from_json(::nlohmann::json& json_schema /*NOLINT*/, + const std::string& path, + zarr::ZarrVersion zarr_version, + Option&&... options) { + // json describing the vars ... + MDIO_ASSIGN_OR_RETURN(auto validated_schema, + Construct(json_schema, path, zarr_version)); + auto [dataset_metadata, json_vars] = validated_schema; + + return mdio::Dataset::Open(dataset_metadata, json_vars, + std::forward