diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 8257318c..3ae0a88f 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -44,7 +44,7 @@ jobs: - name: Build python packages run: | - uv run maturin develop -m object-store-rs/Cargo.toml + uv run maturin develop -m obstore/Cargo.toml - name: Deploy docs env: diff --git a/.github/workflows/test-python.yml b/.github/workflows/test-python.yml index 9a505b5b..d4797d8e 100644 --- a/.github/workflows/test-python.yml +++ b/.github/workflows/test-python.yml @@ -38,7 +38,7 @@ jobs: strategy: fail-fast: true matrix: - python-version: ["3.9", "3.12"] + python-version: ["3.11", "3.12"] steps: - uses: actions/checkout@v4 @@ -47,21 +47,18 @@ jobs: - uses: Swatinem/rust-cache@v2 - - name: Set up Python - id: setup-python - uses: actions/setup-python@v5 - with: - python-version: ${{ matrix.python-version }} - - - name: Install a specific version of uv + - name: Install uv uses: astral-sh/setup-uv@v3 with: enable-cache: true version: "0.4.x" + - name: Set up Python + run: uv python install ${{ matrix.python-version }} + - name: Build rust submodules run: | - uv run maturin develop -m object-store-rs/Cargo.toml + uv run maturin develop -m obstore/Cargo.toml - name: Run python tests run: | diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml index 19b0acfa..2b658ec3 100644 --- a/.github/workflows/wheels.yml +++ b/.github/workflows/wheels.yml @@ -1,7 +1,7 @@ # This file is (mostly) autogenerated by maturin v1.7.1 # To update, run # -# maturin generate-ci github -m object-store-rs/Cargo.toml +# maturin generate-ci github -m obstore/Cargo.toml # name: Build wheels @@ -46,13 +46,13 @@ jobs: uses: PyO3/maturin-action@v1 with: target: ${{ matrix.platform.target }} - args: --release --out dist --find-interpreter --manifest-path object-store-rs/Cargo.toml + args: --release --out dist --find-interpreter --manifest-path obstore/Cargo.toml sccache: "true" manylinux: auto - name: Upload wheels uses: actions/upload-artifact@v4 with: - name: wheels-linux-${{ matrix.platform.target }}-object-store-rs + name: wheels-linux-${{ matrix.platform.target }}-obstore path: dist musllinux: @@ -77,13 +77,13 @@ jobs: uses: PyO3/maturin-action@v1 with: target: ${{ matrix.platform.target }} - args: --release --out dist --find-interpreter --manifest-path object-store-rs/Cargo.toml + args: --release --out dist --find-interpreter --manifest-path obstore/Cargo.toml sccache: "true" manylinux: musllinux_1_2 - name: Upload wheels uses: actions/upload-artifact@v4 with: - name: wheels-musllinux-${{ matrix.platform.target }}-object-store-rs + name: wheels-musllinux-${{ matrix.platform.target }}-obstore path: dist windows: @@ -103,12 +103,12 @@ jobs: uses: PyO3/maturin-action@v1 with: target: ${{ matrix.platform.target }} - args: --release --out dist --find-interpreter --manifest-path object-store-rs/Cargo.toml + args: --release --out dist --find-interpreter --manifest-path obstore/Cargo.toml sccache: "true" - name: Upload wheels uses: actions/upload-artifact@v4 with: - name: wheels-windows-${{ matrix.platform.target }}-object-store-rs + name: wheels-windows-${{ matrix.platform.target }}-obstore path: dist macos: @@ -129,12 +129,12 @@ jobs: uses: PyO3/maturin-action@v1 with: target: ${{ matrix.platform.target }} - args: --release --out dist --find-interpreter --manifest-path object-store-rs/Cargo.toml + args: --release --out dist --find-interpreter --manifest-path obstore/Cargo.toml sccache: "true" - name: Upload wheels uses: actions/upload-artifact@v4 with: - name: wheels-macos-${{ matrix.platform.target }}-object-store-rs + name: wheels-macos-${{ matrix.platform.target }}-obstore path: dist # sdist: @@ -147,11 +147,11 @@ jobs: # uses: PyO3/maturin-action@v1 # with: # command: sdist - # args: --out dist --manifest-path object-store-rs/Cargo.toml + # args: --out dist --manifest-path obstore/Cargo.toml # - name: Upload sdist # uses: actions/upload-artifact@v4 # with: - # name: wheels-sdist-object-store-rs + # name: wheels-sdist-obstore # path: dist release: @@ -159,7 +159,7 @@ jobs: name: Release # environment: # name: release - # url: https://pypi.org/p/object-store-rs + # url: https://pypi.org/p/obstore # permissions: # # IMPORTANT: this permission is mandatory for trusted publishing # id-token: write @@ -168,7 +168,7 @@ jobs: steps: - uses: actions/download-artifact@v4 with: - pattern: wheels-*-object-store-rs + pattern: wheels-*-obstore merge-multiple: true path: dist - uses: actions/setup-python@v4 diff --git a/CHANGELOG.md b/CHANGELOG.md index e1fb479d..2a5e1522 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,18 @@ # Changelog +## [0.2.0] - 2024-10-25 + +## What's Changed + +- Streaming list results. `list` now returns an async or sync generator. by @kylebarron in https://github.com/developmentseed/obstore/pull/35 +- Optionally return list result as arrow. The `return_arrow` keyword argument returns chunks from `list` as Arrow RecordBatches, which is faster than materializing Python dicts/lists. by @kylebarron in https://github.com/developmentseed/obstore/pull/38 +- Return buffer protocol object from `get_range` and `get_ranges`. Enables zero-copy data exchange from Rust into Python. by @kylebarron in https://github.com/developmentseed/obstore/pull/39 +- Add put options. Enables custom tags and attributes, as well as "put if not exists". by @kylebarron in https://github.com/developmentseed/obstore/pull/50 +- Rename to obstore by @kylebarron in https://github.com/developmentseed/obstore/pull/45 +- Add custom exceptions. by @kylebarron in https://github.com/developmentseed/obstore/pull/48 + +**Full Changelog**: https://github.com/developmentseed/obstore/compare/py-v0.1.0...py-v0.2.0 + ## [0.1.0] - 2024-10-21 - Initial release. diff --git a/Cargo.lock b/Cargo.lock index 3f15fc3b..76ca020a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,6 +17,29 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" +[[package]] +name = "ahash" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" +dependencies = [ + "cfg-if", + "const-random", + "getrandom", + "once_cell", + "version_check", + "zerocopy", +] + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + [[package]] name = "android-tzdata" version = "0.1.1" @@ -32,6 +55,224 @@ dependencies = [ "libc", ] +[[package]] +name = "arrow" +version = "53.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9ba0d7248932f4e2a12fb37f0a2e3ec82b3bdedbac2a1dce186e036843b8f8c" +dependencies = [ + "arrow-arith", + "arrow-array", + "arrow-buffer", + "arrow-cast", + "arrow-csv", + "arrow-data", + "arrow-ipc", + "arrow-json", + "arrow-ord", + "arrow-row", + "arrow-schema", + "arrow-select", + "arrow-string", +] + +[[package]] +name = "arrow-arith" +version = "53.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d60afcdc004841a5c8d8da4f4fa22d64eb19c0c01ef4bcedd77f175a7cf6e38f" +dependencies = [ + "arrow-array", + "arrow-buffer", + "arrow-data", + "arrow-schema", + "chrono", + "half", + "num", +] + +[[package]] +name = "arrow-array" +version = "53.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f16835e8599dbbb1659fd869d865254c4cf32c6c2bb60b6942ac9fc36bfa5da" +dependencies = [ + "ahash", + "arrow-buffer", + "arrow-data", + "arrow-schema", + "chrono", + "chrono-tz", + "half", + "hashbrown 0.14.5", + "num", +] + +[[package]] +name = "arrow-buffer" +version = "53.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a1f34f0faae77da6b142db61deba2cb6d60167592b178be317b341440acba80" +dependencies = [ + "bytes", + "half", + "num", +] + +[[package]] +name = "arrow-cast" +version = "53.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "450e4abb5775bca0740bec0bcf1b1a5ae07eff43bd625661c4436d8e8e4540c4" +dependencies = [ + "arrow-array", + "arrow-buffer", + "arrow-data", + "arrow-schema", + "arrow-select", + "atoi", + "base64", + "chrono", + "half", + "lexical-core", + "num", + "ryu", +] + +[[package]] +name = "arrow-csv" +version = "53.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3a4e4d63830a341713e35d9a42452fbc6241d5f42fa5cf6a4681b8ad91370c4" +dependencies = [ + "arrow-array", + "arrow-buffer", + "arrow-cast", + "arrow-data", + "arrow-schema", + "chrono", + "csv", + "csv-core", + "lazy_static", + "lexical-core", + "regex", +] + +[[package]] +name = "arrow-data" +version = "53.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b1e618bbf714c7a9e8d97203c806734f012ff71ae3adc8ad1b075689f540634" +dependencies = [ + "arrow-buffer", + "arrow-schema", + "half", + "num", +] + +[[package]] +name = "arrow-ipc" +version = "53.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f98e983549259a2b97049af7edfb8f28b8911682040e99a94e4ceb1196bd65c2" +dependencies = [ + "arrow-array", + "arrow-buffer", + "arrow-cast", + "arrow-data", + "arrow-schema", + "flatbuffers", +] + +[[package]] +name = "arrow-json" +version = "53.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b198b9c6fcf086501730efbbcb483317b39330a116125af7bb06467d04b352a3" +dependencies = [ + "arrow-array", + "arrow-buffer", + "arrow-cast", + "arrow-data", + "arrow-schema", + "chrono", + "half", + "indexmap", + "lexical-core", + "num", + "serde", + "serde_json", +] + +[[package]] +name = "arrow-ord" +version = "53.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2427f37b4459a4b9e533045abe87a5183a5e0995a3fc2c2fd45027ae2cc4ef3f" +dependencies = [ + "arrow-array", + "arrow-buffer", + "arrow-data", + "arrow-schema", + "arrow-select", + "half", + "num", +] + +[[package]] +name = "arrow-row" +version = "53.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15959657d92e2261a7a323517640af87f5afd9fd8a6492e424ebee2203c567f6" +dependencies = [ + "ahash", + "arrow-array", + "arrow-buffer", + "arrow-data", + "arrow-schema", + "half", +] + +[[package]] +name = "arrow-schema" +version = "53.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbf0388a18fd7f7f3fe3de01852d30f54ed5182f9004db700fbe3ba843ed2794" +dependencies = [ + "bitflags 2.6.0", +] + +[[package]] +name = "arrow-select" +version = "53.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b83e5723d307a38bf00ecd2972cd078d1339c7fd3eb044f609958a9a24463f3a" +dependencies = [ + "ahash", + "arrow-array", + "arrow-buffer", + "arrow-data", + "arrow-schema", + "num", +] + +[[package]] +name = "arrow-string" +version = "53.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ab3db7c09dd826e74079661d84ed01ed06547cf75d52c2818ef776d0d852305" +dependencies = [ + "arrow-array", + "arrow-buffer", + "arrow-data", + "arrow-schema", + "arrow-select", + "memchr", + "num", + "regex", + "regex-syntax", +] + [[package]] name = "async-trait" version = "0.1.83" @@ -43,6 +284,15 @@ dependencies = [ "syn", ] +[[package]] +name = "atoi" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f28d99ec8bfea296261ca1af174f24225171fea9664ba9003cbebee704810528" +dependencies = [ + "num-traits", +] + [[package]] name = "atomic-waker" version = "1.1.2" @@ -76,6 +326,12 @@ version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + [[package]] name = "bitflags" version = "2.6.0" @@ -105,15 +361,15 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.7.2" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "428d9aa8fbc0670b7b8d6030a7fadd0f86151cae55e4dbbece15f3780a3dfaf3" +checksum = "9ac0150caa2ae65ca5bd83f25c7de183dea78d4d366469f148435e2acfbad0da" [[package]] name = "cc" -version = "1.1.30" +version = "1.1.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b16803a61b81d9eabb7eae2588776c4c1e584b738ede45fdbb4c972cec1e9945" +checksum = "c2e7962b54006dcfcc61cb72735f4d89bb97061dd6a7ed882ec6b8ee53714c6f" dependencies = [ "shlex", ] @@ -139,6 +395,47 @@ dependencies = [ "windows-targets", ] +[[package]] +name = "chrono-tz" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd6dd8046d00723a59a2f8c5f295c515b9bb9a331ee4f8f3d4dd49e428acd3b6" +dependencies = [ + "chrono", + "chrono-tz-build", + "phf", +] + +[[package]] +name = "chrono-tz-build" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e94fea34d77a245229e7746bd2beb786cd2a896f306ff491fb8cecb3074b10a7" +dependencies = [ + "parse-zoneinfo", + "phf_codegen", +] + +[[package]] +name = "const-random" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87e00182fe74b066627d63b85fd550ac2998d4b0bd86bfed477a0ae4c7c71359" +dependencies = [ + "const-random-macro", +] + +[[package]] +name = "const-random-macro" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e" +dependencies = [ + "getrandom", + "once_cell", + "tiny-keccak", +] + [[package]] name = "core-foundation" version = "0.9.4" @@ -155,6 +452,12 @@ version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + [[package]] name = "crypto-common" version = "0.1.6" @@ -165,6 +468,27 @@ dependencies = [ "typenum", ] +[[package]] +name = "csv" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac574ff4d437a7b5ad237ef331c17ccca63c46479e5b5453eb8e10bb99a759fe" +dependencies = [ + "csv-core", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "csv-core" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5efa2b3d7902f4b634a20cae3c9c4e6209dc4779feb6863329607560143efa70" +dependencies = [ + "memchr", +] + [[package]] name = "digest" version = "0.10.7" @@ -187,6 +511,16 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" +[[package]] +name = "flatbuffers" +version = "24.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8add37afff2d4ffa83bc748a70b4b1370984f6980768554182424ef71447c35f" +dependencies = [ + "bitflags 1.3.2", + "rustc_version", +] + [[package]] name = "fnv" version = "1.0.7" @@ -337,6 +671,23 @@ dependencies = [ "tracing", ] +[[package]] +name = "half" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dd08c532ae367adf81c312a4580bc67f1d0fe8bc9c460520283f4c0ff277888" +dependencies = [ + "cfg-if", + "crunchy", + "num-traits", +] + +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" + [[package]] name = "hashbrown" version = "0.15.0" @@ -498,7 +849,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" dependencies = [ "equivalent", - "hashbrown", + "hashbrown 0.15.0", ] [[package]] @@ -537,12 +888,88 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "lexical-core" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0431c65b318a590c1de6b8fd6e72798c92291d27762d94c9e6c37ed7a73d8458" +dependencies = [ + "lexical-parse-float", + "lexical-parse-integer", + "lexical-util", + "lexical-write-float", + "lexical-write-integer", +] + +[[package]] +name = "lexical-parse-float" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb17a4bdb9b418051aa59d41d65b1c9be5affab314a872e5ad7f06231fb3b4e0" +dependencies = [ + "lexical-parse-integer", + "lexical-util", + "static_assertions", +] + +[[package]] +name = "lexical-parse-integer" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5df98f4a4ab53bf8b175b363a34c7af608fe31f93cc1fb1bf07130622ca4ef61" +dependencies = [ + "lexical-util", + "static_assertions", +] + +[[package]] +name = "lexical-util" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85314db53332e5c192b6bca611fb10c114a80d1b831ddac0af1e9be1b9232ca0" +dependencies = [ + "static_assertions", +] + +[[package]] +name = "lexical-write-float" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e7c3ad4e37db81c1cbe7cf34610340adc09c322871972f74877a712abc6c809" +dependencies = [ + "lexical-util", + "lexical-write-integer", + "static_assertions", +] + +[[package]] +name = "lexical-write-integer" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb89e9f6958b83258afa3deed90b5de9ef68eef090ad5086c791cd2345610162" +dependencies = [ + "lexical-util", + "static_assertions", +] + [[package]] name = "libc" version = "0.2.161" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9489c2807c139ffd9c1794f4af0ebe86a828db53ecdc7fea2111d0fed085d1" +[[package]] +name = "libm" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" + [[package]] name = "lock_api" version = "0.4.12" @@ -559,6 +986,16 @@ version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" +[[package]] +name = "matrixmultiply" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9380b911e3e96d10c1f415da0876389aaf1b56759054eeb0de7df940c456ba1a" +dependencies = [ + "autocfg", + "rawpointer", +] + [[package]] name = "md-5" version = "0.10.6" @@ -611,6 +1048,85 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "ndarray" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "882ed72dce9365842bf196bdeedf5055305f11fc8c03dee7bb0194a6cad34841" +dependencies = [ + "matrixmultiply", + "num-complex", + "num-integer", + "num-traits", + "portable-atomic", + "portable-atomic-util", + "rawpointer", +] + +[[package]] +name = "num" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" +dependencies = [ + "num-bigint", + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits", +] + +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-complex" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" +dependencies = [ + "num-bigint", + "num-integer", + "num-traits", +] + [[package]] name = "num-traits" version = "0.2.19" @@ -618,41 +1134,38 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", + "libm", ] [[package]] -name = "object" -version = "0.36.5" +name = "numpy" +version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aedf0a2d09c573ed1d8d85b30c119153926a2b36dce0ab28322c09a117a4683e" +checksum = "cf314fca279e6e6ac2126a4ff98f26d88aa4ad06bc68fb6ae5cf4bd706758311" dependencies = [ - "memchr", + "half", + "libc", + "ndarray", + "num-complex", + "num-integer", + "num-traits", + "pyo3", + "rustc-hash 1.1.0", ] [[package]] -name = "object-store-rs" -version = "0.1.0" +name = "object" +version = "0.36.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aedf0a2d09c573ed1d8d85b30c119153926a2b36dce0ab28322c09a117a4683e" dependencies = [ - "bytes", - "chrono", - "futures", - "http", - "indexmap", - "object_store", - "pyo3", - "pyo3-async-runtimes", - "pyo3-file", - "pyo3-object_store", - "reqwest", - "tokio", - "url", + "memchr", ] [[package]] name = "object_store" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25a0c4b3a0e31f8b66f71ad8064521efa773910196e2cde791436f13409f3b45" +version = "0.11.1" +source = "git+https://github.com/kylebarron/arrow-rs?branch=kyle/list-returns-static-stream#33062b14efc66ff35353bf785cbd444056b09b66" dependencies = [ "async-trait", "base64", @@ -679,6 +1192,27 @@ dependencies = [ "walkdir", ] +[[package]] +name = "obstore" +version = "0.2.0" +dependencies = [ + "arrow", + "bytes", + "chrono", + "futures", + "http", + "indexmap", + "object_store", + "pyo3", + "pyo3-arrow", + "pyo3-async-runtimes", + "pyo3-file", + "pyo3-object_store", + "reqwest", + "tokio", + "url", +] + [[package]] name = "once_cell" version = "1.20.2" @@ -714,12 +1248,59 @@ dependencies = [ "windows-targets", ] +[[package]] +name = "parse-zoneinfo" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f2a05b18d44e2957b88f96ba460715e295bc1d7510468a2f3d3b44535d26c24" +dependencies = [ + "regex", +] + [[package]] name = "percent-encoding" version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" +[[package]] +name = "phf" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc" +dependencies = [ + "phf_shared", +] + +[[package]] +name = "phf_codegen" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8d39688d359e6b34654d328e262234662d16cc0f60ec8dcbe5e718709342a5a" +dependencies = [ + "phf_generator", + "phf_shared", +] + +[[package]] +name = "phf_generator" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48e4cc64c2ad9ebe670cb8fd69dd50ae301650392e81c05f9bfcb2d5bdbc24b0" +dependencies = [ + "phf_shared", + "rand", +] + +[[package]] +name = "phf_shared" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90fcb95eef784c2ac79119d1dd819e162b5da872ce6f3c3abe1e8ca1c082f72b" +dependencies = [ + "siphasher", +] + [[package]] name = "pin-project-lite" version = "0.2.14" @@ -738,6 +1319,15 @@ version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cc9c68a3f6da06753e9335d63e27f6b9754dd1920d941135b7ea8224f141adb2" +[[package]] +name = "portable-atomic-util" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90a7d5beecc52a491b54d6dd05c7a45ba1801666a5baad9fdbfc6fef8d2d206c" +dependencies = [ + "portable-atomic", +] + [[package]] name = "ppv-lite86" version = "0.2.20" @@ -749,9 +1339,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.88" +version = "1.0.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c3a7fc5db1e57d5a779a352c8cdb57b29aa4c40cc69c3a68a7fedc815fbf2f9" +checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e" dependencies = [ "unicode-ident", ] @@ -776,6 +1366,22 @@ dependencies = [ "unindent", ] +[[package]] +name = "pyo3-arrow" +version = "0.5.1" +source = "git+https://github.com/kylebarron/arro3?rev=d771e39c2748313a86bfcdfa36ec68cc24d9804b#d771e39c2748313a86bfcdfa36ec68cc24d9804b" +dependencies = [ + "arrow", + "arrow-array", + "arrow-buffer", + "arrow-schema", + "half", + "indexmap", + "numpy", + "pyo3", + "thiserror", +] + [[package]] name = "pyo3-async-runtimes" version = "0.21.0" @@ -873,7 +1479,7 @@ dependencies = [ "pin-project-lite", "quinn-proto", "quinn-udp", - "rustc-hash", + "rustc-hash 2.0.0", "rustls", "socket2", "thiserror", @@ -890,7 +1496,7 @@ dependencies = [ "bytes", "rand", "ring", - "rustc-hash", + "rustc-hash 2.0.0", "rustls", "slab", "thiserror", @@ -950,15 +1556,50 @@ dependencies = [ "getrandom", ] +[[package]] +name = "rawpointer" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60a357793950651c4ed0f3f52338f53b2f809f32d83a07f72909fa13e4c6c1e3" + [[package]] name = "redox_syscall" version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f" dependencies = [ - "bitflags", + "bitflags 2.6.0", +] + +[[package]] +name = "regex" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38200e5ee88914975b69f657f0801b6f6dccafd44fd9326302a4aaeecfacb1d8" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", ] +[[package]] +name = "regex-automata" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" + [[package]] name = "reqwest" version = "0.12.8" @@ -1025,12 +1666,27 @@ version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + [[package]] name = "rustc-hash" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "583034fd73374156e66797ed8e5b0d5690409c9226b22d87cb7f19821c05d152" +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + [[package]] name = "rustls" version = "0.23.15" @@ -1120,7 +1776,7 @@ version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" dependencies = [ - "bitflags", + "bitflags 2.6.0", "core-foundation", "core-foundation-sys", "libc", @@ -1137,20 +1793,26 @@ dependencies = [ "libc", ] +[[package]] +name = "semver" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" + [[package]] name = "serde" -version = "1.0.210" +version = "1.0.213" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" +checksum = "3ea7893ff5e2466df8d720bb615088341b295f849602c6956047f8f80f0e9bc1" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.210" +version = "1.0.213" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" +checksum = "7e85ad2009c50b58e87caa8cd6dac16bdf511bbfb7af6c33df902396aa480fa5" dependencies = [ "proc-macro2", "quote", @@ -1159,9 +1821,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.129" +version = "1.0.132" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6dbcf9b78a125ee667ae19388837dd12294b858d101fdd393cb9d5501ef09eb2" +checksum = "d726bfaff4b320266d395898905d0eba0345aae23b54aee3a737e260fd46db03" dependencies = [ "itoa", "memchr", @@ -1187,6 +1849,12 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +[[package]] +name = "siphasher" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" + [[package]] name = "slab" version = "0.4.9" @@ -1239,6 +1907,12 @@ version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + [[package]] name = "subtle" version = "2.6.1" @@ -1247,9 +1921,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" -version = "2.0.79" +version = "2.0.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89132cd0bf050864e1d38dc3bbc07a0eb8e7530af26344d3d2bbbef83499f590" +checksum = "01680f5d178a369f817f43f3d399650272873a8e7588a7872f7e90edc71d60a3" dependencies = [ "proc-macro2", "quote", @@ -1273,24 +1947,33 @@ checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" [[package]] name = "thiserror" -version = "1.0.64" +version = "1.0.65" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d50af8abc119fb8bb6dbabcfa89656f46f84aa0ac7688088608076ad2b459a84" +checksum = "5d11abd9594d9b38965ef50805c5e469ca9cc6f197f883f717e0269a3057b3d5" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.64" +version = "1.0.65" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3" +checksum = "ae71770322cbd277e69d762a16c444af02aa0575ac0d174f0b9562d3b37f8602" dependencies = [ "proc-macro2", "quote", "syn", ] +[[package]] +name = "tiny-keccak" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" +dependencies = [ + "crunchy", +] + [[package]] name = "tinyvec" version = "1.8.0" @@ -1308,9 +1991,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.40.0" +version = "1.41.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2b070231665d27ad9ec9b8df639893f46727666c6767db40317fbe920a5d998" +checksum = "145f3413504347a2be84393cc8a7d2fb4d863b375909ea59f2158261aa258bbb" dependencies = [ "backtrace", "bytes", diff --git a/Cargo.toml b/Cargo.toml index 4e6e7c85..ad737fad 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,12 +1,12 @@ [workspace] -members = ["object-store-rs", "pyo3-object_store"] +members = ["obstore", "pyo3-object_store"] resolver = "2" [workspace.package] authors = ["Kyle Barron "] edition = "2021" -homepage = "https://developmentseed.org/object-store-rs" -repository = "https://github.com/developmentseed/object-store-rs" +homepage = "https://developmentseed.org/obstore" +repository = "https://github.com/developmentseed/obstore" license = "MIT OR Apache-2.0" keywords = ["python"] categories = [] @@ -28,6 +28,9 @@ thiserror = "1" tokio = "1.40" url = "2" +[patch.crates-io] +object_store = { git = "https://github.com/kylebarron/arrow-rs", branch = "kyle/list-returns-static-stream" } + [profile.release] lto = true codegen-units = 1 diff --git a/README.md b/README.md index 4a6b4687..3c9202ce 100644 --- a/README.md +++ b/README.md @@ -1,55 +1,59 @@ -# object-store-rs +# obstore -A Python interface and [pyo3](https://github.com/PyO3/pyo3) integration to the Rust [`object_store`](https://docs.rs/object_store) crate, providing a uniform API for interacting with object storage services and local files. +[![PyPI][pypi_badge]][pypi_link] -Run the same code in multiple clouds via a simple runtime configuration change. + - +[pypi_badge]: https://badge.fury.io/py/obstore.svg +[pypi_link]: https://pypi.org/project/obstore/ + + + +Simple, fast integration with object storage services like Amazon S3, Google Cloud Storage, Azure Blob Storage, and S3-compliant APIs like Cloudflare R2. -- Easy to install with no Python dependencies. - Sync and async API. - Streaming downloads with configurable chunking. +- Streaming `list`, with no need to paginate. +- Support for conditional put ("put if not exists"), as well as custom tags and attributes. - Automatically supports [multipart uploads](https://docs.aws.amazon.com/AmazonS3/latest/userguide/mpuoverview.html) under the hood for large file objects. +- Optionally return list results as [Arrow](https://arrow.apache.org/), which is faster than materializing Python `dict`/`list` objects. +- Easy to install with no required Python dependencies. - The [underlying Rust library](https://docs.rs/object_store) is production quality and used in large scale production systems, such as the Rust package registry [crates.io](https://crates.io/). +- Support for zero-copy data exchange from Rust into Python in `get_range` and `get_ranges`. - Simple API with static type checking. - Helpers for constructing from environment variables and `boto3.Session` objects -Supported object storage providers include: - -- Amazon S3 and S3-compliant APIs like Cloudflare R2 -- Google Cloud Storage -- Azure Blob Gen1 and Gen2 accounts (including ADLS Gen2) -- Local filesystem -- In-memory storage + ## Installation ```sh -pip install object-store-rs +pip install obstore ``` ## Documentation -[Full documentation is available on the website](https://developmentseed.org/object-store-rs). +[Full documentation is available on the website](https://developmentseed.org/obstore). ## Usage ### Constructing a store -Classes to construct a store are exported from the `object_store_rs.store` submodule: +Classes to construct a store are exported from the `obstore.store` submodule: -- [`S3Store`](https://developmentseed.org/object-store-rs/latest/api/store/aws/): Configure a connection to Amazon S3. -- [`GCSStore`](https://developmentseed.org/object-store-rs/latest/api/store/gcs/): Configure a connection to Google Cloud Storage. -- [`AzureStore`](https://developmentseed.org/object-store-rs/latest/api/store/azure/): Configure a connection to Microsoft Azure Blob Storage. -- [`HTTPStore`](https://developmentseed.org/object-store-rs/latest/api/store/http/): Configure a connection to a generic HTTP server -- [`LocalStore`](https://developmentseed.org/object-store-rs/latest/api/store/local/): Local filesystem storage providing the same object store interface. -- [`MemoryStore`](https://developmentseed.org/object-store-rs/latest/api/store/memory/): A fully in-memory implementation of ObjectStore. +- [`S3Store`](https://developmentseed.org/obstore/latest/api/store/aws/): Configure a connection to Amazon S3. +- [`GCSStore`](https://developmentseed.org/obstore/latest/api/store/gcs/): Configure a connection to Google Cloud Storage. +- [`AzureStore`](https://developmentseed.org/obstore/latest/api/store/azure/): Configure a connection to Microsoft Azure Blob Storage. +- [`HTTPStore`](https://developmentseed.org/obstore/latest/api/store/http/): Configure a connection to a generic HTTP server +- [`LocalStore`](https://developmentseed.org/obstore/latest/api/store/local/): Local filesystem storage providing the same object store interface. +- [`MemoryStore`](https://developmentseed.org/obstore/latest/api/store/memory/): A fully in-memory implementation of ObjectStore. #### Example ```py import boto3 -from object_store_rs.store import S3Store +from obstore.store import S3Store session = boto3.Session() store = S3Store.from_session(session, "bucket-name", config={"AWS_REGION": "us-east-1"}) @@ -59,33 +63,33 @@ store = S3Store.from_session(session, "bucket-name", config={"AWS_REGION": "us-e Each store class above has its own configuration, accessible through the `config` named parameter. This is covered in the docs, and string literals are in the type hints. -Additional [HTTP client configuration](https://developmentseed.org/object-store-rs/latest/api/store/config/) is available via the `client_options` named parameter. +Additional [HTTP client configuration](https://developmentseed.org/obstore/latest/api/store/config/) is available via the `client_options` named parameter. ### Interacting with a store All methods for interacting with a store are exported as **top-level functions** (not methods on the `store` object): -- [`copy`](https://developmentseed.org/object-store-rs/latest/api/copy/): Copy an object from one path to another in the same object store. -- [`delete`](https://developmentseed.org/object-store-rs/latest/api/delete/): Delete the object at the specified location. -- [`get`](https://developmentseed.org/object-store-rs/latest/api/get/): Return the bytes that are stored at the specified location. -- [`head`](https://developmentseed.org/object-store-rs/latest/api/head/): Return the metadata for the specified location -- [`list`](https://developmentseed.org/object-store-rs/latest/api/list/): List all the objects with the given prefix. -- [`put`](https://developmentseed.org/object-store-rs/latest/api/put/): Save the provided bytes to the specified location -- [`rename`](https://developmentseed.org/object-store-rs/latest/api/rename/): Move an object from one path to another in the same object store. +- [`copy`](https://developmentseed.org/obstore/latest/api/copy/): Copy an object from one path to another in the same object store. +- [`delete`](https://developmentseed.org/obstore/latest/api/delete/): Delete the object at the specified location. +- [`get`](https://developmentseed.org/obstore/latest/api/get/): Return the bytes that are stored at the specified location. +- [`head`](https://developmentseed.org/obstore/latest/api/head/): Return the metadata for the specified location +- [`list`](https://developmentseed.org/obstore/latest/api/list/): List all the objects with the given prefix. +- [`put`](https://developmentseed.org/obstore/latest/api/put/): Save the provided bytes to the specified location +- [`rename`](https://developmentseed.org/obstore/latest/api/rename/): Move an object from one path to another in the same object store. There are a few additional APIs useful for specific use cases: -- [`get_range`](https://developmentseed.org/object-store-rs/latest/api/get/#object_store_rs.get_range): Get a specific byte range from a file. -- [`get_ranges`](https://developmentseed.org/object-store-rs/latest/api/get/#object_store_rs.get_ranges): Get multiple byte ranges from a single file. -- [`list_with_delimiter`](https://developmentseed.org/object-store-rs/latest/api/list/#object_store_rs.list_with_delimiter): List objects within a specific directory. -- [`sign`](https://developmentseed.org/object-store-rs/latest/api/sign/): Create a signed URL. +- [`get_range`](https://developmentseed.org/obstore/latest/api/get/#obstore.get_range): Get a specific byte range from a file. +- [`get_ranges`](https://developmentseed.org/obstore/latest/api/get/#obstore.get_ranges): Get multiple byte ranges from a single file. +- [`list_with_delimiter`](https://developmentseed.org/obstore/latest/api/list/#obstore.list_with_delimiter): List objects within a specific directory. +- [`sign`](https://developmentseed.org/obstore/latest/api/sign/): Create a signed URL. All methods have a comparable async method with the same name plus an `_async` suffix. #### Example ```py -import object_store_rs as obs +import obstore as obs store = obs.store.MemoryStore() @@ -109,7 +113,7 @@ assert obs.get(store, "other.txt").bytes() == b"hello world!" All of these methods also have `async` counterparts, suffixed with `_async`. ```py -import object_store_rs as obs +import obstore as obs store = obs.store.MemoryStore() diff --git a/docs/api/attributes.md b/docs/api/attributes.md new file mode 100644 index 00000000..66a14665 --- /dev/null +++ b/docs/api/attributes.md @@ -0,0 +1,4 @@ +# Attributes + +::: obstore.Attribute +::: obstore.Attributes diff --git a/docs/api/copy.md b/docs/api/copy.md index c9d0fab3..56014d9b 100644 --- a/docs/api/copy.md +++ b/docs/api/copy.md @@ -1,4 +1,4 @@ # Copy -::: object_store_rs.copy -::: object_store_rs.copy_async +::: obstore.copy +::: obstore.copy_async diff --git a/docs/api/delete.md b/docs/api/delete.md index a7bbc4b7..aef5361e 100644 --- a/docs/api/delete.md +++ b/docs/api/delete.md @@ -1,2 +1,4 @@ -::: object_store_rs.delete -::: object_store_rs.delete_async +# Delete + +::: obstore.delete +::: obstore.delete_async diff --git a/docs/api/exceptions.md b/docs/api/exceptions.md new file mode 100644 index 00000000..8de9fd17 --- /dev/null +++ b/docs/api/exceptions.md @@ -0,0 +1,3 @@ +# Exceptions + +::: obstore.exceptions diff --git a/docs/api/get.md b/docs/api/get.md index af369991..ccf6d0fc 100644 --- a/docs/api/get.md +++ b/docs/api/get.md @@ -1,10 +1,11 @@ # Get -::: object_store_rs.get -::: object_store_rs.get_async -::: object_store_rs.get_range -::: object_store_rs.get_range_async -::: object_store_rs.get_ranges -::: object_store_rs.get_ranges_async -::: object_store_rs.GetOptions -::: object_store_rs.GetResult +::: obstore.get +::: obstore.get_async +::: obstore.get_range +::: obstore.get_range_async +::: obstore.get_ranges +::: obstore.get_ranges_async +::: obstore.GetOptions +::: obstore.GetResult +::: obstore.Buffer diff --git a/docs/api/head.md b/docs/api/head.md index 79f4d2e7..63b1bffb 100644 --- a/docs/api/head.md +++ b/docs/api/head.md @@ -1,4 +1,4 @@ # Head -::: object_store_rs.head -::: object_store_rs.head_async +::: obstore.head +::: obstore.head_async diff --git a/docs/api/list.md b/docs/api/list.md index 931febb8..04797a6b 100644 --- a/docs/api/list.md +++ b/docs/api/list.md @@ -1,8 +1,8 @@ # List -::: object_store_rs.list -::: object_store_rs.list_async -::: object_store_rs.list_with_delimiter -::: object_store_rs.list_with_delimiter_async -::: object_store_rs.ObjectMeta -::: object_store_rs.ListResult +::: obstore.list +::: obstore.list_with_delimiter +::: obstore.list_with_delimiter_async +::: obstore.ObjectMeta +::: obstore.ListResult +::: obstore.ListStream diff --git a/docs/api/put.md b/docs/api/put.md index 923fe232..93954b53 100644 --- a/docs/api/put.md +++ b/docs/api/put.md @@ -1,5 +1,7 @@ # Put -::: object_store_rs.put -::: object_store_rs.put_async -::: object_store_rs.PutResult +::: obstore.put +::: obstore.put_async +::: obstore.PutResult +::: obstore.UpdateVersion +::: obstore.PutMode diff --git a/docs/api/rename.md b/docs/api/rename.md index 67a7aef2..9b97c6fb 100644 --- a/docs/api/rename.md +++ b/docs/api/rename.md @@ -1,4 +1,4 @@ # Rename -::: object_store_rs.rename -::: object_store_rs.rename_async +::: obstore.rename +::: obstore.rename_async diff --git a/docs/api/sign.md b/docs/api/sign.md index 759545fe..690e2e4c 100644 --- a/docs/api/sign.md +++ b/docs/api/sign.md @@ -1,6 +1,6 @@ # Sign -::: object_store_rs.sign -::: object_store_rs.sign_async -::: object_store_rs.SignCapableStore -::: object_store_rs.HTTP_METHOD +::: obstore.sign +::: obstore.sign_async +::: obstore.SignCapableStore +::: obstore.HTTP_METHOD diff --git a/docs/api/store/aws.md b/docs/api/store/aws.md index 25483e20..e129c419 100644 --- a/docs/api/store/aws.md +++ b/docs/api/store/aws.md @@ -1,4 +1,4 @@ # AWS S3 -::: object_store_rs.store.S3Store -::: object_store_rs.store.S3ConfigKey +::: obstore.store.S3Store +::: obstore.store.S3ConfigKey diff --git a/docs/api/store/azure.md b/docs/api/store/azure.md index 2adc32dc..546d0357 100644 --- a/docs/api/store/azure.md +++ b/docs/api/store/azure.md @@ -1,4 +1,4 @@ # Microsoft Azure -::: object_store_rs.store.AzureStore -::: object_store_rs.store.AzureConfigKey +::: obstore.store.AzureStore +::: obstore.store.AzureConfigKey diff --git a/docs/api/store/config.md b/docs/api/store/config.md index b11d41a2..2a4ef8eb 100644 --- a/docs/api/store/config.md +++ b/docs/api/store/config.md @@ -1,5 +1,5 @@ # Configuration -::: object_store_rs.store.ClientConfigKey -::: object_store_rs.store.BackoffConfig -::: object_store_rs.store.RetryConfig +::: obstore.store.ClientConfigKey +::: obstore.store.BackoffConfig +::: obstore.store.RetryConfig diff --git a/docs/api/store/gcs.md b/docs/api/store/gcs.md index 2a8d549e..a89c3cd7 100644 --- a/docs/api/store/gcs.md +++ b/docs/api/store/gcs.md @@ -1,4 +1,4 @@ # Google Cloud Storage -::: object_store_rs.store.GCSStore -::: object_store_rs.store.GCSConfigKey +::: obstore.store.GCSStore +::: obstore.store.GCSConfigKey diff --git a/docs/api/store/http.md b/docs/api/store/http.md index f22ccf9a..53e1001c 100644 --- a/docs/api/store/http.md +++ b/docs/api/store/http.md @@ -1,3 +1,3 @@ # HTTP -::: object_store_rs.store.HTTPStore +::: obstore.store.HTTPStore diff --git a/docs/api/store/index.md b/docs/api/store/index.md index 783029a6..ba12e100 100644 --- a/docs/api/store/index.md +++ b/docs/api/store/index.md @@ -1,3 +1,3 @@ # ObjectStore -::: object_store_rs.store.ObjectStore +::: obstore.store.ObjectStore diff --git a/docs/api/store/local.md b/docs/api/store/local.md index 9c97235b..ceb0cdab 100644 --- a/docs/api/store/local.md +++ b/docs/api/store/local.md @@ -1,3 +1,3 @@ # Local -::: object_store_rs.store.LocalStore +::: obstore.store.LocalStore diff --git a/docs/api/store/memory.md b/docs/api/store/memory.md index 420457e5..ced4fd86 100644 --- a/docs/api/store/memory.md +++ b/docs/api/store/memory.md @@ -1,3 +1,3 @@ # Memory -::: object_store_rs.store.MemoryStore +::: obstore.store.MemoryStore diff --git a/docs/assets/logo_no_text.png b/docs/assets/logo_no_text.png new file mode 100644 index 00000000..f9f3773f Binary files /dev/null and b/docs/assets/logo_no_text.png differ diff --git a/docs/overrides/stylesheets/extra.css b/docs/overrides/stylesheets/extra.css new file mode 100644 index 00000000..5a192e7b --- /dev/null +++ b/docs/overrides/stylesheets/extra.css @@ -0,0 +1,40 @@ +:root, +[data-md-color-scheme="default"] { + /* --md-heading-font: "Oswald"; */ + --md-primary-fg-color: #cf3f02; + --md-default-fg-color: #443f3f; + --boxShadowD: 0px 12px 24px 0px rgba(68, 63, 63, 0.08), + 0px 0px 4px 0px rgba(68, 63, 63, 0.08); +} +body { + margin: 0; + padding: 0; + /* font-size: 16px; */ +} +h1, +h2, +h3, +h4, +h5, +h6 { + font-family: var(--md-heading-font); + font-weight: bold; +} +.md-typeset h1, +.md-typeset h2, +.md-typeset h3, +.md-typeset h4 { + font-weight: normal; + color: var(--md-default-fg-color); +} +.md-button, +.md-typeset .md-button { + font-family: var(--md-heading-font); +} +.md-content .supheading { + font-family: var(--md-heading-font); + text-transform: uppercase; + color: var(--md-primary-fg-color); + font-size: 0.75rem; + font-weight: bold; +} diff --git a/mkdocs.yml b/mkdocs.yml index 58a274c5..eeaa9a72 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -1,11 +1,11 @@ -site_name: object-store-rs -repo_name: developmentseed/object-store-rs -repo_url: https://github.com/developmentseed/object-store-rs +site_name: obstore +repo_name: developmentseed/obstore +repo_url: https://github.com/developmentseed/obstore site_description: A Python interface and pyo3 integration to the Rust object-store crate. site_author: Development Seed # Note: trailing slash recommended with mike: # https://squidfunk.github.io/mkdocs-material/setup/setting-up-versioning/#publishing-a-new-version -site_url: https://developmentseed.org/object-store-rs/ +site_url: https://developmentseed.org/obstore/ docs_dir: docs extra: @@ -39,13 +39,16 @@ nav: - api/put.md - api/rename.md - api/sign.md + - api/attributes.md + - api/exceptions.md watch: - - object-store-rs/python + - obstore/python - docs theme: name: material + logo: assets/logo_no_text.png palette: # Palette toggle for automatic mode - media: "(prefers-color-scheme)" @@ -55,8 +58,8 @@ theme: # Palette toggle for light mode - media: "(prefers-color-scheme: light)" - primary: amber - accent: indigo + primary: default + accent: deep orange toggle: icon: material/brightness-7 name: Switch to dark mode @@ -64,8 +67,8 @@ theme: # Palette toggle for dark mode - media: "(prefers-color-scheme: dark)" scheme: slate - primary: amber - accent: indigo + primary: default + accent: deep orange toggle: icon: material/brightness-4 name: Switch to system preference @@ -83,6 +86,9 @@ theme: - search.suggest - search.share +extra_css: + - overrides/stylesheets/extra.css + plugins: - search - social @@ -112,6 +118,7 @@ plugins: import: - https://docs.python.org/3/objects.inv + - https://kylebarron.dev/arro3/latest/objects.inv # https://github.com/developmentseed/titiler/blob/50934c929cca2fa8d3c408d239015f8da429c6a8/docs/mkdocs.yml#L115-L140 markdown_extensions: diff --git a/object-store-rs/python/object_store_rs/__init__.py b/object-store-rs/python/object_store_rs/__init__.py deleted file mode 100644 index 00be138f..00000000 --- a/object-store-rs/python/object_store_rs/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -from . import store -from ._object_store_rs import * -from ._object_store_rs import ___version - -__version__: str = ___version() diff --git a/object-store-rs/python/object_store_rs/_list.pyi b/object-store-rs/python/object_store_rs/_list.pyi deleted file mode 100644 index 2be0f826..00000000 --- a/object-store-rs/python/object_store_rs/_list.pyi +++ /dev/null @@ -1,112 +0,0 @@ -from datetime import datetime -from typing import List, TypedDict - -from .store import ObjectStore - -class ObjectMeta(TypedDict): - """The metadata that describes an object.""" - - path: str - """The full path to the object""" - - last_modified: datetime - """The last modified time""" - - size: int - """The size in bytes of the object""" - - e_tag: str | None - """The unique identifier for the object - - - """ - - version: str | None - """A version indicator for this object""" - -class ListResult(TypedDict): - """ - Result of a list call that includes objects, prefixes (directories) and a token for - the next set of results. Individual result sets may be limited to 1,000 objects - based on the underlying object storage's limitations. - """ - - common_prefixes: List[str] - """Prefixes that are common (like directories)""" - - objects: List[ObjectMeta] - """Object metadata for the listing""" - -def list( - store: ObjectStore, - prefix: str | None = None, - *, - offset: str | None = None, - max_items: int | None = 2000, -) -> List[ObjectMeta]: - """ - List all the objects with the given prefix. - - Prefixes are evaluated on a path segment basis, i.e. `foo/bar/` is a prefix of - `foo/bar/x` but not of `foo/bar_baz/x`. List is recursive, i.e. `foo/bar/more/x` - will be included. - - Note: the order of returned [`ObjectMeta`][object_store_rs.ObjectMeta] is not - guaranteed - - !!! note - In the future, we'd like to have `list` return an async iterable, just like - `get`, so that we can stream the result of `list`, but we need [some - changes](https://github.com/apache/arrow-rs/issues/6587) in the upstream - object-store repo first. - - Args: - store: The ObjectStore instance to use. - prefix: The prefix within ObjectStore to use for listing. Defaults to None. - - Keyword Args: - offset: If provided, list all the objects with the given prefix and a location greater than `offset`. Defaults to `None`. - max_items: The maximum number of items to return. Defaults to 2000. - - Returns: - A list of `ObjectMeta`. - """ - -async def list_async( - store: ObjectStore, - prefix: str | None = None, - *, - offset: str | None = None, - max_items: int | None = 2000, -) -> List[ObjectMeta]: - """Call `list` asynchronously. - - Refer to the documentation for [list][object_store_rs.list]. - """ - -def list_with_delimiter(store: ObjectStore, prefix: str | None = None) -> ListResult: - """ - List objects with the given prefix and an implementation specific - delimiter. Returns common prefixes (directories) in addition to object - metadata. - - Prefixes are evaluated on a path segment basis, i.e. `foo/bar/` is a prefix of - `foo/bar/x` but not of `foo/bar_baz/x`. List is not recursive, i.e. `foo/bar/more/x` - will not be included. - - Args: - store: The ObjectStore instance to use. - prefix: The prefix within ObjectStore to use for listing. Defaults to None. - - Returns: - ListResult - """ - -async def list_with_delimiter_async( - store: ObjectStore, prefix: str | None = None -) -> ListResult: - """Call `list_with_delimiter` asynchronously. - - Refer to the documentation for - [list_with_delimiter][object_store_rs.list_with_delimiter]. - """ diff --git a/object-store-rs/python/object_store_rs/_put.pyi b/object-store-rs/python/object_store_rs/_put.pyi deleted file mode 100644 index 2951d3be..00000000 --- a/object-store-rs/python/object_store_rs/_put.pyi +++ /dev/null @@ -1,60 +0,0 @@ -from pathlib import Path -from typing import IO, TypedDict - -from .store import ObjectStore - -class PutResult(TypedDict): - """ - Result for a put request. - """ - - e_tag: str | None - """ - The unique identifier for the newly created object - - - """ - - version: str | None - """A version indicator for the newly created object.""" - -def put( - store: ObjectStore, - path: str, - file: IO[bytes] | Path | bytes, - *, - use_multipart: bool | None = None, - chunk_size: int = 5 * 1024 * 1024, - max_concurrency: int = 12, -) -> PutResult: - """Save the provided bytes to the specified location - - The operation is guaranteed to be atomic, it will either successfully write the - entirety of `file` to `location`, or fail. No clients should be able to observe a - partially written object. - - Args: - store: The ObjectStore instance to use. - path: The path within ObjectStore for where to save the file. - file: The object to upload. Can either be file-like, a `Path` to a local file, - or a `bytes` object. - - Keyword args: - use_multipart: Whether to use a multipart upload under the hood. Defaults using a multipart upload if the length of the file is greater than `chunk_size`. - chunk_size: The size of chunks to use within each part of the multipart upload. Defaults to 5 MB. - max_concurrency: The maximum number of chunks to upload concurrently. Defaults to 12. - """ - -async def put_async( - store: ObjectStore, - path: str, - file: IO[bytes] | Path | bytes, - *, - use_multipart: bool | None = None, - chunk_size: int = 5 * 1024 * 1024, - max_concurrency: int = 12, -) -> PutResult: - """Call `put` asynchronously. - - Refer to the documentation for [put][object_store_rs.put]. - """ diff --git a/object-store-rs/src/list.rs b/object-store-rs/src/list.rs deleted file mode 100644 index b9fcf25d..00000000 --- a/object-store-rs/src/list.rs +++ /dev/null @@ -1,163 +0,0 @@ -use std::sync::Arc; - -use futures::stream::BoxStream; -use futures::StreamExt; -use indexmap::IndexMap; -use object_store::path::Path; -use object_store::{ListResult, ObjectMeta, ObjectStore}; -use pyo3::prelude::*; -use pyo3_object_store::error::{PyObjectStoreError, PyObjectStoreResult}; -use pyo3_object_store::PyObjectStore; - -use crate::runtime::get_runtime; - -pub(crate) struct PyObjectMeta(ObjectMeta); - -impl PyObjectMeta { - pub(crate) fn new(meta: ObjectMeta) -> Self { - Self(meta) - } -} - -impl IntoPy for PyObjectMeta { - fn into_py(self, py: Python<'_>) -> PyObject { - let mut dict = IndexMap::with_capacity(5); - // Note, this uses "path" instead of "location" because we standardize the API to accept - // the keyword "path" everywhere. - dict.insert("path", self.0.location.as_ref().into_py(py)); - dict.insert("last_modified", self.0.last_modified.into_py(py)); - dict.insert("size", self.0.size.into_py(py)); - dict.insert("e_tag", self.0.e_tag.into_py(py)); - dict.insert("version", self.0.version.into_py(py)); - dict.into_py(py) - } -} - -pub(crate) struct PyListResult(ListResult); - -impl IntoPy for PyListResult { - fn into_py(self, py: Python<'_>) -> PyObject { - let mut dict = IndexMap::with_capacity(2); - dict.insert( - "common_prefixes", - self.0 - .common_prefixes - .into_iter() - .map(String::from) - .collect::>() - .into_py(py), - ); - dict.insert( - "objects", - self.0 - .objects - .into_iter() - .map(PyObjectMeta) - .collect::>() - .into_py(py), - ); - dict.into_py(py) - } -} - -#[pyfunction] -#[pyo3(signature = (store, prefix = None, *, offset = None, max_items = 2000))] -pub(crate) fn list( - py: Python, - store: PyObjectStore, - prefix: Option, - offset: Option, - max_items: Option, -) -> PyObjectStoreResult> { - let store = store.into_inner(); - let prefix = prefix.map(|s| s.into()); - let runtime = get_runtime(py)?; - py.allow_threads(|| { - let stream = if let Some(offset) = offset { - store.list_with_offset(prefix.as_ref(), &offset.into()) - } else { - store.list(prefix.as_ref()) - }; - let out = runtime.block_on(materialize_list_stream(stream, max_items))?; - Ok::<_, PyObjectStoreError>(out) - }) -} - -#[pyfunction] -#[pyo3(signature = (store, prefix = None, *, offset = None, max_items = 2000))] -pub(crate) fn list_async( - py: Python, - store: PyObjectStore, - prefix: Option, - offset: Option, - max_items: Option, -) -> PyResult> { - let store = store.into_inner(); - let prefix = prefix.map(|s| s.into()); - - pyo3_async_runtimes::tokio::future_into_py(py, async move { - let stream = if let Some(offset) = offset { - store.list_with_offset(prefix.as_ref(), &offset.into()) - } else { - store.list(prefix.as_ref()) - }; - Ok(materialize_list_stream(stream, max_items).await?) - }) -} - -async fn materialize_list_stream( - mut stream: BoxStream<'_, object_store::Result>, - max_items: Option, -) -> PyObjectStoreResult> { - let mut result = vec![]; - while let Some(object) = stream.next().await { - result.push(PyObjectMeta(object?)); - if let Some(max_items) = max_items { - if result.len() >= max_items { - return Ok(result); - } - } - } - - Ok(result) -} - -#[pyfunction] -#[pyo3(signature = (store, prefix = None))] -pub(crate) fn list_with_delimiter( - py: Python, - store: PyObjectStore, - prefix: Option, -) -> PyObjectStoreResult { - let runtime = get_runtime(py)?; - py.allow_threads(|| { - let out = runtime.block_on(list_with_delimiter_materialize( - store.into_inner(), - prefix.map(|s| s.into()).as_ref(), - ))?; - Ok::<_, PyObjectStoreError>(out) - }) -} - -#[pyfunction] -#[pyo3(signature = (store, prefix = None))] -pub(crate) fn list_with_delimiter_async( - py: Python, - store: PyObjectStore, - prefix: Option, -) -> PyResult> { - pyo3_async_runtimes::tokio::future_into_py(py, async move { - let out = - list_with_delimiter_materialize(store.into_inner(), prefix.map(|s| s.into()).as_ref()) - .await?; - Ok(out) - }) -} - -async fn list_with_delimiter_materialize( - store: Arc, - prefix: Option<&Path>, -) -> PyObjectStoreResult { - let list_result = store.list_with_delimiter(prefix).await?; - Ok(PyListResult(list_result)) -} diff --git a/object-store-rs/Cargo.toml b/obstore/Cargo.toml similarity index 73% rename from object-store-rs/Cargo.toml rename to obstore/Cargo.toml index 9d10f27c..62577c8c 100644 --- a/object-store-rs/Cargo.toml +++ b/obstore/Cargo.toml @@ -1,9 +1,9 @@ [package] -name = "object-store-rs" -version = "0.1.0" +name = "obstore" +version = "0.2.0" authors = { workspace = true } edition = { workspace = true } -description = "Core library for representing Arrow data in Python." +description = "A Python interface to the Rust object_store crate, providing a uniform API for interacting with object storage services and local files." readme = "README.md" repository = { workspace = true } homepage = { workspace = true } @@ -14,17 +14,20 @@ rust-version = { workspace = true } # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [lib] -name = "_object_store_rs" +name = "_obstore" crate-type = ["cdylib"] [dependencies] +arrow = "53" bytes = { workspace = true } chrono = { workspace = true } futures = { workspace = true } http = { workspace = true } indexmap = { workspace = true } object_store = { workspace = true } -pyo3 = { workspace = true, features = ["chrono", "abi3-py39"] } +pyo3 = { workspace = true, features = ["chrono"] } +# Latest git to get PyArrowBuffer constructor +pyo3-arrow = { git = "https://github.com/kylebarron/arro3", rev = "d771e39c2748313a86bfcdfa36ec68cc24d9804b" } pyo3-async-runtimes = { workspace = true, features = ["tokio-runtime"] } pyo3-file = { workspace = true } pyo3-object_store = { path = "../pyo3-object_store" } diff --git a/object-store-rs/README.md b/obstore/README.md similarity index 100% rename from object-store-rs/README.md rename to obstore/README.md diff --git a/object-store-rs/pyproject.toml b/obstore/pyproject.toml similarity index 85% rename from object-store-rs/pyproject.toml rename to obstore/pyproject.toml index 5a8f859d..90e8bf76 100644 --- a/object-store-rs/pyproject.toml +++ b/obstore/pyproject.toml @@ -3,7 +3,7 @@ requires = ["maturin>=1.4.0,<2.0"] build-backend = "maturin" [project] -name = "object-store-rs" +name = "obstore" requires-python = ">=3.9" dependencies = [] classifiers = [ @@ -15,6 +15,6 @@ dynamic = ["version"] [tool.maturin] features = ["pyo3/extension-module"] -module-name = "object_store_rs._object_store_rs" +module-name = "obstore._obstore" python-source = "python" strip = true diff --git a/obstore/python/obstore/__init__.py b/obstore/python/obstore/__init__.py new file mode 100644 index 00000000..736e99ff --- /dev/null +++ b/obstore/python/obstore/__init__.py @@ -0,0 +1,5 @@ +from . import store +from ._obstore import * +from ._obstore import ___version + +__version__: str = ___version() diff --git a/obstore/python/obstore/_attributes.pyi b/obstore/python/obstore/_attributes.pyi new file mode 100644 index 00000000..f1b7c2e4 --- /dev/null +++ b/obstore/python/obstore/_attributes.pyi @@ -0,0 +1,47 @@ +from typing import Dict, Literal + +Attribute = ( + Literal[ + "Content-Disposition", + "Content-Encoding", + "Content-Language", + "Content-Type", + "Cache-Control", + ] + | str +) +"""Additional object attribute types. + +- `"Content-Disposition"`: Specifies how the object should be handled by a browser. + + See [Content-Disposition](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Disposition). + +- `"Content-Encoding"`: Specifies the encodings applied to the object. + + See [Content-Encoding](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Encoding). + +- `"Content-Language"`: Specifies the language of the object. + + See [Content-Language](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Language). + +- `"Content-Type"`: Specifies the MIME type of the object. + + This takes precedence over any client configuration. + + See [Content-Type](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Type). + +- `"Cache-Control"`: Overrides cache control policy of the object. + + See [Cache-Control](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control). + +Any other string key specifies a user-defined metadata field for the object. +""" + +Attributes = Dict[Attribute, str] +"""Additional attributes of an object + +Attributes can be specified in [`put`][obstore.put]/[`put_async`][obstore.put_async] and +retrieved from [`get`][obstore.get]/[`get_async`][obstore.get_async]. + +Unlike ObjectMeta, Attributes are not returned by listing APIs +""" diff --git a/object-store-rs/python/object_store_rs/_copy.pyi b/obstore/python/obstore/_copy.pyi similarity index 93% rename from object-store-rs/python/object_store_rs/_copy.pyi rename to obstore/python/obstore/_copy.pyi index 6eba9693..b2517b35 100644 --- a/object-store-rs/python/object_store_rs/_copy.pyi +++ b/obstore/python/obstore/_copy.pyi @@ -22,5 +22,5 @@ async def copy_async( ) -> None: """Call `copy` asynchronously. - Refer to the documentation for [copy][object_store_rs.copy]. + Refer to the documentation for [copy][obstore.copy]. """ diff --git a/object-store-rs/python/object_store_rs/_delete.pyi b/obstore/python/obstore/_delete.pyi similarity index 92% rename from object-store-rs/python/object_store_rs/_delete.pyi rename to obstore/python/obstore/_delete.pyi index 2a763f4e..09327992 100644 --- a/object-store-rs/python/object_store_rs/_delete.pyi +++ b/obstore/python/obstore/_delete.pyi @@ -21,5 +21,5 @@ def delete(store: ObjectStore, paths: str | Sequence[str]) -> None: async def delete_async(store: ObjectStore, paths: str | Sequence[str]) -> None: """Call `delete` asynchronously. - Refer to the documentation for [delete][object_store_rs.delete]. + Refer to the documentation for [delete][obstore.delete]. """ diff --git a/object-store-rs/python/object_store_rs/_get.pyi b/obstore/python/obstore/_get.pyi similarity index 69% rename from object-store-rs/python/object_store_rs/_get.pyi rename to obstore/python/obstore/_get.pyi index 3d481d72..e4ebce99 100644 --- a/object-store-rs/python/object_store_rs/_get.pyi +++ b/obstore/python/obstore/_get.pyi @@ -1,11 +1,21 @@ +import sys from datetime import datetime -from typing import List, Sequence, TypedDict +from typing import List, Sequence, Tuple, TypedDict +from ._attributes import Attributes from ._list import ObjectMeta from .store import ObjectStore -class GetOptions(TypedDict): - """Options for a get request, such as range""" +if sys.version_info >= (3, 12): + from collections.abc import Buffer as _Buffer +else: + from typing_extensions import Buffer as _Buffer + +class GetOptions(TypedDict, total=False): + """Options for a get request. + + All options are optional. + """ if_match: str | None """ @@ -57,11 +67,30 @@ class GetOptions(TypedDict): """ - # range: + # range: Tuple[int | None, int | None] """ Request transfer of only the specified range of bytes otherwise returning [`Error::NotModified`] + The semantics of this tuple are: + + - `(int, int)`: Request a specific range of bytes `(start, end)`. + + If the given range is zero-length or starts after the end of the object, an + error will be returned. Additionally, if the range ends after the end of the + object, the entire remainder of the object will be returned. Otherwise, the + exact requested range will be returned. + + The `end` offset is _exclusive_. + + - `(int, None)`: Request all bytes starting from a given byte offset. + + This is equivalent to `bytes={int}-` as an HTTP header. + + - `(None, int)`: Request the last `int` bytes. Note that here, `int` is _this size + of the request_, not the byte offset. This is equivalent to `bytes=-{int}` as an + HTTP header. + """ @@ -107,6 +136,13 @@ class GetResult: able to call other methods on this object, such as the `meta` attribute. """ + @property + def attributes(self) -> Attributes: + """Additional object attributes. + + This must be accessed _before_ calling `stream`, `bytes`, or `bytes_async`. + """ + def bytes(self) -> bytes: """ Collects the data into bytes @@ -119,7 +155,17 @@ class GetResult: @property def meta(self) -> ObjectMeta: - """The ObjectMeta for this object""" + """The ObjectMeta for this object. + + This must be accessed _before_ calling `stream`, `bytes`, or `bytes_async`. + """ + + @property + def range(self) -> Tuple[int, int]: + """The range of bytes returned by this request. + + This must be accessed _before_ calling `stream`, `bytes`, or `bytes_async`. + """ def stream(self, min_chunk_size: int = 10 * 1024 * 1024) -> BytesStream: """Return a chunked stream over the result's bytes. @@ -160,6 +206,17 @@ class BytesStream: def __next__(self) -> bytes: """Return the next chunk of bytes in the stream.""" +class Buffer(_Buffer): + """ + A buffer implementing the Python buffer protocol, allowing zero-copy access to the + underlying memory provided by Rust. + + You can pass this to [`memoryview`][] for a zero-copy view into the underlying data. + """ + + def as_bytes(self) -> bytes: + """Copy this buffer into a Python `bytes` object.""" + def get( store: ObjectStore, path: str, *, options: GetOptions | None = None ) -> GetResult: @@ -179,10 +236,10 @@ async def get_async( ) -> GetResult: """Call `get` asynchronously. - Refer to the documentation for [get][object_store_rs.get]. + Refer to the documentation for [get][obstore.get]. """ -def get_range(store: ObjectStore, path: str, offset: int, length: int) -> bytes: +def get_range(store: ObjectStore, path: str, offset: int, length: int) -> Buffer: """ Return the bytes that are stored at the specified location in the given byte range. @@ -198,22 +255,23 @@ def get_range(store: ObjectStore, path: str, offset: int, length: int) -> bytes: length: The number of bytes. Returns: - bytes + A `Buffer` object implementing the Python buffer protocol, allowing + zero-copy access to the underlying memory provided by Rust. """ async def get_range_async( store: ObjectStore, path: str, offset: int, length: int -) -> bytes: +) -> Buffer: """Call `get_range` asynchronously. - Refer to the documentation for [get_range][object_store_rs.get_range]. + Refer to the documentation for [get_range][obstore.get_range]. """ def get_ranges( store: ObjectStore, path: str, offsets: Sequence[int], lengths: Sequence[int] -) -> List[bytes]: +) -> List[Buffer]: """ - Return the bytes that are stored at the specified locationin the given byte ranges + Return the bytes that are stored at the specified location in the given byte ranges To improve performance this will: @@ -227,13 +285,15 @@ def get_ranges( lengths: A sequence of `int` representing the number of bytes within each range. Returns: - A sequence of `bytes`, one for each range. + A sequence of `Buffer`, one for each range. This `Buffer` object implements the + Python buffer protocol, allowing zero-copy access to the underlying memory + provided by Rust. """ async def get_ranges_async( store: ObjectStore, path: str, offsets: Sequence[int], lengths: Sequence[int] -) -> List[bytes]: +) -> List[Buffer]: """Call `get_ranges` asynchronously. - Refer to the documentation for [get_ranges][object_store_rs.get_ranges]. + Refer to the documentation for [get_ranges][obstore.get_ranges]. """ diff --git a/object-store-rs/python/object_store_rs/_head.pyi b/obstore/python/obstore/_head.pyi similarity index 87% rename from object-store-rs/python/object_store_rs/_head.pyi rename to obstore/python/obstore/_head.pyi index c44c4236..7e01f5e8 100644 --- a/object-store-rs/python/object_store_rs/_head.pyi +++ b/obstore/python/obstore/_head.pyi @@ -15,5 +15,5 @@ def head(store: ObjectStore, path: str) -> ObjectMeta: async def head_async(store: ObjectStore, path: str) -> ObjectMeta: """Call `head` asynchronously. - Refer to the documentation for [head][object_store_rs.head]. + Refer to the documentation for [head][obstore.head]. """ diff --git a/obstore/python/obstore/_list.pyi b/obstore/python/obstore/_list.pyi new file mode 100644 index 00000000..026f5f7a --- /dev/null +++ b/obstore/python/obstore/_list.pyi @@ -0,0 +1,216 @@ +from datetime import datetime +from typing import Generic, List, Literal, Self, TypedDict, TypeVar, overload + +from arro3.core import RecordBatch + +from .store import ObjectStore + +class ObjectMeta(TypedDict): + """The metadata that describes an object.""" + + path: str + """The full path to the object""" + + last_modified: datetime + """The last modified time""" + + size: int + """The size in bytes of the object""" + + e_tag: str | None + """The unique identifier for the object + + + """ + + version: str | None + """A version indicator for this object""" + +class ListResult(TypedDict): + """ + Result of a list call that includes objects, prefixes (directories) and a token for + the next set of results. Individual result sets may be limited to 1,000 objects + based on the underlying object storage's limitations. + """ + + common_prefixes: List[str] + """Prefixes that are common (like directories)""" + + objects: List[ObjectMeta] + """Object metadata for the listing""" + +ChunkType = TypeVar("ChunkType", List[ObjectMeta], RecordBatch) + +class ListStream(Generic[ChunkType]): + """ + A stream of [ObjectMeta][obstore.ObjectMeta] that can be polled in a sync or + async fashion. + """ + def __aiter__(self) -> Self: + """Return `Self` as an async iterator.""" + + def __iter__(self) -> Self: + """Return `Self` as an async iterator.""" + + async def collect_async(self) -> ChunkType: + """Collect all remaining ObjectMeta objects in the stream. + + This ignores the `chunk_size` parameter from the `list` call and collects all + remaining data into a single chunk. + """ + + def collect(self) -> ChunkType: + """Collect all remaining ObjectMeta objects in the stream. + + This ignores the `chunk_size` parameter from the `list` call and collects all + remaining data into a single chunk. + """ + + async def __anext__(self) -> ChunkType: + """Return the next chunk of ObjectMeta in the stream.""" + + def __next__(self) -> ChunkType: + """Return the next chunk of ObjectMeta in the stream.""" + +@overload +def list( + store: ObjectStore, + prefix: str | None = None, + *, + offset: str | None = None, + chunk_size: int = 50, + return_arrow: Literal[True], +) -> ListStream[RecordBatch]: ... +@overload +def list( + store: ObjectStore, + prefix: str | None = None, + *, + offset: str | None = None, + chunk_size: int = 50, + return_arrow: Literal[False] = False, +) -> ListStream[List[ObjectMeta]]: ... +def list( + store: ObjectStore, + prefix: str | None = None, + *, + offset: str | None = None, + chunk_size: int = 50, + return_arrow: bool = False, +) -> ListStream[RecordBatch] | ListStream[List[ObjectMeta]]: + """ + List all the objects with the given prefix. + + Prefixes are evaluated on a path segment basis, i.e. `foo/bar/` is a prefix of + `foo/bar/x` but not of `foo/bar_baz/x`. List is recursive, i.e. `foo/bar/more/x` + will be included. + + **Examples**: + + Synchronously iterate through list results: + + ```py + import obstore as obs + from obstore.store import MemoryStore + + store = MemoryStore() + for i in range(100): + obs.put(store, f"file{i}.txt", b"foo") + + stream = obs.list(store, chunk_size=10) + for list_result in stream: + print(list_result[0]) + # {'path': 'file0.txt', 'last_modified': datetime.datetime(2024, 10, 23, 19, 19, 28, 781723, tzinfo=datetime.timezone.utc), 'size': 3, 'e_tag': '0', 'version': None} + break + ``` + + Asynchronously iterate through list results. Just change `for` to `async for`: + + ```py + stream = obs.list(store, chunk_size=10) + async for list_result in stream: + print(list_result[2]) + # {'path': 'file10.txt', 'last_modified': datetime.datetime(2024, 10, 23, 19, 21, 46, 224725, tzinfo=datetime.timezone.utc), 'size': 3, 'e_tag': '10', 'version': None} + break + ``` + + Return large list results as [Arrow](https://arrow.apache.org/). This is most useful + with large list operations. In this case you may want to increase the `chunk_size` + parameter. + + ```py + stream = obs.list(store, chunk_size=1000, return_arrow=True) + # Stream is now an iterable/async iterable of `RecordBatch`es + for batch in stream: + print(batch.num_rows) # 100 + + # If desired, convert to a pyarrow RecordBatch (zero-copy) with + # `pyarrow.record_batch(batch)` + break + ``` + + Collect all list results into a single Arrow `RecordBatch`. + + ```py + stream = obs.list(store, return_arrow=True) + batch = stream.collect() + ``` + + !!! note + The order of returned [`ObjectMeta`][obstore.ObjectMeta] is not + guaranteed + + !!! note + There is no async version of this method, because `list` is not async under the + hood, rather it only instantiates a stream, which can be polled in synchronous + or asynchronous fashion. See [`ListStream`][obstore.ListStream]. + + Args: + store: The ObjectStore instance to use. + prefix: The prefix within ObjectStore to use for listing. Defaults to None. + + Keyword Args: + offset: If provided, list all the objects with the given prefix and a location greater than `offset`. Defaults to `None`. + chunk_size: The number of items to collect per chunk in the returned + (async) iterator. All chunks except for the last one will have this many + items. This is ignored in the + [`collect`][obstore.ListStream.collect] and + [`collect_async`][obstore.ListStream.collect_async] methods of + `ListStream`. + return_arrow: If `True`, return each batch of list items as an Arrow + `RecordBatch`, not as a list of Python `dict`s. Arrow removes serialization + overhead between Rust and Python and so this can be significantly faster for + large list operations. Defaults to `False`. + + If this is `True`, the `arro3-core` Python package must be installed. + + Returns: + A ListStream, which you can iterate through to access list results. + """ + +def list_with_delimiter(store: ObjectStore, prefix: str | None = None) -> ListResult: + """ + List objects with the given prefix and an implementation specific + delimiter. Returns common prefixes (directories) in addition to object + metadata. + + Prefixes are evaluated on a path segment basis, i.e. `foo/bar/` is a prefix of + `foo/bar/x` but not of `foo/bar_baz/x`. List is not recursive, i.e. `foo/bar/more/x` + will not be included. + + Args: + store: The ObjectStore instance to use. + prefix: The prefix within ObjectStore to use for listing. Defaults to None. + + Returns: + ListResult + """ + +async def list_with_delimiter_async( + store: ObjectStore, prefix: str | None = None +) -> ListResult: + """Call `list_with_delimiter` asynchronously. + + Refer to the documentation for + [list_with_delimiter][obstore.list_with_delimiter]. + """ diff --git a/object-store-rs/python/object_store_rs/_object_store_rs.pyi b/obstore/python/obstore/_obstore.pyi similarity index 80% rename from object-store-rs/python/object_store_rs/_object_store_rs.pyi rename to obstore/python/obstore/_obstore.pyi index 5dc83ef7..6775160c 100644 --- a/object-store-rs/python/object_store_rs/_object_store_rs.pyi +++ b/obstore/python/obstore/_obstore.pyi @@ -1,7 +1,10 @@ +from ._attributes import Attribute as Attribute +from ._attributes import Attributes as Attributes from ._copy import copy as copy from ._copy import copy_async as copy_async from ._delete import delete as delete from ._delete import delete_async as delete_async +from ._get import Buffer as Buffer from ._get import GetOptions as GetOptions from ._get import GetResult as GetResult from ._get import get as get @@ -13,12 +16,14 @@ from ._get import get_ranges_async as get_ranges_async from ._head import head as head from ._head import head_async as head_async from ._list import ListResult as ListResult +from ._list import ListStream as ListStream from ._list import ObjectMeta as ObjectMeta from ._list import list as list -from ._list import list_async as list_async from ._list import list_with_delimiter as list_with_delimiter from ._list import list_with_delimiter_async as list_with_delimiter_async +from ._put import PutMode as PutMode from ._put import PutResult as PutResult +from ._put import UpdateVersion as UpdateVersion from ._put import put as put from ._put import put_async as put_async from ._rename import rename as rename @@ -27,3 +32,5 @@ from ._sign import HTTP_METHOD as HTTP_METHOD from ._sign import SignCapableStore as SignCapableStore from ._sign import sign as sign from ._sign import sign_async as sign_async + +def ___version() -> str: ... diff --git a/obstore/python/obstore/_put.pyi b/obstore/python/obstore/_put.pyi new file mode 100644 index 00000000..03b9c04d --- /dev/null +++ b/obstore/python/obstore/_put.pyi @@ -0,0 +1,101 @@ +from pathlib import Path +from typing import IO, Dict, Literal, TypedDict + +from ._attributes import Attributes +from .store import ObjectStore + +class UpdateVersion(TypedDict, total=False): + """ + Uniquely identifies a version of an object to update + + Stores will use differing combinations of `e_tag` and `version` to provide + conditional updates, and it is therefore recommended applications preserve both + """ + + e_tag: str | None + """The unique identifier for the newly created object. + + + """ + + version: str | None + """A version indicator for the newly created object.""" + +PutMode = Literal["create", "overwrite"] | UpdateVersion +"""Configure preconditions for the put operation + +If a string is provided, it must be one of: + +- `"overwrite"`: Perform an atomic write operation, overwriting any object present at the provided path. +- `"create"`: Perform an atomic write operation, returning [`AlreadyExistsError`][obstore.exceptions.AlreadyExistsError] if an object already exists at the provided path + +If a `dict` is provided, it must meet the criteria of `UpdateVersion`. In this case, +perform an atomic write operation if the current version of the object matches the +provided [`UpdateVersion`][obstore.UpdateVersion], returning +[`PreconditionError`][obstore.exceptions.PreconditionError] otherwise. +""" + +class PutResult(TypedDict): + """ + Result for a put request. + """ + + e_tag: str | None + """ + The unique identifier for the newly created object + + + """ + + version: str | None + """A version indicator for the newly created object.""" + +def put( + store: ObjectStore, + path: str, + file: IO[bytes] | Path | bytes, + *, + attributes: Attributes | None = None, + tags: Dict[str, str] | None = None, + mode: PutMode | None = None, + use_multipart: bool | None = None, + chunk_size: int = 5 * 1024 * 1024, + max_concurrency: int = 12, +) -> PutResult: + """Save the provided bytes to the specified location + + The operation is guaranteed to be atomic, it will either successfully write the + entirety of `file` to `location`, or fail. No clients should be able to observe a + partially written object. + + Args: + store: The ObjectStore instance to use. + path: The path within ObjectStore for where to save the file. + file: The object to upload. Can either be file-like, a `Path` to a local file, + or a `bytes` object. + + Keyword args: + mode: Configure the `PutMode` for this operation. If this provided and is not `"overwrite"`, a non-multipart upload will be performed. Defaults to `"overwrite"`. + attributes: Provide a set of `Attributes`. Defaults to `None`. + tags: Provide tags for this object. Defaults to `None`. + use_multipart: Whether to use a multipart upload under the hood. Defaults using a multipart upload if the length of the file is greater than `chunk_size`. + chunk_size: The size of chunks to use within each part of the multipart upload. Defaults to 5 MB. + max_concurrency: The maximum number of chunks to upload concurrently. Defaults to 12. + """ + +async def put_async( + store: ObjectStore, + path: str, + file: IO[bytes] | Path | bytes, + *, + attributes: Attributes | None = None, + tags: Dict[str, str] | None = None, + mode: PutMode | None = None, + use_multipart: bool | None = None, + chunk_size: int = 5 * 1024 * 1024, + max_concurrency: int = 12, +) -> PutResult: + """Call `put` asynchronously. + + Refer to the documentation for [put][obstore.put]. + """ diff --git a/object-store-rs/python/object_store_rs/_rename.pyi b/obstore/python/obstore/_rename.pyi similarity index 92% rename from object-store-rs/python/object_store_rs/_rename.pyi rename to obstore/python/obstore/_rename.pyi index 2f5d2529..9bcd6a95 100644 --- a/object-store-rs/python/object_store_rs/_rename.pyi +++ b/obstore/python/obstore/_rename.pyi @@ -23,5 +23,5 @@ async def rename_async( ) -> None: """Call `rename` asynchronously. - Refer to the documentation for [rename][object_store_rs.rename]. + Refer to the documentation for [rename][obstore.rename]. """ diff --git a/object-store-rs/python/object_store_rs/_sign.pyi b/obstore/python/obstore/_sign.pyi similarity index 96% rename from object-store-rs/python/object_store_rs/_sign.pyi rename to obstore/python/obstore/_sign.pyi index 9e6dc9bf..e03636c0 100644 --- a/object-store-rs/python/object_store_rs/_sign.pyi +++ b/obstore/python/obstore/_sign.pyi @@ -68,5 +68,5 @@ async def sign_async( ) -> str | List[str]: """Call `sign` asynchronously. - Refer to the documentation for [sign][object_store_rs.sign]. + Refer to the documentation for [sign][obstore.sign]. """ diff --git a/obstore/python/obstore/exceptions.pyi b/obstore/python/obstore/exceptions.pyi new file mode 100644 index 00000000..d0638fb3 --- /dev/null +++ b/obstore/python/obstore/exceptions.pyi @@ -0,0 +1,38 @@ +class ObstoreError(Exception): + """The base exception class""" + +class GenericError(ObstoreError): + """A fallback error type when no variant matches.""" + +class NotFoundError(ObstoreError): + """Error when the object is not found at given location.""" + +class InvalidPathError(ObstoreError): + """Error for invalid path.""" + +class JoinError(ObstoreError): + """Error when `tokio::spawn` failed.""" + +class NotSupportedError(ObstoreError): + """Error when the attempted operation is not supported.""" + +class AlreadyExistsError(ObstoreError): + """Error when the object already exists.""" + +class PreconditionError(ObstoreError): + """Error when the required conditions failed for the operation.""" + +class NotModifiedError(ObstoreError): + """Error when the object at the location isn't modified.""" + +class PermissionDeniedError(ObstoreError): + """ + Error when the used credentials don't have enough permission + to perform the requested operation + """ + +class UnauthenticatedError(ObstoreError): + """Error when the used credentials lack valid authentication.""" + +class UnknownConfigurationKeyError(ObstoreError): + """Error when a configuration key is invalid for the store used.""" diff --git a/object-store-rs/python/object_store_rs/fsspec.py b/obstore/python/obstore/fsspec.py similarity index 77% rename from object-store-rs/python/object_store_rs/fsspec.py rename to obstore/python/obstore/fsspec.py index 6181d1dc..2339d455 100644 --- a/object-store-rs/python/object_store_rs/fsspec.py +++ b/obstore/python/obstore/fsspec.py @@ -20,22 +20,18 @@ import asyncio from collections import defaultdict -from typing import TYPE_CHECKING, Any, Coroutine, Dict, List, Tuple +from typing import Any, Coroutine, Dict, List, Tuple import fsspec.asyn +import fsspec.spec -import object_store_rs as obs - -if TYPE_CHECKING: - from object_store_rs.store import ObjectStore +import obstore as obs class AsyncFsspecStore(fsspec.asyn.AsyncFileSystem): - store: ObjectStore - def __init__( self, - store: ObjectStore, + store, *args, asynchronous=False, loop=None, @@ -127,9 +123,37 @@ async def _info(self, path, **kwargs): } async def _ls(self, path, detail=True, **kwargs): - if detail: - raise NotImplementedError("Not sure how to format these dicts") - result = await obs.list_with_delimiter_async(self.store, path) objects = result["objects"] - return [object["path"] for object in objects] + prefs = result["common_prefixes"] + if detail: + return [ + { + "name": object["path"], + "size": object["size"], + "type": "file", + "ETag": object["e_tag"], + } + for object in objects + ] + [{"name": object, "size": 0, "type": "directory"} for object in prefs] + else: + return sorted([object["path"] for object in objects] + prefs) + + def _open(self, path, mode="rb", **kwargs): + """Return raw bytes-mode file-like from the file-system""" + out = BufferedFileSimple(self, path, mode) + return out + + +class BufferedFileSimple(fsspec.spec.AbstractBufferedFile): + def __init__(self, fs, path, mode="rb", cache_type="none", **kwargs): + super().__init__(fs, path, mode, mode, cache_type=cache_type, **kwargs) + + def read(self, length=-1): + if length < 0: + data = self.fs.cat_file(self.path, self.loc, self.size) + self.loc = self.size + else: + data = self.fs.cat_file(self.path, self.loc, self.loc + length) + self.loc += length + return data diff --git a/object-store-rs/python/object_store_rs/store/__init__.pyi b/obstore/python/obstore/store/__init__.pyi similarity index 82% rename from object-store-rs/python/object_store_rs/store/__init__.pyi rename to obstore/python/obstore/store/__init__.pyi index 414521b1..f7499c4f 100644 --- a/object-store-rs/python/object_store_rs/store/__init__.pyi +++ b/obstore/python/obstore/store/__init__.pyi @@ -1,4 +1,6 @@ # TODO: move to reusable types package +from pathlib import Path + from ._aws import S3ConfigKey as S3ConfigKey from ._aws import S3Store as S3Store from ._azure import AzureConfigKey as AzureConfigKey @@ -16,11 +18,15 @@ class LocalStore: Can optionally be created with a directory prefix. ```py + from pathlib import Path + store = LocalStore() store = LocalStore(prefix="/path/to/directory") + store = LocalStore(prefix=Path(".")) ``` """ - def __init__(self, prefix: str | None = None) -> None: ... + def __init__(self, prefix: str | Path | None = None) -> None: ... + def __repr__(self) -> str: ... class MemoryStore: """A fully in-memory implementation of ObjectStore. @@ -31,6 +37,7 @@ class MemoryStore: ``` """ def __init__(self) -> None: ... + def __repr__(self) -> str: ... ObjectStore = AzureStore | GCSStore | HTTPStore | S3Store | LocalStore | MemoryStore """All supported ObjectStore implementations.""" diff --git a/object-store-rs/python/object_store_rs/store/_aws.pyi b/obstore/python/obstore/store/_aws.pyi similarity index 99% rename from object-store-rs/python/object_store_rs/store/_aws.pyi rename to obstore/python/obstore/store/_aws.pyi index be0620f2..b288fd01 100644 --- a/object-store-rs/python/object_store_rs/store/_aws.pyi +++ b/obstore/python/obstore/store/_aws.pyi @@ -213,3 +213,5 @@ class S3Store: Returns: S3Store """ + + def __repr__(self) -> str: ... diff --git a/object-store-rs/python/object_store_rs/store/_azure.pyi b/obstore/python/obstore/store/_azure.pyi similarity index 99% rename from object-store-rs/python/object_store_rs/store/_azure.pyi rename to obstore/python/obstore/store/_azure.pyi index 8c7d8956..e36421e0 100644 --- a/object-store-rs/python/object_store_rs/store/_azure.pyi +++ b/obstore/python/obstore/store/_azure.pyi @@ -210,3 +210,5 @@ class AzureStore: Returns: AzureStore """ + + def __repr__(self) -> str: ... diff --git a/object-store-rs/python/object_store_rs/store/_client.pyi b/obstore/python/obstore/store/_client.pyi similarity index 100% rename from object-store-rs/python/object_store_rs/store/_client.pyi rename to obstore/python/obstore/store/_client.pyi diff --git a/object-store-rs/python/object_store_rs/store/_gcs.pyi b/obstore/python/obstore/store/_gcs.pyi similarity index 99% rename from object-store-rs/python/object_store_rs/store/_gcs.pyi rename to obstore/python/obstore/store/_gcs.pyi index 2b74f9cc..71ef74cf 100644 --- a/object-store-rs/python/object_store_rs/store/_gcs.pyi +++ b/obstore/python/obstore/store/_gcs.pyi @@ -103,3 +103,5 @@ class GCSStore: Returns: GCSStore """ + + def __repr__(self) -> str: ... diff --git a/object-store-rs/python/object_store_rs/store/_http.pyi b/obstore/python/obstore/store/_http.pyi similarity index 95% rename from object-store-rs/python/object_store_rs/store/_http.pyi rename to obstore/python/obstore/store/_http.pyi index d9922cd3..c1ce3cfa 100644 --- a/object-store-rs/python/object_store_rs/store/_http.pyi +++ b/obstore/python/obstore/store/_http.pyi @@ -26,3 +26,5 @@ class HTTPStore: Returns: HTTPStore """ + + def __repr__(self) -> str: ... diff --git a/object-store-rs/python/object_store_rs/store/_retry.pyi b/obstore/python/obstore/store/_retry.pyi similarity index 94% rename from object-store-rs/python/object_store_rs/store/_retry.pyi rename to obstore/python/obstore/store/_retry.pyi index 8113ef3d..938f1568 100644 --- a/object-store-rs/python/object_store_rs/store/_retry.pyi +++ b/obstore/python/obstore/store/_retry.pyi @@ -31,7 +31,7 @@ class RetryConfig(TypedDict): * Timeouts for [safe] / read-only requests Requests will be retried up to some limit, using exponential - backoff with jitter. See [`BackoffConfig`][object_store_rs.store.BackoffConfig] for + backoff with jitter. See [`BackoffConfig`][obstore.store.BackoffConfig] for more information [safe]: https://datatracker.ietf.org/doc/html/rfc7231#section-4.2.1 diff --git a/obstore/src/attributes.rs b/obstore/src/attributes.rs new file mode 100644 index 00000000..86de1385 --- /dev/null +++ b/obstore/src/attributes.rs @@ -0,0 +1,79 @@ +use std::borrow::Cow; +use std::collections::HashMap; + +use indexmap::IndexMap; +use object_store::{Attribute, AttributeValue, Attributes}; +use pyo3::prelude::*; +use pyo3::pybacked::PyBackedStr; + +#[derive(Debug, PartialEq, Eq, Hash)] +pub(crate) struct PyAttribute(Attribute); + +impl<'py> FromPyObject<'py> for PyAttribute { + fn extract_bound(ob: &Bound<'py, PyAny>) -> PyResult { + let s = ob.extract::()?.to_ascii_lowercase(); + match s.as_str() { + "content-disposition" | "contentdisposition" => Ok(Self(Attribute::ContentDisposition)), + "Content-Encoding" | "ContentEncoding" => Ok(Self(Attribute::ContentEncoding)), + "Content-Language" | "ContentLanguage" => Ok(Self(Attribute::ContentLanguage)), + "Content-Type" | "ContentType" => Ok(Self(Attribute::ContentType)), + "Cache-Control" | "CacheControl" => Ok(Self(Attribute::CacheControl)), + _ => Ok(Self(Attribute::Metadata(Cow::Owned(s)))), + } + } +} + +fn attribute_to_string(attribute: &Attribute) -> Cow<'static, str> { + match attribute { + Attribute::ContentDisposition => Cow::Borrowed("Content-Disposition"), + Attribute::ContentEncoding => Cow::Borrowed("Content-Encoding"), + Attribute::ContentLanguage => Cow::Borrowed("Content-Language"), + Attribute::ContentType => Cow::Borrowed("Content-Type"), + Attribute::CacheControl => Cow::Borrowed("Cache-Control"), + Attribute::Metadata(x) => x.clone(), + other => panic!("Unexpected attribute: {:?}", other), + } +} + +#[derive(Debug, PartialEq, Eq, Hash)] +pub(crate) struct PyAttributeValue(AttributeValue); + +impl<'py> FromPyObject<'py> for PyAttributeValue { + fn extract_bound(ob: &Bound<'py, PyAny>) -> PyResult { + Ok(Self(ob.extract::()?.into())) + } +} + +#[derive(Debug, PartialEq, Eq)] +pub(crate) struct PyAttributes(Attributes); + +impl PyAttributes { + pub fn new(attributes: Attributes) -> Self { + Self(attributes) + } + + pub fn into_inner(self) -> Attributes { + self.0 + } +} + +impl<'py> FromPyObject<'py> for PyAttributes { + fn extract_bound(ob: &Bound<'py, PyAny>) -> PyResult { + let d = ob.extract::>()?; + let mut attributes = Attributes::with_capacity(d.len()); + for (k, v) in d.into_iter() { + attributes.insert(k.0, v.0); + } + Ok(Self(attributes)) + } +} + +impl IntoPy for PyAttributes { + fn into_py(self, py: Python<'_>) -> PyObject { + let mut d = IndexMap::with_capacity(self.0.len()); + for (k, v) in self.0.into_iter() { + d.insert(attribute_to_string(k), v.as_ref()); + } + d.into_py(py) + } +} diff --git a/object-store-rs/src/copy.rs b/obstore/src/copy.rs similarity index 100% rename from object-store-rs/src/copy.rs rename to obstore/src/copy.rs diff --git a/object-store-rs/src/delete.rs b/obstore/src/delete.rs similarity index 100% rename from object-store-rs/src/delete.rs rename to obstore/src/delete.rs diff --git a/object-store-rs/src/get.rs b/obstore/src/get.rs similarity index 70% rename from object-store-rs/src/get.rs rename to obstore/src/get.rs index d162b53c..20d8b8b8 100644 --- a/object-store-rs/src/get.rs +++ b/obstore/src/get.rs @@ -1,35 +1,65 @@ +use std::collections::HashMap; use std::sync::Arc; +use arrow::buffer::Buffer; use bytes::Bytes; use chrono::{DateTime, Utc}; -use futures::stream::BoxStream; +use futures::stream::{BoxStream, Fuse}; use futures::StreamExt; -use object_store::{GetOptions, GetResult, ObjectStore}; +use object_store::{GetOptions, GetRange, GetResult, ObjectStore}; use pyo3::exceptions::{PyStopAsyncIteration, PyStopIteration, PyValueError}; use pyo3::prelude::*; use pyo3::types::PyBytes; +use pyo3_arrow::buffer::PyArrowBuffer; use pyo3_object_store::error::{PyObjectStoreError, PyObjectStoreResult}; use pyo3_object_store::PyObjectStore; use tokio::sync::Mutex; +use crate::attributes::PyAttributes; use crate::list::PyObjectMeta; use crate::runtime::get_runtime; /// 10MB default chunk size const DEFAULT_BYTES_CHUNK_SIZE: usize = 10 * 1024 * 1024; -#[derive(FromPyObject)] pub(crate) struct PyGetOptions { if_match: Option, if_none_match: Option, if_modified_since: Option>, if_unmodified_since: Option>, - // TODO: - // range: Option>, + // Taking out of public API until we can decide the right way to expose this + // range: Option, version: Option, head: bool, } +impl<'py> FromPyObject<'py> for PyGetOptions { + fn extract_bound(ob: &Bound<'py, PyAny>) -> PyResult { + // Update to use derive(FromPyObject) when default is implemented: + // https://github.com/PyO3/pyo3/issues/4643 + let dict = ob.extract::>>()?; + Ok(Self { + if_match: dict.get("if_match").map(|x| x.extract()).transpose()?, + if_none_match: dict.get("if_none_match").map(|x| x.extract()).transpose()?, + if_modified_since: dict + .get("if_modified_since") + .map(|x| x.extract()) + .transpose()?, + if_unmodified_since: dict + .get("if_unmodified_since") + .map(|x| x.extract()) + .transpose()?, + // range: dict.get("range").map(|x| x.extract()).transpose()?, + version: dict.get("version").map(|x| x.extract()).transpose()?, + head: dict + .get("head") + .map(|x| x.extract()) + .transpose()? + .unwrap_or(false), + }) + } +} + impl From for GetOptions { fn from(value: PyGetOptions) -> Self { Self { @@ -37,13 +67,41 @@ impl From for GetOptions { if_none_match: value.if_none_match, if_modified_since: value.if_modified_since, if_unmodified_since: value.if_unmodified_since, - range: None, + range: Default::default(), version: value.version, head: value.head, } } } +#[allow(dead_code)] +pub(crate) struct PyGetRange(GetRange); + +// TODO: think of a better API here so that the distinction between each of these is easy to +// understand. +impl<'py> FromPyObject<'py> for PyGetRange { + fn extract_bound(ob: &Bound<'py, PyAny>) -> PyResult { + let range = ob.extract::<[Option; 2]>()?; + match (range[0], range[1]) { + (Some(start), Some(end)) => { + if start >= end { + return Err(PyValueError::new_err( + format!("End range must be strictly greater than start range. Got start: {}, end: {}", start, end ), + )); + } + + Ok(Self(GetRange::Bounded(start..end))) + } + (Some(start), None) => Ok(Self(GetRange::Offset(start))), + // Note: in this case `end` means `suffix bytes` + (None, Some(end)) => Ok(Self(GetRange::Suffix(end))), + (None, None) => Err(PyValueError::new_err( + "Cannot provide (None, None) for range.", + )), + } + } +} + #[pyclass(name = "GetResult")] pub(crate) struct PyGetResult(Option); @@ -81,6 +139,15 @@ impl PyGetResult { }) } + #[getter] + fn attributes(&self) -> PyResult { + let inner = self + .0 + .as_ref() + .ok_or(PyValueError::new_err("Result has already been disposed."))?; + Ok(PyAttributes::new(inner.attributes.clone())) + } + #[getter] fn meta(&self) -> PyResult { let inner = self @@ -90,6 +157,15 @@ impl PyGetResult { Ok(PyObjectMeta::new(inner.meta.clone())) } + #[getter] + fn range(&self) -> PyResult<(usize, usize)> { + let inner = self + .0 + .as_ref() + .ok_or(PyValueError::new_err("Result has already been disposed."))?; + Ok((inner.range.start, inner.range.end)) + } + #[pyo3(signature = (min_chunk_size = DEFAULT_BYTES_CHUNK_SIZE))] fn stream(&mut self, min_chunk_size: usize) -> PyResult { let get_result = self @@ -108,23 +184,25 @@ impl PyGetResult { } } +// Note: we fuse the underlying stream so that we can get `None` multiple times. +// See the note on PyListStream for more background. #[pyclass(name = "BytesStream")] pub struct PyBytesStream { - stream: Arc>>>, + stream: Arc>>>>, min_chunk_size: usize, } impl PyBytesStream { fn new(stream: BoxStream<'static, object_store::Result>, min_chunk_size: usize) -> Self { Self { - stream: Arc::new(Mutex::new(stream)), + stream: Arc::new(Mutex::new(stream.fuse())), min_chunk_size, } } } async fn next_stream( - stream: Arc>>>, + stream: Arc>>>>, min_chunk_size: usize, sync: bool, ) -> PyResult { @@ -263,12 +341,12 @@ pub(crate) fn get_range( path: String, offset: usize, length: usize, -) -> PyObjectStoreResult { +) -> PyObjectStoreResult { let runtime = get_runtime(py)?; let range = offset..offset + length; py.allow_threads(|| { let out = runtime.block_on(store.as_ref().get_range(&path.into(), range))?; - Ok::<_, PyObjectStoreError>(PyBytesWrapper::new(out)) + Ok::<_, PyObjectStoreError>(PyArrowBuffer::new(Buffer::from_bytes(out.into()))) }) } @@ -287,7 +365,7 @@ pub(crate) fn get_range_async( .get_range(&path.into(), range) .await .map_err(PyObjectStoreError::ObjectStoreError)?; - Ok(PyBytesWrapper::new(out)) + Ok(PyArrowBuffer::new(Buffer::from_bytes(out.into()))) }) } @@ -298,7 +376,7 @@ pub(crate) fn get_ranges( path: String, offsets: Vec, lengths: Vec, -) -> PyObjectStoreResult> { +) -> PyObjectStoreResult> { let runtime = get_runtime(py)?; let ranges = offsets .into_iter() @@ -307,7 +385,11 @@ pub(crate) fn get_ranges( .collect::>(); py.allow_threads(|| { let out = runtime.block_on(store.as_ref().get_ranges(&path.into(), &ranges))?; - Ok::<_, PyObjectStoreError>(out.into_iter().map(PyBytesWrapper::new).collect()) + Ok::<_, PyObjectStoreError>( + out.into_iter() + .map(|buf| PyArrowBuffer::new(Buffer::from_bytes(buf.into()))) + .collect(), + ) }) } @@ -330,6 +412,9 @@ pub(crate) fn get_ranges_async( .get_ranges(&path.into(), &ranges) .await .map_err(PyObjectStoreError::ObjectStoreError)?; - Ok(out.into_iter().map(PyBytesWrapper::new).collect::>()) + Ok(out + .into_iter() + .map(|buf| PyArrowBuffer::new(Buffer::from_bytes(buf.into()))) + .collect::>()) }) } diff --git a/object-store-rs/src/head.rs b/obstore/src/head.rs similarity index 100% rename from object-store-rs/src/head.rs rename to obstore/src/head.rs diff --git a/object-store-rs/src/lib.rs b/obstore/src/lib.rs similarity index 87% rename from object-store-rs/src/lib.rs rename to obstore/src/lib.rs index cd75d5bf..f8bba398 100644 --- a/object-store-rs/src/lib.rs +++ b/obstore/src/lib.rs @@ -1,5 +1,6 @@ use pyo3::prelude::*; +mod attributes; mod copy; mod delete; mod get; @@ -10,6 +11,7 @@ mod put; mod rename; mod runtime; mod signer; +mod tags; const VERSION: &str = env!("CARGO_PKG_VERSION"); @@ -20,10 +22,11 @@ fn ___version() -> &'static str { /// A Python module implemented in Rust. #[pymodule] -fn _object_store_rs(py: Python, m: &Bound) -> PyResult<()> { +fn _obstore(py: Python, m: &Bound) -> PyResult<()> { m.add_wrapped(wrap_pyfunction!(___version))?; - pyo3_object_store::register_store_module(py, m, "object_store_rs")?; + pyo3_object_store::register_store_module(py, m, "obstore")?; + pyo3_object_store::register_exceptions_module(py, m, "obstore")?; m.add_wrapped(wrap_pyfunction!(copy::copy_async))?; m.add_wrapped(wrap_pyfunction!(copy::copy))?; @@ -37,7 +40,6 @@ fn _object_store_rs(py: Python, m: &Bound) -> PyResult<()> { m.add_wrapped(wrap_pyfunction!(get::get))?; m.add_wrapped(wrap_pyfunction!(head::head_async))?; m.add_wrapped(wrap_pyfunction!(head::head))?; - m.add_wrapped(wrap_pyfunction!(list::list_async))?; m.add_wrapped(wrap_pyfunction!(list::list_with_delimiter_async))?; m.add_wrapped(wrap_pyfunction!(list::list_with_delimiter))?; m.add_wrapped(wrap_pyfunction!(list::list))?; diff --git a/obstore/src/list.rs b/obstore/src/list.rs new file mode 100644 index 00000000..31672745 --- /dev/null +++ b/obstore/src/list.rs @@ -0,0 +1,407 @@ +use std::ops::AddAssign; +use std::sync::Arc; + +use arrow::array::{ + ArrayRef, RecordBatch, StringBuilder, TimestampMicrosecondBuilder, UInt64Builder, +}; +use arrow::datatypes::{DataType, Field, Schema, TimeUnit}; +use futures::stream::{BoxStream, Fuse}; +use futures::StreamExt; +use indexmap::IndexMap; +use object_store::path::Path; +use object_store::{ListResult, ObjectMeta, ObjectStore}; +use pyo3::exceptions::{PyImportError, PyStopAsyncIteration, PyStopIteration}; +use pyo3::intern; +use pyo3::prelude::*; +use pyo3_arrow::PyRecordBatch; +use pyo3_object_store::error::{PyObjectStoreError, PyObjectStoreResult}; +use pyo3_object_store::PyObjectStore; +use tokio::sync::Mutex; + +use crate::runtime::get_runtime; + +pub(crate) struct PyObjectMeta(ObjectMeta); + +impl PyObjectMeta { + pub(crate) fn new(meta: ObjectMeta) -> Self { + Self(meta) + } +} + +impl AsRef for PyObjectMeta { + fn as_ref(&self) -> &ObjectMeta { + &self.0 + } +} + +impl IntoPy for PyObjectMeta { + fn into_py(self, py: Python<'_>) -> PyObject { + let mut dict = IndexMap::with_capacity(5); + // Note, this uses "path" instead of "location" because we standardize the API to accept + // the keyword "path" everywhere. + dict.insert("path", self.0.location.as_ref().into_py(py)); + dict.insert("last_modified", self.0.last_modified.into_py(py)); + dict.insert("size", self.0.size.into_py(py)); + dict.insert("e_tag", self.0.e_tag.into_py(py)); + dict.insert("version", self.0.version.into_py(py)); + dict.into_py(py) + } +} + +// Note: we fuse the underlying stream so that we can get `None` multiple times. +// +// In general, you can't poll an iterator after it's already emitted None. But the issue here is +// that we need _two_ states for the Python async iterator. It needs to first get all returned +// results, and then it needs its **own** PyStopAsyncIteration/PyStopIteration. But these are _two_ +// results to be returned from the Rust call, and we can't return them both at the same time. The +// easiest way to fix this is to safely return `None` from the stream multiple times. The first +// time we see `None` we return any batched results, the second time we see `None`, there are no +// batched results and we return PyStopAsyncIteration/PyStopIteration. +// +// Note: another way we could solve this is by removing any batching from the stream, but batching +// should improve the performance of the Rust/Python bridge. +// +// Ref: +// - https://stackoverflow.com/a/66964599 +// - https://docs.rs/futures/latest/futures/prelude/stream/trait.StreamExt.html#method.fuse +#[pyclass(name = "ListStream")] +pub(crate) struct PyListStream { + stream: Arc>>>>, + chunk_size: usize, + return_arrow: bool, +} + +impl PyListStream { + fn new( + stream: BoxStream<'static, object_store::Result>, + chunk_size: usize, + return_arrow: bool, + ) -> Self { + Self { + stream: Arc::new(Mutex::new(stream.fuse())), + chunk_size, + return_arrow, + } + } +} + +#[pymethods] +impl PyListStream { + fn __aiter__(slf: Py) -> Py { + slf + } + + fn __iter__(slf: Py) -> Py { + slf + } + + fn collect(&self, py: Python) -> PyResult { + let runtime = get_runtime(py)?; + let stream = self.stream.clone(); + runtime.block_on(collect_stream(stream, self.return_arrow)) + } + + fn collect_async<'py>(&'py self, py: Python<'py>) -> PyResult> { + let stream = self.stream.clone(); + pyo3_async_runtimes::tokio::future_into_py(py, collect_stream(stream, self.return_arrow)) + } + + fn __anext__<'py>(&'py self, py: Python<'py>) -> PyResult> { + let stream = self.stream.clone(); + pyo3_async_runtimes::tokio::future_into_py( + py, + next_stream(stream, self.chunk_size, false, self.return_arrow), + ) + } + + fn __next__<'py>(&'py self, py: Python<'py>) -> PyResult { + let runtime = get_runtime(py)?; + let stream = self.stream.clone(); + runtime.block_on(next_stream( + stream, + self.chunk_size, + true, + self.return_arrow, + )) + } +} + +enum PyListIterResult { + Arrow(PyRecordBatchWrapper), + Native(Vec), +} + +impl IntoPy for PyListIterResult { + fn into_py(self, py: Python<'_>) -> PyObject { + match self { + Self::Arrow(x) => x.into_py(py), + Self::Native(x) => x.into_py(py), + } + } +} + +async fn next_stream( + stream: Arc>>>>, + chunk_size: usize, + sync: bool, + return_arrow: bool, +) -> PyResult { + let mut stream = stream.lock().await; + let mut metas: Vec = vec![]; + loop { + match stream.next().await { + Some(Ok(meta)) => { + metas.push(PyObjectMeta(meta)); + if metas.len() >= chunk_size { + match return_arrow { + true => { + return Ok(PyListIterResult::Arrow(object_meta_to_arrow(&metas))); + } + false => { + return Ok(PyListIterResult::Native(metas)); + } + } + } + } + Some(Err(e)) => return Err(PyObjectStoreError::from(e).into()), + None => { + if metas.is_empty() { + // Depending on whether the iteration is sync or not, we raise either a + // StopIteration or a StopAsyncIteration + if sync { + return Err(PyStopIteration::new_err("stream exhausted")); + } else { + return Err(PyStopAsyncIteration::new_err("stream exhausted")); + } + } else { + match return_arrow { + true => { + return Ok(PyListIterResult::Arrow(object_meta_to_arrow(&metas))); + } + false => { + return Ok(PyListIterResult::Native(metas)); + } + } + } + } + }; + } +} + +async fn collect_stream( + stream: Arc>>>>, + return_arrow: bool, +) -> PyResult { + let mut stream = stream.lock().await; + let mut metas: Vec = vec![]; + loop { + match stream.next().await { + Some(Ok(meta)) => { + metas.push(PyObjectMeta(meta)); + } + Some(Err(e)) => return Err(PyObjectStoreError::from(e).into()), + None => match return_arrow { + true => { + return Ok(PyListIterResult::Arrow(object_meta_to_arrow(&metas))); + } + false => { + return Ok(PyListIterResult::Native(metas)); + } + }, + }; + } +} + +struct PyRecordBatchWrapper(PyRecordBatch); + +impl PyRecordBatchWrapper { + fn new(batch: RecordBatch) -> Self { + Self(PyRecordBatch::new(batch)) + } +} + +impl IntoPy for PyRecordBatchWrapper { + fn into_py(self, py: Python<'_>) -> PyObject { + self.0.to_arro3(py).unwrap() + } +} + +/// Array capacities for each string array +struct ObjectMetaCapacity { + location: usize, + e_tag: usize, + version: usize, +} + +impl ObjectMetaCapacity { + fn new() -> Self { + Self { + location: 0, + e_tag: 0, + version: 0, + } + } +} + +impl AddAssign<&ObjectMeta> for ObjectMetaCapacity { + fn add_assign(&mut self, rhs: &ObjectMeta) { + self.location += rhs.location.as_ref().len(); + if let Some(e_tag) = rhs.e_tag.as_ref() { + self.e_tag += e_tag.len(); + } + if let Some(version) = rhs.version.as_ref() { + self.version += version.len(); + } + } +} + +fn object_meta_capacities(metas: &[PyObjectMeta]) -> ObjectMetaCapacity { + let mut capacity = ObjectMetaCapacity::new(); + for meta in metas { + capacity += &meta.0; + } + capacity +} + +fn object_meta_to_arrow(metas: &[PyObjectMeta]) -> PyRecordBatchWrapper { + let capacity = object_meta_capacities(metas); + + let mut location = StringBuilder::with_capacity(metas.len(), capacity.location); + let mut last_modified = TimestampMicrosecondBuilder::with_capacity(metas.len()); + let mut size = UInt64Builder::with_capacity(metas.len()); + let mut e_tag = StringBuilder::with_capacity(metas.len(), capacity.e_tag); + let mut version = StringBuilder::with_capacity(metas.len(), capacity.version); + + for meta in metas { + location.append_value(meta.as_ref().location.as_ref()); + last_modified.append_value(meta.as_ref().last_modified.timestamp_micros()); + size.append_value(meta.as_ref().size as _); + e_tag.append_option(meta.as_ref().e_tag.as_ref()); + version.append_option(meta.as_ref().version.as_ref()); + } + + let fields = vec![ + // Note, this uses "path" instead of "location" because we standardize the API to accept + // the keyword "path" everywhere. + Field::new("path", DataType::Utf8, false), + Field::new( + "last_modified", + DataType::Timestamp(TimeUnit::Microsecond, Some("UTC".into())), + false, + ), + Field::new("size", DataType::UInt64, false), + Field::new("e_tag", DataType::Utf8, true), + Field::new("version", DataType::Utf8, true), + ]; + let schema = Schema::new(fields); + + let columns: Vec = vec![ + Arc::new(location.finish()), + Arc::new(last_modified.finish().with_timezone("UTC")), + Arc::new(size.finish()), + Arc::new(e_tag.finish()), + Arc::new(version.finish()), + ]; + // This unwrap is ok because we know the RecordBatch is valid. + let batch = RecordBatch::try_new(schema.into(), columns).unwrap(); + PyRecordBatchWrapper::new(batch) +} + +pub(crate) struct PyListResult(ListResult); + +impl IntoPy for PyListResult { + fn into_py(self, py: Python<'_>) -> PyObject { + let mut dict = IndexMap::with_capacity(2); + dict.insert( + "common_prefixes", + self.0 + .common_prefixes + .into_iter() + .map(String::from) + .collect::>() + .into_py(py), + ); + dict.insert( + "objects", + self.0 + .objects + .into_iter() + .map(PyObjectMeta) + .collect::>() + .into_py(py), + ); + dict.into_py(py) + } +} + +#[pyfunction] +#[pyo3(signature = (store, prefix = None, *, offset = None, chunk_size = 50, return_arrow = false))] +pub(crate) fn list( + py: Python, + store: PyObjectStore, + prefix: Option, + offset: Option, + chunk_size: usize, + return_arrow: bool, +) -> PyObjectStoreResult { + if return_arrow { + // Ensure that arro3.core is installed if returning as arrow. + // The IntoPy impl is infallible, but `PyRecordBatch::to_arro3` can fail if arro3 is not + // installed. + let msg = concat!( + "arro3.core is a required dependency for returning results as arrow.\n", + "\nInstall with `pip install arro3-core`." + ); + py.import_bound(intern!(py, "arro3.core")) + .map_err(|err| PyImportError::new_err(format!("{}\n\n{}", msg, err)))?; + } + + let store = store.into_inner().clone(); + let prefix = prefix.map(|s| s.into()); + let stream = if let Some(offset) = offset { + store.list_with_offset(prefix.as_ref(), &offset.into()) + } else { + store.list(prefix.as_ref()) + }; + Ok(PyListStream::new(stream, chunk_size, return_arrow)) +} + +#[pyfunction] +#[pyo3(signature = (store, prefix = None))] +pub(crate) fn list_with_delimiter( + py: Python, + store: PyObjectStore, + prefix: Option, +) -> PyObjectStoreResult { + let runtime = get_runtime(py)?; + py.allow_threads(|| { + let out = runtime.block_on(list_with_delimiter_materialize( + store.into_inner(), + prefix.map(|s| s.into()).as_ref(), + ))?; + Ok::<_, PyObjectStoreError>(out) + }) +} + +#[pyfunction] +#[pyo3(signature = (store, prefix = None))] +pub(crate) fn list_with_delimiter_async( + py: Python, + store: PyObjectStore, + prefix: Option, +) -> PyResult> { + pyo3_async_runtimes::tokio::future_into_py(py, async move { + let out = + list_with_delimiter_materialize(store.into_inner(), prefix.map(|s| s.into()).as_ref()) + .await?; + Ok(out) + }) +} + +async fn list_with_delimiter_materialize( + store: Arc, + prefix: Option<&Path>, +) -> PyObjectStoreResult { + let list_result = store.list_with_delimiter(prefix).await?; + Ok(PyListResult(list_result)) +} diff --git a/object-store-rs/src/path.rs b/obstore/src/path.rs similarity index 100% rename from object-store-rs/src/path.rs rename to obstore/src/path.rs diff --git a/object-store-rs/src/put.rs b/obstore/src/put.rs similarity index 54% rename from object-store-rs/src/put.rs rename to obstore/src/put.rs index 776341c8..3b16e9a9 100644 --- a/object-store-rs/src/put.rs +++ b/obstore/src/put.rs @@ -1,3 +1,4 @@ +use std::collections::HashMap; use std::fs::File; use std::io::{BufReader, Cursor, Read, Seek, SeekFrom}; use std::path::PathBuf; @@ -5,14 +6,55 @@ use std::sync::Arc; use indexmap::IndexMap; use object_store::path::Path; -use object_store::{ObjectStore, PutPayload, PutResult, WriteMultipart}; +use object_store::{ + ObjectStore, PutMode, PutMultipartOpts, PutOptions, PutPayload, PutResult, UpdateVersion, + WriteMultipart, +}; +use pyo3::exceptions::PyValueError; use pyo3::prelude::*; -use pyo3::pybacked::PyBackedBytes; +use pyo3::pybacked::{PyBackedBytes, PyBackedStr}; use pyo3_file::PyFileLikeObject; use pyo3_object_store::error::PyObjectStoreResult; use pyo3_object_store::PyObjectStore; +use crate::attributes::PyAttributes; use crate::runtime::get_runtime; +use crate::tags::PyTagSet; + +pub(crate) struct PyPutMode(PutMode); + +impl<'py> FromPyObject<'py> for PyPutMode { + fn extract_bound(ob: &Bound<'py, PyAny>) -> PyResult { + if let Ok(s) = ob.extract::() { + let s = s.to_ascii_lowercase(); + match s.as_str() { + "create" => Ok(Self(PutMode::Create)), + "overwrite" => Ok(Self(PutMode::Overwrite)), + _ => Err(PyValueError::new_err(format!( + "Unexpected input for PutMode: {}", + s + ))), + } + } else { + let update_version = ob.extract::()?; + Ok(Self(PutMode::Update(update_version.0))) + } + } +} + +pub(crate) struct PyUpdateVersion(UpdateVersion); + +impl<'py> FromPyObject<'py> for PyUpdateVersion { + fn extract_bound(ob: &Bound<'py, PyAny>) -> PyResult { + // Update to use derive(FromPyObject) when default is implemented: + // https://github.com/PyO3/pyo3/issues/4643 + let dict = ob.extract::>>()?; + Ok(Self(UpdateVersion { + e_tag: dict.get("e_tag").map(|x| x.extract()).transpose()?, + version: dict.get("version").map(|x| x.extract()).transpose()?, + })) + } +} /// Input types supported by multipart upload #[derive(Debug)] @@ -88,21 +130,33 @@ impl IntoPy for PyPutResult { } #[pyfunction] -#[pyo3(signature = (store, path, file, *, use_multipart = None, chunk_size = 5242880, max_concurrency = 12))] +#[pyo3(signature = (store, path, file, *, attributes = None, tags = None, mode = None, use_multipart = None, chunk_size = 5242880, max_concurrency = 12))] +#[allow(clippy::too_many_arguments)] pub(crate) fn put( py: Python, store: PyObjectStore, path: String, mut file: MultipartPutInput, + attributes: Option, + tags: Option, + mode: Option, use_multipart: Option, chunk_size: usize, max_concurrency: usize, ) -> PyObjectStoreResult { - let use_multipart = if let Some(use_multipart) = use_multipart { + let mut use_multipart = if let Some(use_multipart) = use_multipart { use_multipart } else { file.use_multipart(chunk_size)? }; + + // If mode is provided and not Overwrite, force a non-multipart put + if let Some(mode) = &mode { + if !matches!(mode.0, PutMode::Overwrite) { + use_multipart = false; + } + } + let runtime = get_runtime(py)?; if use_multipart { runtime.block_on(put_multipart_inner( @@ -111,28 +165,49 @@ pub(crate) fn put( file, chunk_size, max_concurrency, + attributes, + tags, )) } else { - runtime.block_on(put_inner(store.into_inner(), &path.into(), file)) + runtime.block_on(put_inner( + store.into_inner(), + &path.into(), + file, + attributes, + tags, + mode, + )) } } #[pyfunction] -#[pyo3(signature = (store, path, file, *, use_multipart = None, chunk_size = 5242880, max_concurrency = 12))] +#[pyo3(signature = (store, path, file, *, attributes = None, tags = None, mode = None, use_multipart = None, chunk_size = 5242880, max_concurrency = 12))] +#[allow(clippy::too_many_arguments)] pub(crate) fn put_async( py: Python, store: PyObjectStore, path: String, mut file: MultipartPutInput, + attributes: Option, + tags: Option, + mode: Option, use_multipart: Option, chunk_size: usize, max_concurrency: usize, ) -> PyResult> { - let use_multipart = if let Some(use_multipart) = use_multipart { + let mut use_multipart = if let Some(use_multipart) = use_multipart { use_multipart } else { file.use_multipart(chunk_size)? }; + + // If mode is provided and not Overwrite, force a non-multipart put + if let Some(mode) = &mode { + if !matches!(mode.0, PutMode::Overwrite) { + use_multipart = false; + } + } + pyo3_async_runtimes::tokio::future_into_py(py, async move { let result = if use_multipart { put_multipart_inner( @@ -141,10 +216,20 @@ pub(crate) fn put_async( file, chunk_size, max_concurrency, + attributes, + tags, ) .await? } else { - put_inner(store.into_inner(), &path.into(), file).await? + put_inner( + store.into_inner(), + &path.into(), + file, + attributes, + tags, + mode, + ) + .await? }; Ok(result) }) @@ -154,12 +239,27 @@ async fn put_inner( store: Arc, path: &Path, mut reader: MultipartPutInput, + attributes: Option, + tags: Option, + mode: Option, ) -> PyObjectStoreResult { + let mut opts = PutOptions::default(); + + if let Some(attributes) = attributes { + opts.attributes = attributes.into_inner(); + } + if let Some(tags) = tags { + opts.tags = tags.into_inner(); + } + if let Some(mode) = mode { + opts.mode = mode.0; + } + let nbytes = reader.nbytes()?; let mut buffer = Vec::with_capacity(nbytes); reader.read_to_end(&mut buffer)?; let payload = PutPayload::from_bytes(buffer.into()); - Ok(PyPutResult(store.put(path, payload).await?)) + Ok(PyPutResult(store.put_opts(path, payload, opts).await?)) } async fn put_multipart_inner( @@ -168,8 +268,19 @@ async fn put_multipart_inner( mut reader: R, chunk_size: usize, max_concurrency: usize, + attributes: Option, + tags: Option, ) -> PyObjectStoreResult { - let upload = store.put_multipart(path).await?; + let mut opts = PutMultipartOpts::default(); + + if let Some(attributes) = attributes { + opts.attributes = attributes.into_inner(); + } + if let Some(tags) = tags { + opts.tags = tags.into_inner(); + } + + let upload = store.put_multipart_opts(path, opts).await?; let mut write = WriteMultipart::new(upload); let mut scratch_buffer = vec![0; chunk_size]; loop { diff --git a/object-store-rs/src/rename.rs b/obstore/src/rename.rs similarity index 100% rename from object-store-rs/src/rename.rs rename to obstore/src/rename.rs diff --git a/object-store-rs/src/runtime.rs b/obstore/src/runtime.rs similarity index 100% rename from object-store-rs/src/runtime.rs rename to obstore/src/runtime.rs diff --git a/object-store-rs/src/signer.rs b/obstore/src/signer.rs similarity index 100% rename from object-store-rs/src/signer.rs rename to obstore/src/signer.rs diff --git a/obstore/src/tags.rs b/obstore/src/tags.rs new file mode 100644 index 00000000..b90f1c8c --- /dev/null +++ b/obstore/src/tags.rs @@ -0,0 +1,24 @@ +use std::collections::HashMap; + +use object_store::TagSet; +use pyo3::prelude::*; +use pyo3::pybacked::PyBackedStr; + +pub(crate) struct PyTagSet(TagSet); + +impl PyTagSet { + pub fn into_inner(self) -> TagSet { + self.0 + } +} + +impl<'py> FromPyObject<'py> for PyTagSet { + fn extract_bound(ob: &Bound<'py, PyAny>) -> PyResult { + let input = ob.extract::>()?; + let mut tag_set = TagSet::default(); + for (key, value) in input.into_iter() { + tag_set.push(&key, &value); + } + Ok(Self(tag_set)) + } +} diff --git a/pyo3-object_store/src/api.rs b/pyo3-object_store/src/api.rs index b831e23c..6b9a6956 100644 --- a/pyo3-object_store/src/api.rs +++ b/pyo3-object_store/src/api.rs @@ -1,6 +1,7 @@ use pyo3::intern; use pyo3::prelude::*; +use crate::error::*; use crate::{PyAzureStore, PyGCSStore, PyHttpStore, PyLocalStore, PyMemoryStore, PyS3Store}; /// Export the default Python API as a submodule named `store` within the given parent module @@ -33,3 +34,58 @@ pub fn register_store_module( Ok(()) } + +/// Export exceptions as a submodule named `exceptions` within the given parent module +// https://github.com/PyO3/pyo3/issues/1517#issuecomment-808664021 +// https://github.com/PyO3/pyo3/issues/759#issuecomment-977835119 +pub fn register_exceptions_module( + py: Python<'_>, + parent_module: &Bound<'_, PyModule>, + parent_module_str: &str, +) -> PyResult<()> { + let full_module_string = format!("{}.exceptions", parent_module_str); + + let child_module = PyModule::new_bound(parent_module.py(), "exceptions")?; + + child_module.add("ObstoreError", py.get_type_bound::())?; + child_module.add("GenericError", py.get_type_bound::())?; + child_module.add("NotFoundError", py.get_type_bound::())?; + child_module.add("InvalidPathError", py.get_type_bound::())?; + child_module.add("JoinError", py.get_type_bound::())?; + child_module.add( + "NotSupportedError", + py.get_type_bound::(), + )?; + child_module.add( + "AlreadyExistsError", + py.get_type_bound::(), + )?; + child_module.add( + "PreconditionError", + py.get_type_bound::(), + )?; + child_module.add("NotModifiedError", py.get_type_bound::())?; + child_module.add( + "PermissionDeniedError", + py.get_type_bound::(), + )?; + child_module.add( + "UnauthenticatedError", + py.get_type_bound::(), + )?; + child_module.add( + "UnknownConfigurationKeyError", + py.get_type_bound::(), + )?; + + parent_module.add_submodule(&child_module)?; + + py.import_bound(intern!(py, "sys"))? + .getattr(intern!(py, "modules"))? + .set_item(full_module_string.as_str(), child_module.to_object(py))?; + + // needs to be set *after* `add_submodule()` + child_module.setattr("__name__", full_module_string)?; + + Ok(()) +} diff --git a/pyo3-object_store/src/aws.rs b/pyo3-object_store/src/aws.rs index c93c273e..20d930ea 100644 --- a/pyo3-object_store/src/aws.rs +++ b/pyo3-object_store/src/aws.rs @@ -71,7 +71,7 @@ impl PyS3Store { ) -> PyObjectStoreResult { // boto3.Session has a region_name attribute, but botocore.session.Session does not. let region = if let Ok(region) = session.getattr(intern!(py, "region_name")) { - Some(region.extract::()?) + region.extract::>()? } else { None }; @@ -140,6 +140,11 @@ impl PyS3Store { } Ok(Self(Arc::new(builder.build()?))) } + + fn __repr__(&self) -> String { + let repr = self.0.to_string(); + repr.replacen("AmazonS3", "S3Store", 1) + } } #[derive(Debug, PartialEq, Eq, Hash)] diff --git a/pyo3-object_store/src/azure.rs b/pyo3-object_store/src/azure.rs index e9750e70..aa84ddad 100644 --- a/pyo3-object_store/src/azure.rs +++ b/pyo3-object_store/src/azure.rs @@ -78,6 +78,11 @@ impl PyAzureStore { } Ok(Self(Arc::new(builder.build()?))) } + + fn __repr__(&self) -> String { + let repr = self.0.to_string(); + repr.replacen("MicrosoftAzure", "AzureStore", 1) + } } #[derive(Debug, PartialEq, Eq, Hash)] diff --git a/pyo3-object_store/src/error.rs b/pyo3-object_store/src/error.rs index 378ce9e5..8c1c554b 100644 --- a/pyo3-object_store/src/error.rs +++ b/pyo3-object_store/src/error.rs @@ -1,13 +1,37 @@ //! Contains the [`PyObjectStoreError`], the Error returned by most fallible functions in this //! crate. -use pyo3::exceptions::{ - PyException, PyFileNotFoundError, PyIOError, PyNotImplementedError, PyValueError, -}; +#![allow(missing_docs)] + +use pyo3::exceptions::{PyFileNotFoundError, PyIOError, PyNotImplementedError, PyValueError}; use pyo3::prelude::*; -use pyo3::DowncastError; +use pyo3::{create_exception, DowncastError}; use thiserror::Error; +// Base exception +create_exception!( + pyo3_object_store, + ObstoreError, + pyo3::exceptions::PyException +); + +// Subclasses from base exception +create_exception!(pyo3_object_store, GenericError, ObstoreError); +create_exception!(pyo3_object_store, NotFoundError, ObstoreError); +create_exception!(pyo3_object_store, InvalidPathError, ObstoreError); +create_exception!(pyo3_object_store, JoinError, ObstoreError); +create_exception!(pyo3_object_store, NotSupportedError, ObstoreError); +create_exception!(pyo3_object_store, AlreadyExistsError, ObstoreError); +create_exception!(pyo3_object_store, PreconditionError, ObstoreError); +create_exception!(pyo3_object_store, NotModifiedError, ObstoreError); +create_exception!(pyo3_object_store, PermissionDeniedError, ObstoreError); +create_exception!(pyo3_object_store, UnauthenticatedError, ObstoreError); +create_exception!( + pyo3_object_store, + UnknownConfigurationKeyError, + ObstoreError +); + /// The Error variants returned by this crate. #[derive(Error, Debug)] #[non_exhaustive] @@ -30,13 +54,42 @@ impl From for PyErr { match error { PyObjectStoreError::PyErr(err) => err, PyObjectStoreError::ObjectStoreError(ref err) => match err { + object_store::Error::Generic { + store: _, + source: _, + } => GenericError::new_err(err.to_string()), object_store::Error::NotFound { path: _, source: _ } => { PyFileNotFoundError::new_err(err.to_string()) } + object_store::Error::InvalidPath { source: _ } => { + InvalidPathError::new_err(err.to_string()) + } + object_store::Error::JoinError { source: _ } => JoinError::new_err(err.to_string()), + object_store::Error::NotSupported { source: _ } => { + NotSupportedError::new_err(err.to_string()) + } + object_store::Error::AlreadyExists { path: _, source: _ } => { + AlreadyExistsError::new_err(err.to_string()) + } + object_store::Error::Precondition { path: _, source: _ } => { + PreconditionError::new_err(err.to_string()) + } + object_store::Error::NotModified { path: _, source: _ } => { + NotModifiedError::new_err(err.to_string()) + } object_store::Error::NotImplemented => { PyNotImplementedError::new_err(err.to_string()) } - _ => PyException::new_err(err.to_string()), + object_store::Error::PermissionDenied { path: _, source: _ } => { + PermissionDeniedError::new_err(err.to_string()) + } + object_store::Error::Unauthenticated { path: _, source: _ } => { + UnauthenticatedError::new_err(err.to_string()) + } + object_store::Error::UnknownConfigurationKey { store: _, key: _ } => { + UnknownConfigurationKeyError::new_err(err.to_string()) + } + _ => GenericError::new_err(err.to_string()), }, PyObjectStoreError::IOError(err) => PyIOError::new_err(err.to_string()), } diff --git a/pyo3-object_store/src/gcp.rs b/pyo3-object_store/src/gcp.rs index 5f6b783d..0ed2499b 100644 --- a/pyo3-object_store/src/gcp.rs +++ b/pyo3-object_store/src/gcp.rs @@ -78,6 +78,11 @@ impl PyGCSStore { } Ok(Self(Arc::new(builder.build()?))) } + + fn __repr__(&self) -> String { + let repr = self.0.to_string(); + repr.replacen("GoogleCloudStorage", "GCSStore", 1) + } } #[derive(Debug, PartialEq, Eq, Hash)] diff --git a/pyo3-object_store/src/http.rs b/pyo3-object_store/src/http.rs index 086f35fd..b43f7f59 100644 --- a/pyo3-object_store/src/http.rs +++ b/pyo3-object_store/src/http.rs @@ -23,6 +23,10 @@ impl PyHttpStore { pub fn into_inner(self) -> Arc { self.0 } + + fn __repr__(&self) -> String { + self.0.to_string() + } } #[pymethods] diff --git a/pyo3-object_store/src/lib.rs b/pyo3-object_store/src/lib.rs index a52cbe23..f81bdc40 100644 --- a/pyo3-object_store/src/lib.rs +++ b/pyo3-object_store/src/lib.rs @@ -13,7 +13,7 @@ mod memory; mod retry; mod store; -pub use api::register_store_module; +pub use api::{register_exceptions_module, register_store_module}; pub use aws::PyS3Store; pub use azure::PyAzureStore; pub use client::{PyClientConfigKey, PyClientOptions}; diff --git a/pyo3-object_store/src/local.rs b/pyo3-object_store/src/local.rs index fd5d4693..6b1cc2aa 100644 --- a/pyo3-object_store/src/local.rs +++ b/pyo3-object_store/src/local.rs @@ -34,4 +34,9 @@ impl PyLocalStore { }; Ok(Self(Arc::new(fs))) } + + fn __repr__(&self) -> String { + let repr = self.0.to_string(); + repr.replacen("LocalFileSystem", "LocalStore", 1) + } } diff --git a/pyo3-object_store/src/memory.rs b/pyo3-object_store/src/memory.rs index f697ea1b..e2b0aa5b 100644 --- a/pyo3-object_store/src/memory.rs +++ b/pyo3-object_store/src/memory.rs @@ -1,7 +1,9 @@ use std::sync::Arc; use object_store::memory::InMemory; +use pyo3::intern; use pyo3::prelude::*; +use pyo3::types::PyString; /// A Python-facing wrapper around an [`InMemory`]. #[pyclass(name = "MemoryStore")] @@ -13,11 +15,15 @@ impl AsRef> for PyMemoryStore { } } -impl PyMemoryStore { +impl<'py> PyMemoryStore { /// Consume self and return the underlying [`InMemory`]. pub fn into_inner(self) -> Arc { self.0 } + + fn __repr__(&'py self, py: Python<'py>) -> &Bound<'py, PyString> { + intern!(py, "MemoryStore") + } } #[pymethods] diff --git a/pyproject.toml b/pyproject.toml index 4b7e3eba..88237cd2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,6 +8,7 @@ dependencies = [] [tool.uv] dev-dependencies = [ + "arro3-core>=0.4.2", "black>=24.10.0", "boto3>=1.35.38", "fsspec>=2024.10.0", @@ -18,6 +19,7 @@ dev-dependencies = [ "mkdocs-material[imaging]>=9.5.40", "mkdocs>=1.6.1", "mkdocstrings[python]>=0.26.1", + "moto[s3,server]>=5.0.18", "pandas>=2.2.3", "pip>=24.2", "pyarrow>=17.0.0", @@ -41,3 +43,7 @@ select = [ "F401", # Allow unused imports in __init__.py files "F403", # unable to detect undefined names ] + +[tool.pytest.ini_options] +addopts = "-v" +testpaths = ["tests"] diff --git a/tests/store/__init__.py b/tests/store/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/store/test_local.py b/tests/store/test_local.py new file mode 100644 index 00000000..9fea8790 --- /dev/null +++ b/tests/store/test_local.py @@ -0,0 +1,17 @@ +from pathlib import Path + +import obstore as obs +from obstore.store import LocalStore + + +def test_local_store(): + here = Path(".") + store = LocalStore(here) + list_result = obs.list(store).collect() + assert any("test_local.py" in x["path"] for x in list_result) + + +def test_repr(): + here = Path(".") + store = LocalStore(here) + assert repr(store).startswith("LocalStore") diff --git a/tests/store/test_s3.py b/tests/store/test_s3.py new file mode 100644 index 00000000..aa2eb6f7 --- /dev/null +++ b/tests/store/test_s3.py @@ -0,0 +1,17 @@ +import pytest + +import obstore as obs +from obstore.store import S3Store + + +@pytest.mark.asyncio +async def test_list_async(s3_store: S3Store): + list_result = await obs.list(s3_store).collect_async() + assert any("afile" in x["path"] for x in list_result) + + +@pytest.mark.asyncio +async def test_get_async(s3_store: S3Store): + resp = await obs.get_async(s3_store, "afile") + buf = await resp.bytes_async() + assert buf == b"hello world" diff --git a/tests/test_delete.py b/tests/test_delete.py index f16ce1f1..d0ba0813 100644 --- a/tests/test_delete.py +++ b/tests/test_delete.py @@ -1,8 +1,9 @@ from tempfile import TemporaryDirectory -import object_store_rs as obs import pytest -from object_store_rs.store import LocalStore, MemoryStore + +import obstore as obs +from obstore.store import LocalStore, MemoryStore def test_delete_one(): @@ -12,11 +13,11 @@ def test_delete_one(): obs.put(store, "file2.txt", b"bar") obs.put(store, "file3.txt", b"baz") - assert len(obs.list(store)) == 3 + assert len(obs.list(store).collect()) == 3 obs.delete(store, "file1.txt") obs.delete(store, "file2.txt") obs.delete(store, "file3.txt") - assert len(obs.list(store)) == 0 + assert len(obs.list(store).collect()) == 0 def test_delete_many(): @@ -26,12 +27,12 @@ def test_delete_many(): obs.put(store, "file2.txt", b"bar") obs.put(store, "file3.txt", b"baz") - assert len(obs.list(store)) == 3 + assert len(obs.list(store).collect()) == 3 obs.delete( store, ["file1.txt", "file2.txt", "file3.txt"], ) - assert len(obs.list(store)) == 0 + assert len(obs.list(store).collect()) == 0 # Local filesystem errors if the file does not exist. @@ -43,13 +44,13 @@ def test_delete_one_local_fs(): obs.put(store, "file2.txt", b"bar") obs.put(store, "file3.txt", b"baz") - assert len(obs.list(store)) == 3 + assert len(obs.list(store).collect()) == 3 obs.delete(store, "file1.txt") obs.delete(store, "file2.txt") obs.delete(store, "file3.txt") - assert len(obs.list(store)) == 0 + assert len(obs.list(store).collect()) == 0 - with pytest.raises(Exception, match="No such file"): + with pytest.raises(FileNotFoundError): obs.delete(store, "file1.txt") @@ -61,13 +62,13 @@ def test_delete_many_local_fs(): obs.put(store, "file2.txt", b"bar") obs.put(store, "file3.txt", b"baz") - assert len(obs.list(store)) == 3 + assert len(obs.list(store).collect()) == 3 obs.delete( store, ["file1.txt", "file2.txt", "file3.txt"], ) - with pytest.raises(Exception, match="No such file"): + with pytest.raises(FileNotFoundError): obs.delete( store, ["file1.txt", "file2.txt", "file3.txt"], diff --git a/tests/test_fsspec.py b/tests/test_fsspec.py index a6e3ce5d..982c8ec0 100644 --- a/tests/test_fsspec.py +++ b/tests/test_fsspec.py @@ -1,11 +1,30 @@ -import boto3 -import object_store_rs as obs +import pytest + +pytest.importorskip("moto") import pyarrow.parquet as pq -from object_store_rs.fsspec import AsyncFsspecStore -# session = boto3.Session() +import obstore as obs +from obstore.fsspec import AsyncFsspecStore + + +@pytest.fixture() +def fs(s3_store): + return AsyncFsspecStore(s3_store) + + +def test_list(fs): + out = fs.ls("", detail=False) + assert out == ["afile"] + fs.pipe_file("dir/bfile", b"data") + out = fs.ls("", detail=False) + assert out == ["afile", "dir"] + out = fs.ls("", detail=True) + assert out[0]["type"] == "file" + assert out[1]["type"] == "directory" + -store = obs.store.HTTPStore.from_url("https://github.com") -fs = AsyncFsspecStore(store) -url = "opengeospatial/geoparquet/raw/refs/heads/main/examples/example.parquet" -test = pq.read_metadata(url, filesystem=fs) +def test_remote_parquet(): + store = obs.store.HTTPStore.from_url("https://github.com") + fs = AsyncFsspecStore(store) + url = "opengeospatial/geoparquet/raw/refs/heads/main/examples/example.parquet" + pq.read_metadata(url, filesystem=fs) diff --git a/tests/test_get.py b/tests/test_get.py index fcc7e091..d1d08b9c 100644 --- a/tests/test_get.py +++ b/tests/test_get.py @@ -1,6 +1,7 @@ -import object_store_rs as obs import pytest -from object_store_rs.store import MemoryStore + +import obstore as obs +from obstore.store import MemoryStore def test_stream_sync(): @@ -44,3 +45,45 @@ async def test_stream_async(): pos += size assert pos == len(data) + + +@pytest.mark.skip("Skip until we restore range in get_options") +def test_get_with_options(): + store = MemoryStore() + + data = b"the quick brown fox jumps over the lazy dog," * 100 + path = "big-data.txt" + + obs.put(store, path, data) + + result = obs.get(store, path, options={"range": (5, 10)}) + assert result.range == (5, 10) + buf = result.bytes() + assert buf == data[5:10] + + +def test_get_range(): + store = MemoryStore() + + data = b"the quick brown fox jumps over the lazy dog," * 100 + path = "big-data.txt" + + obs.put(store, path, data) + buffer = obs.get_range(store, path, 5, 10) + view = memoryview(buffer) + assert view == data[5:15] + + +def test_get_ranges(): + store = MemoryStore() + + data = b"the quick brown fox jumps over the lazy dog," * 100 + path = "big-data.txt" + + obs.put(store, path, data) + offsets = [5, 10, 15, 20] + lengths = [10, 10, 10, 10] + buffers = obs.get_ranges(store, path, offsets, lengths) + + for offset, length, buffer in zip(offsets, lengths, buffers): + assert memoryview(buffer) == data[offset : offset + length] diff --git a/tests/test_list.py b/tests/test_list.py index 37686eac..8e6b1dfb 100644 --- a/tests/test_list.py +++ b/tests/test_list.py @@ -1,15 +1,60 @@ -import object_store_rs as obs -from object_store_rs.store import MemoryStore +import pytest +from arro3.core import RecordBatch +import obstore as obs +from obstore.store import MemoryStore -def test_list_max_items(): + +def test_list(): store = MemoryStore() obs.put(store, "file1.txt", b"foo") obs.put(store, "file2.txt", b"bar") obs.put(store, "file3.txt", b"baz") - assert len(obs.list(store)) == 3 - assert len(obs.list(store, max_items=2)) == 2 - assert len(obs.list(store, max_items=1)) == 1 - assert len(obs.list(store, max_items=0)) == 1 + stream = obs.list(store) + result = stream.collect() + assert len(result) == 3 + + +def test_list_as_arrow(): + store = MemoryStore() + + for i in range(100): + obs.put(store, f"file{i}.txt", b"foo") + + stream = obs.list(store, return_arrow=True, chunk_size=10) + yielded_batches = 0 + for batch in stream: + assert isinstance(batch, RecordBatch) + yielded_batches += 1 + assert batch.num_rows == 10 + + assert yielded_batches == 10 + + stream = obs.list(store, return_arrow=True, chunk_size=10) + batch = stream.collect() + assert isinstance(batch, RecordBatch) + assert batch.num_rows == 100 + + +@pytest.mark.asyncio +async def test_list_stream_async(): + store = MemoryStore() + + for i in range(100): + await obs.put_async(store, f"file{i}.txt", b"foo") + + stream = obs.list(store, return_arrow=True, chunk_size=10) + yielded_batches = 0 + async for batch in stream: + assert isinstance(batch, RecordBatch) + yielded_batches += 1 + assert batch.num_rows == 10 + + assert yielded_batches == 10 + + stream = obs.list(store, return_arrow=True, chunk_size=10) + batch = await stream.collect_async() + assert isinstance(batch, RecordBatch) + assert batch.num_rows == 100 diff --git a/tests/test_put.py b/tests/test_put.py index 415280e6..6576941c 100644 --- a/tests/test_put.py +++ b/tests/test_put.py @@ -1,5 +1,8 @@ -import object_store_rs as obs -from object_store_rs.store import MemoryStore +import pytest + +import obstore as obs +from obstore.exceptions import AlreadyExistsError +from obstore.store import MemoryStore def test_put_non_multipart(): @@ -24,3 +27,15 @@ def test_put_multipart_large(): obs.put(store, path, data, use_multipart=True) assert obs.get(store, path).bytes() == data + + +def test_put_mode(): + store = MemoryStore() + + obs.put(store, "file1.txt", b"foo") + obs.put(store, "file1.txt", b"bar", mode="overwrite") + + with pytest.raises(AlreadyExistsError): + obs.put(store, "file1.txt", b"foo", mode="create") + + assert obs.get(store, "file1.txt").bytes() == b"bar" diff --git a/uv.lock b/uv.lock index 2316bb8f..58e975dd 100644 --- a/uv.lock +++ b/uv.lock @@ -2,7 +2,26 @@ version = 1 requires-python = ">=3.11" resolution-markers = [ "python_full_version < '3.12'", - "python_full_version >= '3.12'", + "python_full_version == '3.12.*'", + "python_full_version >= '3.13'", +] + +[[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 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643 }, +] + +[[package]] +name = "antlr4-python3-runtime" +version = "4.13.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/33/5f/2cdf6f7aca3b20d3f316e9f505292e1f256a32089bd702034c29ebde6242/antlr4_python3_runtime-4.13.2.tar.gz", hash = "sha256:909b647e1d2fc2b70180ac586df3933e38919c85f98ccc656a96cd3f25ef3916", size = 117467 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/89/03/a851e84fcbb85214dc637b6378121ef9a0dd61b4c65264675d8a5c9b1ae7/antlr4_python3_runtime-4.13.2-py3-none-any.whl", hash = "sha256:fe3835eb8d33daece0e799090eda89719dbccee7aa39ef94eed3818cafa5a7e8", size = 144462 }, ] [[package]] @@ -14,6 +33,35 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/81/29/5ecc3a15d5a33e31b26c11426c45c501e439cb865d0bff96315d86443b78/appnope-0.1.4-py2.py3-none-any.whl", hash = "sha256:502575ee11cd7a28c0205f379b525beefebab9d161b7c964670864014ed7213c", size = 4321 }, ] +[[package]] +name = "arro3-core" +version = "0.4.2" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ed/a0/e0f302bc8d1f71bdf1da35f58ceea87ce28f98c3c138e1a68fb9ca936db7/arro3_core-0.4.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:7132b800fb00f3b515e0c4ee0be4347d68ba0c83248f3cb12532811a074347b7", size = 2339721 }, + { url = "https://files.pythonhosted.org/packages/86/28/3cdbf0d84e3130ece43844c4d1ddd8c02913dfad48c1eaded684173a357b/arro3_core-0.4.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1dc027766e5ae91a8659113a32cc70a4a0146602d644d50254383a87e1b8d020", size = 2108762 }, + { url = "https://files.pythonhosted.org/packages/96/5f/408d99139bc1a1d445348876501e2a90df8c0c0d311cf13127e8b74f8dbf/arro3_core-0.4.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:75bfacb4b0b62e92227bbf0b78f7a9e971382ca99eb8cc9ec13b01d3d169ad74", size = 2378271 }, + { url = "https://files.pythonhosted.org/packages/db/87/e2e7ec3ed0bb8576da5d50d4bcebcc6277aa0f38bc2daeef09f9b3ac2415/arro3_core-0.4.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0f6a80edb578a82c154254e6a1ae022aef8040d65e746515f98ec7a3c9977a3b", size = 2686987 }, + { url = "https://files.pythonhosted.org/packages/93/5c/a13ef6f2698e541965af600edf1863772de6e8936d565cda754d40a74e4a/arro3_core-0.4.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:84874cb24aaea76845b6bdd633fc24cf2cb12fa25c6f65cbf5c1b3cda78671cd", size = 2468849 }, + { url = "https://files.pythonhosted.org/packages/e4/49/4784ccdcd9d5fb70a9bceaee3b3c1783c46e071c9155c675c65745381c77/arro3_core-0.4.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a710ea4003749272c0f98f34986686aed831de05fff03bab836dbd2c473bf652", size = 3430903 }, + { url = "https://files.pythonhosted.org/packages/80/2c/66016380d11e39e8f1f84f1390178af3b534a6fb71d1a896843f9c9b775d/arro3_core-0.4.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fbd621ccce42b30a5ab62f9587a9d11577bc3ad11fbe26af6756a44fb0beb122", size = 2466430 }, + { url = "https://files.pythonhosted.org/packages/1a/50/e5d8ff011ac5900bfff621356e726f3a7efbac19b9a92a2d75643123fc53/arro3_core-0.4.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e1279a554bd04fa4b6f78af74cac181784796a8b22e26ee81d75d7a58d7b66e6", size = 2625081 }, + { url = "https://files.pythonhosted.org/packages/4b/f6/bc71e236a516ad247c87d0e34dd69657251ea0f5664d6b2228ad64663e0a/arro3_core-0.4.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c2c34c727e4f6bdbe03734a10d63b58f58a6fd3b1a31383d1a4ee6ab7dbf7eac", size = 2523557 }, + { url = "https://files.pythonhosted.org/packages/5c/ad/636640e9d6c1b2dc61decc85b0a2d859dfa09333c9840e5153f9046d66c9/arro3_core-0.4.2-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:ff9173d2a8449774856dc490717ff049b763f19dbf6a8ceb6dda1e3993cd41a1", size = 2966756 }, + { url = "https://files.pythonhosted.org/packages/43/ca/be595d0b8ba2a1fdaa65203549ac77a094d80b6103861b5a963339a6c52a/arro3_core-0.4.2-cp311-none-win_amd64.whl", hash = "sha256:7dbab08ab5a11b53215761044ed5d488a9ed4565666af556829b1b90c668082b", size = 2350694 }, + { url = "https://files.pythonhosted.org/packages/f7/23/b456564a8eb2e3d50cc98e9c2e32c8709824ad70a6eb5eac9b828c7ebaaf/arro3_core-0.4.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:4818c4aaee1f7a82015ce12f25438c1c5fcfee8dfb7101c2c7111400d1d63d65", size = 2339593 }, + { url = "https://files.pythonhosted.org/packages/64/db/12669fde7619198e038b8f96885c895be0e80d3e5c758fbb6f644eef5ee7/arro3_core-0.4.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:dd8afe0d87afc971543885307ad15595e4f4bf10cf07826289c71feca99c52ee", size = 2110199 }, + { url = "https://files.pythonhosted.org/packages/d2/40/04cdec23879ff91758b77d30209cad99330ca71a9d72245e144a1e8b3caf/arro3_core-0.4.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:19bdef051ec52dde34f529f840f4432ce3fe6473dafd1300ed14a3cc2c84750e", size = 2379144 }, + { url = "https://files.pythonhosted.org/packages/ed/29/753df145b86984ad4521636f4f0694abda264cd40730939d0516646ea058/arro3_core-0.4.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:17975f730e844655eb0e897abd60b5d796716fa1fe4ded586a66bc7b73a037c6", size = 2693330 }, + { url = "https://files.pythonhosted.org/packages/e5/71/47afa935bc7292c90762cd6e1c99f96238a79dbe49cf3bcf6ea4543b747c/arro3_core-0.4.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1a922fd0e592ea0aeca26eb8b3fee523d70028281d20d069a753a9cda48de85a", size = 2470944 }, + { url = "https://files.pythonhosted.org/packages/57/4a/f8f244337375fb8fca7413f7b31bd9895c53a8c27f131bf6fd4d238758ea/arro3_core-0.4.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ba82e64814e95b43059f19ba29f000e6faeedb95006f83e4b5bda3e24a7d4e02", size = 3432229 }, + { url = "https://files.pythonhosted.org/packages/ee/ea/607a57268e86069cb29314d9c4873a1e962b7e2f5597a72cc30e649a45b4/arro3_core-0.4.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:207b37364b0cb3b57b6aee0662813b609943d3b8950ec7746a8f424a856d471b", size = 2469231 }, + { url = "https://files.pythonhosted.org/packages/c1/f6/52b4497dbfd6691d1db25f0d9508e87222826c58e944b7c25ea0d61ba4e5/arro3_core-0.4.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ba5499d4f314458ed1c7a6eca56823a0f03ce387f22c71c5f1fe1ceff2acadd6", size = 2630985 }, + { url = "https://files.pythonhosted.org/packages/2c/41/b0375557090b3f5a6bddf575de0690b8a4e2e2cc24d1a62251c5a5b819e8/arro3_core-0.4.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:55d7975169d676e64dcc5a69339ab6e2043cef654957b9121380abb9160cf8b3", size = 2518942 }, + { url = "https://files.pythonhosted.org/packages/5d/43/0ac2a65199491f1d6c778a73a0e84dee63d71c2b57281ea85b4a68ccaf52/arro3_core-0.4.2-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:3c84d0249d1558c21e4f0cef0794ac33e222d495640c315a8b75756764403bbe", size = 2958466 }, + { url = "https://files.pythonhosted.org/packages/d5/f2/ad051d6c6fc204fdd98ab3d44dc34104d5096bb98b4ac6f1648855ea7baf/arro3_core-0.4.2-cp312-none-win_amd64.whl", hash = "sha256:9318084172d378b54ddbfe8048d59ea2918ad5ebf5b1bc0ad3c97d6e241264a2", size = 2356892 }, +] + [[package]] name = "asttokens" version = "2.4.1" @@ -26,6 +74,43 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/45/86/4736ac618d82a20d87d2f92ae19441ebc7ac9e7a581d7e58bbe79233b24a/asttokens-2.4.1-py2.py3-none-any.whl", hash = "sha256:051ed49c3dcae8913ea7cd08e46a606dba30b79993209636c4875bc1d637bc24", size = 27764 }, ] +[[package]] +name = "attrs" +version = "24.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fc/0f/aafca9af9315aee06a89ffde799a10a582fe8de76c563ee80bbcdc08b3fb/attrs-24.2.0.tar.gz", hash = "sha256:5cfb1b9148b5b086569baec03f20d7b6bf3bcacc9a42bebf87ffaaca362f6346", size = 792678 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6a/21/5b6702a7f963e95456c0de2d495f67bf5fd62840ac655dc451586d23d39a/attrs-24.2.0-py3-none-any.whl", hash = "sha256:81921eb96de3191c8258c199618104dd27ac608d9366f5e35d011eae1867ede2", size = 63001 }, +] + +[[package]] +name = "aws-sam-translator" +version = "1.91.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "boto3" }, + { name = "jsonschema" }, + { name = "pydantic" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/1c/41/fbd5be5d25c6bdceb92ecb5232a794eb7a3a9cfdf111f28b27cb3c19fb77/aws_sam_translator-1.91.0.tar.gz", hash = "sha256:0cdfbc598f384c430c3ec064f6008d80c5a0d58f1dc45ca4e331ae5c43cb4697", size = 325096 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4b/90/e3942b5f3e9f7a5079ea2e660ff17eeba9f2292139b20d2cde5f0c828957/aws_sam_translator-1.91.0-py3-none-any.whl", hash = "sha256:9ebf4b53c226338e6b89d14d8583bc4559b87f0be52ed8d577c5a1dc2db14962", size = 383402 }, +] + +[[package]] +name = "aws-xray-sdk" +version = "2.14.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "botocore" }, + { name = "wrapt" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e0/6c/8e7fb2a45f20afc5c19d52807b560793fb48b0feca1de7de116b62a7893e/aws_xray_sdk-2.14.0.tar.gz", hash = "sha256:aab843c331af9ab9ba5cefb3a303832a19db186140894a523edafc024cc0493c", size = 93976 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/41/69/b417833a8926fa5491e5346d7c233bf7d8a9b12ba1f4ef41ccea2494000c/aws_xray_sdk-2.14.0-py2.py3-none-any.whl", hash = "sha256:cfbe6feea3d26613a2a869d14c9246a844285c97087ad8f296f901633554ad94", size = 101922 }, +] + [[package]] name = "babel" version = "2.16.0" @@ -63,6 +148,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/8d/a7/4b27c50537ebca8bec139b872861f9d2bf501c5ec51fcf897cb924d9e264/black-24.10.0-py3-none-any.whl", hash = "sha256:3bb2b7a1f7b685f85b11fed1ef10f8a9148bceb49853e47a294a3dd963c1dd7d", size = 206898 }, ] +[[package]] +name = "blinker" +version = "1.8.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1e/57/a6a1721eff09598fb01f3c7cda070c1b6a0f12d63c83236edf79a440abcc/blinker-1.8.2.tar.gz", hash = "sha256:8f77b09d3bf7c795e969e9486f39c2c5e9c39d4ee07424be2bc594ece9642d83", size = 23161 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bb/2a/10164ed1f31196a2f7f3799368a821765c62851ead0e630ab52b8e14b4d0/blinker-1.8.2-py3-none-any.whl", hash = "sha256:1779309f71bf239144b9399d06ae925637cf6634cf6bd131104184531bf67c01", size = 9456 }, +] + [[package]] name = "boto3" version = "1.35.43" @@ -173,6 +267,24 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/7c/fc/6a8cb64e5f0324877d503c854da15d76c1e50eb722e320b15345c4d0c6de/cffi-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:f6a16c31041f09ead72d69f583767292f750d24913dadacf5756b966aacb3f1a", size = 182009 }, ] +[[package]] +name = "cfn-lint" +version = "1.18.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "aws-sam-translator" }, + { name = "jsonpatch" }, + { name = "networkx" }, + { name = "pyyaml" }, + { name = "regex" }, + { name = "sympy" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/1a/7d/c63c5c6c5723b2c07a30647087ea79f06e3c6b794e9a9fa3e35e131538dd/cfn_lint-1.18.2.tar.gz", hash = "sha256:9ee576db7804839a7f11a7509205bf39ec2663cea2d0c2c09f35c41e2bc03673", size = 2742664 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d6/a6/ce689aec9735c6fd082ba1376f1be41e2ef8e062e5361d0cc424b678f5cb/cfn_lint-1.18.2-py3-none-any.whl", hash = "sha256:52097e8a13c896e7a2776af14116735476ac80e9570bf94c71584f4500a1e42c", size = 4644229 }, +] + [[package]] name = "charset-normalizer" version = "3.4.0" @@ -260,6 +372,35 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e6/75/49e5bfe642f71f272236b5b2d2691cf915a7283cc0ceda56357b61daa538/comm-0.2.2-py3-none-any.whl", hash = "sha256:e6fb86cb70ff661ee8c9c14e7d36d6de3b4066f1441be4063df9c5009f0a64d3", size = 7180 }, ] +[[package]] +name = "cryptography" +version = "43.0.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0d/05/07b55d1fa21ac18c3a8c79f764e2514e6f6a9698f1be44994f5adf0d29db/cryptography-43.0.3.tar.gz", hash = "sha256:315b9001266a492a6ff443b61238f956b214dbec9910a081ba5b6646a055a805", size = 686989 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1f/f3/01fdf26701a26f4b4dbc337a26883ad5bccaa6f1bbbdd29cd89e22f18a1c/cryptography-43.0.3-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:bf7a1932ac4176486eab36a19ed4c0492da5d97123f1406cf15e41b05e787d2e", size = 6225303 }, + { url = "https://files.pythonhosted.org/packages/a3/01/4896f3d1b392025d4fcbecf40fdea92d3df8662123f6835d0af828d148fd/cryptography-43.0.3-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63efa177ff54aec6e1c0aefaa1a241232dcd37413835a9b674b6e3f0ae2bfd3e", size = 3760905 }, + { url = "https://files.pythonhosted.org/packages/0a/be/f9a1f673f0ed4b7f6c643164e513dbad28dd4f2dcdf5715004f172ef24b6/cryptography-43.0.3-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e1ce50266f4f70bf41a2c6dc4358afadae90e2a1e5342d3c08883df1675374f", size = 3977271 }, + { url = "https://files.pythonhosted.org/packages/4e/49/80c3a7b5514d1b416d7350830e8c422a4d667b6d9b16a9392ebfd4a5388a/cryptography-43.0.3-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:443c4a81bb10daed9a8f334365fe52542771f25aedaf889fd323a853ce7377d6", size = 3746606 }, + { url = "https://files.pythonhosted.org/packages/0e/16/a28ddf78ac6e7e3f25ebcef69ab15c2c6be5ff9743dd0709a69a4f968472/cryptography-43.0.3-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:74f57f24754fe349223792466a709f8e0c093205ff0dca557af51072ff47ab18", size = 3986484 }, + { url = "https://files.pythonhosted.org/packages/01/f5/69ae8da70c19864a32b0315049866c4d411cce423ec169993d0434218762/cryptography-43.0.3-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:9762ea51a8fc2a88b70cf2995e5675b38d93bf36bd67d91721c309df184f49bd", size = 3852131 }, + { url = "https://files.pythonhosted.org/packages/fd/db/e74911d95c040f9afd3612b1f732e52b3e517cb80de8bf183be0b7d413c6/cryptography-43.0.3-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:81ef806b1fef6b06dcebad789f988d3b37ccaee225695cf3e07648eee0fc6b73", size = 4075647 }, + { url = "https://files.pythonhosted.org/packages/56/48/7b6b190f1462818b324e674fa20d1d5ef3e24f2328675b9b16189cbf0b3c/cryptography-43.0.3-cp37-abi3-win32.whl", hash = "sha256:cbeb489927bd7af4aa98d4b261af9a5bc025bd87f0e3547e11584be9e9427be2", size = 2623873 }, + { url = "https://files.pythonhosted.org/packages/eb/b1/0ebff61a004f7f89e7b65ca95f2f2375679d43d0290672f7713ee3162aff/cryptography-43.0.3-cp37-abi3-win_amd64.whl", hash = "sha256:f46304d6f0c6ab8e52770addfa2fc41e6629495548862279641972b6215451cd", size = 3068039 }, + { url = "https://files.pythonhosted.org/packages/30/d5/c8b32c047e2e81dd172138f772e81d852c51f0f2ad2ae8a24f1122e9e9a7/cryptography-43.0.3-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:8ac43ae87929a5982f5948ceda07001ee5e83227fd69cf55b109144938d96984", size = 6222984 }, + { url = "https://files.pythonhosted.org/packages/2f/78/55356eb9075d0be6e81b59f45c7b48df87f76a20e73893872170471f3ee8/cryptography-43.0.3-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:846da004a5804145a5f441b8530b4bf35afbf7da70f82409f151695b127213d5", size = 3762968 }, + { url = "https://files.pythonhosted.org/packages/2a/2c/488776a3dc843f95f86d2f957ca0fc3407d0242b50bede7fad1e339be03f/cryptography-43.0.3-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f996e7268af62598f2fc1204afa98a3b5712313a55c4c9d434aef49cadc91d4", size = 3977754 }, + { url = "https://files.pythonhosted.org/packages/7c/04/2345ca92f7a22f601a9c62961741ef7dd0127c39f7310dffa0041c80f16f/cryptography-43.0.3-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:f7b178f11ed3664fd0e995a47ed2b5ff0a12d893e41dd0494f406d1cf555cab7", size = 3749458 }, + { url = "https://files.pythonhosted.org/packages/ac/25/e715fa0bc24ac2114ed69da33adf451a38abb6f3f24ec207908112e9ba53/cryptography-43.0.3-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:c2e6fc39c4ab499049df3bdf567f768a723a5e8464816e8f009f121a5a9f4405", size = 3988220 }, + { url = "https://files.pythonhosted.org/packages/21/ce/b9c9ff56c7164d8e2edfb6c9305045fbc0df4508ccfdb13ee66eb8c95b0e/cryptography-43.0.3-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:e1be4655c7ef6e1bbe6b5d0403526601323420bcf414598955968c9ef3eb7d16", size = 3853898 }, + { url = "https://files.pythonhosted.org/packages/2a/33/b3682992ab2e9476b9c81fff22f02c8b0a1e6e1d49ee1750a67d85fd7ed2/cryptography-43.0.3-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:df6b6c6d742395dd77a23ea3728ab62f98379eff8fb61be2744d4679ab678f73", size = 4076592 }, + { url = "https://files.pythonhosted.org/packages/81/1e/ffcc41b3cebd64ca90b28fd58141c5f68c83d48563c88333ab660e002cd3/cryptography-43.0.3-cp39-abi3-win32.whl", hash = "sha256:d56e96520b1020449bbace2b78b603442e7e378a9b3bd68de65c782db1507995", size = 2623145 }, + { url = "https://files.pythonhosted.org/packages/87/5c/3dab83cc4aba1f4b0e733e3f0c3e7d4386440d660ba5b1e3ff995feb734d/cryptography-43.0.3-cp39-abi3-win_amd64.whl", hash = "sha256:0c580952eef9bf68c4747774cde7ec1d85a6e61de97281f2dba83c7d2c806362", size = 3068026 }, +] + [[package]] name = "cssselect2" version = "0.7.0" @@ -312,6 +453,20 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/07/6c/aa3f2f849e01cb6a001cd8554a88d4c77c5c1a31c95bdf1cf9301e6d9ef4/defusedxml-0.7.1-py2.py3-none-any.whl", hash = "sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61", size = 25604 }, ] +[[package]] +name = "docker" +version = "7.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pywin32", marker = "sys_platform == 'win32'" }, + { name = "requests" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/91/9b/4a2ea29aeba62471211598dac5d96825bb49348fa07e906ea930394a83ce/docker-7.1.0.tar.gz", hash = "sha256:ad8c70e6e3f8926cb8a92619b832b4ea5299e2831c14284663184e200546fa6c", size = 117834 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e3/26/57c6fb270950d476074c087527a558ccb6f4436657314bfb6cdf484114c4/docker-7.1.0-py3-none-any.whl", hash = "sha256:c96b93b7f0a746f9e77d325bcfb87422a3d8bd4f03136ae8a85b37f1898d5fc0", size = 147774 }, +] + [[package]] name = "executing" version = "2.1.0" @@ -321,6 +476,34 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b5/fd/afcd0496feca3276f509df3dbd5dae726fcc756f1a08d9e25abe1733f962/executing-2.1.0-py2.py3-none-any.whl", hash = "sha256:8d63781349375b5ebccc3142f4b30350c0cd9c79f921cde38be2be4637e98eaf", size = 25805 }, ] +[[package]] +name = "flask" +version = "3.0.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "blinker" }, + { name = "click" }, + { name = "itsdangerous" }, + { name = "jinja2" }, + { name = "werkzeug" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/41/e1/d104c83026f8d35dfd2c261df7d64738341067526406b40190bc063e829a/flask-3.0.3.tar.gz", hash = "sha256:ceb27b0af3823ea2737928a4d99d125a06175b8512c445cbd9a9ce200ef76842", size = 676315 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/61/80/ffe1da13ad9300f87c93af113edd0638c75138c42a0994becfacac078c06/flask-3.0.3-py3-none-any.whl", hash = "sha256:34e815dfaa43340d1d15a5c3a02b8476004037eb4840b34910c6e21679d288f3", size = 101735 }, +] + +[[package]] +name = "flask-cors" +version = "5.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "flask" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/4f/d0/d9e52b154e603b0faccc0b7c2ad36a764d8755ef4036acbf1582a67fb86b/flask_cors-5.0.0.tar.gz", hash = "sha256:5aadb4b950c4e93745034594d9f3ea6591f734bb3662e16e255ffbf5e89c88ef", size = 30954 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/56/07/1afa0514c876282bebc1c9aee83c6bb98fe6415cf57b88d9b06e7e29bf9c/Flask_Cors-5.0.0-py2.py3-none-any.whl", hash = "sha256:b9e307d082a9261c100d8fb0ba909eec6a228ed1b60a8315fd85f783d61910bc", size = 14463 }, +] + [[package]] name = "fsspec" version = "2024.10.0" @@ -342,6 +525,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f7/ec/67fbef5d497f86283db54c22eec6f6140243aae73265799baaaa19cd17fb/ghp_import-2.1.0-py3-none-any.whl", hash = "sha256:8337dd7b50877f163d4c0289bc1f1c7f127550241988d568c1db512c4324a619", size = 11034 }, ] +[[package]] +name = "graphql-core" +version = "3.2.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2e/b5/ebc6fe3852e2d2fdaf682dddfc366934f3d2c9ef9b6d1b0e6ca348d936ba/graphql_core-3.2.5.tar.gz", hash = "sha256:e671b90ed653c808715645e3998b7ab67d382d55467b7e2978549111bbabf8d5", size = 504664 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e3/dc/078bd6b304de790618ebb95e2aedaadb78f4527ac43a9ad8815f006636b6/graphql_core-3.2.5-py3-none-any.whl", hash = "sha256:2f150d5096448aa4f8ab26268567bbfeef823769893b39c1a2e1409590939c8a", size = 203189 }, +] + [[package]] name = "griffe" version = "1.4.1" @@ -450,6 +642,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f4/3a/5d8680279ada9571de8469220069d27024ee47624af534e537c9ff49a450/ipython-8.28.0-py3-none-any.whl", hash = "sha256:530ef1e7bb693724d3cdc37287c80b07ad9b25986c007a53aa1857272dac3f35", size = 819456 }, ] +[[package]] +name = "itsdangerous" +version = "2.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9c/cb/8ac0172223afbccb63986cc25049b154ecfb5e85932587206f42317be31d/itsdangerous-2.2.0.tar.gz", hash = "sha256:e0050c0b7da1eea53ffaf149c0cfbb5c6e2e2b69c4bef22c81fa6eb73e5f6173", size = 54410 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/96/92447566d16df59b2a776c0fb82dbc4d9e07cd95062562af01e408583fc4/itsdangerous-2.2.0-py3-none-any.whl", hash = "sha256:c6242fc49e35958c8b15141343aa660db5fc54d4f13a1db01a3f5891b98700ef", size = 16234 }, +] + [[package]] name = "jedi" version = "0.19.1" @@ -483,6 +684,102 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/31/b4/b9b800c45527aadd64d5b442f9b932b00648617eb5d63d2c7a6587b7cafc/jmespath-1.0.1-py3-none-any.whl", hash = "sha256:02e2e4cc71b5bcab88332eebf907519190dd9e6e82107fa7f83b1003a6252980", size = 20256 }, ] +[[package]] +name = "joserfc" +version = "1.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cryptography" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/dc/56/be93d6e313d0e8c4be6f459dafeb543d8a5a663afa10cb4521363052f824/joserfc-1.0.0.tar.gz", hash = "sha256:298a9820c76576f8ca63375d1851cc092f3f225508305c7a36c4632cec38f7bc", size = 169317 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4c/c4/4d2b7d8482652e610242261bf25059b33832a345d36e9335473bcdf5a0d2/joserfc-1.0.0-py3-none-any.whl", hash = "sha256:1de2c3ac203db8fceb2e84c1e78ba357030b195c21af046a1411711927654a09", size = 60806 }, +] + +[[package]] +name = "jsondiff" +version = "2.2.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyyaml" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/35/48/841137f1843fa215ea284834d1514b8e9e20962bda63a636c7417e02f8fb/jsondiff-2.2.1.tar.gz", hash = "sha256:658d162c8a86ba86de26303cd86a7b37e1b2c1ec98b569a60e2ca6180545f7fe", size = 26649 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/63/94/a8066f84d62ab666d61ef97deba1a33126e3e5c0c0da2c458ada17053ed6/jsondiff-2.2.1-py3-none-any.whl", hash = "sha256:b1f0f7e2421881848b1d556d541ac01a91680cfcc14f51a9b62cdf4da0e56722", size = 13440 }, +] + +[[package]] +name = "jsonpatch" +version = "1.33" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jsonpointer" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/42/78/18813351fe5d63acad16aec57f94ec2b70a09e53ca98145589e185423873/jsonpatch-1.33.tar.gz", hash = "sha256:9fcd4009c41e6d12348b4a0ff2563ba56a2923a7dfee731d004e212e1ee5030c", size = 21699 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/73/07/02e16ed01e04a374e644b575638ec7987ae846d25ad97bcc9945a3ee4b0e/jsonpatch-1.33-py2.py3-none-any.whl", hash = "sha256:0ae28c0cd062bbd8b8ecc26d7d164fbbea9652a1a3693f3b956c1eae5145dade", size = 12898 }, +] + +[[package]] +name = "jsonpath-ng" +version = "1.7.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "ply" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/6d/86/08646239a313f895186ff0a4573452038eed8c86f54380b3ebac34d32fb2/jsonpath-ng-1.7.0.tar.gz", hash = "sha256:f6f5f7fd4e5ff79c785f1573b394043b39849fb2bb47bcead935d12b00beab3c", size = 37838 } + +[[package]] +name = "jsonpointer" +version = "3.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6a/0a/eebeb1fa92507ea94016a2a790b93c2ae41a7e18778f85471dc54475ed25/jsonpointer-3.0.0.tar.gz", hash = "sha256:2b2d729f2091522d61c3b31f82e11870f60b68f43fbc705cb76bf4b832af59ef", size = 9114 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/71/92/5e77f98553e9e75130c78900d000368476aed74276eb8ae8796f65f00918/jsonpointer-3.0.0-py2.py3-none-any.whl", hash = "sha256:13e088adc14fca8b6aa8177c044e12701e6ad4b28ff10e65f2267a90109c9942", size = 7595 }, +] + +[[package]] +name = "jsonschema" +version = "4.23.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs" }, + { name = "jsonschema-specifications" }, + { name = "referencing" }, + { name = "rpds-py" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/38/2e/03362ee4034a4c917f697890ccd4aec0800ccf9ded7f511971c75451deec/jsonschema-4.23.0.tar.gz", hash = "sha256:d71497fef26351a33265337fa77ffeb82423f3ea21283cd9467bb03999266bc4", size = 325778 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/69/4a/4f9dbeb84e8850557c02365a0eee0649abe5eb1d84af92a25731c6c0f922/jsonschema-4.23.0-py3-none-any.whl", hash = "sha256:fbadb6f8b144a8f8cf9f0b89ba94501d143e50411a1278633f56a7acf7fd5566", size = 88462 }, +] + +[[package]] +name = "jsonschema-path" +version = "0.3.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pathable" }, + { name = "pyyaml" }, + { name = "referencing" }, + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/85/39/3a58b63a997b0cf824536d6f84fff82645a1ca8de222ee63586adab44dfa/jsonschema_path-0.3.3.tar.gz", hash = "sha256:f02e5481a4288ec062f8e68c808569e427d905bedfecb7f2e4c69ef77957c382", size = 11589 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/53/b0/69237e85976916b2e37586b7ddc48b9547fc38b440e25103d084b2b02ab3/jsonschema_path-0.3.3-py3-none-any.whl", hash = "sha256:203aff257f8038cd3c67be614fe6b2001043408cb1b4e36576bc4921e09d83c4", size = 14817 }, +] + +[[package]] +name = "jsonschema-specifications" +version = "2023.12.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "referencing" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f8/b9/cc0cc592e7c195fb8a650c1d5990b10175cf13b4c97465c72ec841de9e4b/jsonschema_specifications-2023.12.1.tar.gz", hash = "sha256:48a76787b3e70f5ed53f1160d2b81f586e4ca6d1548c5de7085d1682674764cc", size = 13983 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ee/07/44bd408781594c4d0a027666ef27fab1e441b109dc3b76b4f836f8fd04fe/jsonschema_specifications-2023.12.1-py3-none-any.whl", hash = "sha256:87e4fdf3a94858b8a2ba2778d9ba57d8a9cafca7c7489c46ba0d30a8bc6a9c3c", size = 18482 }, +] + [[package]] name = "jupyter-client" version = "8.6.3" @@ -513,6 +810,29 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c9/fb/108ecd1fe961941959ad0ee4e12ee7b8b1477247f30b1fdfd83ceaf017f0/jupyter_core-5.7.2-py3-none-any.whl", hash = "sha256:4f7315d2f6b4bcf2e3e7cb6e46772eba760ae459cd1f59d29eb57b0a01bd7409", size = 28965 }, ] +[[package]] +name = "lazy-object-proxy" +version = "1.10.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2c/f0/f02e2d150d581a294efded4020094a371bbab42423fe78625ac18854d89b/lazy-object-proxy-1.10.0.tar.gz", hash = "sha256:78247b6d45f43a52ef35c25b5581459e85117225408a4128a3daf8bf9648ac69", size = 43271 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ff/e1/99a7ec68b892c9b8c6212617f54e7e9b0304d47edad8c0ff043ae3aeb1a9/lazy_object_proxy-1.10.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:fec03caabbc6b59ea4a638bee5fce7117be8e99a4103d9d5ad77f15d6f81020c", size = 27434 }, + { url = "https://files.pythonhosted.org/packages/1a/76/6a41de4b44d1dcfe4c720d4606de0d7b69b6b450f0bdce16f2e1fb8abc89/lazy_object_proxy-1.10.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:02c83f957782cbbe8136bee26416686a6ae998c7b6191711a04da776dc9e47d4", size = 70687 }, + { url = "https://files.pythonhosted.org/packages/1e/5d/eaa12126e8989c9bdd21d864cbba2b258cb9ee2f574ada1462a0004cfad8/lazy_object_proxy-1.10.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:009e6bb1f1935a62889ddc8541514b6a9e1fcf302667dcb049a0be5c8f613e56", size = 69757 }, + { url = "https://files.pythonhosted.org/packages/53/a9/6f22cfe9572929656988b72c0de266c5d10755369b575322725f67364c4e/lazy_object_proxy-1.10.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:75fc59fc450050b1b3c203c35020bc41bd2695ed692a392924c6ce180c6f1dc9", size = 73709 }, + { url = "https://files.pythonhosted.org/packages/bd/e6/b10fd94710a99a6309f3ad61a4eb480944bbb17fcb41bd2d852fdbee57ee/lazy_object_proxy-1.10.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:782e2c9b2aab1708ffb07d4bf377d12901d7a1d99e5e410d648d892f8967ab1f", size = 73191 }, + { url = "https://files.pythonhosted.org/packages/c9/78/a9b9d314da02fe66b632f2354e20e40fc3508befb450b5a17987a222b383/lazy_object_proxy-1.10.0-cp311-cp311-win32.whl", hash = "sha256:edb45bb8278574710e68a6b021599a10ce730d156e5b254941754a9cc0b17d03", size = 25773 }, + { url = "https://files.pythonhosted.org/packages/94/e6/e2d3b0c9efe61f72dc327ce2355941f540e0b0d1f2b3490cbab6bab7d3ea/lazy_object_proxy-1.10.0-cp311-cp311-win_amd64.whl", hash = "sha256:e271058822765ad5e3bca7f05f2ace0de58a3f4e62045a8c90a0dfd2f8ad8cc6", size = 27550 }, + { url = "https://files.pythonhosted.org/packages/d0/5d/768a7f2ccebb29604def61842fd54f6f5f75c79e366ee8748dda84de0b13/lazy_object_proxy-1.10.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:e98c8af98d5707dcdecc9ab0863c0ea6e88545d42ca7c3feffb6b4d1e370c7ba", size = 27560 }, + { url = "https://files.pythonhosted.org/packages/b3/ce/f369815549dbfa4bebed541fa4e1561d69e4f268a1f6f77da886df182dab/lazy_object_proxy-1.10.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:952c81d415b9b80ea261d2372d2a4a2332a3890c2b83e0535f263ddfe43f0d43", size = 72403 }, + { url = "https://files.pythonhosted.org/packages/44/46/3771e0a4315044aa7b67da892b2fb1f59dfcf0eaff2c8967b2a0a85d5896/lazy_object_proxy-1.10.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80b39d3a151309efc8cc48675918891b865bdf742a8616a337cb0090791a0de9", size = 72401 }, + { url = "https://files.pythonhosted.org/packages/81/39/84ce4740718e1c700bd04d3457ac92b2e9ce76529911583e7a2bf4d96eb2/lazy_object_proxy-1.10.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:e221060b701e2aa2ea991542900dd13907a5c90fa80e199dbf5a03359019e7a3", size = 75375 }, + { url = "https://files.pythonhosted.org/packages/86/3b/d6b65da2b864822324745c0a73fe7fd86c67ccea54173682c3081d7adea8/lazy_object_proxy-1.10.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:92f09ff65ecff3108e56526f9e2481b8116c0b9e1425325e13245abfd79bdb1b", size = 75466 }, + { url = "https://files.pythonhosted.org/packages/f5/33/467a093bf004a70022cb410c590d937134bba2faa17bf9dc42a48f49af35/lazy_object_proxy-1.10.0-cp312-cp312-win32.whl", hash = "sha256:3ad54b9ddbe20ae9f7c1b29e52f123120772b06dbb18ec6be9101369d63a4074", size = 25914 }, + { url = "https://files.pythonhosted.org/packages/77/ce/7956dc5ac2f8b62291b798c8363c81810e22a9effe469629d297d087e350/lazy_object_proxy-1.10.0-cp312-cp312-win_amd64.whl", hash = "sha256:127a789c75151db6af398b8972178afe6bda7d6f68730c057fbbc2e96b08d282", size = 27525 }, + { url = "https://files.pythonhosted.org/packages/31/8b/94dc8d58704ab87b39faed6f2fc0090b9d90e2e2aa2bbec35c79f3d2a054/lazy_object_proxy-1.10.0-pp310.pp311.pp312.pp38.pp39-none-any.whl", hash = "sha256:80fa48bd89c8f2f456fc0765c11c23bf5af827febacd2f523ca5bc1893fcc09d", size = 16405 }, +] + [[package]] name = "markdown" version = "3.7" @@ -757,6 +1077,58 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/65/e8/3cf3467fb8e31f68bfc8a2bfd5f4891c1eaa584b0c62b76c783d24d1901d/mkdocstrings_python-1.12.1-py3-none-any.whl", hash = "sha256:205244488199c9aa2a39787ad6a0c862d39b74078ea9aa2be817bc972399563f", size = 111657 }, ] +[[package]] +name = "moto" +version = "5.0.18" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "boto3" }, + { name = "botocore" }, + { name = "cryptography" }, + { name = "jinja2" }, + { name = "python-dateutil" }, + { name = "requests" }, + { name = "responses" }, + { name = "werkzeug" }, + { name = "xmltodict" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b4/c0/6637d2d8e113d909ac794944e041928ba7d43c3b540a896501776a6024e2/moto-5.0.18.tar.gz", hash = "sha256:8a7ad2f53a2e6cc9db2ff65c0e0d4b5d7e78bc00b825c9e1ff6cc394371e76e9", size = 5509110 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c3/4f/b9624a14e397234fa2b9345d64357d08ba2f49ff1b1eb4a083c6a7329ed5/moto-5.0.18-py2.py3-none-any.whl", hash = "sha256:8e25401f7d7910e19a732b417e0d503ef86cf4de9114a273dd62679a42f3be1c", size = 3701707 }, +] + +[package.optional-dependencies] +s3 = [ + { name = "py-partiql-parser" }, + { name = "pyyaml" }, +] +server = [ + { name = "antlr4-python3-runtime" }, + { name = "aws-xray-sdk" }, + { name = "cfn-lint" }, + { name = "docker" }, + { name = "flask" }, + { name = "flask-cors" }, + { name = "graphql-core" }, + { name = "joserfc" }, + { name = "jsondiff" }, + { name = "jsonpath-ng" }, + { name = "openapi-spec-validator" }, + { name = "py-partiql-parser" }, + { name = "pyparsing" }, + { name = "pyyaml" }, + { name = "setuptools" }, +] + +[[package]] +name = "mpmath" +version = "1.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e0/47/dd32fa426cc72114383ac549964eecb20ecfd886d1e5ccf5340b55b02f57/mpmath-1.3.0.tar.gz", hash = "sha256:7a28eb2a9774d00c7bc92411c19a89209d5da7c4c9a9e227be8330a23a25b91f", size = 508106 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/43/e3/7d92a15f894aa0c9c4b49b8ee9ac9850d6e63b03c9c32c0367a13ae62209/mpmath-1.3.0-py3-none-any.whl", hash = "sha256:a0b2b9fe80bbcd81a6647ff13108738cfb482d481d826cc0e02f5b35e5c88d2c", size = 536198 }, +] + [[package]] name = "mypy-extensions" version = "1.0.0" @@ -775,6 +1147,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/a0/c4/c2971a3ba4c6103a3d10c4b0f24f461ddc027f0f09763220cf35ca1401b3/nest_asyncio-1.6.0-py3-none-any.whl", hash = "sha256:87af6efd6b5e897c81050477ef65c62e2b2f35d51703cae01aff2905b1852e1c", size = 5195 }, ] +[[package]] +name = "networkx" +version = "3.4.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fd/1d/06475e1cd5264c0b870ea2cc6fdb3e37177c1e565c43f56ff17a10e3937f/networkx-3.4.2.tar.gz", hash = "sha256:307c3669428c5362aab27c8a1260aa8f47c4e91d3891f48be0141738d8d053e1", size = 2151368 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b9/54/dd730b32ea14ea797530a4479b2ed46a6fb250f682a9cfb997e968bf0261/networkx-3.4.2-py3-none-any.whl", hash = "sha256:df5d4365b724cf81b8c6a7312509d0c22386097011ad1abe274afd5e9d3bbc5f", size = 1723263 }, +] + [[package]] name = "numpy" version = "2.1.2" @@ -821,6 +1202,35 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/48/6f/129e3c17e3befe7fefdeaa6890f4c4df3f3cf0831aa053802c3862da67aa/numpy-2.1.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:ef444c57d664d35cac4e18c298c47d7b504c66b17c2ea91312e979fcfbdfb08a", size = 14066202 }, ] +[[package]] +name = "openapi-schema-validator" +version = "0.6.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jsonschema" }, + { name = "jsonschema-specifications" }, + { name = "rfc3339-validator" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5c/b2/7d5bdf2b26b6a95ebf4fbec294acaf4306c713f3a47c2453962511110248/openapi_schema_validator-0.6.2.tar.gz", hash = "sha256:11a95c9c9017912964e3e5f2545a5b11c3814880681fcacfb73b1759bb4f2804", size = 11860 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/dc/9aefae8891454130968ff079ece851d1ae9ccf6fb7965761f47c50c04853/openapi_schema_validator-0.6.2-py3-none-any.whl", hash = "sha256:c4887c1347c669eb7cded9090f4438b710845cd0f90d1fb9e1b3303fb37339f8", size = 8750 }, +] + +[[package]] +name = "openapi-spec-validator" +version = "0.7.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jsonschema" }, + { name = "jsonschema-path" }, + { name = "lazy-object-proxy" }, + { name = "openapi-schema-validator" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/67/fe/21954ff978239dc29ebb313f5c87eeb4ec929b694b9667323086730998e2/openapi_spec_validator-0.7.1.tar.gz", hash = "sha256:8577b85a8268685da6f8aa30990b83b7960d4d1117e901d451b5d572605e5ec7", size = 37985 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2b/4d/e744fff95aaf3aeafc968d5ba7297c8cda0d1ecb8e3acd21b25adae4d835/openapi_spec_validator-0.7.1-py3-none-any.whl", hash = "sha256:3c81825043f24ccbcd2f4b149b11e8231abce5ba84f37065e14ec947d8f4e959", size = 38998 }, +] + [[package]] name = "packaging" version = "24.1" @@ -889,6 +1299,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c6/ac/dac4a63f978e4dcb3c6d3a78c4d8e0192a113d288502a1216950c41b1027/parso-0.8.4-py2.py3-none-any.whl", hash = "sha256:a418670a20291dacd2dddc80c377c5c3791378ee1e8d12bffc35420643d43f18", size = 103650 }, ] +[[package]] +name = "pathable" +version = "0.4.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9d/ed/e0e29300253b61dea3b7ec3a31f5d061d577c2a6fd1e35c5cfd0e6f2cd6d/pathable-0.4.3.tar.gz", hash = "sha256:5c869d315be50776cc8a993f3af43e0c60dc01506b399643f919034ebf4cdcab", size = 8679 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5b/0a/acfb251ba01009d3053f04f4661e96abf9d485266b04a0a4deebc702d9cb/pathable-0.4.3-py3-none-any.whl", hash = "sha256:cdd7b1f9d7d5c8b8d3315dbf5a86b2596053ae845f056f57d97c0eefff84da14", size = 9587 }, +] + [[package]] name = "pathspec" version = "0.12.1" @@ -978,6 +1397,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/88/5f/e351af9a41f866ac3f1fac4ca0613908d9a41741cfcf2228f4ad853b697d/pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669", size = 20556 }, ] +[[package]] +name = "ply" +version = "3.11" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e5/69/882ee5c9d017149285cab114ebeab373308ef0f874fcdac9beb90e0ac4da/ply-3.11.tar.gz", hash = "sha256:00c7c1aaa88358b9c765b6d3000c6eec0ba42abca5351b095321aef446081da3", size = 159130 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a3/58/35da89ee790598a0700ea49b2a66594140f44dec458c07e8e3d4979137fc/ply-3.11-py2.py3-none-any.whl", hash = "sha256:096f9b8350b65ebd2fd1346b12452efe5b9607f7482813ffca50c22722a807ce", size = 49567 }, +] + [[package]] name = "prompt-toolkit" version = "3.0.48" @@ -996,8 +1424,6 @@ version = "6.0.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/18/c7/8c6872f7372eb6a6b2e4708b88419fb46b857f7a2e1892966b851cc79fc9/psutil-6.0.0.tar.gz", hash = "sha256:8faae4f310b6d969fa26ca0545338b21f73c6b15db7c4a8d934a5482faa818f2", size = 508067 } wheels = [ - { url = "https://files.pythonhosted.org/packages/c5/66/78c9c3020f573c58101dc43a44f6855d01bbbd747e24da2f0c4491200ea3/psutil-6.0.0-cp27-none-win32.whl", hash = "sha256:02b69001f44cc73c1c5279d02b30a817e339ceb258ad75997325e0e6169d8b35", size = 249766 }, - { url = "https://files.pythonhosted.org/packages/e1/3f/2403aa9558bea4d3854b0e5e567bc3dd8e9fbc1fc4453c0aa9aafeb75467/psutil-6.0.0-cp27-none-win_amd64.whl", hash = "sha256:21f1fb635deccd510f69f485b87433460a603919b45e2a324ad65b0cc74f8fb1", size = 253024 }, { url = "https://files.pythonhosted.org/packages/0b/37/f8da2fbd29690b3557cca414c1949f92162981920699cd62095a984983bf/psutil-6.0.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:c588a7e9b1173b6e866756dde596fd4cad94f9399daf99ad8c3258b3cb2b47a0", size = 250961 }, { url = "https://files.pythonhosted.org/packages/35/56/72f86175e81c656a01c4401cd3b1c923f891b31fbcebe98985894176d7c9/psutil-6.0.0-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ed2440ada7ef7d0d608f20ad89a04ec47d2d3ab7190896cd62ca5fc4fe08bf0", size = 287478 }, { url = "https://files.pythonhosted.org/packages/19/74/f59e7e0d392bc1070e9a70e2f9190d652487ac115bb16e2eff6b22ad1d24/psutil-6.0.0-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5fd9a97c8e94059b0ef54a7d4baf13b405011176c3b6ff257c247cae0d560ecd", size = 290455 }, @@ -1026,28 +1452,47 @@ wheels = [ ] [[package]] -name = "pyarrow" -version = "17.0.0" +name = "py-partiql-parser" +version = "0.5.6" source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "numpy" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/27/4e/ea6d43f324169f8aec0e57569443a38bab4b398d09769ca64f7b4d467de3/pyarrow-17.0.0.tar.gz", hash = "sha256:4beca9521ed2c0921c1023e68d097d0299b62c362639ea315572a58f3f50fd28", size = 1112479 } +sdist = { url = "https://files.pythonhosted.org/packages/de/91/9d499c960abea0efda9e81aa62bd8dd1eab1daa12db9b3b0064fe2f8b3c7/py_partiql_parser-0.5.6.tar.gz", hash = "sha256:6339f6bf85573a35686529fc3f491302e71dd091711dfe8df3be89a93767f97b", size = 16926 } wheels = [ - { url = "https://files.pythonhosted.org/packages/f9/46/ce89f87c2936f5bb9d879473b9663ce7a4b1f4359acc2f0eb39865eaa1af/pyarrow-17.0.0-cp311-cp311-macosx_10_15_x86_64.whl", hash = "sha256:1c8856e2ef09eb87ecf937104aacfa0708f22dfeb039c363ec99735190ffb977", size = 29028748 }, - { url = "https://files.pythonhosted.org/packages/8d/8e/ce2e9b2146de422f6638333c01903140e9ada244a2a477918a368306c64c/pyarrow-17.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2e19f569567efcbbd42084e87f948778eb371d308e137a0f97afe19bb860ccb3", size = 27190965 }, - { url = "https://files.pythonhosted.org/packages/3b/c8/5675719570eb1acd809481c6d64e2136ffb340bc387f4ca62dce79516cea/pyarrow-17.0.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6b244dc8e08a23b3e352899a006a26ae7b4d0da7bb636872fa8f5884e70acf15", size = 39269081 }, - { url = "https://files.pythonhosted.org/packages/5e/78/3931194f16ab681ebb87ad252e7b8d2c8b23dad49706cadc865dff4a1dd3/pyarrow-17.0.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b72e87fe3e1db343995562f7fff8aee354b55ee83d13afba65400c178ab2597", size = 39864921 }, - { url = "https://files.pythonhosted.org/packages/d8/81/69b6606093363f55a2a574c018901c40952d4e902e670656d18213c71ad7/pyarrow-17.0.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:dc5c31c37409dfbc5d014047817cb4ccd8c1ea25d19576acf1a001fe07f5b420", size = 38740798 }, - { url = "https://files.pythonhosted.org/packages/4c/21/9ca93b84b92ef927814cb7ba37f0774a484c849d58f0b692b16af8eebcfb/pyarrow-17.0.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:e3343cb1e88bc2ea605986d4b94948716edc7a8d14afd4e2c097232f729758b4", size = 39871877 }, - { url = "https://files.pythonhosted.org/packages/30/d1/63a7c248432c71c7d3ee803e706590a0b81ce1a8d2b2ae49677774b813bb/pyarrow-17.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:a27532c38f3de9eb3e90ecab63dfda948a8ca859a66e3a47f5f42d1e403c4d03", size = 25151089 }, - { url = "https://files.pythonhosted.org/packages/d4/62/ce6ac1275a432b4a27c55fe96c58147f111d8ba1ad800a112d31859fae2f/pyarrow-17.0.0-cp312-cp312-macosx_10_15_x86_64.whl", hash = "sha256:9b8a823cea605221e61f34859dcc03207e52e409ccf6354634143e23af7c8d22", size = 29019418 }, - { url = "https://files.pythonhosted.org/packages/8e/0a/dbd0c134e7a0c30bea439675cc120012337202e5fac7163ba839aa3691d2/pyarrow-17.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f1e70de6cb5790a50b01d2b686d54aaf73da01266850b05e3af2a1bc89e16053", size = 27152197 }, - { url = "https://files.pythonhosted.org/packages/cb/05/3f4a16498349db79090767620d6dc23c1ec0c658a668d61d76b87706c65d/pyarrow-17.0.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0071ce35788c6f9077ff9ecba4858108eebe2ea5a3f7cf2cf55ebc1dbc6ee24a", size = 39263026 }, - { url = "https://files.pythonhosted.org/packages/c2/0c/ea2107236740be8fa0e0d4a293a095c9f43546a2465bb7df34eee9126b09/pyarrow-17.0.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:757074882f844411fcca735e39aae74248a1531367a7c80799b4266390ae51cc", size = 39880798 }, - { url = "https://files.pythonhosted.org/packages/f6/b0/b9164a8bc495083c10c281cc65064553ec87b7537d6f742a89d5953a2a3e/pyarrow-17.0.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:9ba11c4f16976e89146781a83833df7f82077cdab7dc6232c897789343f7891a", size = 38715172 }, - { url = "https://files.pythonhosted.org/packages/f1/c4/9625418a1413005e486c006e56675334929fad864347c5ae7c1b2e7fe639/pyarrow-17.0.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:b0c6ac301093b42d34410b187bba560b17c0330f64907bfa4f7f7f2444b0cf9b", size = 39874508 }, - { url = "https://files.pythonhosted.org/packages/ae/49/baafe2a964f663413be3bd1cf5c45ed98c5e42e804e2328e18f4570027c1/pyarrow-17.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:392bc9feabc647338e6c89267635e111d71edad5fcffba204425a7c8d13610d7", size = 25099235 }, + { url = "https://files.pythonhosted.org/packages/2c/24/46262d45ee9e54a181c440fe1a3d87fd538d69f10c8311f699e555119d1f/py_partiql_parser-0.5.6-py2.py3-none-any.whl", hash = "sha256:622d7b0444becd08c1f4e9e73b31690f4b1c309ab6e5ed45bf607fe71319309f", size = 23237 }, +] + +[[package]] +name = "pyarrow" +version = "18.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ec/41/6bfd027410ba2cc35da4682394fdc4285dc345b1d99f7bd55e96255d0c7d/pyarrow-18.0.0.tar.gz", hash = "sha256:a6aa027b1a9d2970cf328ccd6dbe4a996bc13c39fd427f502782f5bdb9ca20f5", size = 1118457 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d6/63/a4854246fb3d1387e176e2989d919b8186ce3806ca244fbed27217608708/pyarrow-18.0.0-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:d5795e37c0a33baa618c5e054cd61f586cf76850a251e2b21355e4085def6280", size = 29532160 }, + { url = "https://files.pythonhosted.org/packages/53/dc/9a6672fb35d36323f4548b08064fb264353024538f60adaedf0c6df6b31d/pyarrow-18.0.0-cp311-cp311-macosx_12_0_x86_64.whl", hash = "sha256:5f0510608ccd6e7f02ca8596962afb8c6cc84c453e7be0da4d85f5f4f7b0328a", size = 30844030 }, + { url = "https://files.pythonhosted.org/packages/8e/f9/cfcee70dcb48bc0fee6265a5d2502ea85ccdab54957fd2dd5b327dfc8807/pyarrow-18.0.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:616ea2826c03c16e87f517c46296621a7c51e30400f6d0a61be645f203aa2b93", size = 39177238 }, + { url = "https://files.pythonhosted.org/packages/17/de/cd37c379dc1aa379956b15d9c89ff920cf48c239f64fbed0ca97dffa3acc/pyarrow-18.0.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a1824f5b029ddd289919f354bc285992cb4e32da518758c136271cf66046ef22", size = 40089208 }, + { url = "https://files.pythonhosted.org/packages/dd/80/83453dcceaa49d7aa42b0b6aaa7a0797231b9aee1cc213f286e0be3bdf89/pyarrow-18.0.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:6dd1b52d0d58dd8f685ced9971eb49f697d753aa7912f0a8f50833c7a7426319", size = 38606715 }, + { url = "https://files.pythonhosted.org/packages/18/f4/5687ead1672920b5ed8840398551cc3a96a1389be68b68d18aca3944e525/pyarrow-18.0.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:320ae9bd45ad7ecc12ec858b3e8e462578de060832b98fc4d671dee9f10d9954", size = 40040879 }, + { url = "https://files.pythonhosted.org/packages/49/11/ea314ad45f45d3245f0768dba711fd3d5deb25a9e08af298d0924ab94aee/pyarrow-18.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:2c992716cffb1088414f2b478f7af0175fd0a76fea80841b1706baa8fb0ebaad", size = 25105360 }, + { url = "https://files.pythonhosted.org/packages/e4/ea/a7f77688e6c529723b37589af4db3e7179414e223878301907c5bd49d6bc/pyarrow-18.0.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:e7ab04f272f98ebffd2a0661e4e126036f6936391ba2889ed2d44c5006237802", size = 29493113 }, + { url = "https://files.pythonhosted.org/packages/79/8a/a3af902af623a1cf4f9d4d27d81e634caf1585a819b7530728a8147e391c/pyarrow-18.0.0-cp312-cp312-macosx_12_0_x86_64.whl", hash = "sha256:03f40b65a43be159d2f97fd64dc998f769d0995a50c00f07aab58b0b3da87e1f", size = 30833386 }, + { url = "https://files.pythonhosted.org/packages/46/1e/f38b22e12e2ce9ee7c9d805ce234f68b23a0568b9a6bea223e3a99ca0068/pyarrow-18.0.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:be08af84808dff63a76860847c48ec0416928a7b3a17c2f49a072cac7c45efbd", size = 39170798 }, + { url = "https://files.pythonhosted.org/packages/f8/fb/fd0ef3e0f03227ab183f8dc941f4ef59636d8c382e246954601dd29cf1b0/pyarrow-18.0.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c70c1965cde991b711a98448ccda3486f2a336457cf4ec4dca257a926e149c9", size = 40103326 }, + { url = "https://files.pythonhosted.org/packages/7c/bd/5de139adba486db5ccc1b7ecab51e328a9dce354c82c6d26c2f642b178d3/pyarrow-18.0.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:00178509f379415a3fcf855af020e3340254f990a8534294ec3cf674d6e255fd", size = 38583592 }, + { url = "https://files.pythonhosted.org/packages/8d/1f/9bb3b3a644892d631dbbe99053cdb5295092d2696b4bcd3d21f29624c689/pyarrow-18.0.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:a71ab0589a63a3e987beb2bc172e05f000a5c5be2636b4b263c44034e215b5d7", size = 40043128 }, + { url = "https://files.pythonhosted.org/packages/74/39/323621402c2b1ce7ba600d03c81cf9645b862350d7c495f3fcef37850d1d/pyarrow-18.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:fe92efcdbfa0bcf2fa602e466d7f2905500f33f09eb90bf0bcf2e6ca41b574c8", size = 25075300 }, + { url = "https://files.pythonhosted.org/packages/13/38/4a8f8e97301adbb51c0bae7e0bc39e6878609c9337543bbbd2e9b1b3046e/pyarrow-18.0.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:907ee0aa8ca576f5e0cdc20b5aeb2ad4d3953a3b4769fc4b499e00ef0266f02f", size = 29475921 }, + { url = "https://files.pythonhosted.org/packages/11/75/43aad9b0678dfcdf5cc4d632f0ead92abe5666ce5b5cc985abab75e0d410/pyarrow-18.0.0-cp313-cp313-macosx_12_0_x86_64.whl", hash = "sha256:66dcc216ebae2eb4c37b223feaf82f15b69d502821dde2da138ec5a3716e7463", size = 30811777 }, + { url = "https://files.pythonhosted.org/packages/1e/b7/477bcba6ff7e65d8045d0b6c04b36f12051385f533189617a652f551e742/pyarrow-18.0.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bc1daf7c425f58527900876354390ee41b0ae962a73ad0959b9d829def583bb1", size = 39163582 }, + { url = "https://files.pythonhosted.org/packages/c8/a7/37be6828370a98b3ed1125daf41dc651b27e2a9506a3682da305db757f32/pyarrow-18.0.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:871b292d4b696b09120ed5bde894f79ee2a5f109cb84470546471df264cae136", size = 40095799 }, + { url = "https://files.pythonhosted.org/packages/5a/a0/a4eb68c3495c5e72b404c9106c4af2d02860b0a64bc9450023ed9a412c0b/pyarrow-18.0.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:082ba62bdcb939824ba1ce10b8acef5ab621da1f4c4805e07bfd153617ac19d4", size = 38575191 }, + { url = "https://files.pythonhosted.org/packages/95/1f/6c629156ed4b8e2262da57868930cbb8cffba318b8413043acd02db9ad97/pyarrow-18.0.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:2c664ab88b9766413197733c1720d3dcd4190e8fa3bbdc3710384630a0a7207b", size = 40031824 }, + { url = "https://files.pythonhosted.org/packages/00/4f/5add0884b3ee6f4f1875e9cd0e69a30905798fa1497a80ab6df4645b54b4/pyarrow-18.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:dc892be34dbd058e8d189b47db1e33a227d965ea8805a235c8a7286f7fd17d3a", size = 25068305 }, + { url = "https://files.pythonhosted.org/packages/84/f7/fa53f3062dd2e390b8b021ce2d8de064a141b4bffc2add05471b5b2ee0eb/pyarrow-18.0.0-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:28f9c39a56d2c78bf6b87dcc699d520ab850919d4a8c7418cd20eda49874a2ea", size = 29503390 }, + { url = "https://files.pythonhosted.org/packages/2b/d3/03bc8a5356d95098878c0fa076e69992c6abc212898cd7286cfeab0f2c60/pyarrow-18.0.0-cp313-cp313t-macosx_12_0_x86_64.whl", hash = "sha256:f1a198a50c409ab2d009fbf20956ace84567d67f2c5701511d4dd561fae6f32e", size = 30806216 }, + { url = "https://files.pythonhosted.org/packages/75/04/3b27d1352d3252abf42b0a83a2e7f6fcb7665cc98a5d3777f427eaa166bc/pyarrow-18.0.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b5bd7fd32e3ace012d43925ea4fc8bd1b02cc6cc1e9813b518302950e89b5a22", size = 39086243 }, + { url = "https://files.pythonhosted.org/packages/30/97/861dfbe3987156f817f3d7e6feb239de1e085a6b576f62454b7bc42c2713/pyarrow-18.0.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:336addb8b6f5208be1b2398442c703a710b6b937b1a046065ee4db65e782ff5a", size = 40055188 }, + { url = "https://files.pythonhosted.org/packages/25/3a/14f024a1c8fb5ff67d79b616fe218bbfa06f23f198e762c6a900a843796a/pyarrow-18.0.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:45476490dd4adec5472c92b4d253e245258745d0ccaabe706f8d03288ed60a79", size = 38511444 }, + { url = "https://files.pythonhosted.org/packages/92/a2/81c1dd744b322c0c548f793deb521bf23500806d754128ddf6f978736dff/pyarrow-18.0.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:b46591222c864e7da7faa3b19455196416cd8355ff6c2cc2e65726a760a3c420", size = 40006508 }, ] [[package]] @@ -1059,6 +1504,67 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/13/a3/a812df4e2dd5696d1f351d58b8fe16a405b234ad2886a0dab9183fb78109/pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc", size = 117552 }, ] +[[package]] +name = "pydantic" +version = "2.9.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "annotated-types" }, + { name = "pydantic-core" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a9/b7/d9e3f12af310e1120c21603644a1cd86f59060e040ec5c3a80b8f05fae30/pydantic-2.9.2.tar.gz", hash = "sha256:d155cef71265d1e9807ed1c32b4c8deec042a44a50a4188b25ac67ecd81a9c0f", size = 769917 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/df/e4/ba44652d562cbf0bf320e0f3810206149c8a4e99cdbf66da82e97ab53a15/pydantic-2.9.2-py3-none-any.whl", hash = "sha256:f048cec7b26778210e28a0459867920654d48e5e62db0958433636cde4254f12", size = 434928 }, +] + +[[package]] +name = "pydantic-core" +version = "2.23.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e2/aa/6b6a9b9f8537b872f552ddd46dd3da230367754b6f707b8e1e963f515ea3/pydantic_core-2.23.4.tar.gz", hash = "sha256:2584f7cf844ac4d970fba483a717dbe10c1c1c96a969bf65d61ffe94df1b2863", size = 402156 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5d/30/890a583cd3f2be27ecf32b479d5d615710bb926d92da03e3f7838ff3e58b/pydantic_core-2.23.4-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:77733e3892bb0a7fa797826361ce8a9184d25c8dffaec60b7ffe928153680ba8", size = 1865160 }, + { url = "https://files.pythonhosted.org/packages/1d/9a/b634442e1253bc6889c87afe8bb59447f106ee042140bd57680b3b113ec7/pydantic_core-2.23.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1b84d168f6c48fabd1f2027a3d1bdfe62f92cade1fb273a5d68e621da0e44e6d", size = 1776777 }, + { url = "https://files.pythonhosted.org/packages/75/9a/7816295124a6b08c24c96f9ce73085032d8bcbaf7e5a781cd41aa910c891/pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:df49e7a0861a8c36d089c1ed57d308623d60416dab2647a4a17fe050ba85de0e", size = 1799244 }, + { url = "https://files.pythonhosted.org/packages/a9/8f/89c1405176903e567c5f99ec53387449e62f1121894aa9fc2c4fdc51a59b/pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ff02b6d461a6de369f07ec15e465a88895f3223eb75073ffea56b84d9331f607", size = 1805307 }, + { url = "https://files.pythonhosted.org/packages/d5/a5/1a194447d0da1ef492e3470680c66048fef56fc1f1a25cafbea4bc1d1c48/pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:996a38a83508c54c78a5f41456b0103c30508fed9abcad0a59b876d7398f25fd", size = 2000663 }, + { url = "https://files.pythonhosted.org/packages/13/a5/1df8541651de4455e7d587cf556201b4f7997191e110bca3b589218745a5/pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d97683ddee4723ae8c95d1eddac7c192e8c552da0c73a925a89fa8649bf13eea", size = 2655941 }, + { url = "https://files.pythonhosted.org/packages/44/31/a3899b5ce02c4316865e390107f145089876dff7e1dfc770a231d836aed8/pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:216f9b2d7713eb98cb83c80b9c794de1f6b7e3145eef40400c62e86cee5f4e1e", size = 2052105 }, + { url = "https://files.pythonhosted.org/packages/1b/aa/98e190f8745d5ec831f6d5449344c48c0627ac5fed4e5340a44b74878f8e/pydantic_core-2.23.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6f783e0ec4803c787bcea93e13e9932edab72068f68ecffdf86a99fd5918878b", size = 1919967 }, + { url = "https://files.pythonhosted.org/packages/ae/35/b6e00b6abb2acfee3e8f85558c02a0822e9a8b2f2d812ea8b9079b118ba0/pydantic_core-2.23.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d0776dea117cf5272382634bd2a5c1b6eb16767c223c6a5317cd3e2a757c61a0", size = 1964291 }, + { url = "https://files.pythonhosted.org/packages/13/46/7bee6d32b69191cd649bbbd2361af79c472d72cb29bb2024f0b6e350ba06/pydantic_core-2.23.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d5f7a395a8cf1621939692dba2a6b6a830efa6b3cee787d82c7de1ad2930de64", size = 2109666 }, + { url = "https://files.pythonhosted.org/packages/39/ef/7b34f1b122a81b68ed0a7d0e564da9ccdc9a2924c8d6c6b5b11fa3a56970/pydantic_core-2.23.4-cp311-none-win32.whl", hash = "sha256:74b9127ffea03643e998e0c5ad9bd3811d3dac8c676e47db17b0ee7c3c3bf35f", size = 1732940 }, + { url = "https://files.pythonhosted.org/packages/2f/76/37b7e76c645843ff46c1d73e046207311ef298d3f7b2f7d8f6ac60113071/pydantic_core-2.23.4-cp311-none-win_amd64.whl", hash = "sha256:98d134c954828488b153d88ba1f34e14259284f256180ce659e8d83e9c05eaa3", size = 1916804 }, + { url = "https://files.pythonhosted.org/packages/74/7b/8e315f80666194b354966ec84b7d567da77ad927ed6323db4006cf915f3f/pydantic_core-2.23.4-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:f3e0da4ebaef65158d4dfd7d3678aad692f7666877df0002b8a522cdf088f231", size = 1856459 }, + { url = "https://files.pythonhosted.org/packages/14/de/866bdce10ed808323d437612aca1ec9971b981e1c52e5e42ad9b8e17a6f6/pydantic_core-2.23.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f69a8e0b033b747bb3e36a44e7732f0c99f7edd5cea723d45bc0d6e95377ffee", size = 1770007 }, + { url = "https://files.pythonhosted.org/packages/dc/69/8edd5c3cd48bb833a3f7ef9b81d7666ccddd3c9a635225214e044b6e8281/pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:723314c1d51722ab28bfcd5240d858512ffd3116449c557a1336cbe3919beb87", size = 1790245 }, + { url = "https://files.pythonhosted.org/packages/80/33/9c24334e3af796ce80d2274940aae38dd4e5676298b4398eff103a79e02d/pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bb2802e667b7051a1bebbfe93684841cc9351004e2badbd6411bf357ab8d5ac8", size = 1801260 }, + { url = "https://files.pythonhosted.org/packages/a5/6f/e9567fd90104b79b101ca9d120219644d3314962caa7948dd8b965e9f83e/pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d18ca8148bebe1b0a382a27a8ee60350091a6ddaf475fa05ef50dc35b5df6327", size = 1996872 }, + { url = "https://files.pythonhosted.org/packages/2d/ad/b5f0fe9e6cfee915dd144edbd10b6e9c9c9c9d7a56b69256d124b8ac682e/pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:33e3d65a85a2a4a0dc3b092b938a4062b1a05f3a9abde65ea93b233bca0e03f2", size = 2661617 }, + { url = "https://files.pythonhosted.org/packages/06/c8/7d4b708f8d05a5cbfda3243aad468052c6e99de7d0937c9146c24d9f12e9/pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:128585782e5bfa515c590ccee4b727fb76925dd04a98864182b22e89a4e6ed36", size = 2071831 }, + { url = "https://files.pythonhosted.org/packages/89/4d/3079d00c47f22c9a9a8220db088b309ad6e600a73d7a69473e3a8e5e3ea3/pydantic_core-2.23.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:68665f4c17edcceecc112dfed5dbe6f92261fb9d6054b47d01bf6371a6196126", size = 1917453 }, + { url = "https://files.pythonhosted.org/packages/e9/88/9df5b7ce880a4703fcc2d76c8c2d8eb9f861f79d0c56f4b8f5f2607ccec8/pydantic_core-2.23.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:20152074317d9bed6b7a95ade3b7d6054845d70584216160860425f4fbd5ee9e", size = 1968793 }, + { url = "https://files.pythonhosted.org/packages/e3/b9/41f7efe80f6ce2ed3ee3c2dcfe10ab7adc1172f778cc9659509a79518c43/pydantic_core-2.23.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:9261d3ce84fa1d38ed649c3638feefeae23d32ba9182963e465d58d62203bd24", size = 2116872 }, + { url = "https://files.pythonhosted.org/packages/63/08/b59b7a92e03dd25554b0436554bf23e7c29abae7cce4b1c459cd92746811/pydantic_core-2.23.4-cp312-none-win32.whl", hash = "sha256:4ba762ed58e8d68657fc1281e9bb72e1c3e79cc5d464be146e260c541ec12d84", size = 1738535 }, + { url = "https://files.pythonhosted.org/packages/88/8d/479293e4d39ab409747926eec4329de5b7129beaedc3786eca070605d07f/pydantic_core-2.23.4-cp312-none-win_amd64.whl", hash = "sha256:97df63000f4fea395b2824da80e169731088656d1818a11b95f3b173747b6cd9", size = 1917992 }, + { url = "https://files.pythonhosted.org/packages/ad/ef/16ee2df472bf0e419b6bc68c05bf0145c49247a1095e85cee1463c6a44a1/pydantic_core-2.23.4-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:7530e201d10d7d14abce4fb54cfe5b94a0aefc87da539d0346a484ead376c3cc", size = 1856143 }, + { url = "https://files.pythonhosted.org/packages/da/fa/bc3dbb83605669a34a93308e297ab22be82dfb9dcf88c6cf4b4f264e0a42/pydantic_core-2.23.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:df933278128ea1cd77772673c73954e53a1c95a4fdf41eef97c2b779271bd0bd", size = 1770063 }, + { url = "https://files.pythonhosted.org/packages/4e/48/e813f3bbd257a712303ebdf55c8dc46f9589ec74b384c9f652597df3288d/pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cb3da3fd1b6a5d0279a01877713dbda118a2a4fc6f0d821a57da2e464793f05", size = 1790013 }, + { url = "https://files.pythonhosted.org/packages/b4/e0/56eda3a37929a1d297fcab1966db8c339023bcca0b64c5a84896db3fcc5c/pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:42c6dcb030aefb668a2b7009c85b27f90e51e6a3b4d5c9bc4c57631292015b0d", size = 1801077 }, + { url = "https://files.pythonhosted.org/packages/04/be/5e49376769bfbf82486da6c5c1683b891809365c20d7c7e52792ce4c71f3/pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:696dd8d674d6ce621ab9d45b205df149399e4bb9aa34102c970b721554828510", size = 1996782 }, + { url = "https://files.pythonhosted.org/packages/bc/24/e3ee6c04f1d58cc15f37bcc62f32c7478ff55142b7b3e6d42ea374ea427c/pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2971bb5ffe72cc0f555c13e19b23c85b654dd2a8f7ab493c262071377bfce9f6", size = 2661375 }, + { url = "https://files.pythonhosted.org/packages/c1/f8/11a9006de4e89d016b8de74ebb1db727dc100608bb1e6bbe9d56a3cbbcce/pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8394d940e5d400d04cad4f75c0598665cbb81aecefaca82ca85bd28264af7f9b", size = 2071635 }, + { url = "https://files.pythonhosted.org/packages/7c/45/bdce5779b59f468bdf262a5bc9eecbae87f271c51aef628d8c073b4b4b4c/pydantic_core-2.23.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0dff76e0602ca7d4cdaacc1ac4c005e0ce0dcfe095d5b5259163a80d3a10d327", size = 1916994 }, + { url = "https://files.pythonhosted.org/packages/d8/fa/c648308fe711ee1f88192cad6026ab4f925396d1293e8356de7e55be89b5/pydantic_core-2.23.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:7d32706badfe136888bdea71c0def994644e09fff0bfe47441deaed8e96fdbc6", size = 1968877 }, + { url = "https://files.pythonhosted.org/packages/16/16/b805c74b35607d24d37103007f899abc4880923b04929547ae68d478b7f4/pydantic_core-2.23.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ed541d70698978a20eb63d8c5d72f2cc6d7079d9d90f6b50bad07826f1320f5f", size = 2116814 }, + { url = "https://files.pythonhosted.org/packages/d1/58/5305e723d9fcdf1c5a655e6a4cc2a07128bf644ff4b1d98daf7a9dbf57da/pydantic_core-2.23.4-cp313-none-win32.whl", hash = "sha256:3d5639516376dce1940ea36edf408c554475369f5da2abd45d44621cb616f769", size = 1738360 }, + { url = "https://files.pythonhosted.org/packages/a5/ae/e14b0ff8b3f48e02394d8acd911376b7b66e164535687ef7dc24ea03072f/pydantic_core-2.23.4-cp313-none-win_amd64.whl", hash = "sha256:5a1504ad17ba4210df3a045132a7baeeba5a200e930f57512ee02909fc5c4cb5", size = 1919411 }, +] + [[package]] name = "pygments" version = "2.18.0" @@ -1257,6 +1763,19 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/7b/d6/32fd69744afb53995619bc5effa2a405ae0d343cd3e747d0fbc43fe894ee/pyzmq-26.2.0-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:470d4a4f6d48fb34e92d768b4e8a5cc3780db0d69107abf1cd7ff734b9766eb0", size = 1392485 }, ] +[[package]] +name = "referencing" +version = "0.35.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs" }, + { name = "rpds-py" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/99/5b/73ca1f8e72fff6fa52119dbd185f73a907b1989428917b24cff660129b6d/referencing-0.35.1.tar.gz", hash = "sha256:25b42124a6c8b632a425174f24087783efb348a6f1e0008e63cd4466fedf703c", size = 62991 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/59/2056f61236782a2c86b33906c025d4f4a0b17be0161b63b70fd9e8775d36/referencing-0.35.1-py3-none-any.whl", hash = "sha256:eda6d3234d62814d1c64e305c1331c9a3a6132da475ab6382eaa997b21ee75de", size = 26684 }, +] + [[package]] name = "regex" version = "2024.9.11" @@ -1325,6 +1844,79 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f9/9b/335f9764261e915ed497fcdeb11df5dfd6f7bf257d4a6a2a686d80da4d54/requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6", size = 64928 }, ] +[[package]] +name = "responses" +version = "0.25.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyyaml" }, + { name = "requests" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/67/24/1d67c8974daa502e860b4a5b57ad6de0d7dbc0b1160ef7148189a24a40e1/responses-0.25.3.tar.gz", hash = "sha256:617b9247abd9ae28313d57a75880422d55ec63c29d33d629697590a034358dba", size = 77798 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/12/24/93293d0be0db9da1ed8dfc5e6af700fdd40e8f10a928704dd179db9f03c1/responses-0.25.3-py3-none-any.whl", hash = "sha256:521efcbc82081ab8daa588e08f7e8a64ce79b91c39f6e62199b19159bea7dbcb", size = 55238 }, +] + +[[package]] +name = "rfc3339-validator" +version = "0.1.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/28/ea/a9387748e2d111c3c2b275ba970b735e04e15cdb1eb30693b6b5708c4dbd/rfc3339_validator-0.1.4.tar.gz", hash = "sha256:138a2abdf93304ad60530167e51d2dfb9549521a836871b88d7f4695d0022f6b", size = 5513 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7b/44/4e421b96b67b2daff264473f7465db72fbdf36a07e05494f50300cc7b0c6/rfc3339_validator-0.1.4-py2.py3-none-any.whl", hash = "sha256:24f6ec1eda14ef823da9e36ec7113124b39c04d50a4d3d3a3c2859577e7791fa", size = 3490 }, +] + +[[package]] +name = "rpds-py" +version = "0.20.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/55/64/b693f262791b818880d17268f3f8181ef799b0d187f6f731b1772e05a29a/rpds_py-0.20.0.tar.gz", hash = "sha256:d72a210824facfdaf8768cf2d7ca25a042c30320b3020de2fa04640920d4e121", size = 25814 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ab/2a/191374c52d7be0b056cc2a04d718d2244c152f915d4a8d2db2aacc526189/rpds_py-0.20.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:ac2f4f7a98934c2ed6505aead07b979e6f999389f16b714448fb39bbaa86a489", size = 318369 }, + { url = "https://files.pythonhosted.org/packages/0e/6a/2c9fdcc6d235ac0d61ec4fd9981184689c3e682abd05e3caa49bccb9c298/rpds_py-0.20.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:220002c1b846db9afd83371d08d239fdc865e8f8c5795bbaec20916a76db3318", size = 311303 }, + { url = "https://files.pythonhosted.org/packages/d2/b2/725487d29633f64ef8f9cbf4729111a0b61702c8f8e94db1653930f52cce/rpds_py-0.20.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8d7919548df3f25374a1f5d01fbcd38dacab338ef5f33e044744b5c36729c8db", size = 366424 }, + { url = "https://files.pythonhosted.org/packages/7a/8c/668195ab9226d01b7cf7cd9e59c1c0be1df05d602df7ec0cf46f857dcf59/rpds_py-0.20.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:758406267907b3781beee0f0edfe4a179fbd97c0be2e9b1154d7f0a1279cf8e5", size = 368359 }, + { url = "https://files.pythonhosted.org/packages/52/28/356f6a39c1adeb02cf3e5dd526f5e8e54e17899bef045397abcfbf50dffa/rpds_py-0.20.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3d61339e9f84a3f0767b1995adfb171a0d00a1185192718a17af6e124728e0f5", size = 394886 }, + { url = "https://files.pythonhosted.org/packages/a2/65/640fb1a89080a8fb6f4bebd3dafb65a2edba82e2e44c33e6eb0f3e7956f1/rpds_py-0.20.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1259c7b3705ac0a0bd38197565a5d603218591d3f6cee6e614e380b6ba61c6f6", size = 432416 }, + { url = "https://files.pythonhosted.org/packages/a7/e8/85835077b782555d6b3416874b702ea6ebd7db1f145283c9252968670dd5/rpds_py-0.20.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5c1dc0f53856b9cc9a0ccca0a7cc61d3d20a7088201c0937f3f4048c1718a209", size = 354819 }, + { url = "https://files.pythonhosted.org/packages/4f/87/1ac631e923d65cbf36fbcfc6eaa702a169496de1311e54be142f178e53ee/rpds_py-0.20.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7e60cb630f674a31f0368ed32b2a6b4331b8350d67de53c0359992444b116dd3", size = 373282 }, + { url = "https://files.pythonhosted.org/packages/e4/ce/cb316f7970189e217b998191c7cf0da2ede3d5437932c86a7210dc1e9994/rpds_py-0.20.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:dbe982f38565bb50cb7fb061ebf762c2f254ca3d8c20d4006878766e84266272", size = 541540 }, + { url = "https://files.pythonhosted.org/packages/90/d7/4112d7655ec8aff168ecc91d4ceb51c557336edde7e6ccf6463691a2f253/rpds_py-0.20.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:514b3293b64187172bc77c8fb0cdae26981618021053b30d8371c3a902d4d5ad", size = 547640 }, + { url = "https://files.pythonhosted.org/packages/ab/44/4f61d64dfed98cc71623f3a7fcb612df636a208b4b2c6611eaa985e130a9/rpds_py-0.20.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d0a26ffe9d4dd35e4dfdd1e71f46401cff0181c75ac174711ccff0459135fa58", size = 525555 }, + { url = "https://files.pythonhosted.org/packages/35/f2/a862d81eacb21f340d584cd1c749c289979f9a60e9229f78bffc0418a199/rpds_py-0.20.0-cp311-none-win32.whl", hash = "sha256:89c19a494bf3ad08c1da49445cc5d13d8fefc265f48ee7e7556839acdacf69d0", size = 199338 }, + { url = "https://files.pythonhosted.org/packages/cc/ec/77d0674f9af4872919f3738018558dd9d37ad3f7ad792d062eadd4af7cba/rpds_py-0.20.0-cp311-none-win_amd64.whl", hash = "sha256:c638144ce971df84650d3ed0096e2ae7af8e62ecbbb7b201c8935c370df00a2c", size = 213585 }, + { url = "https://files.pythonhosted.org/packages/89/b7/f9682c5cc37fcc035f4a0fc33c1fe92ec9cbfdee0cdfd071cf948f53e0df/rpds_py-0.20.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a84ab91cbe7aab97f7446652d0ed37d35b68a465aeef8fc41932a9d7eee2c1a6", size = 321468 }, + { url = "https://files.pythonhosted.org/packages/b8/ad/fc82be4eaceb8d444cb6fc1956ce972b3a0795104279de05e0e4131d0a47/rpds_py-0.20.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:56e27147a5a4c2c21633ff8475d185734c0e4befd1c989b5b95a5d0db699b21b", size = 313062 }, + { url = "https://files.pythonhosted.org/packages/0e/1c/6039e80b13a08569a304dc13476dc986352dca4598e909384db043b4e2bb/rpds_py-0.20.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2580b0c34583b85efec8c5c5ec9edf2dfe817330cc882ee972ae650e7b5ef739", size = 370168 }, + { url = "https://files.pythonhosted.org/packages/dc/c9/5b9aa35acfb58946b4b785bc8e700ac313669e02fb100f3efa6176a83e81/rpds_py-0.20.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b80d4a7900cf6b66bb9cee5c352b2d708e29e5a37fe9bf784fa97fc11504bf6c", size = 371376 }, + { url = "https://files.pythonhosted.org/packages/7b/dd/0e0dbeb70d8a5357d2814764d467ded98d81d90d3570de4fb05ec7224f6b/rpds_py-0.20.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:50eccbf054e62a7b2209b28dc7a22d6254860209d6753e6b78cfaeb0075d7bee", size = 397200 }, + { url = "https://files.pythonhosted.org/packages/e4/da/a47d931eb688ccfd77a7389e45935c79c41e8098d984d87335004baccb1d/rpds_py-0.20.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:49a8063ea4296b3a7e81a5dfb8f7b2d73f0b1c20c2af401fb0cdf22e14711a96", size = 426824 }, + { url = "https://files.pythonhosted.org/packages/0f/f7/a59a673594e6c2ff2dbc44b00fd4ecdec2fc399bb6a7bd82d612699a0121/rpds_py-0.20.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ea438162a9fcbee3ecf36c23e6c68237479f89f962f82dae83dc15feeceb37e4", size = 357967 }, + { url = "https://files.pythonhosted.org/packages/5f/61/3ba1905396b2cb7088f9503a460b87da33452da54d478cb9241f6ad16d00/rpds_py-0.20.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:18d7585c463087bddcfa74c2ba267339f14f2515158ac4db30b1f9cbdb62c8ef", size = 378905 }, + { url = "https://files.pythonhosted.org/packages/08/31/6d0df9356b4edb0a3a077f1ef714e25ad21f9f5382fc490c2383691885ea/rpds_py-0.20.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d4c7d1a051eeb39f5c9547e82ea27cbcc28338482242e3e0b7768033cb083821", size = 546348 }, + { url = "https://files.pythonhosted.org/packages/ae/15/d33c021de5cb793101df9961c3c746dfc476953dbbf5db337d8010dffd4e/rpds_py-0.20.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:e4df1e3b3bec320790f699890d41c59d250f6beda159ea3c44c3f5bac1976940", size = 553152 }, + { url = "https://files.pythonhosted.org/packages/70/2d/5536d28c507a4679179ab15aa0049440e4d3dd6752050fa0843ed11e9354/rpds_py-0.20.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2cf126d33a91ee6eedc7f3197b53e87a2acdac63602c0f03a02dd69e4b138174", size = 528807 }, + { url = "https://files.pythonhosted.org/packages/e3/62/7ebe6ec0d3dd6130921f8cffb7e34afb7f71b3819aa0446a24c5e81245ec/rpds_py-0.20.0-cp312-none-win32.whl", hash = "sha256:8bc7690f7caee50b04a79bf017a8d020c1f48c2a1077ffe172abec59870f1139", size = 200993 }, + { url = "https://files.pythonhosted.org/packages/ec/2f/b938864d66b86a6e4acadefdc56de75ef56f7cafdfd568a6464605457bd5/rpds_py-0.20.0-cp312-none-win_amd64.whl", hash = "sha256:0e13e6952ef264c40587d510ad676a988df19adea20444c2b295e536457bc585", size = 214458 }, + { url = "https://files.pythonhosted.org/packages/99/32/43b919a0a423c270a838ac2726b1c7168b946f2563fd99a51aaa9692d00f/rpds_py-0.20.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:aa9a0521aeca7d4941499a73ad7d4f8ffa3d1affc50b9ea11d992cd7eff18a29", size = 321465 }, + { url = "https://files.pythonhosted.org/packages/58/a9/c4d899cb28e9e47b0ff12462e8f827381f243176036f17bef9c1604667f2/rpds_py-0.20.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4a1f1d51eccb7e6c32ae89243cb352389228ea62f89cd80823ea7dd1b98e0b91", size = 312900 }, + { url = "https://files.pythonhosted.org/packages/8f/90/9e51670575b5dfaa8c823369ef7d943087bfb73d4f124a99ad6ef19a2b26/rpds_py-0.20.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8a86a9b96070674fc88b6f9f71a97d2c1d3e5165574615d1f9168ecba4cecb24", size = 370973 }, + { url = "https://files.pythonhosted.org/packages/fc/c1/523f2a03f853fc0d4c1acbef161747e9ab7df0a8abf6236106e333540921/rpds_py-0.20.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6c8ef2ebf76df43f5750b46851ed1cdf8f109d7787ca40035fe19fbdc1acc5a7", size = 370890 }, + { url = "https://files.pythonhosted.org/packages/51/ca/2458a771f16b0931de4d384decbe43016710bc948036c8f4562d6e063437/rpds_py-0.20.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b74b25f024b421d5859d156750ea9a65651793d51b76a2e9238c05c9d5f203a9", size = 397174 }, + { url = "https://files.pythonhosted.org/packages/00/7d/6e06807f6305ea2408b364efb0eef83a6e21b5e7b5267ad6b473b9a7e416/rpds_py-0.20.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:57eb94a8c16ab08fef6404301c38318e2c5a32216bf5de453e2714c964c125c8", size = 426449 }, + { url = "https://files.pythonhosted.org/packages/8c/d1/6c9e65260a819a1714510a7d69ac1d68aa23ee9ce8a2d9da12187263c8fc/rpds_py-0.20.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e1940dae14e715e2e02dfd5b0f64a52e8374a517a1e531ad9412319dc3ac7879", size = 357698 }, + { url = "https://files.pythonhosted.org/packages/5d/fb/ecea8b5286d2f03eec922be7173a03ed17278944f7c124348f535116db15/rpds_py-0.20.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d20277fd62e1b992a50c43f13fbe13277a31f8c9f70d59759c88f644d66c619f", size = 378530 }, + { url = "https://files.pythonhosted.org/packages/e3/e3/ac72f858957f52a109c588589b73bd2fad4a0fc82387fb55fb34aeb0f9cd/rpds_py-0.20.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:06db23d43f26478303e954c34c75182356ca9aa7797d22c5345b16871ab9c45c", size = 545753 }, + { url = "https://files.pythonhosted.org/packages/b2/a4/a27683b519d5fc98e4390a3b130117d80fd475c67aeda8aac83c0e8e326a/rpds_py-0.20.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b2a5db5397d82fa847e4c624b0c98fe59d2d9b7cf0ce6de09e4d2e80f8f5b3f2", size = 552443 }, + { url = "https://files.pythonhosted.org/packages/a1/ed/c074d248409b4432b1ccb2056974175fa0af2d1bc1f9c21121f80a358fa3/rpds_py-0.20.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5a35df9f5548fd79cb2f52d27182108c3e6641a4feb0f39067911bf2adaa3e57", size = 528380 }, + { url = "https://files.pythonhosted.org/packages/d5/bd/04caf938895d2d78201e89c0c8a94dfd9990c34a19ff52fb01d0912343e3/rpds_py-0.20.0-cp313-none-win32.whl", hash = "sha256:fd2d84f40633bc475ef2d5490b9c19543fbf18596dcb1b291e3a12ea5d722f7a", size = 200540 }, + { url = "https://files.pythonhosted.org/packages/95/cc/109eb8b9863680411ae703664abacaa035820c7755acc9686d5dd02cdd2e/rpds_py-0.20.0-cp313-none-win_amd64.whl", hash = "sha256:9bc2d153989e3216b0559251b0c260cfd168ec78b1fac33dd485750a228db5a2", size = 214111 }, +] + [[package]] name = "s3transfer" version = "0.10.3" @@ -1337,6 +1929,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e5/c0/b0fba8259b61c938c9733da9346b9f93e00881a9db22aafdd72f6ae0ec05/s3transfer-0.10.3-py3-none-any.whl", hash = "sha256:263ed587a5803c6c708d3ce44dc4dfedaab4c1a32e8329bab818933d79ddcf5d", size = 82625 }, ] +[[package]] +name = "setuptools" +version = "75.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ed/22/a438e0caa4576f8c383fa4d35f1cc01655a46c75be358960d815bfbb12bd/setuptools-75.3.0.tar.gz", hash = "sha256:fba5dd4d766e97be1b1681d98712680ae8f2f26d7881245f2ce9e40714f1a686", size = 1351577 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/90/12/282ee9bce8b58130cb762fbc9beabd531549952cac11fc56add11dcb7ea0/setuptools-75.3.0-py3-none-any.whl", hash = "sha256:f2504966861356aa38616760c0f66568e535562374995367b4e69c7143cf6bcd", size = 1251070 }, +] + [[package]] name = "six" version = "1.16.0" @@ -1360,6 +1961,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f1/7b/ce1eafaf1a76852e2ec9b22edecf1daa58175c090266e9f6c64afcd81d91/stack_data-0.6.3-py3-none-any.whl", hash = "sha256:d5558e0c25a4cb0853cddad3d77da9891a08cb85dd9f9f91b9f8cd66e511e695", size = 24521 }, ] +[[package]] +name = "sympy" +version = "1.13.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mpmath" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/11/8a/5a7fd6284fa8caac23a26c9ddf9c30485a48169344b4bd3b0f02fef1890f/sympy-1.13.3.tar.gz", hash = "sha256:b27fd2c6530e0ab39e275fc9b683895367e51d5da91baa8d3d64db2565fec4d9", size = 7533196 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/99/ff/c87e0622b1dadea79d2fb0b25ade9ed98954c9033722eb707053d310d4f3/sympy-1.13.3-py3-none-any.whl", hash = "sha256:54612cf55a62755ee71824ce692986f23c88ffa77207b30c1368eda4a7060f73", size = 6189483 }, +] + [[package]] name = "test-env" version = "0.1.0" @@ -1367,6 +1980,7 @@ source = { virtual = "." } [package.dev-dependencies] dev = [ + { name = "arro3-core" }, { name = "black" }, { name = "boto3" }, { name = "fsspec" }, @@ -1377,6 +1991,7 @@ dev = [ { name = "mkdocs" }, { name = "mkdocs-material", extra = ["imaging"] }, { name = "mkdocstrings", extra = ["python"] }, + { name = "moto", extra = ["s3", "server"] }, { name = "pandas" }, { name = "pip" }, { name = "pyarrow" }, @@ -1388,6 +2003,7 @@ dev = [ [package.metadata.requires-dev] dev = [ + { name = "arro3-core", specifier = ">=0.4.2" }, { name = "black", specifier = ">=24.10.0" }, { name = "boto3", specifier = ">=1.35.38" }, { name = "fsspec", specifier = ">=2024.10.0" }, @@ -1398,6 +2014,7 @@ dev = [ { name = "mkdocs", specifier = ">=1.6.1" }, { name = "mkdocs-material", extras = ["imaging"], specifier = ">=9.5.40" }, { name = "mkdocstrings", extras = ["python"], specifier = ">=0.26.1" }, + { name = "moto", extras = ["s3", "server"], specifier = ">=5.0.18" }, { name = "pandas", specifier = ">=2.2.3" }, { name = "pip", specifier = ">=24.2" }, { name = "pyarrow", specifier = ">=17.0.0" }, @@ -1525,6 +2142,56 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f4/24/2a3e3df732393fed8b3ebf2ec078f05546de641fe1b667ee316ec1dcf3b7/webencodings-0.5.1-py2.py3-none-any.whl", hash = "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78", size = 11774 }, ] +[[package]] +name = "werkzeug" +version = "3.0.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d4/f9/0ba83eaa0df9b9e9d1efeb2ea351d0677c37d41ee5d0f91e98423c7281c9/werkzeug-3.0.6.tar.gz", hash = "sha256:a8dd59d4de28ca70471a34cba79bed5f7ef2e036a76b3ab0835474246eb41f8d", size = 805170 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6c/69/05837f91dfe42109203ffa3e488214ff86a6d68b2ed6c167da6cdc42349b/werkzeug-3.0.6-py3-none-any.whl", hash = "sha256:1bc0c2310d2fbb07b1dd1105eba2f7af72f322e1e455f2f93c993bee8c8a5f17", size = 227979 }, +] + +[[package]] +name = "wrapt" +version = "1.16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/95/4c/063a912e20bcef7124e0df97282a8af3ff3e4b603ce84c481d6d7346be0a/wrapt-1.16.0.tar.gz", hash = "sha256:5f370f952971e7d17c7d1ead40e49f32345a7f7a5373571ef44d800d06b1899d", size = 53972 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fd/03/c188ac517f402775b90d6f312955a5e53b866c964b32119f2ed76315697e/wrapt-1.16.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1a5db485fe2de4403f13fafdc231b0dbae5eca4359232d2efc79025527375b09", size = 37313 }, + { url = "https://files.pythonhosted.org/packages/0f/16/ea627d7817394db04518f62934a5de59874b587b792300991b3c347ff5e0/wrapt-1.16.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:75ea7d0ee2a15733684badb16de6794894ed9c55aa5e9903260922f0482e687d", size = 38164 }, + { url = "https://files.pythonhosted.org/packages/7f/a7/f1212ba098f3de0fd244e2de0f8791ad2539c03bef6c05a9fcb03e45b089/wrapt-1.16.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a452f9ca3e3267cd4d0fcf2edd0d035b1934ac2bd7e0e57ac91ad6b95c0c6389", size = 80890 }, + { url = "https://files.pythonhosted.org/packages/b7/96/bb5e08b3d6db003c9ab219c487714c13a237ee7dcc572a555eaf1ce7dc82/wrapt-1.16.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:43aa59eadec7890d9958748db829df269f0368521ba6dc68cc172d5d03ed8060", size = 73118 }, + { url = "https://files.pythonhosted.org/packages/6e/52/2da48b35193e39ac53cfb141467d9f259851522d0e8c87153f0ba4205fb1/wrapt-1.16.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:72554a23c78a8e7aa02abbd699d129eead8b147a23c56e08d08dfc29cfdddca1", size = 80746 }, + { url = "https://files.pythonhosted.org/packages/11/fb/18ec40265ab81c0e82a934de04596b6ce972c27ba2592c8b53d5585e6bcd/wrapt-1.16.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d2efee35b4b0a347e0d99d28e884dfd82797852d62fcd7ebdeee26f3ceb72cf3", size = 85668 }, + { url = "https://files.pythonhosted.org/packages/0f/ef/0ecb1fa23145560431b970418dce575cfaec555ab08617d82eb92afc7ccf/wrapt-1.16.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:6dcfcffe73710be01d90cae08c3e548d90932d37b39ef83969ae135d36ef3956", size = 78556 }, + { url = "https://files.pythonhosted.org/packages/25/62/cd284b2b747f175b5a96cbd8092b32e7369edab0644c45784871528eb852/wrapt-1.16.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:eb6e651000a19c96f452c85132811d25e9264d836951022d6e81df2fff38337d", size = 85712 }, + { url = "https://files.pythonhosted.org/packages/e5/a7/47b7ff74fbadf81b696872d5ba504966591a3468f1bc86bca2f407baef68/wrapt-1.16.0-cp311-cp311-win32.whl", hash = "sha256:66027d667efe95cc4fa945af59f92c5a02c6f5bb6012bff9e60542c74c75c362", size = 35327 }, + { url = "https://files.pythonhosted.org/packages/cf/c3/0084351951d9579ae83a3d9e38c140371e4c6b038136909235079f2e6e78/wrapt-1.16.0-cp311-cp311-win_amd64.whl", hash = "sha256:aefbc4cb0a54f91af643660a0a150ce2c090d3652cf4052a5397fb2de549cd89", size = 37523 }, + { url = "https://files.pythonhosted.org/packages/92/17/224132494c1e23521868cdd57cd1e903f3b6a7ba6996b7b8f077ff8ac7fe/wrapt-1.16.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:5eb404d89131ec9b4f748fa5cfb5346802e5ee8836f57d516576e61f304f3b7b", size = 37614 }, + { url = "https://files.pythonhosted.org/packages/6a/d7/cfcd73e8f4858079ac59d9db1ec5a1349bc486ae8e9ba55698cc1f4a1dff/wrapt-1.16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9090c9e676d5236a6948330e83cb89969f433b1943a558968f659ead07cb3b36", size = 38316 }, + { url = "https://files.pythonhosted.org/packages/7e/79/5ff0a5c54bda5aec75b36453d06be4f83d5cd4932cc84b7cb2b52cee23e2/wrapt-1.16.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:94265b00870aa407bd0cbcfd536f17ecde43b94fb8d228560a1e9d3041462d73", size = 86322 }, + { url = "https://files.pythonhosted.org/packages/c4/81/e799bf5d419f422d8712108837c1d9bf6ebe3cb2a81ad94413449543a923/wrapt-1.16.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f2058f813d4f2b5e3a9eb2eb3faf8f1d99b81c3e51aeda4b168406443e8ba809", size = 79055 }, + { url = "https://files.pythonhosted.org/packages/62/62/30ca2405de6a20448ee557ab2cd61ab9c5900be7cbd18a2639db595f0b98/wrapt-1.16.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:98b5e1f498a8ca1858a1cdbffb023bfd954da4e3fa2c0cb5853d40014557248b", size = 87291 }, + { url = "https://files.pythonhosted.org/packages/49/4e/5d2f6d7b57fc9956bf06e944eb00463551f7d52fc73ca35cfc4c2cdb7aed/wrapt-1.16.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:14d7dc606219cdd7405133c713f2c218d4252f2a469003f8c46bb92d5d095d81", size = 90374 }, + { url = "https://files.pythonhosted.org/packages/a6/9b/c2c21b44ff5b9bf14a83252a8b973fb84923764ff63db3e6dfc3895cf2e0/wrapt-1.16.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:49aac49dc4782cb04f58986e81ea0b4768e4ff197b57324dcbd7699c5dfb40b9", size = 83896 }, + { url = "https://files.pythonhosted.org/packages/14/26/93a9fa02c6f257df54d7570dfe8011995138118d11939a4ecd82cb849613/wrapt-1.16.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:418abb18146475c310d7a6dc71143d6f7adec5b004ac9ce08dc7a34e2babdc5c", size = 91738 }, + { url = "https://files.pythonhosted.org/packages/a2/5b/4660897233eb2c8c4de3dc7cefed114c61bacb3c28327e64150dc44ee2f6/wrapt-1.16.0-cp312-cp312-win32.whl", hash = "sha256:685f568fa5e627e93f3b52fda002c7ed2fa1800b50ce51f6ed1d572d8ab3e7fc", size = 35568 }, + { url = "https://files.pythonhosted.org/packages/5c/cc/8297f9658506b224aa4bd71906447dea6bb0ba629861a758c28f67428b91/wrapt-1.16.0-cp312-cp312-win_amd64.whl", hash = "sha256:dcdba5c86e368442528f7060039eda390cc4091bfd1dca41e8046af7c910dda8", size = 37653 }, + { url = "https://files.pythonhosted.org/packages/ff/21/abdedb4cdf6ff41ebf01a74087740a709e2edb146490e4d9beea054b0b7a/wrapt-1.16.0-py3-none-any.whl", hash = "sha256:6906c4100a8fcbf2fa735f6059214bb13b97f75b1a61777fcf6432121ef12ef1", size = 23362 }, +] + +[[package]] +name = "xmltodict" +version = "0.14.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/50/05/51dcca9a9bf5e1bce52582683ce50980bcadbc4fa5143b9f2b19ab99958f/xmltodict-0.14.2.tar.gz", hash = "sha256:201e7c28bb210e374999d1dde6382923ab0ed1a8a5faeece48ab525b7810a553", size = 51942 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d6/45/fc303eb433e8a2a271739c98e953728422fa61a3c1f36077a49e395c972e/xmltodict-0.14.2-py2.py3-none-any.whl", hash = "sha256:20cc7d723ed729276e808f26fb6b3599f786cbc37e06c65e192ba77c40f20aac", size = 9981 }, +] + [[package]] name = "zipp" version = "3.20.2"