├── .github └── workflows │ ├── CI.yml │ └── pr-tests.yml ├── .gitignore ├── CONTRIBUTING.md ├── Cargo.lock ├── Cargo.toml ├── LICENSE.md ├── README.md ├── build.rs ├── docs └── api.md ├── example.py ├── examples ├── batch │ ├── .gitignore │ ├── README.md │ └── main.py ├── encryption │ ├── .gitignore │ ├── README.md │ └── main.py ├── execute_script.py ├── local │ ├── .gitignore │ ├── README.md │ └── main.py ├── memory │ ├── README.md │ └── main.py ├── remote │ ├── README.md │ └── main.py ├── remote_connect.py ├── sqlalchemy │ ├── dialect.py │ └── example.py ├── statements.sql ├── sync │ ├── .gitignore │ ├── README.md │ └── main.py ├── sync_write.py ├── transaction │ ├── .gitignore │ ├── README.md │ └── main.py ├── vector.py └── vector │ ├── .gitignore │ ├── README.md │ └── main.py ├── perf-libsql.py ├── perf-sqlite3.py ├── pyproject.toml ├── shell.nix ├── src └── lib.rs └── tests └── test_suite.py /.github/workflows/CI.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | tags: 8 | - v* 9 | pull_request: 10 | workflow_dispatch: 11 | 12 | permissions: 13 | contents: read 14 | 15 | jobs: 16 | linux: 17 | runs-on: ubuntu-latest 18 | strategy: 19 | matrix: 20 | target: [x86_64] 21 | steps: 22 | - uses: actions/checkout@v3 23 | - uses: actions/setup-python@v4 24 | with: 25 | python-version: '3.10' 26 | - name: Build wheels 27 | uses: PyO3/maturin-action@v1 28 | with: 29 | target: ${{ matrix.target }} 30 | args: --release --out dist --find-interpreter 31 | sccache: 'true' 32 | manylinux: auto 33 | - name: Upload wheels 34 | uses: actions/upload-artifact@v4 35 | with: 36 | name: wheels-linux-${{ matrix.target }} 37 | path: dist 38 | 39 | macos-x86_64: 40 | runs-on: macos-13 41 | strategy: 42 | matrix: 43 | target: [x86_64] 44 | steps: 45 | - uses: actions/checkout@v3 46 | - uses: actions/setup-python@v4 47 | with: 48 | python-version: '3.10' 49 | - name: Build wheels 50 | uses: PyO3/maturin-action@v1 51 | env: 52 | CXX: clang++ 53 | CC: clang 54 | with: 55 | target: ${{ matrix.target }} 56 | args: --release --out dist --find-interpreter 57 | sccache: 'true' 58 | - name: Upload wheels 59 | uses: actions/upload-artifact@v4 60 | with: 61 | name: wheels-macos-x86_64 62 | path: dist 63 | 64 | macos-arm64: 65 | runs-on: macos-13-xlarge 66 | strategy: 67 | matrix: 68 | target: [aarch64] 69 | steps: 70 | - uses: actions/checkout@v3 71 | - uses: actions/setup-python@v4 72 | env: 73 | CXX: clang++ 74 | CC: clang 75 | with: 76 | python-version: '3.10' 77 | - name: Build wheels 78 | uses: PyO3/maturin-action@v1 79 | with: 80 | target: ${{ matrix.target }} 81 | args: --release --out dist --find-interpreter 82 | sccache: 'true' 83 | - name: Upload wheels 84 | uses: actions/upload-artifact@v4 85 | with: 86 | name: wheels-macos-arm64 87 | path: dist 88 | 89 | sdist: 90 | runs-on: ubuntu-latest 91 | steps: 92 | - uses: actions/checkout@v3 93 | - name: Build sdist 94 | uses: PyO3/maturin-action@v1 95 | with: 96 | command: sdist 97 | args: --out dist 98 | - name: Upload sdist 99 | uses: actions/upload-artifact@v4 100 | with: 101 | name: sdist 102 | path: dist 103 | 104 | release: 105 | name: Release 106 | runs-on: ubuntu-latest 107 | if: "startsWith(github.ref, 'refs/tags/')" 108 | needs: [linux, macos-arm64, macos-x86_64, sdist] 109 | steps: 110 | - uses: actions/download-artifact@v4 111 | with: 112 | pattern: wheels-* 113 | path: dist 114 | merge-multiple: true 115 | - uses: actions/download-artifact@v4 116 | with: 117 | name: sdist 118 | path: dist 119 | - name: Publish to PyPI 120 | uses: PyO3/maturin-action@v1 121 | env: 122 | MATURIN_PYPI_TOKEN: ${{ secrets.PYPI_API_TOKEN }} 123 | with: 124 | command: upload 125 | args: --skip-existing dist/* 126 | 127 | -------------------------------------------------------------------------------- /.github/workflows/pr-tests.yml: -------------------------------------------------------------------------------- 1 | name: pytest 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | test: 10 | name: Run pytest 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - name: Checkout code 15 | uses: actions/checkout@v2 16 | 17 | - name: Set up Python 18 | uses: actions/setup-python@v2 19 | with: 20 | python-version: '>=3.10' 21 | 22 | - name: Set up cargo cache 23 | uses: actions/cache@v3 24 | continue-on-error: false 25 | with: 26 | path: | 27 | ~/.cargo/bin/ 28 | ~/.cargo/registry/index/ 29 | ~/.cargo/registry/cache/ 30 | ~/.cargo/git/db/ 31 | target/ 32 | key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} 33 | restore-keys: ${{ runner.os }}-cargo- 34 | 35 | - name: Buidl and run tests 36 | run: | 37 | python3 -m venv .env # maturin requires a virtualenv 38 | source .env/bin/activate 39 | pip3 install maturin pytest 40 | maturin develop 41 | pytest 42 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .env 2 | target 3 | *.db 4 | client_wal_index 5 | tests/__pycache__ 6 | venv 7 | env -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to libSQL Python SDK 2 | 3 | ## Developing 4 | 5 | Setup the development environment: 6 | 7 | ```sh 8 | python3 -m venv .env 9 | source .env/bin/activate 10 | pip3 install maturin pyperf pytest 11 | ``` 12 | 13 | Or you can use NIX to drop you into a shell with everything installed 14 | 15 | ``` 16 | nix-shell 17 | ``` 18 | 19 | Build the development version and use it: 20 | 21 | ``` 22 | maturin develop && python3 example.py 23 | ``` 24 | 25 | Run the tests: 26 | 27 | ```sh 28 | pytest 29 | ``` 30 | 31 | Run the libSQL benchmarks: 32 | 33 | ```sh 34 | python3 perf-libsql.py 35 | ``` 36 | 37 | Run the SQLite benchmarks for comparison: 38 | 39 | ```sh 40 | python3 perf-sqlite3.py 41 | ``` 42 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 4 4 | 5 | [[package]] 6 | name = "addr2line" 7 | version = "0.22.0" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "6e4503c46a5c0c7844e948c9a4d6acd9f50cccb4de1c48eb9e291ea17470c678" 10 | dependencies = [ 11 | "gimli", 12 | ] 13 | 14 | [[package]] 15 | name = "adler" 16 | version = "1.0.2" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" 19 | 20 | [[package]] 21 | name = "aes" 22 | version = "0.8.4" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" 25 | dependencies = [ 26 | "cfg-if", 27 | "cipher", 28 | "cpufeatures", 29 | ] 30 | 31 | [[package]] 32 | name = "ahash" 33 | version = "0.8.11" 34 | source = "registry+https://github.com/rust-lang/crates.io-index" 35 | checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" 36 | dependencies = [ 37 | "cfg-if", 38 | "once_cell", 39 | "version_check", 40 | "zerocopy", 41 | ] 42 | 43 | [[package]] 44 | name = "aho-corasick" 45 | version = "1.1.3" 46 | source = "registry+https://github.com/rust-lang/crates.io-index" 47 | checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" 48 | dependencies = [ 49 | "memchr", 50 | ] 51 | 52 | [[package]] 53 | name = "allocator-api2" 54 | version = "0.2.18" 55 | source = "registry+https://github.com/rust-lang/crates.io-index" 56 | checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" 57 | 58 | [[package]] 59 | name = "android-tzdata" 60 | version = "0.1.1" 61 | source = "registry+https://github.com/rust-lang/crates.io-index" 62 | checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" 63 | 64 | [[package]] 65 | name = "android_system_properties" 66 | version = "0.1.5" 67 | source = "registry+https://github.com/rust-lang/crates.io-index" 68 | checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" 69 | dependencies = [ 70 | "libc", 71 | ] 72 | 73 | [[package]] 74 | name = "anyhow" 75 | version = "1.0.86" 76 | source = "registry+https://github.com/rust-lang/crates.io-index" 77 | checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" 78 | 79 | [[package]] 80 | name = "async-stream" 81 | version = "0.3.5" 82 | source = "registry+https://github.com/rust-lang/crates.io-index" 83 | checksum = "cd56dd203fef61ac097dd65721a419ddccb106b2d2b70ba60a6b529f03961a51" 84 | dependencies = [ 85 | "async-stream-impl", 86 | "futures-core", 87 | "pin-project-lite", 88 | ] 89 | 90 | [[package]] 91 | name = "async-stream-impl" 92 | version = "0.3.5" 93 | source = "registry+https://github.com/rust-lang/crates.io-index" 94 | checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" 95 | dependencies = [ 96 | "proc-macro2", 97 | "quote", 98 | "syn 2.0.72", 99 | ] 100 | 101 | [[package]] 102 | name = "async-trait" 103 | version = "0.1.81" 104 | source = "registry+https://github.com/rust-lang/crates.io-index" 105 | checksum = "6e0c28dcc82d7c8ead5cb13beb15405b57b8546e93215673ff8ca0349a028107" 106 | dependencies = [ 107 | "proc-macro2", 108 | "quote", 109 | "syn 2.0.72", 110 | ] 111 | 112 | [[package]] 113 | name = "autocfg" 114 | version = "1.3.0" 115 | source = "registry+https://github.com/rust-lang/crates.io-index" 116 | checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" 117 | 118 | [[package]] 119 | name = "axum" 120 | version = "0.6.20" 121 | source = "registry+https://github.com/rust-lang/crates.io-index" 122 | checksum = "3b829e4e32b91e643de6eafe82b1d90675f5874230191a4ffbc1b336dec4d6bf" 123 | dependencies = [ 124 | "async-trait", 125 | "axum-core", 126 | "bitflags 1.3.2", 127 | "bytes", 128 | "futures-util", 129 | "http", 130 | "http-body", 131 | "hyper", 132 | "itoa", 133 | "matchit", 134 | "memchr", 135 | "mime", 136 | "percent-encoding", 137 | "pin-project-lite", 138 | "rustversion", 139 | "serde", 140 | "sync_wrapper", 141 | "tower", 142 | "tower-layer", 143 | "tower-service", 144 | ] 145 | 146 | [[package]] 147 | name = "axum-core" 148 | version = "0.3.4" 149 | source = "registry+https://github.com/rust-lang/crates.io-index" 150 | checksum = "759fa577a247914fd3f7f76d62972792636412fbfd634cd452f6a385a74d2d2c" 151 | dependencies = [ 152 | "async-trait", 153 | "bytes", 154 | "futures-util", 155 | "http", 156 | "http-body", 157 | "mime", 158 | "rustversion", 159 | "tower-layer", 160 | "tower-service", 161 | ] 162 | 163 | [[package]] 164 | name = "backtrace" 165 | version = "0.3.73" 166 | source = "registry+https://github.com/rust-lang/crates.io-index" 167 | checksum = "5cc23269a4f8976d0a4d2e7109211a419fe30e8d88d677cd60b6bc79c5732e0a" 168 | dependencies = [ 169 | "addr2line", 170 | "cc", 171 | "cfg-if", 172 | "libc", 173 | "miniz_oxide", 174 | "object", 175 | "rustc-demangle", 176 | ] 177 | 178 | [[package]] 179 | name = "base64" 180 | version = "0.21.7" 181 | source = "registry+https://github.com/rust-lang/crates.io-index" 182 | checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" 183 | 184 | [[package]] 185 | name = "base64" 186 | version = "0.22.1" 187 | source = "registry+https://github.com/rust-lang/crates.io-index" 188 | checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" 189 | 190 | [[package]] 191 | name = "bincode" 192 | version = "1.3.3" 193 | source = "registry+https://github.com/rust-lang/crates.io-index" 194 | checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" 195 | dependencies = [ 196 | "serde", 197 | ] 198 | 199 | [[package]] 200 | name = "bindgen" 201 | version = "0.66.1" 202 | source = "registry+https://github.com/rust-lang/crates.io-index" 203 | checksum = "f2b84e06fc203107bfbad243f4aba2af864eb7db3b1cf46ea0a023b0b433d2a7" 204 | dependencies = [ 205 | "bitflags 2.6.0", 206 | "cexpr", 207 | "clang-sys", 208 | "lazy_static", 209 | "lazycell", 210 | "log", 211 | "peeking_take_while", 212 | "prettyplease", 213 | "proc-macro2", 214 | "quote", 215 | "regex", 216 | "rustc-hash", 217 | "shlex", 218 | "syn 2.0.72", 219 | "which", 220 | ] 221 | 222 | [[package]] 223 | name = "bitflags" 224 | version = "1.3.2" 225 | source = "registry+https://github.com/rust-lang/crates.io-index" 226 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 227 | 228 | [[package]] 229 | name = "bitflags" 230 | version = "2.6.0" 231 | source = "registry+https://github.com/rust-lang/crates.io-index" 232 | checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" 233 | 234 | [[package]] 235 | name = "block-padding" 236 | version = "0.3.3" 237 | source = "registry+https://github.com/rust-lang/crates.io-index" 238 | checksum = "a8894febbff9f758034a5b8e12d87918f56dfc64a8e1fe757d65e29041538d93" 239 | dependencies = [ 240 | "generic-array", 241 | ] 242 | 243 | [[package]] 244 | name = "bumpalo" 245 | version = "3.17.0" 246 | source = "registry+https://github.com/rust-lang/crates.io-index" 247 | checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" 248 | 249 | [[package]] 250 | name = "byteorder" 251 | version = "1.5.0" 252 | source = "registry+https://github.com/rust-lang/crates.io-index" 253 | checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" 254 | 255 | [[package]] 256 | name = "bytes" 257 | version = "1.6.1" 258 | source = "registry+https://github.com/rust-lang/crates.io-index" 259 | checksum = "a12916984aab3fa6e39d655a33e09c0071eb36d6ab3aea5c2d78551f1df6d952" 260 | dependencies = [ 261 | "serde", 262 | ] 263 | 264 | [[package]] 265 | name = "cbc" 266 | version = "0.1.2" 267 | source = "registry+https://github.com/rust-lang/crates.io-index" 268 | checksum = "26b52a9543ae338f279b96b0b9fed9c8093744685043739079ce85cd58f289a6" 269 | dependencies = [ 270 | "cipher", 271 | ] 272 | 273 | [[package]] 274 | name = "cc" 275 | version = "1.1.6" 276 | source = "registry+https://github.com/rust-lang/crates.io-index" 277 | checksum = "2aba8f4e9906c7ce3c73463f62a7f0c65183ada1a2d47e397cc8810827f9694f" 278 | 279 | [[package]] 280 | name = "cexpr" 281 | version = "0.6.0" 282 | source = "registry+https://github.com/rust-lang/crates.io-index" 283 | checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" 284 | dependencies = [ 285 | "nom", 286 | ] 287 | 288 | [[package]] 289 | name = "cfg-if" 290 | version = "1.0.0" 291 | source = "registry+https://github.com/rust-lang/crates.io-index" 292 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 293 | 294 | [[package]] 295 | name = "chrono" 296 | version = "0.4.40" 297 | source = "registry+https://github.com/rust-lang/crates.io-index" 298 | checksum = "1a7964611d71df112cb1730f2ee67324fcf4d0fc6606acbbe9bfe06df124637c" 299 | dependencies = [ 300 | "android-tzdata", 301 | "iana-time-zone", 302 | "js-sys", 303 | "num-traits", 304 | "wasm-bindgen", 305 | "windows-link", 306 | ] 307 | 308 | [[package]] 309 | name = "cipher" 310 | version = "0.4.4" 311 | source = "registry+https://github.com/rust-lang/crates.io-index" 312 | checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" 313 | dependencies = [ 314 | "crypto-common", 315 | "inout", 316 | ] 317 | 318 | [[package]] 319 | name = "clang-sys" 320 | version = "1.8.1" 321 | source = "registry+https://github.com/rust-lang/crates.io-index" 322 | checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" 323 | dependencies = [ 324 | "glob", 325 | "libc", 326 | "libloading", 327 | ] 328 | 329 | [[package]] 330 | name = "core-foundation" 331 | version = "0.9.4" 332 | source = "registry+https://github.com/rust-lang/crates.io-index" 333 | checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" 334 | dependencies = [ 335 | "core-foundation-sys", 336 | "libc", 337 | ] 338 | 339 | [[package]] 340 | name = "core-foundation-sys" 341 | version = "0.8.6" 342 | source = "registry+https://github.com/rust-lang/crates.io-index" 343 | checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" 344 | 345 | [[package]] 346 | name = "cpufeatures" 347 | version = "0.2.12" 348 | source = "registry+https://github.com/rust-lang/crates.io-index" 349 | checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" 350 | dependencies = [ 351 | "libc", 352 | ] 353 | 354 | [[package]] 355 | name = "crc32fast" 356 | version = "1.4.2" 357 | source = "registry+https://github.com/rust-lang/crates.io-index" 358 | checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" 359 | dependencies = [ 360 | "cfg-if", 361 | ] 362 | 363 | [[package]] 364 | name = "crypto-common" 365 | version = "0.1.6" 366 | source = "registry+https://github.com/rust-lang/crates.io-index" 367 | checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" 368 | dependencies = [ 369 | "generic-array", 370 | "typenum", 371 | ] 372 | 373 | [[package]] 374 | name = "either" 375 | version = "1.13.0" 376 | source = "registry+https://github.com/rust-lang/crates.io-index" 377 | checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" 378 | 379 | [[package]] 380 | name = "equivalent" 381 | version = "1.0.1" 382 | source = "registry+https://github.com/rust-lang/crates.io-index" 383 | checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" 384 | 385 | [[package]] 386 | name = "errno" 387 | version = "0.3.9" 388 | source = "registry+https://github.com/rust-lang/crates.io-index" 389 | checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" 390 | dependencies = [ 391 | "libc", 392 | "windows-sys", 393 | ] 394 | 395 | [[package]] 396 | name = "fallible-iterator" 397 | version = "0.2.0" 398 | source = "registry+https://github.com/rust-lang/crates.io-index" 399 | checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" 400 | 401 | [[package]] 402 | name = "fallible-iterator" 403 | version = "0.3.0" 404 | source = "registry+https://github.com/rust-lang/crates.io-index" 405 | checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649" 406 | 407 | [[package]] 408 | name = "fallible-streaming-iterator" 409 | version = "0.1.9" 410 | source = "registry+https://github.com/rust-lang/crates.io-index" 411 | checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" 412 | 413 | [[package]] 414 | name = "fnv" 415 | version = "1.0.7" 416 | source = "registry+https://github.com/rust-lang/crates.io-index" 417 | checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 418 | 419 | [[package]] 420 | name = "futures" 421 | version = "0.3.30" 422 | source = "registry+https://github.com/rust-lang/crates.io-index" 423 | checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" 424 | dependencies = [ 425 | "futures-channel", 426 | "futures-core", 427 | "futures-executor", 428 | "futures-io", 429 | "futures-sink", 430 | "futures-task", 431 | "futures-util", 432 | ] 433 | 434 | [[package]] 435 | name = "futures-channel" 436 | version = "0.3.30" 437 | source = "registry+https://github.com/rust-lang/crates.io-index" 438 | checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" 439 | dependencies = [ 440 | "futures-core", 441 | "futures-sink", 442 | ] 443 | 444 | [[package]] 445 | name = "futures-core" 446 | version = "0.3.30" 447 | source = "registry+https://github.com/rust-lang/crates.io-index" 448 | checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" 449 | 450 | [[package]] 451 | name = "futures-executor" 452 | version = "0.3.30" 453 | source = "registry+https://github.com/rust-lang/crates.io-index" 454 | checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" 455 | dependencies = [ 456 | "futures-core", 457 | "futures-task", 458 | "futures-util", 459 | ] 460 | 461 | [[package]] 462 | name = "futures-io" 463 | version = "0.3.30" 464 | source = "registry+https://github.com/rust-lang/crates.io-index" 465 | checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" 466 | 467 | [[package]] 468 | name = "futures-macro" 469 | version = "0.3.30" 470 | source = "registry+https://github.com/rust-lang/crates.io-index" 471 | checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" 472 | dependencies = [ 473 | "proc-macro2", 474 | "quote", 475 | "syn 2.0.72", 476 | ] 477 | 478 | [[package]] 479 | name = "futures-sink" 480 | version = "0.3.30" 481 | source = "registry+https://github.com/rust-lang/crates.io-index" 482 | checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" 483 | 484 | [[package]] 485 | name = "futures-task" 486 | version = "0.3.30" 487 | source = "registry+https://github.com/rust-lang/crates.io-index" 488 | checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" 489 | 490 | [[package]] 491 | name = "futures-util" 492 | version = "0.3.30" 493 | source = "registry+https://github.com/rust-lang/crates.io-index" 494 | checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" 495 | dependencies = [ 496 | "futures-channel", 497 | "futures-core", 498 | "futures-io", 499 | "futures-macro", 500 | "futures-sink", 501 | "futures-task", 502 | "memchr", 503 | "pin-project-lite", 504 | "pin-utils", 505 | "slab", 506 | ] 507 | 508 | [[package]] 509 | name = "generic-array" 510 | version = "0.14.7" 511 | source = "registry+https://github.com/rust-lang/crates.io-index" 512 | checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" 513 | dependencies = [ 514 | "typenum", 515 | "version_check", 516 | ] 517 | 518 | [[package]] 519 | name = "getrandom" 520 | version = "0.2.15" 521 | source = "registry+https://github.com/rust-lang/crates.io-index" 522 | checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" 523 | dependencies = [ 524 | "cfg-if", 525 | "libc", 526 | "wasi", 527 | ] 528 | 529 | [[package]] 530 | name = "gimli" 531 | version = "0.29.0" 532 | source = "registry+https://github.com/rust-lang/crates.io-index" 533 | checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd" 534 | 535 | [[package]] 536 | name = "glob" 537 | version = "0.3.1" 538 | source = "registry+https://github.com/rust-lang/crates.io-index" 539 | checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" 540 | 541 | [[package]] 542 | name = "h2" 543 | version = "0.3.26" 544 | source = "registry+https://github.com/rust-lang/crates.io-index" 545 | checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8" 546 | dependencies = [ 547 | "bytes", 548 | "fnv", 549 | "futures-core", 550 | "futures-sink", 551 | "futures-util", 552 | "http", 553 | "indexmap 2.2.6", 554 | "slab", 555 | "tokio", 556 | "tokio-util", 557 | "tracing", 558 | ] 559 | 560 | [[package]] 561 | name = "hashbrown" 562 | version = "0.12.3" 563 | source = "registry+https://github.com/rust-lang/crates.io-index" 564 | checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" 565 | 566 | [[package]] 567 | name = "hashbrown" 568 | version = "0.14.5" 569 | source = "registry+https://github.com/rust-lang/crates.io-index" 570 | checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" 571 | dependencies = [ 572 | "ahash", 573 | "allocator-api2", 574 | ] 575 | 576 | [[package]] 577 | name = "hashlink" 578 | version = "0.8.4" 579 | source = "registry+https://github.com/rust-lang/crates.io-index" 580 | checksum = "e8094feaf31ff591f651a2664fb9cfd92bba7a60ce3197265e9482ebe753c8f7" 581 | dependencies = [ 582 | "hashbrown 0.14.5", 583 | ] 584 | 585 | [[package]] 586 | name = "hermit-abi" 587 | version = "0.3.9" 588 | source = "registry+https://github.com/rust-lang/crates.io-index" 589 | checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" 590 | 591 | [[package]] 592 | name = "home" 593 | version = "0.5.9" 594 | source = "registry+https://github.com/rust-lang/crates.io-index" 595 | checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" 596 | dependencies = [ 597 | "windows-sys", 598 | ] 599 | 600 | [[package]] 601 | name = "http" 602 | version = "0.2.12" 603 | source = "registry+https://github.com/rust-lang/crates.io-index" 604 | checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" 605 | dependencies = [ 606 | "bytes", 607 | "fnv", 608 | "itoa", 609 | ] 610 | 611 | [[package]] 612 | name = "http-body" 613 | version = "0.4.6" 614 | source = "registry+https://github.com/rust-lang/crates.io-index" 615 | checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" 616 | dependencies = [ 617 | "bytes", 618 | "http", 619 | "pin-project-lite", 620 | ] 621 | 622 | [[package]] 623 | name = "http-range-header" 624 | version = "0.3.1" 625 | source = "registry+https://github.com/rust-lang/crates.io-index" 626 | checksum = "add0ab9360ddbd88cfeb3bd9574a1d85cfdfa14db10b3e21d3700dbc4328758f" 627 | 628 | [[package]] 629 | name = "httparse" 630 | version = "1.9.4" 631 | source = "registry+https://github.com/rust-lang/crates.io-index" 632 | checksum = "0fcc0b4a115bf80b728eb8ea024ad5bd707b615bfed49e0665b6e0f86fd082d9" 633 | 634 | [[package]] 635 | name = "httpdate" 636 | version = "1.0.3" 637 | source = "registry+https://github.com/rust-lang/crates.io-index" 638 | checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" 639 | 640 | [[package]] 641 | name = "hyper" 642 | version = "0.14.30" 643 | source = "registry+https://github.com/rust-lang/crates.io-index" 644 | checksum = "a152ddd61dfaec7273fe8419ab357f33aee0d914c5f4efbf0d96fa749eea5ec9" 645 | dependencies = [ 646 | "bytes", 647 | "futures-channel", 648 | "futures-core", 649 | "futures-util", 650 | "h2", 651 | "http", 652 | "http-body", 653 | "httparse", 654 | "httpdate", 655 | "itoa", 656 | "pin-project-lite", 657 | "socket2", 658 | "tokio", 659 | "tower-service", 660 | "tracing", 661 | "want", 662 | ] 663 | 664 | [[package]] 665 | name = "hyper-rustls" 666 | version = "0.25.0" 667 | source = "registry+https://github.com/rust-lang/crates.io-index" 668 | checksum = "399c78f9338483cb7e630c8474b07268983c6bd5acee012e4211f9f7bb21b070" 669 | dependencies = [ 670 | "futures-util", 671 | "http", 672 | "hyper", 673 | "log", 674 | "rustls", 675 | "rustls-native-certs", 676 | "rustls-pki-types", 677 | "tokio", 678 | "tokio-rustls", 679 | "webpki-roots", 680 | ] 681 | 682 | [[package]] 683 | name = "hyper-timeout" 684 | version = "0.4.1" 685 | source = "registry+https://github.com/rust-lang/crates.io-index" 686 | checksum = "bbb958482e8c7be4bc3cf272a766a2b0bf1a6755e7a6ae777f017a31d11b13b1" 687 | dependencies = [ 688 | "hyper", 689 | "pin-project-lite", 690 | "tokio", 691 | "tokio-io-timeout", 692 | ] 693 | 694 | [[package]] 695 | name = "iana-time-zone" 696 | version = "0.1.62" 697 | source = "registry+https://github.com/rust-lang/crates.io-index" 698 | checksum = "b2fd658b06e56721792c5df4475705b6cda790e9298d19d2f8af083457bcd127" 699 | dependencies = [ 700 | "android_system_properties", 701 | "core-foundation-sys", 702 | "iana-time-zone-haiku", 703 | "js-sys", 704 | "log", 705 | "wasm-bindgen", 706 | "windows-core", 707 | ] 708 | 709 | [[package]] 710 | name = "iana-time-zone-haiku" 711 | version = "0.1.2" 712 | source = "registry+https://github.com/rust-lang/crates.io-index" 713 | checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" 714 | dependencies = [ 715 | "cc", 716 | ] 717 | 718 | [[package]] 719 | name = "indexmap" 720 | version = "1.9.3" 721 | source = "registry+https://github.com/rust-lang/crates.io-index" 722 | checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" 723 | dependencies = [ 724 | "autocfg", 725 | "hashbrown 0.12.3", 726 | ] 727 | 728 | [[package]] 729 | name = "indexmap" 730 | version = "2.2.6" 731 | source = "registry+https://github.com/rust-lang/crates.io-index" 732 | checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" 733 | dependencies = [ 734 | "equivalent", 735 | "hashbrown 0.14.5", 736 | ] 737 | 738 | [[package]] 739 | name = "indoc" 740 | version = "1.0.9" 741 | source = "registry+https://github.com/rust-lang/crates.io-index" 742 | checksum = "bfa799dd5ed20a7e349f3b4639aa80d74549c81716d9ec4f994c9b5815598306" 743 | 744 | [[package]] 745 | name = "inout" 746 | version = "0.1.3" 747 | source = "registry+https://github.com/rust-lang/crates.io-index" 748 | checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" 749 | dependencies = [ 750 | "block-padding", 751 | "generic-array", 752 | ] 753 | 754 | [[package]] 755 | name = "itertools" 756 | version = "0.12.1" 757 | source = "registry+https://github.com/rust-lang/crates.io-index" 758 | checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" 759 | dependencies = [ 760 | "either", 761 | ] 762 | 763 | [[package]] 764 | name = "itoa" 765 | version = "1.0.11" 766 | source = "registry+https://github.com/rust-lang/crates.io-index" 767 | checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" 768 | 769 | [[package]] 770 | name = "js-sys" 771 | version = "0.3.77" 772 | source = "registry+https://github.com/rust-lang/crates.io-index" 773 | checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" 774 | dependencies = [ 775 | "once_cell", 776 | "wasm-bindgen", 777 | ] 778 | 779 | [[package]] 780 | name = "lazy_static" 781 | version = "1.5.0" 782 | source = "registry+https://github.com/rust-lang/crates.io-index" 783 | checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" 784 | 785 | [[package]] 786 | name = "lazycell" 787 | version = "1.3.0" 788 | source = "registry+https://github.com/rust-lang/crates.io-index" 789 | checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" 790 | 791 | [[package]] 792 | name = "libc" 793 | version = "0.2.155" 794 | source = "registry+https://github.com/rust-lang/crates.io-index" 795 | checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" 796 | 797 | [[package]] 798 | name = "libloading" 799 | version = "0.8.5" 800 | source = "registry+https://github.com/rust-lang/crates.io-index" 801 | checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4" 802 | dependencies = [ 803 | "cfg-if", 804 | "windows-targets", 805 | ] 806 | 807 | [[package]] 808 | name = "libsql" 809 | version = "0.9.8" 810 | source = "registry+https://github.com/rust-lang/crates.io-index" 811 | checksum = "ac8482f800f3f515e1d96af82bbb47d57d1edbc585642316eebc045f72a1930e" 812 | dependencies = [ 813 | "anyhow", 814 | "async-stream", 815 | "async-trait", 816 | "base64 0.21.7", 817 | "bincode", 818 | "bitflags 2.6.0", 819 | "bytes", 820 | "chrono", 821 | "crc32fast", 822 | "fallible-iterator 0.3.0", 823 | "futures", 824 | "http", 825 | "hyper", 826 | "hyper-rustls", 827 | "libsql-hrana", 828 | "libsql-sqlite3-parser", 829 | "libsql-sys", 830 | "libsql_replication", 831 | "parking_lot", 832 | "serde", 833 | "serde_json", 834 | "thiserror", 835 | "tokio", 836 | "tokio-stream", 837 | "tokio-util", 838 | "tonic", 839 | "tonic-web", 840 | "tower", 841 | "tower-http", 842 | "tracing", 843 | "uuid", 844 | "zerocopy", 845 | ] 846 | 847 | [[package]] 848 | name = "libsql-ffi" 849 | version = "0.9.8" 850 | source = "registry+https://github.com/rust-lang/crates.io-index" 851 | checksum = "1f78ac450ec98334db75ffda145c9f17377f6aa0c5a2529d233844b7b7841a48" 852 | dependencies = [ 853 | "bindgen", 854 | "cc", 855 | "glob", 856 | ] 857 | 858 | [[package]] 859 | name = "libsql-hrana" 860 | version = "0.9.8" 861 | source = "registry+https://github.com/rust-lang/crates.io-index" 862 | checksum = "7df6ae47bfedf165fb22d1aad3ca983b0aa27782fea48f98b5142f36c02b459d" 863 | dependencies = [ 864 | "base64 0.21.7", 865 | "bytes", 866 | "prost", 867 | "serde", 868 | ] 869 | 870 | [[package]] 871 | name = "libsql-python" 872 | version = "0.0.53" 873 | dependencies = [ 874 | "libsql", 875 | "pyo3", 876 | "pyo3-build-config", 877 | "tokio", 878 | "tracing-subscriber", 879 | "version_check", 880 | ] 881 | 882 | [[package]] 883 | name = "libsql-rusqlite" 884 | version = "0.9.8" 885 | source = "registry+https://github.com/rust-lang/crates.io-index" 886 | checksum = "9a462d9e10715139636d7f17351ba28c4b5fbc439110282ef3aa53770c3a8ba5" 887 | dependencies = [ 888 | "bitflags 2.6.0", 889 | "fallible-iterator 0.2.0", 890 | "fallible-streaming-iterator", 891 | "hashlink", 892 | "libsql-ffi", 893 | "smallvec", 894 | ] 895 | 896 | [[package]] 897 | name = "libsql-sqlite3-parser" 898 | version = "0.13.0" 899 | source = "registry+https://github.com/rust-lang/crates.io-index" 900 | checksum = "15a90128c708356af8f7d767c9ac2946692c9112b4f74f07b99a01a60680e413" 901 | dependencies = [ 902 | "bitflags 2.6.0", 903 | "cc", 904 | "fallible-iterator 0.3.0", 905 | "indexmap 2.2.6", 906 | "log", 907 | "memchr", 908 | "phf", 909 | "phf_codegen", 910 | "phf_shared", 911 | "uncased", 912 | ] 913 | 914 | [[package]] 915 | name = "libsql-sys" 916 | version = "0.9.8" 917 | source = "registry+https://github.com/rust-lang/crates.io-index" 918 | checksum = "76ca92a20f9b274aecfa2861e0a447d43f2b4c48dc6c8b63b6702d22c51e15eb" 919 | dependencies = [ 920 | "bytes", 921 | "libsql-ffi", 922 | "libsql-rusqlite", 923 | "once_cell", 924 | "tracing", 925 | "zerocopy", 926 | ] 927 | 928 | [[package]] 929 | name = "libsql_replication" 930 | version = "0.9.8" 931 | source = "registry+https://github.com/rust-lang/crates.io-index" 932 | checksum = "0e7f478774f05f272943a6d6b22c401c14178070c9919a8c678205b69bb70d16" 933 | dependencies = [ 934 | "aes", 935 | "async-stream", 936 | "async-trait", 937 | "bytes", 938 | "cbc", 939 | "libsql-rusqlite", 940 | "libsql-sys", 941 | "parking_lot", 942 | "prost", 943 | "serde", 944 | "thiserror", 945 | "tokio", 946 | "tokio-stream", 947 | "tokio-util", 948 | "tonic", 949 | "tracing", 950 | "uuid", 951 | "zerocopy", 952 | ] 953 | 954 | [[package]] 955 | name = "linux-raw-sys" 956 | version = "0.4.14" 957 | source = "registry+https://github.com/rust-lang/crates.io-index" 958 | checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" 959 | 960 | [[package]] 961 | name = "lock_api" 962 | version = "0.4.12" 963 | source = "registry+https://github.com/rust-lang/crates.io-index" 964 | checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" 965 | dependencies = [ 966 | "autocfg", 967 | "scopeguard", 968 | ] 969 | 970 | [[package]] 971 | name = "log" 972 | version = "0.4.22" 973 | source = "registry+https://github.com/rust-lang/crates.io-index" 974 | checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" 975 | 976 | [[package]] 977 | name = "matchit" 978 | version = "0.7.3" 979 | source = "registry+https://github.com/rust-lang/crates.io-index" 980 | checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" 981 | 982 | [[package]] 983 | name = "memchr" 984 | version = "2.7.4" 985 | source = "registry+https://github.com/rust-lang/crates.io-index" 986 | checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" 987 | 988 | [[package]] 989 | name = "memoffset" 990 | version = "0.9.1" 991 | source = "registry+https://github.com/rust-lang/crates.io-index" 992 | checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" 993 | dependencies = [ 994 | "autocfg", 995 | ] 996 | 997 | [[package]] 998 | name = "mime" 999 | version = "0.3.17" 1000 | source = "registry+https://github.com/rust-lang/crates.io-index" 1001 | checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" 1002 | 1003 | [[package]] 1004 | name = "minimal-lexical" 1005 | version = "0.2.1" 1006 | source = "registry+https://github.com/rust-lang/crates.io-index" 1007 | checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" 1008 | 1009 | [[package]] 1010 | name = "miniz_oxide" 1011 | version = "0.7.4" 1012 | source = "registry+https://github.com/rust-lang/crates.io-index" 1013 | checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08" 1014 | dependencies = [ 1015 | "adler", 1016 | ] 1017 | 1018 | [[package]] 1019 | name = "mio" 1020 | version = "1.0.1" 1021 | source = "registry+https://github.com/rust-lang/crates.io-index" 1022 | checksum = "4569e456d394deccd22ce1c1913e6ea0e54519f577285001215d33557431afe4" 1023 | dependencies = [ 1024 | "hermit-abi", 1025 | "libc", 1026 | "wasi", 1027 | "windows-sys", 1028 | ] 1029 | 1030 | [[package]] 1031 | name = "nom" 1032 | version = "7.1.3" 1033 | source = "registry+https://github.com/rust-lang/crates.io-index" 1034 | checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" 1035 | dependencies = [ 1036 | "memchr", 1037 | "minimal-lexical", 1038 | ] 1039 | 1040 | [[package]] 1041 | name = "nu-ansi-term" 1042 | version = "0.46.0" 1043 | source = "registry+https://github.com/rust-lang/crates.io-index" 1044 | checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" 1045 | dependencies = [ 1046 | "overload", 1047 | "winapi", 1048 | ] 1049 | 1050 | [[package]] 1051 | name = "num-traits" 1052 | version = "0.2.19" 1053 | source = "registry+https://github.com/rust-lang/crates.io-index" 1054 | checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" 1055 | dependencies = [ 1056 | "autocfg", 1057 | ] 1058 | 1059 | [[package]] 1060 | name = "object" 1061 | version = "0.36.2" 1062 | source = "registry+https://github.com/rust-lang/crates.io-index" 1063 | checksum = "3f203fa8daa7bb185f760ae12bd8e097f63d17041dcdcaf675ac54cdf863170e" 1064 | dependencies = [ 1065 | "memchr", 1066 | ] 1067 | 1068 | [[package]] 1069 | name = "once_cell" 1070 | version = "1.19.0" 1071 | source = "registry+https://github.com/rust-lang/crates.io-index" 1072 | checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" 1073 | 1074 | [[package]] 1075 | name = "openssl-probe" 1076 | version = "0.1.5" 1077 | source = "registry+https://github.com/rust-lang/crates.io-index" 1078 | checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" 1079 | 1080 | [[package]] 1081 | name = "overload" 1082 | version = "0.1.1" 1083 | source = "registry+https://github.com/rust-lang/crates.io-index" 1084 | checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" 1085 | 1086 | [[package]] 1087 | name = "parking_lot" 1088 | version = "0.12.3" 1089 | source = "registry+https://github.com/rust-lang/crates.io-index" 1090 | checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" 1091 | dependencies = [ 1092 | "lock_api", 1093 | "parking_lot_core", 1094 | ] 1095 | 1096 | [[package]] 1097 | name = "parking_lot_core" 1098 | version = "0.9.10" 1099 | source = "registry+https://github.com/rust-lang/crates.io-index" 1100 | checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" 1101 | dependencies = [ 1102 | "cfg-if", 1103 | "libc", 1104 | "redox_syscall", 1105 | "smallvec", 1106 | "windows-targets", 1107 | ] 1108 | 1109 | [[package]] 1110 | name = "peeking_take_while" 1111 | version = "0.1.2" 1112 | source = "registry+https://github.com/rust-lang/crates.io-index" 1113 | checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" 1114 | 1115 | [[package]] 1116 | name = "percent-encoding" 1117 | version = "2.3.1" 1118 | source = "registry+https://github.com/rust-lang/crates.io-index" 1119 | checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" 1120 | 1121 | [[package]] 1122 | name = "phf" 1123 | version = "0.11.2" 1124 | source = "registry+https://github.com/rust-lang/crates.io-index" 1125 | checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc" 1126 | dependencies = [ 1127 | "phf_shared", 1128 | ] 1129 | 1130 | [[package]] 1131 | name = "phf_codegen" 1132 | version = "0.11.2" 1133 | source = "registry+https://github.com/rust-lang/crates.io-index" 1134 | checksum = "e8d39688d359e6b34654d328e262234662d16cc0f60ec8dcbe5e718709342a5a" 1135 | dependencies = [ 1136 | "phf_generator", 1137 | "phf_shared", 1138 | ] 1139 | 1140 | [[package]] 1141 | name = "phf_generator" 1142 | version = "0.11.2" 1143 | source = "registry+https://github.com/rust-lang/crates.io-index" 1144 | checksum = "48e4cc64c2ad9ebe670cb8fd69dd50ae301650392e81c05f9bfcb2d5bdbc24b0" 1145 | dependencies = [ 1146 | "phf_shared", 1147 | "rand", 1148 | ] 1149 | 1150 | [[package]] 1151 | name = "phf_shared" 1152 | version = "0.11.2" 1153 | source = "registry+https://github.com/rust-lang/crates.io-index" 1154 | checksum = "90fcb95eef784c2ac79119d1dd819e162b5da872ce6f3c3abe1e8ca1c082f72b" 1155 | dependencies = [ 1156 | "siphasher", 1157 | "uncased", 1158 | ] 1159 | 1160 | [[package]] 1161 | name = "pin-project" 1162 | version = "1.1.5" 1163 | source = "registry+https://github.com/rust-lang/crates.io-index" 1164 | checksum = "b6bf43b791c5b9e34c3d182969b4abb522f9343702850a2e57f460d00d09b4b3" 1165 | dependencies = [ 1166 | "pin-project-internal", 1167 | ] 1168 | 1169 | [[package]] 1170 | name = "pin-project-internal" 1171 | version = "1.1.5" 1172 | source = "registry+https://github.com/rust-lang/crates.io-index" 1173 | checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" 1174 | dependencies = [ 1175 | "proc-macro2", 1176 | "quote", 1177 | "syn 2.0.72", 1178 | ] 1179 | 1180 | [[package]] 1181 | name = "pin-project-lite" 1182 | version = "0.2.14" 1183 | source = "registry+https://github.com/rust-lang/crates.io-index" 1184 | checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" 1185 | 1186 | [[package]] 1187 | name = "pin-utils" 1188 | version = "0.1.0" 1189 | source = "registry+https://github.com/rust-lang/crates.io-index" 1190 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 1191 | 1192 | [[package]] 1193 | name = "ppv-lite86" 1194 | version = "0.2.17" 1195 | source = "registry+https://github.com/rust-lang/crates.io-index" 1196 | checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" 1197 | 1198 | [[package]] 1199 | name = "prettyplease" 1200 | version = "0.2.20" 1201 | source = "registry+https://github.com/rust-lang/crates.io-index" 1202 | checksum = "5f12335488a2f3b0a83b14edad48dca9879ce89b2edd10e80237e4e852dd645e" 1203 | dependencies = [ 1204 | "proc-macro2", 1205 | "syn 2.0.72", 1206 | ] 1207 | 1208 | [[package]] 1209 | name = "proc-macro2" 1210 | version = "1.0.86" 1211 | source = "registry+https://github.com/rust-lang/crates.io-index" 1212 | checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" 1213 | dependencies = [ 1214 | "unicode-ident", 1215 | ] 1216 | 1217 | [[package]] 1218 | name = "prost" 1219 | version = "0.12.6" 1220 | source = "registry+https://github.com/rust-lang/crates.io-index" 1221 | checksum = "deb1435c188b76130da55f17a466d252ff7b1418b2ad3e037d127b94e3411f29" 1222 | dependencies = [ 1223 | "bytes", 1224 | "prost-derive", 1225 | ] 1226 | 1227 | [[package]] 1228 | name = "prost-derive" 1229 | version = "0.12.6" 1230 | source = "registry+https://github.com/rust-lang/crates.io-index" 1231 | checksum = "81bddcdb20abf9501610992b6759a4c888aef7d1a7247ef75e2404275ac24af1" 1232 | dependencies = [ 1233 | "anyhow", 1234 | "itertools", 1235 | "proc-macro2", 1236 | "quote", 1237 | "syn 2.0.72", 1238 | ] 1239 | 1240 | [[package]] 1241 | name = "pyo3" 1242 | version = "0.19.2" 1243 | source = "registry+https://github.com/rust-lang/crates.io-index" 1244 | checksum = "e681a6cfdc4adcc93b4d3cf993749a4552018ee0a9b65fc0ccfad74352c72a38" 1245 | dependencies = [ 1246 | "cfg-if", 1247 | "indoc", 1248 | "libc", 1249 | "memoffset", 1250 | "parking_lot", 1251 | "pyo3-build-config", 1252 | "pyo3-ffi", 1253 | "pyo3-macros", 1254 | "unindent", 1255 | ] 1256 | 1257 | [[package]] 1258 | name = "pyo3-build-config" 1259 | version = "0.19.2" 1260 | source = "registry+https://github.com/rust-lang/crates.io-index" 1261 | checksum = "076c73d0bc438f7a4ef6fdd0c3bb4732149136abd952b110ac93e4edb13a6ba5" 1262 | dependencies = [ 1263 | "once_cell", 1264 | "target-lexicon", 1265 | ] 1266 | 1267 | [[package]] 1268 | name = "pyo3-ffi" 1269 | version = "0.19.2" 1270 | source = "registry+https://github.com/rust-lang/crates.io-index" 1271 | checksum = "e53cee42e77ebe256066ba8aa77eff722b3bb91f3419177cf4cd0f304d3284d9" 1272 | dependencies = [ 1273 | "libc", 1274 | "pyo3-build-config", 1275 | ] 1276 | 1277 | [[package]] 1278 | name = "pyo3-macros" 1279 | version = "0.19.2" 1280 | source = "registry+https://github.com/rust-lang/crates.io-index" 1281 | checksum = "dfeb4c99597e136528c6dd7d5e3de5434d1ceaf487436a3f03b2d56b6fc9efd1" 1282 | dependencies = [ 1283 | "proc-macro2", 1284 | "pyo3-macros-backend", 1285 | "quote", 1286 | "syn 1.0.109", 1287 | ] 1288 | 1289 | [[package]] 1290 | name = "pyo3-macros-backend" 1291 | version = "0.19.2" 1292 | source = "registry+https://github.com/rust-lang/crates.io-index" 1293 | checksum = "947dc12175c254889edc0c02e399476c2f652b4b9ebd123aa655c224de259536" 1294 | dependencies = [ 1295 | "proc-macro2", 1296 | "quote", 1297 | "syn 1.0.109", 1298 | ] 1299 | 1300 | [[package]] 1301 | name = "quote" 1302 | version = "1.0.36" 1303 | source = "registry+https://github.com/rust-lang/crates.io-index" 1304 | checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" 1305 | dependencies = [ 1306 | "proc-macro2", 1307 | ] 1308 | 1309 | [[package]] 1310 | name = "rand" 1311 | version = "0.8.5" 1312 | source = "registry+https://github.com/rust-lang/crates.io-index" 1313 | checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" 1314 | dependencies = [ 1315 | "libc", 1316 | "rand_chacha", 1317 | "rand_core", 1318 | ] 1319 | 1320 | [[package]] 1321 | name = "rand_chacha" 1322 | version = "0.3.1" 1323 | source = "registry+https://github.com/rust-lang/crates.io-index" 1324 | checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" 1325 | dependencies = [ 1326 | "ppv-lite86", 1327 | "rand_core", 1328 | ] 1329 | 1330 | [[package]] 1331 | name = "rand_core" 1332 | version = "0.6.4" 1333 | source = "registry+https://github.com/rust-lang/crates.io-index" 1334 | checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" 1335 | dependencies = [ 1336 | "getrandom", 1337 | ] 1338 | 1339 | [[package]] 1340 | name = "redox_syscall" 1341 | version = "0.5.3" 1342 | source = "registry+https://github.com/rust-lang/crates.io-index" 1343 | checksum = "2a908a6e00f1fdd0dfd9c0eb08ce85126f6d8bbda50017e74bc4a4b7d4a926a4" 1344 | dependencies = [ 1345 | "bitflags 2.6.0", 1346 | ] 1347 | 1348 | [[package]] 1349 | name = "regex" 1350 | version = "1.10.5" 1351 | source = "registry+https://github.com/rust-lang/crates.io-index" 1352 | checksum = "b91213439dad192326a0d7c6ee3955910425f441d7038e0d6933b0aec5c4517f" 1353 | dependencies = [ 1354 | "aho-corasick", 1355 | "memchr", 1356 | "regex-automata", 1357 | "regex-syntax", 1358 | ] 1359 | 1360 | [[package]] 1361 | name = "regex-automata" 1362 | version = "0.4.7" 1363 | source = "registry+https://github.com/rust-lang/crates.io-index" 1364 | checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" 1365 | dependencies = [ 1366 | "aho-corasick", 1367 | "memchr", 1368 | "regex-syntax", 1369 | ] 1370 | 1371 | [[package]] 1372 | name = "regex-syntax" 1373 | version = "0.8.4" 1374 | source = "registry+https://github.com/rust-lang/crates.io-index" 1375 | checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" 1376 | 1377 | [[package]] 1378 | name = "ring" 1379 | version = "0.17.8" 1380 | source = "registry+https://github.com/rust-lang/crates.io-index" 1381 | checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" 1382 | dependencies = [ 1383 | "cc", 1384 | "cfg-if", 1385 | "getrandom", 1386 | "libc", 1387 | "spin", 1388 | "untrusted", 1389 | "windows-sys", 1390 | ] 1391 | 1392 | [[package]] 1393 | name = "rustc-demangle" 1394 | version = "0.1.24" 1395 | source = "registry+https://github.com/rust-lang/crates.io-index" 1396 | checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" 1397 | 1398 | [[package]] 1399 | name = "rustc-hash" 1400 | version = "1.1.0" 1401 | source = "registry+https://github.com/rust-lang/crates.io-index" 1402 | checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" 1403 | 1404 | [[package]] 1405 | name = "rustix" 1406 | version = "0.38.34" 1407 | source = "registry+https://github.com/rust-lang/crates.io-index" 1408 | checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" 1409 | dependencies = [ 1410 | "bitflags 2.6.0", 1411 | "errno", 1412 | "libc", 1413 | "linux-raw-sys", 1414 | "windows-sys", 1415 | ] 1416 | 1417 | [[package]] 1418 | name = "rustls" 1419 | version = "0.22.4" 1420 | source = "registry+https://github.com/rust-lang/crates.io-index" 1421 | checksum = "bf4ef73721ac7bcd79b2b315da7779d8fc09718c6b3d2d1b2d94850eb8c18432" 1422 | dependencies = [ 1423 | "log", 1424 | "ring", 1425 | "rustls-pki-types", 1426 | "rustls-webpki", 1427 | "subtle", 1428 | "zeroize", 1429 | ] 1430 | 1431 | [[package]] 1432 | name = "rustls-native-certs" 1433 | version = "0.7.1" 1434 | source = "registry+https://github.com/rust-lang/crates.io-index" 1435 | checksum = "a88d6d420651b496bdd98684116959239430022a115c1240e6c3993be0b15fba" 1436 | dependencies = [ 1437 | "openssl-probe", 1438 | "rustls-pemfile", 1439 | "rustls-pki-types", 1440 | "schannel", 1441 | "security-framework", 1442 | ] 1443 | 1444 | [[package]] 1445 | name = "rustls-pemfile" 1446 | version = "2.1.2" 1447 | source = "registry+https://github.com/rust-lang/crates.io-index" 1448 | checksum = "29993a25686778eb88d4189742cd713c9bce943bc54251a33509dc63cbacf73d" 1449 | dependencies = [ 1450 | "base64 0.22.1", 1451 | "rustls-pki-types", 1452 | ] 1453 | 1454 | [[package]] 1455 | name = "rustls-pki-types" 1456 | version = "1.7.0" 1457 | source = "registry+https://github.com/rust-lang/crates.io-index" 1458 | checksum = "976295e77ce332211c0d24d92c0e83e50f5c5f046d11082cea19f3df13a3562d" 1459 | 1460 | [[package]] 1461 | name = "rustls-webpki" 1462 | version = "0.102.6" 1463 | source = "registry+https://github.com/rust-lang/crates.io-index" 1464 | checksum = "8e6b52d4fda176fd835fdc55a835d4a89b8499cad995885a21149d5ad62f852e" 1465 | dependencies = [ 1466 | "ring", 1467 | "rustls-pki-types", 1468 | "untrusted", 1469 | ] 1470 | 1471 | [[package]] 1472 | name = "rustversion" 1473 | version = "1.0.17" 1474 | source = "registry+https://github.com/rust-lang/crates.io-index" 1475 | checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" 1476 | 1477 | [[package]] 1478 | name = "ryu" 1479 | version = "1.0.18" 1480 | source = "registry+https://github.com/rust-lang/crates.io-index" 1481 | checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" 1482 | 1483 | [[package]] 1484 | name = "schannel" 1485 | version = "0.1.23" 1486 | source = "registry+https://github.com/rust-lang/crates.io-index" 1487 | checksum = "fbc91545643bcf3a0bbb6569265615222618bdf33ce4ffbbd13c4bbd4c093534" 1488 | dependencies = [ 1489 | "windows-sys", 1490 | ] 1491 | 1492 | [[package]] 1493 | name = "scopeguard" 1494 | version = "1.2.0" 1495 | source = "registry+https://github.com/rust-lang/crates.io-index" 1496 | checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" 1497 | 1498 | [[package]] 1499 | name = "security-framework" 1500 | version = "2.11.1" 1501 | source = "registry+https://github.com/rust-lang/crates.io-index" 1502 | checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" 1503 | dependencies = [ 1504 | "bitflags 2.6.0", 1505 | "core-foundation", 1506 | "core-foundation-sys", 1507 | "libc", 1508 | "security-framework-sys", 1509 | ] 1510 | 1511 | [[package]] 1512 | name = "security-framework-sys" 1513 | version = "2.11.1" 1514 | source = "registry+https://github.com/rust-lang/crates.io-index" 1515 | checksum = "75da29fe9b9b08fe9d6b22b5b4bcbc75d8db3aa31e639aa56bb62e9d46bfceaf" 1516 | dependencies = [ 1517 | "core-foundation-sys", 1518 | "libc", 1519 | ] 1520 | 1521 | [[package]] 1522 | name = "serde" 1523 | version = "1.0.204" 1524 | source = "registry+https://github.com/rust-lang/crates.io-index" 1525 | checksum = "bc76f558e0cbb2a839d37354c575f1dc3fdc6546b5be373ba43d95f231bf7c12" 1526 | dependencies = [ 1527 | "serde_derive", 1528 | ] 1529 | 1530 | [[package]] 1531 | name = "serde_derive" 1532 | version = "1.0.204" 1533 | source = "registry+https://github.com/rust-lang/crates.io-index" 1534 | checksum = "e0cd7e117be63d3c3678776753929474f3b04a43a080c744d6b0ae2a8c28e222" 1535 | dependencies = [ 1536 | "proc-macro2", 1537 | "quote", 1538 | "syn 2.0.72", 1539 | ] 1540 | 1541 | [[package]] 1542 | name = "serde_json" 1543 | version = "1.0.120" 1544 | source = "registry+https://github.com/rust-lang/crates.io-index" 1545 | checksum = "4e0d21c9a8cae1235ad58a00c11cb40d4b1e5c784f1ef2c537876ed6ffd8b7c5" 1546 | dependencies = [ 1547 | "itoa", 1548 | "ryu", 1549 | "serde", 1550 | ] 1551 | 1552 | [[package]] 1553 | name = "sharded-slab" 1554 | version = "0.1.7" 1555 | source = "registry+https://github.com/rust-lang/crates.io-index" 1556 | checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" 1557 | dependencies = [ 1558 | "lazy_static", 1559 | ] 1560 | 1561 | [[package]] 1562 | name = "shlex" 1563 | version = "1.3.0" 1564 | source = "registry+https://github.com/rust-lang/crates.io-index" 1565 | checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" 1566 | 1567 | [[package]] 1568 | name = "signal-hook-registry" 1569 | version = "1.4.2" 1570 | source = "registry+https://github.com/rust-lang/crates.io-index" 1571 | checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" 1572 | dependencies = [ 1573 | "libc", 1574 | ] 1575 | 1576 | [[package]] 1577 | name = "siphasher" 1578 | version = "0.3.11" 1579 | source = "registry+https://github.com/rust-lang/crates.io-index" 1580 | checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" 1581 | 1582 | [[package]] 1583 | name = "slab" 1584 | version = "0.4.9" 1585 | source = "registry+https://github.com/rust-lang/crates.io-index" 1586 | checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" 1587 | dependencies = [ 1588 | "autocfg", 1589 | ] 1590 | 1591 | [[package]] 1592 | name = "smallvec" 1593 | version = "1.13.2" 1594 | source = "registry+https://github.com/rust-lang/crates.io-index" 1595 | checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" 1596 | 1597 | [[package]] 1598 | name = "socket2" 1599 | version = "0.5.7" 1600 | source = "registry+https://github.com/rust-lang/crates.io-index" 1601 | checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" 1602 | dependencies = [ 1603 | "libc", 1604 | "windows-sys", 1605 | ] 1606 | 1607 | [[package]] 1608 | name = "spin" 1609 | version = "0.9.8" 1610 | source = "registry+https://github.com/rust-lang/crates.io-index" 1611 | checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" 1612 | 1613 | [[package]] 1614 | name = "subtle" 1615 | version = "2.6.1" 1616 | source = "registry+https://github.com/rust-lang/crates.io-index" 1617 | checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" 1618 | 1619 | [[package]] 1620 | name = "syn" 1621 | version = "1.0.109" 1622 | source = "registry+https://github.com/rust-lang/crates.io-index" 1623 | checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" 1624 | dependencies = [ 1625 | "proc-macro2", 1626 | "quote", 1627 | "unicode-ident", 1628 | ] 1629 | 1630 | [[package]] 1631 | name = "syn" 1632 | version = "2.0.72" 1633 | source = "registry+https://github.com/rust-lang/crates.io-index" 1634 | checksum = "dc4b9b9bf2add8093d3f2c0204471e951b2285580335de42f9d2534f3ae7a8af" 1635 | dependencies = [ 1636 | "proc-macro2", 1637 | "quote", 1638 | "unicode-ident", 1639 | ] 1640 | 1641 | [[package]] 1642 | name = "sync_wrapper" 1643 | version = "0.1.2" 1644 | source = "registry+https://github.com/rust-lang/crates.io-index" 1645 | checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" 1646 | 1647 | [[package]] 1648 | name = "target-lexicon" 1649 | version = "0.12.15" 1650 | source = "registry+https://github.com/rust-lang/crates.io-index" 1651 | checksum = "4873307b7c257eddcb50c9bedf158eb669578359fb28428bef438fec8e6ba7c2" 1652 | 1653 | [[package]] 1654 | name = "thiserror" 1655 | version = "1.0.63" 1656 | source = "registry+https://github.com/rust-lang/crates.io-index" 1657 | checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724" 1658 | dependencies = [ 1659 | "thiserror-impl", 1660 | ] 1661 | 1662 | [[package]] 1663 | name = "thiserror-impl" 1664 | version = "1.0.63" 1665 | source = "registry+https://github.com/rust-lang/crates.io-index" 1666 | checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" 1667 | dependencies = [ 1668 | "proc-macro2", 1669 | "quote", 1670 | "syn 2.0.72", 1671 | ] 1672 | 1673 | [[package]] 1674 | name = "thread_local" 1675 | version = "1.1.8" 1676 | source = "registry+https://github.com/rust-lang/crates.io-index" 1677 | checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" 1678 | dependencies = [ 1679 | "cfg-if", 1680 | "once_cell", 1681 | ] 1682 | 1683 | [[package]] 1684 | name = "tokio" 1685 | version = "1.39.1" 1686 | source = "registry+https://github.com/rust-lang/crates.io-index" 1687 | checksum = "d040ac2b29ab03b09d4129c2f5bbd012a3ac2f79d38ff506a4bf8dd34b0eac8a" 1688 | dependencies = [ 1689 | "backtrace", 1690 | "bytes", 1691 | "libc", 1692 | "mio", 1693 | "parking_lot", 1694 | "pin-project-lite", 1695 | "signal-hook-registry", 1696 | "socket2", 1697 | "tokio-macros", 1698 | "windows-sys", 1699 | ] 1700 | 1701 | [[package]] 1702 | name = "tokio-io-timeout" 1703 | version = "1.2.0" 1704 | source = "registry+https://github.com/rust-lang/crates.io-index" 1705 | checksum = "30b74022ada614a1b4834de765f9bb43877f910cc8ce4be40e89042c9223a8bf" 1706 | dependencies = [ 1707 | "pin-project-lite", 1708 | "tokio", 1709 | ] 1710 | 1711 | [[package]] 1712 | name = "tokio-macros" 1713 | version = "2.4.0" 1714 | source = "registry+https://github.com/rust-lang/crates.io-index" 1715 | checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" 1716 | dependencies = [ 1717 | "proc-macro2", 1718 | "quote", 1719 | "syn 2.0.72", 1720 | ] 1721 | 1722 | [[package]] 1723 | name = "tokio-rustls" 1724 | version = "0.25.0" 1725 | source = "registry+https://github.com/rust-lang/crates.io-index" 1726 | checksum = "775e0c0f0adb3a2f22a00c4745d728b479985fc15ee7ca6a2608388c5569860f" 1727 | dependencies = [ 1728 | "rustls", 1729 | "rustls-pki-types", 1730 | "tokio", 1731 | ] 1732 | 1733 | [[package]] 1734 | name = "tokio-stream" 1735 | version = "0.1.15" 1736 | source = "registry+https://github.com/rust-lang/crates.io-index" 1737 | checksum = "267ac89e0bec6e691e5813911606935d77c476ff49024f98abcea3e7b15e37af" 1738 | dependencies = [ 1739 | "futures-core", 1740 | "pin-project-lite", 1741 | "tokio", 1742 | ] 1743 | 1744 | [[package]] 1745 | name = "tokio-util" 1746 | version = "0.7.11" 1747 | source = "registry+https://github.com/rust-lang/crates.io-index" 1748 | checksum = "9cf6b47b3771c49ac75ad09a6162f53ad4b8088b76ac60e8ec1455b31a189fe1" 1749 | dependencies = [ 1750 | "bytes", 1751 | "futures-core", 1752 | "futures-sink", 1753 | "pin-project-lite", 1754 | "tokio", 1755 | ] 1756 | 1757 | [[package]] 1758 | name = "tonic" 1759 | version = "0.11.0" 1760 | source = "registry+https://github.com/rust-lang/crates.io-index" 1761 | checksum = "76c4eb7a4e9ef9d4763600161f12f5070b92a578e1b634db88a6887844c91a13" 1762 | dependencies = [ 1763 | "async-stream", 1764 | "async-trait", 1765 | "axum", 1766 | "base64 0.21.7", 1767 | "bytes", 1768 | "h2", 1769 | "http", 1770 | "http-body", 1771 | "hyper", 1772 | "hyper-timeout", 1773 | "percent-encoding", 1774 | "pin-project", 1775 | "prost", 1776 | "tokio", 1777 | "tokio-stream", 1778 | "tower", 1779 | "tower-layer", 1780 | "tower-service", 1781 | "tracing", 1782 | ] 1783 | 1784 | [[package]] 1785 | name = "tonic-web" 1786 | version = "0.11.0" 1787 | source = "registry+https://github.com/rust-lang/crates.io-index" 1788 | checksum = "dc3b0e1cedbf19fdfb78ef3d672cb9928e0a91a9cb4629cc0c916e8cff8aaaa1" 1789 | dependencies = [ 1790 | "base64 0.21.7", 1791 | "bytes", 1792 | "http", 1793 | "http-body", 1794 | "hyper", 1795 | "pin-project", 1796 | "tokio-stream", 1797 | "tonic", 1798 | "tower-http", 1799 | "tower-layer", 1800 | "tower-service", 1801 | "tracing", 1802 | ] 1803 | 1804 | [[package]] 1805 | name = "tower" 1806 | version = "0.4.13" 1807 | source = "registry+https://github.com/rust-lang/crates.io-index" 1808 | checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" 1809 | dependencies = [ 1810 | "futures-core", 1811 | "futures-util", 1812 | "indexmap 1.9.3", 1813 | "pin-project", 1814 | "pin-project-lite", 1815 | "rand", 1816 | "slab", 1817 | "tokio", 1818 | "tokio-util", 1819 | "tower-layer", 1820 | "tower-service", 1821 | "tracing", 1822 | ] 1823 | 1824 | [[package]] 1825 | name = "tower-http" 1826 | version = "0.4.4" 1827 | source = "registry+https://github.com/rust-lang/crates.io-index" 1828 | checksum = "61c5bb1d698276a2443e5ecfabc1008bf15a36c12e6a7176e7bf089ea9131140" 1829 | dependencies = [ 1830 | "bitflags 2.6.0", 1831 | "bytes", 1832 | "futures-core", 1833 | "futures-util", 1834 | "http", 1835 | "http-body", 1836 | "http-range-header", 1837 | "pin-project-lite", 1838 | "tower", 1839 | "tower-layer", 1840 | "tower-service", 1841 | "tracing", 1842 | ] 1843 | 1844 | [[package]] 1845 | name = "tower-layer" 1846 | version = "0.3.2" 1847 | source = "registry+https://github.com/rust-lang/crates.io-index" 1848 | checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0" 1849 | 1850 | [[package]] 1851 | name = "tower-service" 1852 | version = "0.3.2" 1853 | source = "registry+https://github.com/rust-lang/crates.io-index" 1854 | checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" 1855 | 1856 | [[package]] 1857 | name = "tracing" 1858 | version = "0.1.40" 1859 | source = "registry+https://github.com/rust-lang/crates.io-index" 1860 | checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" 1861 | dependencies = [ 1862 | "log", 1863 | "pin-project-lite", 1864 | "tracing-attributes", 1865 | "tracing-core", 1866 | ] 1867 | 1868 | [[package]] 1869 | name = "tracing-attributes" 1870 | version = "0.1.27" 1871 | source = "registry+https://github.com/rust-lang/crates.io-index" 1872 | checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" 1873 | dependencies = [ 1874 | "proc-macro2", 1875 | "quote", 1876 | "syn 2.0.72", 1877 | ] 1878 | 1879 | [[package]] 1880 | name = "tracing-core" 1881 | version = "0.1.32" 1882 | source = "registry+https://github.com/rust-lang/crates.io-index" 1883 | checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" 1884 | dependencies = [ 1885 | "once_cell", 1886 | "valuable", 1887 | ] 1888 | 1889 | [[package]] 1890 | name = "tracing-log" 1891 | version = "0.2.0" 1892 | source = "registry+https://github.com/rust-lang/crates.io-index" 1893 | checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" 1894 | dependencies = [ 1895 | "log", 1896 | "once_cell", 1897 | "tracing-core", 1898 | ] 1899 | 1900 | [[package]] 1901 | name = "tracing-subscriber" 1902 | version = "0.3.18" 1903 | source = "registry+https://github.com/rust-lang/crates.io-index" 1904 | checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" 1905 | dependencies = [ 1906 | "nu-ansi-term", 1907 | "sharded-slab", 1908 | "smallvec", 1909 | "thread_local", 1910 | "tracing-core", 1911 | "tracing-log", 1912 | ] 1913 | 1914 | [[package]] 1915 | name = "try-lock" 1916 | version = "0.2.5" 1917 | source = "registry+https://github.com/rust-lang/crates.io-index" 1918 | checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" 1919 | 1920 | [[package]] 1921 | name = "typenum" 1922 | version = "1.17.0" 1923 | source = "registry+https://github.com/rust-lang/crates.io-index" 1924 | checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" 1925 | 1926 | [[package]] 1927 | name = "uncased" 1928 | version = "0.9.10" 1929 | source = "registry+https://github.com/rust-lang/crates.io-index" 1930 | checksum = "e1b88fcfe09e89d3866a5c11019378088af2d24c3fbd4f0543f96b479ec90697" 1931 | dependencies = [ 1932 | "version_check", 1933 | ] 1934 | 1935 | [[package]] 1936 | name = "unicode-ident" 1937 | version = "1.0.12" 1938 | source = "registry+https://github.com/rust-lang/crates.io-index" 1939 | checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" 1940 | 1941 | [[package]] 1942 | name = "unindent" 1943 | version = "0.1.11" 1944 | source = "registry+https://github.com/rust-lang/crates.io-index" 1945 | checksum = "e1766d682d402817b5ac4490b3c3002d91dfa0d22812f341609f97b08757359c" 1946 | 1947 | [[package]] 1948 | name = "untrusted" 1949 | version = "0.9.0" 1950 | source = "registry+https://github.com/rust-lang/crates.io-index" 1951 | checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" 1952 | 1953 | [[package]] 1954 | name = "uuid" 1955 | version = "1.10.0" 1956 | source = "registry+https://github.com/rust-lang/crates.io-index" 1957 | checksum = "81dfa00651efa65069b0b6b651f4aaa31ba9e3c3ce0137aaad053604ee7e0314" 1958 | dependencies = [ 1959 | "getrandom", 1960 | "serde", 1961 | ] 1962 | 1963 | [[package]] 1964 | name = "valuable" 1965 | version = "0.1.0" 1966 | source = "registry+https://github.com/rust-lang/crates.io-index" 1967 | checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" 1968 | 1969 | [[package]] 1970 | name = "version_check" 1971 | version = "0.9.5" 1972 | source = "registry+https://github.com/rust-lang/crates.io-index" 1973 | checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" 1974 | 1975 | [[package]] 1976 | name = "want" 1977 | version = "0.3.1" 1978 | source = "registry+https://github.com/rust-lang/crates.io-index" 1979 | checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" 1980 | dependencies = [ 1981 | "try-lock", 1982 | ] 1983 | 1984 | [[package]] 1985 | name = "wasi" 1986 | version = "0.11.0+wasi-snapshot-preview1" 1987 | source = "registry+https://github.com/rust-lang/crates.io-index" 1988 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 1989 | 1990 | [[package]] 1991 | name = "wasm-bindgen" 1992 | version = "0.2.100" 1993 | source = "registry+https://github.com/rust-lang/crates.io-index" 1994 | checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" 1995 | dependencies = [ 1996 | "cfg-if", 1997 | "once_cell", 1998 | "rustversion", 1999 | "wasm-bindgen-macro", 2000 | ] 2001 | 2002 | [[package]] 2003 | name = "wasm-bindgen-backend" 2004 | version = "0.2.100" 2005 | source = "registry+https://github.com/rust-lang/crates.io-index" 2006 | checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" 2007 | dependencies = [ 2008 | "bumpalo", 2009 | "log", 2010 | "proc-macro2", 2011 | "quote", 2012 | "syn 2.0.72", 2013 | "wasm-bindgen-shared", 2014 | ] 2015 | 2016 | [[package]] 2017 | name = "wasm-bindgen-macro" 2018 | version = "0.2.100" 2019 | source = "registry+https://github.com/rust-lang/crates.io-index" 2020 | checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" 2021 | dependencies = [ 2022 | "quote", 2023 | "wasm-bindgen-macro-support", 2024 | ] 2025 | 2026 | [[package]] 2027 | name = "wasm-bindgen-macro-support" 2028 | version = "0.2.100" 2029 | source = "registry+https://github.com/rust-lang/crates.io-index" 2030 | checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" 2031 | dependencies = [ 2032 | "proc-macro2", 2033 | "quote", 2034 | "syn 2.0.72", 2035 | "wasm-bindgen-backend", 2036 | "wasm-bindgen-shared", 2037 | ] 2038 | 2039 | [[package]] 2040 | name = "wasm-bindgen-shared" 2041 | version = "0.2.100" 2042 | source = "registry+https://github.com/rust-lang/crates.io-index" 2043 | checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" 2044 | dependencies = [ 2045 | "unicode-ident", 2046 | ] 2047 | 2048 | [[package]] 2049 | name = "webpki-roots" 2050 | version = "0.26.3" 2051 | source = "registry+https://github.com/rust-lang/crates.io-index" 2052 | checksum = "bd7c23921eeb1713a4e851530e9b9756e4fb0e89978582942612524cf09f01cd" 2053 | dependencies = [ 2054 | "rustls-pki-types", 2055 | ] 2056 | 2057 | [[package]] 2058 | name = "which" 2059 | version = "4.4.2" 2060 | source = "registry+https://github.com/rust-lang/crates.io-index" 2061 | checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" 2062 | dependencies = [ 2063 | "either", 2064 | "home", 2065 | "once_cell", 2066 | "rustix", 2067 | ] 2068 | 2069 | [[package]] 2070 | name = "winapi" 2071 | version = "0.3.9" 2072 | source = "registry+https://github.com/rust-lang/crates.io-index" 2073 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 2074 | dependencies = [ 2075 | "winapi-i686-pc-windows-gnu", 2076 | "winapi-x86_64-pc-windows-gnu", 2077 | ] 2078 | 2079 | [[package]] 2080 | name = "winapi-i686-pc-windows-gnu" 2081 | version = "0.4.0" 2082 | source = "registry+https://github.com/rust-lang/crates.io-index" 2083 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 2084 | 2085 | [[package]] 2086 | name = "winapi-x86_64-pc-windows-gnu" 2087 | version = "0.4.0" 2088 | source = "registry+https://github.com/rust-lang/crates.io-index" 2089 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 2090 | 2091 | [[package]] 2092 | name = "windows-core" 2093 | version = "0.52.0" 2094 | source = "registry+https://github.com/rust-lang/crates.io-index" 2095 | checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" 2096 | dependencies = [ 2097 | "windows-targets", 2098 | ] 2099 | 2100 | [[package]] 2101 | name = "windows-link" 2102 | version = "0.1.1" 2103 | source = "registry+https://github.com/rust-lang/crates.io-index" 2104 | checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38" 2105 | 2106 | [[package]] 2107 | name = "windows-sys" 2108 | version = "0.52.0" 2109 | source = "registry+https://github.com/rust-lang/crates.io-index" 2110 | checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" 2111 | dependencies = [ 2112 | "windows-targets", 2113 | ] 2114 | 2115 | [[package]] 2116 | name = "windows-targets" 2117 | version = "0.52.6" 2118 | source = "registry+https://github.com/rust-lang/crates.io-index" 2119 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 2120 | dependencies = [ 2121 | "windows_aarch64_gnullvm", 2122 | "windows_aarch64_msvc", 2123 | "windows_i686_gnu", 2124 | "windows_i686_gnullvm", 2125 | "windows_i686_msvc", 2126 | "windows_x86_64_gnu", 2127 | "windows_x86_64_gnullvm", 2128 | "windows_x86_64_msvc", 2129 | ] 2130 | 2131 | [[package]] 2132 | name = "windows_aarch64_gnullvm" 2133 | version = "0.52.6" 2134 | source = "registry+https://github.com/rust-lang/crates.io-index" 2135 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 2136 | 2137 | [[package]] 2138 | name = "windows_aarch64_msvc" 2139 | version = "0.52.6" 2140 | source = "registry+https://github.com/rust-lang/crates.io-index" 2141 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 2142 | 2143 | [[package]] 2144 | name = "windows_i686_gnu" 2145 | version = "0.52.6" 2146 | source = "registry+https://github.com/rust-lang/crates.io-index" 2147 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 2148 | 2149 | [[package]] 2150 | name = "windows_i686_gnullvm" 2151 | version = "0.52.6" 2152 | source = "registry+https://github.com/rust-lang/crates.io-index" 2153 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 2154 | 2155 | [[package]] 2156 | name = "windows_i686_msvc" 2157 | version = "0.52.6" 2158 | source = "registry+https://github.com/rust-lang/crates.io-index" 2159 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 2160 | 2161 | [[package]] 2162 | name = "windows_x86_64_gnu" 2163 | version = "0.52.6" 2164 | source = "registry+https://github.com/rust-lang/crates.io-index" 2165 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 2166 | 2167 | [[package]] 2168 | name = "windows_x86_64_gnullvm" 2169 | version = "0.52.6" 2170 | source = "registry+https://github.com/rust-lang/crates.io-index" 2171 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 2172 | 2173 | [[package]] 2174 | name = "windows_x86_64_msvc" 2175 | version = "0.52.6" 2176 | source = "registry+https://github.com/rust-lang/crates.io-index" 2177 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 2178 | 2179 | [[package]] 2180 | name = "zerocopy" 2181 | version = "0.7.35" 2182 | source = "registry+https://github.com/rust-lang/crates.io-index" 2183 | checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" 2184 | dependencies = [ 2185 | "byteorder", 2186 | "zerocopy-derive", 2187 | ] 2188 | 2189 | [[package]] 2190 | name = "zerocopy-derive" 2191 | version = "0.7.35" 2192 | source = "registry+https://github.com/rust-lang/crates.io-index" 2193 | checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" 2194 | dependencies = [ 2195 | "proc-macro2", 2196 | "quote", 2197 | "syn 2.0.72", 2198 | ] 2199 | 2200 | [[package]] 2201 | name = "zeroize" 2202 | version = "1.8.1" 2203 | source = "registry+https://github.com/rust-lang/crates.io-index" 2204 | checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" 2205 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "libsql-python" 3 | version = "0.0.53" 4 | edition = "2021" 5 | 6 | [lib] 7 | name = "libsql_experimental" 8 | crate-type = ["cdylib"] 9 | 10 | [dependencies] 11 | pyo3 = "0.19.0" 12 | libsql = { version = "0.9.8", features = ["encryption"] } 13 | tokio = { version = "1.29.1", features = [ "rt-multi-thread" ] } 14 | tracing-subscriber = "0.3" 15 | 16 | [build-dependencies] 17 | version_check = "0.9.5" 18 | # used where logic has to be version/distribution specific, e.g. pypy 19 | pyo3-build-config = { version = "0.19.0" } 20 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright 2023 the libSQL authors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | Turso + Python 4 |

Turso + Python (experimental)

5 | 6 |

7 | 8 | This package is experimental, which means it is not consider to be production grade. Furthermore, the package currently only supports Linux and macOS. 9 | 10 |

11 | SQLite for Production. Powered by libSQL. 12 |

13 | 14 |

15 | Turso · 16 | Quickstart · 17 | Examples · 18 | Docs · 19 | Discord · 20 | Blog & Tutorials 21 |

22 | 23 |

24 | 25 | PyPI 26 | 27 | 28 | discord activity 29 | 30 |

31 | 32 | --- 33 | 34 | ## Documentation 35 | 36 | 1. [Turso Quickstart](https://docs.turso.tech/quickstart) — Learn how create and connect your first database. 37 | 2. [SDK Quickstart](https://docs.turso.tech/sdk/python/quickstart) — Learn how to install and execute queries using the libSQL client. 38 | 3. [SDK Reference](https://docs.turso.tech/sdk/python/reference) — Dive deeper with the libSQL SDK reference and examples. 39 | 40 | ### What is Turso? 41 | 42 | [Turso](https://turso.tech) is a SQLite-compatible database built on [libSQL](https://docs.turso.tech/libsql), the Open Contribution fork of SQLite. It enables scaling to hundreds of thousands of databases per organization and supports replication to any location, including your own servers, for microsecond-latency access. 43 | 44 | Learn more about what you can do with Turso: 45 | 46 | - [Embedded Replicas](https://docs.turso.tech/features/embedded-replicas) 47 | - [Platform API](https://docs.turso.tech/features/platform-api) 48 | - [Branching](https://docs.turso.tech/features/branching) 49 | - [Point-in-Time Recovery](https://docs.turso.tech/features/point-in-time-recovery) 50 | -------------------------------------------------------------------------------- /build.rs: -------------------------------------------------------------------------------- 1 | pub fn main() { 2 | pyo3_build_config::use_pyo3_cfgs(); 3 | } 4 | -------------------------------------------------------------------------------- /docs/api.md: -------------------------------------------------------------------------------- 1 | # `libsql`: SQLite compatible interface for libSQL 2 | 3 | ## Module functions 4 | 5 | ### connect(database) ⇒ Connection 6 | 7 | Creates a new database connection. 8 | 9 | | Param | Type | Description | 10 | | -------- | ------------------- | ------------------------- | 11 | | database | string | Path to the database file | 12 | 13 | ## `Connection` objects 14 | 15 | ### cursor() ⇒ Cursor 16 | 17 | Creates a new database cursor. 18 | 19 | ### blobopen() 20 | 21 | Unimplemented. 22 | 23 | ### commit() 24 | 25 | Commits the current transaction and starts a new one. 26 | 27 | ### rollback() 28 | 29 | Rolls back the current transaction and starts a new one. 30 | 31 | ### close() 32 | 33 | Closes the database connection. 34 | 35 | ### execute(sql, parameters=()) 36 | 37 | Create a new cursor object and executes the SQL statement. 38 | 39 | ### executemany(sql, parameters) 40 | 41 | Create a new cursor object and Execute the SQL statement for every item in `parameters` array. 42 | 43 | | Param | Type | Description | 44 | | ---------- | ------------------- | ---------------------------------------------- | 45 | | sql | string | Path to the database file | 46 | | parameters | array | Array of parameter tuples to execute SQL with. | 47 | 48 | ### executescript() 49 | 50 | Unimplemented. 51 | 52 | ### create_function() 53 | 54 | Unimplemented. 55 | 56 | ### create_aggregate() 57 | 58 | Unimplemented. 59 | 60 | ### create_window_function() 61 | 62 | Unimplemented. 63 | 64 | ### create_collation() 65 | 66 | Unimplemented. 67 | 68 | ### interrupt() 69 | 70 | Unimplemented. 71 | 72 | ### set_authorizer() 73 | 74 | Unimplemented. 75 | 76 | ### set_progress_handler() 77 | 78 | Unimplemented. 79 | 80 | ### set_trace_callback() 81 | 82 | Unimplemented. 83 | 84 | ### enable_load_extension() 85 | 86 | Unimplemented. 87 | 88 | ### load_extension() 89 | 90 | Unimplemented. 91 | 92 | ### iterdump() 93 | 94 | Unimplemented. 95 | 96 | ### backup() 97 | 98 | Unimplemented. 99 | 100 | ### getlimit() 101 | 102 | Unimplemented. 103 | 104 | ### setlimit() 105 | 106 | Unimplemented. 107 | 108 | ### getconfig() 109 | 110 | Unimplemented. 111 | 112 | ### setconfig() 113 | 114 | Unimplemented. 115 | 116 | ### serialize() 117 | 118 | Unimplemented. 119 | 120 | ### deserialize() 121 | 122 | Unimplemented. 123 | 124 | ### autocommit 125 | 126 | Starting from Python 3.12, the autocommit property is supported, controlling PEP 249 transaction handling behavior. By default, autocommit is set to LEGACY_TRANSACTION_CONTROL, but this will change to False in a future Python release. For more details, refer to [Connection.autocommit](https://docs.python.org/3/library/sqlite3.html#sqlite3.Connection.autocommit) in the official Python documentation. 127 | 128 | ### in_transaction 129 | 130 | Returns `True` if there's an active transaction with uncommitted changes; otherwise returns `False`. 131 | 132 | ### isolation_level 133 | 134 | Transaction handling mode configuration. If `isolation_level` is set to `"DEFERRED"`, `"IMMEDIATE"`, or `"EXCLUSIVE"`, transactions begin implicitly, but need to be committed manually. If `isolation_level` is set to `None`, then database is in auto-commit mode, executing each statement in its own transaction. 135 | 136 | ### row_factory 137 | 138 | Unimplemented. 139 | 140 | ### text_factory 141 | 142 | Unimplemented. 143 | 144 | ### total_changes 145 | 146 | Unimplemented. 147 | 148 | ## `Cursor` objects 149 | 150 | ### execute(sql, parameters=()) 151 | 152 | Execute one SQL statement. 153 | 154 | ### executemany(sql, parameters) 155 | 156 | Execute the SQL statement for every item in `parameters` array. 157 | 158 | | Param | Type | Description | 159 | | ---------- | ------------------- | ---------------------------------------------- | 160 | | sql | string | Path to the database file | 161 | | parameters | array | Array of parameter tuples to execute SQL with. | 162 | 163 | ### executescript() 164 | 165 | Unimplemented. 166 | 167 | ### fetchone() 168 | 169 | Return next row in result set. 170 | 171 | ### fetchmany(size = cursor.arraysize) 172 | 173 | Return `size` next rows in result set. If there are no more rows left, returns an empty list. 174 | 175 | ### fetchall() 176 | 177 | Return all rows in result set. 178 | 179 | ### close() 180 | 181 | Unimplemented. 182 | 183 | ### setinputsizes() 184 | 185 | Unimplemented. 186 | 187 | ### setoutputsize() 188 | 189 | Unimplemented. 190 | 191 | ### arraysize 192 | 193 | The number of rows returned by `fetchmany()` by default. 194 | 195 | ### connection 196 | 197 | Unimplemented. 198 | 199 | ### description 200 | 201 | Column names of the query that was run last. 202 | 203 | ### lastrowid 204 | 205 | Returns the row ID of the last inserted row. 206 | 207 | ### rowcount 208 | 209 | Returns the number of rows changed by `INSERT`, `UPDATE`, `DELETE`, and `REPLACE` statements. For other types of statements, returns -1. 210 | 211 | ### row_factory 212 | 213 | Unimplemented. 214 | 215 | -------------------------------------------------------------------------------- /example.py: -------------------------------------------------------------------------------- 1 | import libsql_experimental 2 | 3 | con = libsql_experimental.connect("hello.db", sync_url="http://localhost:8080", 4 | auth_token="") 5 | 6 | con.sync() 7 | 8 | cur = con.cursor() 9 | 10 | cur.execute("CREATE TABLE IF NOT EXISTS users (id INTEGER, email TEXT);") 11 | cur.execute("INSERT INTO users VALUES (1, 'penberg@iki.fi')") 12 | 13 | print(cur.execute("SELECT * FROM users").fetchone()) 14 | -------------------------------------------------------------------------------- /examples/batch/.gitignore: -------------------------------------------------------------------------------- 1 | local.db-journal 2 | -------------------------------------------------------------------------------- /examples/batch/README.md: -------------------------------------------------------------------------------- 1 | # Local 2 | 3 | This example demonstrates how to use libSQL to execute a batch of SQL statements. 4 | 5 | ## Install Dependencies 6 | 7 | ```bash 8 | pip install libsql-experimental 9 | ``` 10 | 11 | ## Running 12 | 13 | Execute the example: 14 | 15 | ```bash 16 | python3 main.py 17 | ``` 18 | 19 | This will create a local database, execute a batch of SQL statements (creating tables, inserting data, etc.), and then query the results. 20 | -------------------------------------------------------------------------------- /examples/batch/main.py: -------------------------------------------------------------------------------- 1 | import libsql_experimental as libsql 2 | 3 | conn = libsql.connect("local.db") 4 | cur = conn.cursor() 5 | 6 | cur.executescript( 7 | """ 8 | DROP TABLE IF EXISTS users; 9 | CREATE TABLE users (id INTEGER, name TEXT); 10 | INSERT INTO users VALUES (1, 'first@example.org'); 11 | INSERT INTO users VALUES (2, 'second@example.org'); 12 | INSERT INTO users VALUES (3, 'third@example.org'); 13 | """ 14 | ) 15 | 16 | print(conn.execute("select * from users").fetchall()) 17 | -------------------------------------------------------------------------------- /examples/encryption/.gitignore: -------------------------------------------------------------------------------- 1 | local.db-journal 2 | -------------------------------------------------------------------------------- /examples/encryption/README.md: -------------------------------------------------------------------------------- 1 | # Local 2 | 3 | This example demonstrates how to create and use an encrypted SQLite database with libSQL. 4 | 5 | ## Install Dependencies 6 | 7 | ```bash 8 | pip install libsql-experimental 9 | ``` 10 | 11 | ## Running 12 | 13 | Execute the example: 14 | 15 | ```bash 16 | python3 main.py 17 | ``` 18 | 19 | This will setup an encrypted SQLite database, execute a batch of SQL statements, and then query the results. 20 | -------------------------------------------------------------------------------- /examples/encryption/main.py: -------------------------------------------------------------------------------- 1 | import libsql_experimental as libsql 2 | 3 | # You should set the ENCRYPTION_KEY in a environment variable 4 | # For demo purposes, we're using a fixed key 5 | encryption_key= "my-safe-encryption-key"; 6 | 7 | conn = libsql.connect("local.db", encryption_key=encryption_key) 8 | cur = conn.cursor() 9 | 10 | conn.execute("CREATE TABLE IF NOT EXISTS users (name TEXT);") 11 | conn.execute("INSERT INTO users VALUES ('first@example.com');") 12 | conn.execute("INSERT INTO users VALUES ('second@example.com');") 13 | conn.execute("INSERT INTO users VALUES ('third@example.com');") 14 | 15 | 16 | print(conn.execute("select * from users").fetchall()) 17 | -------------------------------------------------------------------------------- /examples/execute_script.py: -------------------------------------------------------------------------------- 1 | """ 2 | A short example showing how to execute a script containing a bunch of sql statements. 3 | """ 4 | import os 5 | 6 | import libsql_experimental as libsql 7 | 8 | def execute_script(conn, file_path: os.PathLike): 9 | with open(file_path, 'r') as file: 10 | script = file.read() 11 | 12 | conn.executescript(script) 13 | conn.commit() 14 | 15 | conn = libsql.connect(':memory:') 16 | script_path = os.path.join(os.path.dirname(__file__), 'statements.sql') 17 | execute_script(conn, script_path) 18 | 19 | # Retrieve the data from the 'users' table and print it 20 | cursor = conn.cursor() 21 | cursor.execute("SELECT * FROM users") 22 | rows = cursor.fetchall() 23 | print("Data in the 'users' table:") 24 | for row in rows: 25 | print(row) 26 | -------------------------------------------------------------------------------- /examples/local/.gitignore: -------------------------------------------------------------------------------- 1 | local.db-journal 2 | -------------------------------------------------------------------------------- /examples/local/README.md: -------------------------------------------------------------------------------- 1 | # Local 2 | 3 | This example demonstrates how to use libSQL with a local SQLite file. 4 | 5 | ## Install Dependencies 6 | 7 | ```bash 8 | pip install libsql-experimental 9 | ``` 10 | 11 | ## Running 12 | 13 | Execute the example: 14 | 15 | ```bash 16 | python3 main.py 17 | ``` 18 | 19 | This will connect to a local SQLite, insert some data, and query it. 20 | -------------------------------------------------------------------------------- /examples/local/main.py: -------------------------------------------------------------------------------- 1 | import libsql_experimental as libsql 2 | 3 | conn = libsql.connect("local.db") 4 | cur = conn.cursor() 5 | 6 | conn.execute("CREATE TABLE IF NOT EXISTS users (name TEXT);") 7 | conn.execute("INSERT INTO users VALUES ('first@example.com');") 8 | conn.execute("INSERT INTO users VALUES ('second@example.com');") 9 | conn.execute("INSERT INTO users VALUES ('third@example.com');") 10 | 11 | 12 | print(conn.execute("select * from users").fetchall()) 13 | -------------------------------------------------------------------------------- /examples/memory/README.md: -------------------------------------------------------------------------------- 1 | # Local 2 | 3 | This example demonstrates how to use libSQL with an in-memory SQLite database. 4 | 5 | ## Install Dependencies 6 | 7 | ```bash 8 | pip install libsql-experimental 9 | ``` 10 | 11 | ## Running 12 | 13 | Execute the example: 14 | 15 | ```bash 16 | python3 main.py 17 | ``` 18 | 19 | This will create an in-memory SQLite database, insert some data, and then query the results. 20 | -------------------------------------------------------------------------------- /examples/memory/main.py: -------------------------------------------------------------------------------- 1 | import libsql_experimental as libsql 2 | 3 | conn = libsql.connect(":memory:") 4 | cur = conn.cursor() 5 | 6 | conn.execute("CREATE TABLE IF NOT EXISTS users (name TEXT);") 7 | conn.execute("INSERT INTO users VALUES ('first@example.com');") 8 | conn.execute("INSERT INTO users VALUES ('second@example.com');") 9 | conn.execute("INSERT INTO users VALUES ('third@example.com');") 10 | 11 | 12 | print(conn.execute("select * from users").fetchall()) 13 | -------------------------------------------------------------------------------- /examples/remote/README.md: -------------------------------------------------------------------------------- 1 | # Local 2 | 3 | This example demonstrates how to use libSQL with a remote database. 4 | 5 | ## Install Dependencies 6 | 7 | ```bash 8 | pip install libsql-experimental 9 | ``` 10 | 11 | ## Running 12 | 13 | Execute the example: 14 | 15 | ```bash 16 | TURSO_DATABASE_URL="..." TURSO_AUTH_TOKEN="..." python3 main.py 17 | ``` 18 | 19 | This will connect to a remote database, insert some data, and query it. 20 | -------------------------------------------------------------------------------- /examples/remote/main.py: -------------------------------------------------------------------------------- 1 | import libsql_experimental as libsql 2 | import os 3 | 4 | url = os.getenv("TURSO_DATABASE_URL") 5 | auth_token = os.getenv("TURSO_AUTH_TOKEN") 6 | 7 | conn = libsql.connect(url, auth_token=auth_token) 8 | cur = conn.cursor() 9 | 10 | 11 | conn.execute("DROP TABLE IF EXISTS users;") 12 | conn.execute("CREATE TABLE IF NOT EXISTS users (name TEXT);") 13 | conn.execute("INSERT INTO users VALUES ('first@example.com');") 14 | conn.execute("INSERT INTO users VALUES ('second@example.com');") 15 | conn.execute("INSERT INTO users VALUES ('third@example.com');") 16 | 17 | conn.commit() 18 | 19 | print(conn.execute("select * from users").fetchall()) 20 | -------------------------------------------------------------------------------- /examples/remote_connect.py: -------------------------------------------------------------------------------- 1 | """ 2 | A short example showing how to connect to a remote libsql or Turso database 3 | 4 | Set the LIBSQL_URL and LIBSQL_AUTH_TOKEN environment variables to point to a database. 5 | """ 6 | import os 7 | 8 | import libsql_experimental as libsql 9 | 10 | print(F"connecting to {os.getenv('LIBSQL_URL')}") 11 | conn = libsql.connect(database=os.getenv('LIBSQL_URL'), 12 | auth_token=os.getenv("LIBSQL_AUTH_TOKEN")) 13 | conn.execute("CREATE TABLE IF NOT EXISTS users (id INTEGER);") 14 | conn.execute("INSERT INTO users(id) VALUES (10);") 15 | conn.commit() 16 | 17 | print(conn.execute("select * from users").fetchall()) 18 | -------------------------------------------------------------------------------- /examples/sqlalchemy/dialect.py: -------------------------------------------------------------------------------- 1 | import os 2 | import urllib.parse 3 | 4 | from sqlalchemy import util 5 | from sqlalchemy.dialects import registry as _registry 6 | from sqlalchemy.dialects.sqlite.pysqlite import SQLiteDialect_pysqlite 7 | 8 | __version__ = "0.1.0-pre" 9 | 10 | _registry.register( 11 | "sqlite.libsql", "sqlalchemy_libsql", "SQLiteDialect_libsql" 12 | ) 13 | 14 | 15 | def _build_connection_url(url, query, secure): 16 | # sorting of keys is for unit test support 17 | query_str = urllib.parse.urlencode(sorted(query.items())) 18 | 19 | if not url.host: 20 | if query_str: 21 | return f"{url.database}?{query_str}" 22 | return url.database 23 | elif secure: # yes, pop to remove 24 | scheme = "wss" 25 | else: 26 | scheme = "ws" 27 | 28 | if url.username and url.password: 29 | netloc = f"{url.username}:{url.password}@{url.host}" 30 | elif url.username: 31 | netloc = f"{url.username}@{url.host}" 32 | else: 33 | netloc = url.host 34 | 35 | if url.port: 36 | netloc += f":{url.port}" 37 | 38 | return urllib.parse.urlunsplit( 39 | ( 40 | scheme, 41 | netloc, 42 | url.database or "", 43 | query_str, 44 | "", # fragment 45 | ) 46 | ) 47 | 48 | 49 | class SQLiteDialect_libsql(SQLiteDialect_pysqlite): 50 | driver = "libsql" 51 | # need to be set explicitly 52 | supports_statement_cache = SQLiteDialect_pysqlite.supports_statement_cache 53 | 54 | @classmethod 55 | def import_dbapi(cls): 56 | import libsql_experimental as libsql 57 | 58 | return libsql 59 | 60 | def on_connect(self): 61 | import libsql_experimental as libsql 62 | 63 | sqlite3_connect = super().on_connect() 64 | 65 | def connect(conn): 66 | # LibSQL: there is no support for create_function() 67 | if isinstance(conn, Connection): 68 | return 69 | return sqlite3_connect(conn) 70 | 71 | return connect 72 | 73 | def create_connect_args(self, url): 74 | pysqlite_args = ( 75 | ("uri", bool), 76 | ("timeout", float), 77 | ("isolation_level", str), 78 | ("detect_types", int), 79 | ("check_same_thread", bool), 80 | ("cached_statements", int), 81 | ("secure", bool), # LibSQL extra, selects between ws and wss 82 | ) 83 | opts = url.query 84 | libsql_opts = {} 85 | for key, type_ in pysqlite_args: 86 | util.coerce_kw_type(opts, key, type_, dest=libsql_opts) 87 | 88 | if url.host: 89 | libsql_opts["uri"] = True 90 | 91 | if libsql_opts.get("uri", False): 92 | uri_opts = dict(opts) 93 | # here, we are actually separating the parameters that go to 94 | # sqlite3/pysqlite vs. those that go the SQLite URI. What if 95 | # two names conflict? again, this seems to be not the case right 96 | # now, and in the case that new names are added to 97 | # either side which overlap, again the sqlite3/pysqlite parameters 98 | # can be passed through connect_args instead of in the URL. 99 | # If SQLite native URIs add a parameter like "timeout" that 100 | # we already have listed here for the python driver, then we need 101 | # to adjust for that here. 102 | for key, type_ in pysqlite_args: 103 | uri_opts.pop(key, None) 104 | 105 | secure = libsql_opts.pop("secure", False) 106 | connect_url = _build_connection_url(url, uri_opts, secure) 107 | else: 108 | connect_url = url.database or ":memory:" 109 | if connect_url != ":memory:": 110 | connect_url = os.path.abspath(connect_url) 111 | 112 | libsql_opts.setdefault( 113 | "check_same_thread", not self._is_url_file_db(url) 114 | ) 115 | 116 | return ([connect_url], libsql_opts) 117 | 118 | 119 | dialect = SQLiteDialect_libsql 120 | -------------------------------------------------------------------------------- /examples/sqlalchemy/example.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import dialect 4 | 5 | from typing import List 6 | from typing import Optional 7 | from sqlalchemy import ForeignKey 8 | from sqlalchemy import String 9 | from sqlalchemy.orm import DeclarativeBase 10 | from sqlalchemy.orm import Mapped 11 | from sqlalchemy.orm import mapped_column 12 | from sqlalchemy.orm import relationship 13 | 14 | class Base(DeclarativeBase): 15 | pass 16 | 17 | class User(Base): 18 | __tablename__ = "user_account" 19 | id: Mapped[int] = mapped_column(primary_key=True) 20 | name: Mapped[str] = mapped_column(String(30)) 21 | fullname: Mapped[Optional[str]] 22 | addresses: Mapped[List["Address"]] = relationship( 23 | back_populates="user", cascade="all, delete-orphan" 24 | ) 25 | def __repr__(self) -> str: 26 | return f"User(id={self.id!r}, name={self.name!r}, fullname={self.fullname!r})" 27 | 28 | class Address(Base): 29 | __tablename__ = "address" 30 | id: Mapped[int] = mapped_column(primary_key=True) 31 | email_address: Mapped[str] 32 | user_id: Mapped[int] = mapped_column(ForeignKey("user_account.id")) 33 | user: Mapped["User"] = relationship(back_populates="addresses") 34 | def __repr__(self) -> str: 35 | return f"Address(id={self.id!r}, email_address={self.email_address!r})" 36 | 37 | from sqlalchemy import create_engine 38 | engine = create_engine("sqlite+libsql://", echo=True) 39 | 40 | Base.metadata.create_all(engine) 41 | 42 | from sqlalchemy.orm import Session 43 | 44 | with Session(engine) as session: 45 | spongebob = User( 46 | name="spongebob", 47 | fullname="Spongebob Squarepants", 48 | addresses=[Address(email_address="spongebob@sqlalchemy.org")], 49 | ) 50 | sandy = User( 51 | name="sandy", 52 | fullname="Sandy Cheeks", 53 | addresses=[ 54 | Address(email_address="sandy@sqlalchemy.org"), 55 | Address(email_address="sandy@squirrelpower.org"), 56 | ], 57 | ) 58 | patrick = User(name="patrick", fullname="Patrick Star") 59 | session.add_all([spongebob, sandy, patrick]) 60 | session.commit() 61 | 62 | from sqlalchemy import select 63 | 64 | session = Session(engine) 65 | 66 | stmt = select(User).where(User.name.in_(["spongebob", "sandy"])) 67 | 68 | for user in session.scalars(stmt): 69 | print(user) 70 | -------------------------------------------------------------------------------- /examples/statements.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE IF NOT EXISTS users ( 2 | id INTEGER PRIMARY KEY, 3 | name TEXT, 4 | age INTEGER 5 | ); 6 | 7 | INSERT INTO users (name, age) VALUES ('Alice', 25); 8 | INSERT INTO users (name, age) VALUES ('Bob', 30); 9 | 10 | SELECT * FROM users; 11 | 12 | UPDATE users SET age = 26 WHERE name = 'Alice'; 13 | 14 | SELECT * FROM users; 15 | -------------------------------------------------------------------------------- /examples/sync/.gitignore: -------------------------------------------------------------------------------- 1 | local.db* 2 | -------------------------------------------------------------------------------- /examples/sync/README.md: -------------------------------------------------------------------------------- 1 | # Local 2 | 3 | This example demonstrates how to use libSQL with a synced database (local file synced with a remote database). 4 | 5 | ## Install Dependencies 6 | 7 | ```bash 8 | pip install libsql-experimental 9 | ``` 10 | 11 | ## Running 12 | 13 | Execute the example: 14 | 15 | ```bash 16 | TURSO_DATABASE_URL="..." TURSO_AUTH_TOKEN="..." python3 main.py 17 | ``` 18 | 19 | This will create a local database file that syncs with a remote database, insert some data, and query it. 20 | -------------------------------------------------------------------------------- /examples/sync/main.py: -------------------------------------------------------------------------------- 1 | import libsql_experimental as libsql 2 | import os 3 | 4 | url = os.getenv("TURSO_DATABASE_URL") 5 | auth_token = os.getenv("TURSO_AUTH_TOKEN") 6 | 7 | conn = libsql.connect("local.db", sync_url=url, auth_token=auth_token) 8 | conn.sync() 9 | 10 | cur = conn.cursor() 11 | 12 | conn.execute("DROP TABLE IF EXISTS users;") 13 | conn.execute("CREATE TABLE IF NOT EXISTS users (name TEXT);") 14 | conn.execute("INSERT INTO users VALUES ('first@example.com');") 15 | conn.execute("INSERT INTO users VALUES ('second@example.com');") 16 | conn.execute("INSERT INTO users VALUES ('third@example.com');") 17 | 18 | 19 | print(conn.execute("select * from users").fetchall()) 20 | -------------------------------------------------------------------------------- /examples/sync_write.py: -------------------------------------------------------------------------------- 1 | """ 2 | A short example showing how to create an embedded replica, make writes and then read them 3 | 4 | Set the LIBSQL_URL and LIBSQL_AUTH_TOKEN environment variables to point to a database. 5 | """ 6 | import os 7 | 8 | import libsql_experimental as libsql 9 | 10 | print(F"syncing with {os.getenv('LIBSQL_URL')}") 11 | conn = libsql.connect("hello.db", sync_url=os.getenv("LIBSQL_URL"), 12 | auth_token=os.getenv("LIBSQL_AUTH_TOKEN")) 13 | conn.execute("CREATE TABLE IF NOT EXISTS users (id INTEGER);") 14 | conn.execute("INSERT INTO users(id) VALUES (1);") 15 | conn.commit() 16 | conn.sync() 17 | 18 | print(conn.execute("select * from users").fetchall()) 19 | -------------------------------------------------------------------------------- /examples/transaction/.gitignore: -------------------------------------------------------------------------------- 1 | local.db-journal 2 | -------------------------------------------------------------------------------- /examples/transaction/README.md: -------------------------------------------------------------------------------- 1 | # Local 2 | 3 | This example demonstrates how to create and use an encrypted SQLite database with libSQL. 4 | 5 | ## Install Dependencies 6 | 7 | ```bash 8 | pip install libsql-experimental 9 | ``` 10 | 11 | ## Running 12 | 13 | Execute the example: 14 | 15 | ```bash 16 | python3 main.py 17 | ``` 18 | 19 | This example will: 20 | 21 | 1. Create a new table called `users`. 22 | 2. Start a transaction. 23 | 3. Insert multiple users within the transaction. 24 | 4. Demonstrate how to rollback a transaction. 25 | 5. Start another transaction. 26 | 6. Insert more users and commit the transaction. 27 | 7. Query and display the final state of the `users` table. 28 | -------------------------------------------------------------------------------- /examples/transaction/main.py: -------------------------------------------------------------------------------- 1 | import libsql_experimental as libsql 2 | 3 | conn = libsql.connect("local.db") 4 | cur = conn.cursor() 5 | 6 | conn.execute("DROP TABLE IF EXISTS users") 7 | conn.execute("CREATE TABLE users (name TEXT);") 8 | conn.execute("INSERT INTO users VALUES ('first@example.com');") 9 | conn.execute("INSERT INTO users VALUES ('second@example.com');") 10 | 11 | conn.rollback() 12 | 13 | conn.execute("INSERT INTO users VALUES ('third@example.com');") 14 | 15 | print(conn.execute("select * from users").fetchall()) 16 | -------------------------------------------------------------------------------- /examples/vector.py: -------------------------------------------------------------------------------- 1 | import libsql_experimental as libsql 2 | 3 | conn = libsql.connect("vector.db") 4 | 5 | conn.execute( 6 | "CREATE TABLE movies (title TEXT, year INT, embedding F32_BLOB(3))") 7 | conn.execute("CREATE INDEX movies_idx ON movies (libsql_vector_idx(embedding))") 8 | 9 | conn.execute(""" 10 | INSERT INTO movies (title, year, embedding) VALUES 11 | ('Napoleon', 2023, vector('[1,2,3]')), 12 | ('Black Hawk Down', 2001, vector('[10,11,12]')), 13 | ('Gladiator', 2000, vector('[7,8,9]')), 14 | ('Blade Runner', 1982, vector('[4,5,6]')); 15 | """) 16 | 17 | cur = conn.execute(""" 18 | SELECT title, year 19 | FROM vector_top_k('movies_idx', '[4,5,6]', 3) 20 | JOIN movies ON movies.rowid = id 21 | """) 22 | 23 | print(cur.fetchall()) 24 | -------------------------------------------------------------------------------- /examples/vector/.gitignore: -------------------------------------------------------------------------------- 1 | local.db-journal 2 | -------------------------------------------------------------------------------- /examples/vector/README.md: -------------------------------------------------------------------------------- 1 | # Local 2 | 3 | This example demonstrates how to use libSQL vector search with a local database. 4 | 5 | ## Install Dependencies 6 | 7 | ```bash 8 | pip install libsql-experimental 9 | ``` 10 | 11 | ## Running 12 | 13 | Execute the example: 14 | 15 | ```bash 16 | python3 main.py 17 | ``` 18 | 19 | This will setup a local SQLite database, insert some data, and then query the results using the vector similarity search function. 20 | -------------------------------------------------------------------------------- /examples/vector/main.py: -------------------------------------------------------------------------------- 1 | import libsql_experimental as libsql 2 | 3 | conn = libsql.connect("local.db") 4 | 5 | conn.execute("DROP TABLE IF EXISTS movies") 6 | conn.execute("CREATE TABLE IF NOT EXISTS movies (title TEXT, year INT, embedding F32_BLOB(3))") 7 | conn.execute("CREATE INDEX movies_idx ON movies (libsql_vector_idx(embedding))") 8 | conn.execute("INSERT INTO movies (title, year, embedding) VALUES ('Napoleon', 2023, vector32('[1,2,3]')), ('Black Hawk Down', 2001, vector32('[10,11,12]')), ('Gladiator', 2000, vector32('[7,8,9]')), ('Blade Runner', 1982, vector32('[4,5,6]'))") 9 | 10 | print(conn.execute("SELECT title, year FROM vector_top_k('movies_idx', '[4,5,6]', 3) JOIN movies ON movies.rowid = id").fetchall()) 11 | -------------------------------------------------------------------------------- /perf-libsql.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import libsql_experimental 3 | import pyperf 4 | import time 5 | 6 | con = libsql_experimental.connect(":memory:") 7 | cur = con.cursor() 8 | 9 | def func(): 10 | res = cur.execute("SELECT 1") 11 | res.fetchone() 12 | 13 | runner = pyperf.Runner() 14 | runner.bench_func('execute SELECT 1', func) 15 | -------------------------------------------------------------------------------- /perf-sqlite3.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import pyperf 3 | import sqlite3 4 | import time 5 | 6 | con = sqlite3.connect(":memory:") 7 | cur = con.cursor() 8 | 9 | def func(): 10 | res = cur.execute("SELECT 1") 11 | res.fetchone() 12 | 13 | runner = pyperf.Runner() 14 | runner.bench_func('execute SELECT 1', func) 15 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["maturin>=1.1,<2.0"] 3 | build-backend = "maturin" 4 | 5 | [project] 6 | name = "libsql-experimental" 7 | version = "0.0.53" 8 | requires-python = ">=3.7" 9 | classifiers = [ 10 | "Programming Language :: Rust", 11 | "Programming Language :: Python :: Implementation :: CPython", 12 | "Programming Language :: Python :: Implementation :: PyPy", 13 | ] 14 | 15 | 16 | [tool.maturin] 17 | features = ["pyo3/extension-module"] 18 | -------------------------------------------------------------------------------- /shell.nix: -------------------------------------------------------------------------------- 1 | { pkgs ? import { } }: 2 | (pkgs.buildFHSUserEnv { 3 | name = "pipzone"; 4 | targetPkgs = pkgs: (with pkgs; [ 5 | python312 6 | python312Packages.pip 7 | python312Packages.virtualenv 8 | python312Packages.pytest 9 | python312Packages.pyperf 10 | maturin 11 | ]); 12 | runScript = "bash"; 13 | profile = '' 14 | virtualenv .venv 15 | source .venv/bin/activate 16 | ''; 17 | }).env 18 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | use ::libsql as libsql_core; 2 | use pyo3::create_exception; 3 | use pyo3::exceptions::PyValueError; 4 | use pyo3::prelude::*; 5 | use pyo3::types::{PyList, PyTuple}; 6 | use std::cell::{OnceCell, RefCell}; 7 | use std::sync::{Arc, OnceLock}; 8 | use tokio::runtime::{Handle, Runtime}; 9 | 10 | const LEGACY_TRANSACTION_CONTROL: i32 = -1; 11 | 12 | fn rt() -> Handle { 13 | static RT: OnceLock = OnceLock::new(); 14 | 15 | RT.get_or_init(|| { 16 | tokio::runtime::Builder::new_multi_thread() 17 | .worker_threads(1) 18 | .enable_all() 19 | .build() 20 | .unwrap() 21 | }) 22 | .handle() 23 | .clone() 24 | } 25 | 26 | fn to_py_err(error: libsql_core::errors::Error) -> PyErr { 27 | let msg = match error { 28 | libsql::Error::SqliteFailure(_, err) => err, 29 | _ => error.to_string(), 30 | }; 31 | PyValueError::new_err(msg) 32 | } 33 | 34 | fn is_remote_path(path: &str) -> bool { 35 | path.starts_with("libsql://") || path.starts_with("http://") || path.starts_with("https://") 36 | } 37 | 38 | #[pyfunction] 39 | #[cfg(not(Py_3_12))] 40 | #[pyo3(signature = (database, isolation_level="DEFERRED".to_string(), check_same_thread=true, uri=false, sync_url=None, sync_interval=None, auth_token="", encryption_key=None))] 41 | fn connect( 42 | py: Python<'_>, 43 | database: String, 44 | isolation_level: Option, 45 | check_same_thread: bool, 46 | uri: bool, 47 | sync_url: Option, 48 | sync_interval: Option, 49 | auth_token: &str, 50 | encryption_key: Option, 51 | ) -> PyResult { 52 | let conn = _connect_core( 53 | py, 54 | database, 55 | isolation_level, 56 | check_same_thread, 57 | uri, 58 | sync_url, 59 | sync_interval, 60 | auth_token, 61 | encryption_key, 62 | )?; 63 | Ok(conn) 64 | } 65 | 66 | #[pyfunction] 67 | #[cfg(Py_3_12)] 68 | #[pyo3(signature = (database, isolation_level="DEFERRED".to_string(), check_same_thread=true, uri=false, sync_url=None, sync_interval=None, auth_token="", encryption_key=None, autocommit = LEGACY_TRANSACTION_CONTROL))] 69 | fn connect( 70 | py: Python<'_>, 71 | database: String, 72 | isolation_level: Option, 73 | check_same_thread: bool, 74 | uri: bool, 75 | sync_url: Option, 76 | sync_interval: Option, 77 | auth_token: &str, 78 | encryption_key: Option, 79 | autocommit: i32, 80 | ) -> PyResult { 81 | let mut conn = _connect_core( 82 | py, 83 | database, 84 | isolation_level.clone(), 85 | check_same_thread, 86 | uri, 87 | sync_url, 88 | sync_interval, 89 | auth_token, 90 | encryption_key, 91 | )?; 92 | 93 | conn.autocommit = 94 | if autocommit == LEGACY_TRANSACTION_CONTROL || autocommit == 1 || autocommit == 0 { 95 | autocommit 96 | } else { 97 | return Err(PyValueError::new_err( 98 | "autocommit must be True, False, or sqlite3.LEGACY_TRANSACTION_CONTROL", 99 | )); 100 | }; 101 | Ok(conn) 102 | } 103 | 104 | fn _connect_core( 105 | py: Python<'_>, 106 | database: String, 107 | isolation_level: Option, 108 | check_same_thread: bool, 109 | uri: bool, 110 | sync_url: Option, 111 | sync_interval: Option, 112 | auth_token: &str, 113 | encryption_key: Option, 114 | ) -> PyResult { 115 | let ver = env!("CARGO_PKG_VERSION"); 116 | let ver = format!("libsql-python-rpc-{ver}"); 117 | let rt = rt(); 118 | let encryption_config = match encryption_key { 119 | Some(key) => { 120 | let cipher = libsql::Cipher::default(); 121 | let encryption_config = libsql::EncryptionConfig::new(cipher, key.into()); 122 | Some(encryption_config) 123 | } 124 | None => None, 125 | }; 126 | let db = if is_remote_path(&database) { 127 | let result = libsql::Database::open_remote_internal(database.clone(), auth_token, ver); 128 | result.map_err(to_py_err)? 129 | } else { 130 | match sync_url { 131 | Some(sync_url) => { 132 | let sync_interval = sync_interval.map(|i| std::time::Duration::from_secs_f64(i)); 133 | let mut builder = 134 | libsql::Builder::new_remote_replica(database, sync_url, auth_token.to_string()); 135 | if let Some(encryption_config) = encryption_config { 136 | builder = builder.encryption_config(encryption_config); 137 | } 138 | if let Some(sync_interval) = sync_interval { 139 | builder = builder.sync_interval(sync_interval); 140 | } 141 | let fut = builder.build(); 142 | tokio::pin!(fut); 143 | let result = rt.block_on(check_signals(py, fut)); 144 | result.map_err(to_py_err)? 145 | } 146 | None => { 147 | let mut builder = libsql::Builder::new_local(database); 148 | if let Some(config) = encryption_config { 149 | builder = builder.encryption_config(config); 150 | } 151 | let fut = builder.build(); 152 | tokio::pin!(fut); 153 | let result = rt.block_on(check_signals(py, fut)); 154 | result.map_err(to_py_err)? 155 | } 156 | } 157 | }; 158 | 159 | let autocommit = isolation_level.is_none() as i32; 160 | let conn = db.connect().map_err(to_py_err)?; 161 | Ok(Connection { 162 | db, 163 | conn: RefCell::new(Some(Arc::new(ConnectionGuard { 164 | conn: Some(conn), 165 | handle: rt.clone(), 166 | }))), 167 | isolation_level, 168 | autocommit, 169 | }) 170 | } 171 | 172 | // We need to add a drop guard that runs when we finally drop our 173 | // only reference to libsql_core::Connection. This is because when 174 | // hrana is enabled it needs access to the tokio api to spawn a close 175 | // call in the background. So this adds the ability that when drop is called 176 | // on ConnectionGuard it will drop the connection with a tokio context entered. 177 | struct ConnectionGuard { 178 | conn: Option, 179 | handle: tokio::runtime::Handle, 180 | } 181 | 182 | impl std::ops::Deref for ConnectionGuard { 183 | type Target = libsql_core::Connection; 184 | 185 | fn deref(&self) -> &Self::Target { 186 | &self.conn.as_ref().expect("Connection already dropped") 187 | } 188 | } 189 | 190 | impl Drop for ConnectionGuard { 191 | fn drop(&mut self) { 192 | let _enter = self.handle.enter(); 193 | if let Some(conn) = self.conn.take() { 194 | drop(conn); 195 | } 196 | } 197 | } 198 | 199 | #[pyclass] 200 | pub struct Connection { 201 | db: libsql_core::Database, 202 | conn: RefCell>>, 203 | isolation_level: Option, 204 | autocommit: i32, 205 | } 206 | 207 | // SAFETY: The libsql crate guarantees that `Connection` is thread-safe. 208 | unsafe impl Send for Connection {} 209 | 210 | #[pymethods] 211 | impl Connection { 212 | fn close(self_: PyRef<'_, Self>, py: Python<'_>) -> PyResult<()> { 213 | self_.conn.replace(None); 214 | Ok(()) 215 | } 216 | 217 | fn cursor(&self) -> PyResult { 218 | Ok(Cursor { 219 | arraysize: 1, 220 | conn: RefCell::new(Some(self.conn.borrow().as_ref().unwrap().clone())), 221 | stmt: RefCell::new(None), 222 | rows: RefCell::new(None), 223 | rowcount: RefCell::new(0), 224 | autocommit: self.autocommit, 225 | isolation_level: self.isolation_level.clone(), 226 | done: RefCell::new(false), 227 | }) 228 | } 229 | 230 | fn sync(self_: PyRef<'_, Self>, py: Python<'_>) -> PyResult<()> { 231 | let fut = { 232 | let _enter = rt().enter(); 233 | self_.db.sync() 234 | }; 235 | tokio::pin!(fut); 236 | 237 | rt().block_on(check_signals(py, fut)).map_err(to_py_err)?; 238 | Ok(()) 239 | } 240 | 241 | fn commit(self_: PyRef<'_, Self>) -> PyResult<()> { 242 | // TODO: Switch to libSQL transaction API 243 | if !self_.conn.borrow().as_ref().unwrap().is_autocommit() { 244 | rt().block_on(async { 245 | self_ 246 | .conn 247 | .borrow() 248 | .as_ref() 249 | .unwrap() 250 | .execute("COMMIT", ()) 251 | .await 252 | }) 253 | .map_err(to_py_err)?; 254 | } 255 | Ok(()) 256 | } 257 | 258 | fn rollback(self_: PyRef<'_, Self>) -> PyResult<()> { 259 | // TODO: Switch to libSQL transaction API 260 | if !self_.conn.borrow().as_ref().unwrap().is_autocommit() { 261 | rt().block_on(async { 262 | self_ 263 | .conn 264 | .borrow() 265 | .as_ref() 266 | .unwrap() 267 | .execute("ROLLBACK", ()) 268 | .await 269 | }) 270 | .map_err(to_py_err)?; 271 | } 272 | Ok(()) 273 | } 274 | 275 | fn execute( 276 | self_: PyRef<'_, Self>, 277 | sql: String, 278 | parameters: Option<&PyTuple>, 279 | ) -> PyResult { 280 | let cursor = Connection::cursor(&self_)?; 281 | rt().block_on(async { execute(&cursor, sql, parameters).await })?; 282 | Ok(cursor) 283 | } 284 | 285 | fn executemany( 286 | self_: PyRef<'_, Self>, 287 | sql: String, 288 | parameters: Option<&PyList>, 289 | ) -> PyResult { 290 | let cursor = Connection::cursor(&self_)?; 291 | for parameters in parameters.unwrap().iter() { 292 | let parameters = parameters.extract::<&PyTuple>()?; 293 | rt().block_on(async { execute(&cursor, sql.clone(), Some(parameters)).await })?; 294 | } 295 | Ok(cursor) 296 | } 297 | 298 | fn executescript(self_: PyRef<'_, Self>, script: String) -> PyResult<()> { 299 | let _ = rt() 300 | .block_on(async { 301 | self_ 302 | .conn 303 | .borrow() 304 | .as_ref() 305 | .unwrap() 306 | .execute_batch(&script) 307 | .await 308 | }) 309 | .map_err(to_py_err); 310 | Ok(()) 311 | } 312 | 313 | #[getter] 314 | fn isolation_level(self_: PyRef<'_, Self>) -> Option { 315 | self_.isolation_level.clone() 316 | } 317 | 318 | #[getter] 319 | fn in_transaction(self_: PyRef<'_, Self>) -> PyResult { 320 | #[cfg(Py_3_12)] 321 | { 322 | return Ok( 323 | !self_.conn.borrow().as_ref().unwrap().is_autocommit() || self_.autocommit == 0 324 | ); 325 | } 326 | Ok(!self_.conn.borrow().as_ref().unwrap().is_autocommit()) 327 | } 328 | 329 | #[getter] 330 | #[cfg(Py_3_12)] 331 | fn get_autocommit(self_: PyRef<'_, Self>) -> PyResult { 332 | Ok(self_.autocommit) 333 | } 334 | 335 | #[setter] 336 | #[cfg(Py_3_12)] 337 | fn set_autocommit(mut self_: PyRefMut<'_, Self>, autocommit: i32) -> PyResult<()> { 338 | if autocommit != LEGACY_TRANSACTION_CONTROL && autocommit != 1 && autocommit != 0 { 339 | return Err(PyValueError::new_err( 340 | "autocommit must be True, False, or sqlite3.LEGACY_TRANSACTION_CONTROL", 341 | )); 342 | } 343 | self_.autocommit = autocommit; 344 | Ok(()) 345 | } 346 | } 347 | 348 | #[pyclass] 349 | pub struct Cursor { 350 | #[pyo3(get, set)] 351 | arraysize: usize, 352 | conn: RefCell>>, 353 | stmt: RefCell>, 354 | rows: RefCell>, 355 | rowcount: RefCell, 356 | done: RefCell, 357 | isolation_level: Option, 358 | autocommit: i32, 359 | } 360 | 361 | // SAFETY: The libsql crate guarantees that `Connection` is thread-safe. 362 | unsafe impl Send for Cursor {} 363 | 364 | impl Drop for Cursor { 365 | fn drop(&mut self) { 366 | let _enter = rt().enter(); 367 | self.conn.replace(None); 368 | self.stmt.replace(None); 369 | self.rows.replace(None); 370 | } 371 | } 372 | 373 | #[pymethods] 374 | impl Cursor { 375 | fn close(self_: PyRef<'_, Self>) -> PyResult<()> { 376 | rt().block_on(async { 377 | let cursor: &Cursor = &self_; 378 | cursor.conn.replace(None); 379 | cursor.stmt.replace(None); 380 | cursor.rows.replace(None); 381 | }); 382 | Ok(()) 383 | } 384 | 385 | fn execute<'a>( 386 | self_: PyRef<'a, Self>, 387 | sql: String, 388 | parameters: Option<&PyTuple>, 389 | ) -> PyResult> { 390 | rt().block_on(async { execute(&self_, sql, parameters).await })?; 391 | Ok(self_) 392 | } 393 | 394 | fn executemany<'a>( 395 | self_: PyRef<'a, Self>, 396 | sql: String, 397 | parameters: Option<&PyList>, 398 | ) -> PyResult> { 399 | for parameters in parameters.unwrap().iter() { 400 | let parameters = parameters.extract::<&PyTuple>()?; 401 | rt().block_on(async { execute(&self_, sql.clone(), Some(parameters)).await })?; 402 | } 403 | Ok(self_) 404 | } 405 | 406 | fn executescript<'a>( 407 | self_: PyRef<'a, Self>, 408 | script: String, 409 | ) -> PyResult> { 410 | rt().block_on(async { 411 | self_ 412 | .conn 413 | .borrow() 414 | .as_ref() 415 | .unwrap() 416 | .execute_batch(&script) 417 | .await 418 | }) 419 | .map_err(to_py_err)?; 420 | Ok(self_) 421 | } 422 | 423 | #[getter] 424 | fn description(self_: PyRef<'_, Self>) -> PyResult> { 425 | let stmt = self_.stmt.borrow(); 426 | let mut elements: Vec> = vec![]; 427 | match stmt.as_ref() { 428 | Some(stmt) => { 429 | for column in stmt.columns() { 430 | let name = column.name(); 431 | let element = ( 432 | name, 433 | self_.py().None(), 434 | self_.py().None(), 435 | self_.py().None(), 436 | self_.py().None(), 437 | self_.py().None(), 438 | self_.py().None(), 439 | ) 440 | .to_object(self_.py()); 441 | elements.push(element); 442 | } 443 | let elements = PyTuple::new(self_.py(), elements); 444 | Ok(Some(elements)) 445 | } 446 | None => Ok(None), 447 | } 448 | } 449 | 450 | fn fetchone(self_: PyRef<'_, Self>) -> PyResult> { 451 | let mut rows = self_.rows.borrow_mut(); 452 | match rows.as_mut() { 453 | Some(rows) => { 454 | let row = rt().block_on(rows.next()).map_err(to_py_err)?; 455 | match row { 456 | Some(row) => { 457 | let row = convert_row(self_.py(), row, rows.column_count())?; 458 | Ok(Some(row)) 459 | } 460 | None => Ok(None), 461 | } 462 | } 463 | None => Ok(None), 464 | } 465 | } 466 | 467 | fn fetchmany(self_: PyRef<'_, Self>, size: Option) -> PyResult> { 468 | let mut rows = self_.rows.borrow_mut(); 469 | match rows.as_mut() { 470 | Some(rows) => { 471 | let size = size.unwrap_or(self_.arraysize as i64); 472 | let mut elements: Vec> = vec![]; 473 | // The libSQL Rows.next() method restarts the iteration if it 474 | // has reached the end, which is why we need to check if we're 475 | // done before iterating. 476 | if !*self_.done.borrow() { 477 | for _ in 0..size { 478 | let row = rt() 479 | .block_on(async { rows.next().await }) 480 | .map_err(to_py_err)?; 481 | match row { 482 | Some(row) => { 483 | let row = convert_row(self_.py(), row, rows.column_count())?; 484 | elements.push(row.into()); 485 | } 486 | None => { 487 | self_.done.replace(true); 488 | break; 489 | } 490 | } 491 | } 492 | } 493 | Ok(Some(PyList::new(self_.py(), elements))) 494 | } 495 | None => Ok(None), 496 | } 497 | } 498 | 499 | fn fetchall(self_: PyRef<'_, Self>) -> PyResult> { 500 | let mut rows = self_.rows.borrow_mut(); 501 | match rows.as_mut() { 502 | Some(rows) => { 503 | let mut elements: Vec> = vec![]; 504 | loop { 505 | let row = rt() 506 | .block_on(async { rows.next().await }) 507 | .map_err(to_py_err)?; 508 | match row { 509 | Some(row) => { 510 | let row = convert_row(self_.py(), row, rows.column_count())?; 511 | elements.push(row.into()); 512 | } 513 | None => break, 514 | } 515 | } 516 | Ok(Some(PyList::new(self_.py(), elements))) 517 | } 518 | None => Ok(None), 519 | } 520 | } 521 | 522 | #[getter] 523 | fn lastrowid(self_: PyRef<'_, Self>) -> PyResult> { 524 | let stmt = self_.stmt.borrow(); 525 | match stmt.as_ref() { 526 | Some(_) => Ok(Some( 527 | self_.conn.borrow().as_ref().unwrap().last_insert_rowid(), 528 | )), 529 | None => Ok(None), 530 | } 531 | } 532 | 533 | #[getter] 534 | fn rowcount(self_: PyRef<'_, Self>) -> PyResult { 535 | Ok(*self_.rowcount.borrow()) 536 | } 537 | } 538 | 539 | async fn begin_transaction(conn: &libsql_core::Connection) -> PyResult<()> { 540 | conn.execute("BEGIN", ()).await.map_err(to_py_err)?; 541 | Ok(()) 542 | } 543 | 544 | async fn execute(cursor: &Cursor, sql: String, parameters: Option<&PyTuple>) -> PyResult<()> { 545 | if cursor.conn.borrow().as_ref().is_none() { 546 | return Err(PyValueError::new_err("Connection already closed")); 547 | } 548 | let stmt_is_dml = stmt_is_dml(&sql); 549 | let autocommit = determine_autocommit(cursor); 550 | if !autocommit && stmt_is_dml && cursor.conn.borrow().as_ref().unwrap().is_autocommit() { 551 | begin_transaction(&cursor.conn.borrow().as_ref().unwrap()).await?; 552 | } 553 | let params = match parameters { 554 | Some(parameters) => { 555 | let mut params = vec![]; 556 | for param in parameters.iter() { 557 | let param = if param.is_none() { 558 | libsql_core::Value::Null 559 | } else if let Ok(value) = param.extract::() { 560 | libsql_core::Value::Integer(value as i64) 561 | } else if let Ok(value) = param.extract::() { 562 | libsql_core::Value::Real(value) 563 | } else if let Ok(value) = param.extract::<&str>() { 564 | libsql_core::Value::Text(value.to_string()) 565 | } else if let Ok(value) = param.extract::<&[u8]>() { 566 | libsql_core::Value::Blob(value.to_vec()) 567 | } else { 568 | return Err(PyValueError::new_err("Unsupported parameter type")); 569 | }; 570 | params.push(param); 571 | } 572 | libsql_core::params::Params::Positional(params) 573 | } 574 | None => libsql_core::params::Params::None, 575 | }; 576 | let mut stmt = cursor 577 | .conn 578 | .borrow() 579 | .as_ref() 580 | .unwrap() 581 | .prepare(&sql) 582 | .await 583 | .map_err(to_py_err)?; 584 | 585 | if stmt.columns().iter().len() > 0 { 586 | let rows = stmt.query(params).await.map_err(to_py_err)?; 587 | cursor.rows.replace(Some(rows)); 588 | } else { 589 | stmt.execute(params).await.map_err(to_py_err)?; 590 | cursor.rows.replace(None); 591 | } 592 | 593 | let mut rowcount = cursor.rowcount.borrow_mut(); 594 | *rowcount += cursor.conn.borrow().as_ref().unwrap().changes() as i64; 595 | 596 | cursor.stmt.replace(Some(stmt)); 597 | Ok(()) 598 | } 599 | 600 | fn determine_autocommit(cursor: &Cursor) -> bool { 601 | #[cfg(Py_3_12)] 602 | { 603 | match cursor.autocommit { 604 | LEGACY_TRANSACTION_CONTROL => cursor.isolation_level.is_none(), 605 | _ => cursor.autocommit != 0, 606 | } 607 | } 608 | 609 | #[cfg(not(Py_3_12))] 610 | { 611 | cursor.isolation_level.is_none() 612 | } 613 | } 614 | 615 | fn stmt_is_dml(sql: &str) -> bool { 616 | let sql = sql.trim(); 617 | let sql = sql.to_uppercase(); 618 | sql.starts_with("INSERT") || sql.starts_with("UPDATE") || sql.starts_with("DELETE") 619 | } 620 | 621 | fn convert_row(py: Python, row: libsql_core::Row, column_count: i32) -> PyResult<&PyTuple> { 622 | let mut elements: Vec> = vec![]; 623 | for col_idx in 0..column_count { 624 | let libsql_value = row.get_value(col_idx).map_err(to_py_err)?; 625 | let value = match libsql_value { 626 | libsql_core::Value::Integer(v) => { 627 | let value = v as i64; 628 | value.into_py(py) 629 | } 630 | libsql_core::Value::Real(v) => v.into_py(py), 631 | libsql_core::Value::Text(v) => v.into_py(py), 632 | libsql_core::Value::Blob(v) => { 633 | let value = v.as_slice(); 634 | value.into_py(py) 635 | } 636 | libsql_core::Value::Null => py.None(), 637 | }; 638 | elements.push(value); 639 | } 640 | Ok(PyTuple::new(py, elements)) 641 | } 642 | 643 | create_exception!(libsql_experimental, Error, pyo3::exceptions::PyException); 644 | 645 | #[pymodule] 646 | fn libsql_experimental(py: Python, m: &PyModule) -> PyResult<()> { 647 | let _ = tracing_subscriber::fmt::try_init(); 648 | m.add("LEGACY_TRANSACTION_CONTROL", LEGACY_TRANSACTION_CONTROL)?; 649 | m.add("paramstyle", "qmark")?; 650 | m.add("sqlite_version_info", (3, 42, 0))?; 651 | m.add("Error", py.get_type::())?; 652 | m.add_function(wrap_pyfunction!(connect, m)?)?; 653 | m.add_class::()?; 654 | m.add_class::()?; 655 | Ok(()) 656 | } 657 | 658 | async fn check_signals(py: Python<'_>, mut fut: std::pin::Pin<&mut F>) -> R 659 | where 660 | F: std::future::Future, 661 | { 662 | loop { 663 | tokio::select! { 664 | out = &mut fut => { 665 | break out; 666 | } 667 | 668 | _ = tokio::time::sleep(std::time::Duration::from_millis(300)) => { 669 | py.check_signals().unwrap(); 670 | } 671 | } 672 | } 673 | } 674 | -------------------------------------------------------------------------------- /tests/test_suite.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import sqlite3 4 | import sys 5 | import libsql_experimental 6 | import pytest 7 | 8 | 9 | @pytest.mark.parametrize("provider", ["libsql", "sqlite"]) 10 | def test_connection_close(provider): 11 | conn = connect(provider, ":memory:") 12 | conn.close() 13 | 14 | @pytest.mark.parametrize("provider", ["libsql", "sqlite"]) 15 | def test_execute(provider): 16 | conn = connect(provider, ":memory:") 17 | conn.execute("CREATE TABLE users (id INTEGER, email TEXT)") 18 | conn.execute("INSERT INTO users VALUES (1, 'alice@example.com')") 19 | res = conn.execute("SELECT * FROM users") 20 | assert (1, "alice@example.com") == res.fetchone() 21 | 22 | 23 | @pytest.mark.parametrize("provider", ["libsql", "sqlite"]) 24 | def test_cursor_execute(provider): 25 | conn = connect(provider, ":memory:") 26 | cur = conn.cursor() 27 | cur.execute("CREATE TABLE users (id INTEGER, email TEXT)") 28 | cur.execute("INSERT INTO users VALUES (1, 'alice@example.com')") 29 | res = cur.execute("SELECT * FROM users") 30 | assert (1, "alice@example.com") == res.fetchone() 31 | 32 | @pytest.mark.parametrize("provider", ["libsql", "sqlite"]) 33 | def test_cursor_close(provider): 34 | conn = connect(provider, ":memory:") 35 | cur = conn.cursor() 36 | cur.execute("CREATE TABLE users (id INTEGER, email TEXT)") 37 | cur.execute("INSERT INTO users VALUES (1, 'alice@example.com')") 38 | cur.execute("INSERT INTO users VALUES (2, 'bob@example.com')") 39 | res = cur.execute("SELECT * FROM users") 40 | assert [(1, "alice@example.com"), (2, "bob@example.com")] == res.fetchall() 41 | cur.close() 42 | with pytest.raises(Exception): 43 | cur.execute("SELECT * FROM users") 44 | 45 | @pytest.mark.parametrize("provider", ["libsql", "sqlite"]) 46 | def test_executemany(provider): 47 | conn = connect(provider, ":memory:") 48 | cur = conn.cursor() 49 | cur.execute("CREATE TABLE users (id INTEGER, email TEXT)") 50 | data = [(1, "alice@example.com"), (2, "bob@example.com")] 51 | conn.executemany("INSERT INTO users VALUES (?, ?)", data) 52 | res = cur.execute("SELECT * FROM users") 53 | assert [(1, "alice@example.com"), (2, "bob@example.com")] == res.fetchall() 54 | 55 | 56 | @pytest.mark.parametrize("provider", ["libsql", "sqlite"]) 57 | def test_cursor_fetchone(provider): 58 | conn = connect(provider, ":memory:") 59 | cur = conn.cursor() 60 | cur.execute("CREATE TABLE users (id INTEGER, email TEXT)") 61 | data = [(1, "alice@example.com"), (2, "bob@example.com")] 62 | cur.executemany("INSERT INTO users VALUES (?, ?)", data) 63 | res = cur.execute("SELECT * FROM users") 64 | assert (1, "alice@example.com") == res.fetchone() 65 | 66 | 67 | @pytest.mark.parametrize("provider", ["sqlite", "libsql"]) 68 | def test_cursor_fetchmany(provider): 69 | conn = connect(provider, ":memory:") 70 | cur = conn.cursor() 71 | cur.execute("CREATE TABLE users (id INTEGER, email TEXT)") 72 | data = [ 73 | (1, "alice@example.com"), 74 | (2, "bob@example.com"), 75 | (3, "carol@example.com"), 76 | (4, "dave@example.com"), 77 | (5, "erin@example.com"), 78 | ] 79 | cur.executemany("INSERT INTO users VALUES (?, ?)", data) 80 | res = cur.execute("SELECT * FROM users") 81 | assert [(1, "alice@example.com"), (2, "bob@example.com")] == res.fetchmany(2) 82 | assert [(3, "carol@example.com"), (4, "dave@example.com")] == res.fetchmany(2) 83 | assert [(5, "erin@example.com")] == res.fetchmany(2) 84 | assert [] == res.fetchmany(2) 85 | 86 | 87 | @pytest.mark.parametrize("provider", ["sqlite", "libsql"]) 88 | def test_cursor_execute_blob(provider): 89 | conn = connect(provider, ":memory:") 90 | cur = conn.cursor() 91 | cur.execute("CREATE TABLE users (id INTEGER, data BLOB)") 92 | cur.execute("INSERT INTO users VALUES (?, ?)", (1, b"foobar")) 93 | res = cur.execute("SELECT * FROM users") 94 | assert (1, b"foobar") == res.fetchone() 95 | 96 | 97 | @pytest.mark.parametrize("provider", ["libsql", "sqlite"]) 98 | def test_cursor_executemany(provider): 99 | conn = connect(provider, ":memory:") 100 | cur = conn.cursor() 101 | cur.execute("CREATE TABLE users (id INTEGER, email TEXT)") 102 | data = [(1, "alice@example.com"), (2, "bob@example.com")] 103 | cur.executemany("INSERT INTO users VALUES (?, ?)", data) 104 | res = cur.execute("SELECT * FROM users") 105 | assert [(1, "alice@example.com"), (2, "bob@example.com")] == res.fetchall() 106 | 107 | 108 | @pytest.mark.parametrize("provider", ["libsql", "sqlite"]) 109 | def test_cursor_executescript(provider): 110 | conn = connect(provider, ":memory:") 111 | cur = conn.cursor() 112 | cur.executescript( 113 | """ 114 | CREATE TABLE users (id INTEGER, email TEXT); 115 | INSERT INTO users VALUES (1, 'alice@example.org'); 116 | INSERT INTO users VALUES (2, 'bob@example.org'); 117 | """ 118 | ) 119 | res = cur.execute("SELECT * FROM users") 120 | assert (1, "alice@example.org") == res.fetchone() 121 | assert (2, "bob@example.org") == res.fetchone() 122 | 123 | 124 | @pytest.mark.parametrize("provider", ["libsql", "sqlite"]) 125 | def test_lastrowid(provider): 126 | conn = connect(provider, ":memory:") 127 | cur = conn.cursor() 128 | assert cur.lastrowid is None 129 | cur.execute("CREATE TABLE users (id INTEGER, email TEXT)") 130 | assert cur.lastrowid == 0 131 | cur.execute("INSERT INTO users VALUES (1, 'alice@example.com')") 132 | assert cur.lastrowid == 1 133 | cur.execute("INSERT INTO users VALUES (?, ?)", (2, "bob@example.com")) 134 | assert cur.lastrowid == 2 135 | 136 | 137 | @pytest.mark.parametrize("provider", ["libsql", "sqlite"]) 138 | def test_basic(provider): 139 | conn = connect(provider, ":memory:") 140 | cur = conn.cursor() 141 | cur.execute("CREATE TABLE users (id INTEGER, email TEXT)") 142 | cur.execute("INSERT INTO users VALUES (1, 'alice@example.com')") 143 | res = cur.execute("SELECT * FROM users") 144 | assert ( 145 | ("id", None, None, None, None, None, None), 146 | ("email", None, None, None, None, None, None), 147 | ) == res.description 148 | 149 | 150 | @pytest.mark.parametrize("provider", ["libsql", "sqlite"]) 151 | def test_commit_and_rollback(provider): 152 | conn = connect(provider, ":memory:") 153 | cur = conn.cursor() 154 | cur.execute("CREATE TABLE users (id INTEGER, email TEXT)") 155 | conn.commit() 156 | cur.execute("INSERT INTO users VALUES (1, 'alice@example.com')") 157 | res = cur.execute("SELECT * FROM users") 158 | assert (1, "alice@example.com") == res.fetchone() 159 | conn.rollback() 160 | res = cur.execute("SELECT * FROM users") 161 | assert res.fetchone() is None 162 | 163 | 164 | @pytest.mark.parametrize("provider", ["libsql", "sqlite"]) 165 | def test_autocommit(provider): 166 | conn = connect(provider, ":memory:", None) 167 | assert conn.isolation_level == None 168 | assert conn.in_transaction == False 169 | cur = conn.cursor() 170 | assert conn.in_transaction == False 171 | cur.execute("CREATE TABLE users (id INTEGER, email TEXT)") 172 | cur.execute("INSERT INTO users VALUES (?, ?)", (1, "alice@example.com")) 173 | assert conn.in_transaction == False 174 | res = cur.execute("SELECT * FROM users") 175 | assert (1, "alice@example.com") == res.fetchone() 176 | conn.rollback() 177 | res = cur.execute("SELECT * FROM users") 178 | assert (1, "alice@example.com") == res.fetchone() 179 | 180 | 181 | @pytest.mark.parametrize("provider", ["libsql", "sqlite"]) 182 | @pytest.mark.skipif(sys.version_info < (3, 12), reason="requires python3.12 or higher") 183 | def test_connection_autocommit(provider): 184 | # Test LEGACY_TRANSACTION_CONTROL (-1) 185 | conn = connect(provider, ":memory:", None, autocommit=-1) 186 | assert conn.isolation_level is None 187 | assert conn.autocommit == -1 188 | cur = conn.cursor() 189 | assert conn.in_transaction is False 190 | cur.execute("CREATE TABLE users (id INTEGER, email TEXT)") 191 | cur.execute("INSERT INTO users VALUES (?, ?)", (1, "alice@example.com")) 192 | assert conn.in_transaction is False 193 | res = cur.execute("SELECT * FROM users") 194 | assert (1, "alice@example.com") == res.fetchone() 195 | 196 | conn = connect(provider, ":memory:", isolation_level="DEFERRED", autocommit=-1) 197 | assert conn.isolation_level == "DEFERRED" 198 | assert conn.autocommit == -1 199 | cur = conn.cursor() 200 | assert conn.in_transaction is False 201 | cur.execute("CREATE TABLE users (id INTEGER, email TEXT)") 202 | cur.execute("INSERT INTO users VALUES (?, ?)", (1, "alice@example.com")) 203 | assert conn.in_transaction is True 204 | res = cur.execute("SELECT * FROM users") 205 | assert (1, "alice@example.com") == res.fetchone() 206 | 207 | # Test autocommit Enabled (True) 208 | conn = connect(provider, ":memory:", None, autocommit=True) 209 | assert conn.isolation_level == None 210 | assert conn.autocommit == True 211 | cur = conn.cursor() 212 | assert conn.in_transaction is False 213 | cur.execute("CREATE TABLE users (id INTEGER, email TEXT)") 214 | cur.execute("INSERT INTO users VALUES (?, ?)", (1, "bob@example.com")) 215 | assert conn.in_transaction is False 216 | res = cur.execute("SELECT * FROM users") 217 | assert (1, "bob@example.com") == res.fetchone() 218 | 219 | conn = connect(provider, ":memory:", isolation_level="DEFERRED", autocommit=True) 220 | assert conn.isolation_level == "DEFERRED" 221 | assert conn.autocommit == True 222 | cur = conn.cursor() 223 | assert conn.in_transaction is False 224 | cur.execute("CREATE TABLE users (id INTEGER, email TEXT)") 225 | cur.execute("INSERT INTO users VALUES (?, ?)", (1, "bob@example.com")) 226 | assert conn.in_transaction is False 227 | res = cur.execute("SELECT * FROM users") 228 | assert (1, "bob@example.com") == res.fetchone() 229 | 230 | # Test autocommit Disabled (False) 231 | conn = connect(provider, ":memory:", isolation_level="DEFERRED", autocommit=False) 232 | assert conn.isolation_level == "DEFERRED" 233 | assert conn.autocommit == False 234 | cur = conn.cursor() 235 | assert conn.in_transaction is True 236 | cur.execute("CREATE TABLE users (id INTEGER, email TEXT)") 237 | cur.execute("INSERT INTO users VALUES (?, ?)", (1, "carol@example.com")) 238 | assert conn.in_transaction is True 239 | conn.commit() 240 | assert conn.in_transaction is True 241 | res = cur.execute("SELECT * FROM users") 242 | assert (1, "carol@example.com") == res.fetchone() 243 | 244 | # Test invalid autocommit value (should raise an error) 245 | with pytest.raises(ValueError): 246 | connect(provider, ":memory:", None, autocommit=999) 247 | 248 | 249 | @pytest.mark.parametrize("provider", ["libsql", "sqlite"]) 250 | def test_params(provider): 251 | conn = connect(provider, ":memory:") 252 | cur = conn.cursor() 253 | cur.execute("CREATE TABLE users (id INTEGER, email TEXT)") 254 | cur.execute("INSERT INTO users VALUES (?, ?)", (1, "alice@example.com")) 255 | res = cur.execute("SELECT * FROM users") 256 | assert (1, "alice@example.com") == res.fetchone() 257 | 258 | @pytest.mark.parametrize("provider", ["libsql", "sqlite"]) 259 | def test_none_param(provider): 260 | conn = connect(provider, ":memory:") 261 | cur = conn.cursor() 262 | cur.execute("CREATE TABLE users (id INTEGER, email TEXT)") 263 | cur.execute("INSERT INTO users VALUES (?, ?)", (1, None)) 264 | cur.execute("INSERT INTO users VALUES (?, ?)", (2, "alice@example.com")) 265 | res = cur.execute("SELECT * FROM users ORDER BY id") 266 | results = res.fetchall() 267 | assert results[0] == (1, None) 268 | assert results[1] == (2, "alice@example.com") 269 | 270 | @pytest.mark.parametrize("provider", ["libsql", "sqlite"]) 271 | def test_fetchmany(provider): 272 | conn = connect(provider, ":memory:") 273 | cur = conn.cursor() 274 | cur.execute("CREATE TABLE users (id INTEGER, email TEXT)") 275 | cur.execute("INSERT INTO users VALUES (?, ?)", (1, "alice@example.com")) 276 | cur.execute("INSERT INTO users VALUES (?, ?)", (2, "bob@example.com")) 277 | res = cur.execute("SELECT * FROM users") 278 | assert [(1, "alice@example.com"), (2, "bob@example.com")] == res.fetchall() 279 | 280 | 281 | @pytest.mark.parametrize("provider", ["libsql", "sqlite"]) 282 | def test_in_transaction(provider): 283 | conn = connect(provider, ":memory:") 284 | assert conn.in_transaction == False 285 | cur = conn.cursor() 286 | assert conn.in_transaction == False 287 | cur.execute("CREATE TABLE users (id INTEGER, email TEXT)") 288 | cur.execute("INSERT INTO users VALUES (?, ?)", (1, "alice@example.com")) 289 | cur.execute("INSERT INTO users VALUES (?, ?)", (2, "bob@example.com")) 290 | assert conn.in_transaction == True 291 | 292 | 293 | @pytest.mark.parametrize("provider", ["libsql-remote", "libsql", "sqlite"]) 294 | def test_fetch_expression(provider): 295 | dbname = "/tmp/test.db" if provider == "libsql-remote" else ":memory:" 296 | try: 297 | conn = connect(provider, dbname) 298 | except Exception as e: 299 | pytest.skip(str(e)) 300 | cur = conn.cursor() 301 | cur.execute("DROP TABLE IF EXISTS users") 302 | cur.execute("CREATE TABLE users (id INTEGER, email TEXT)") 303 | cur.execute("INSERT INTO users VALUES (1, 'alice@example.com')") 304 | res = cur.execute("SELECT QUOTE(email) FROM users") 305 | assert [("'alice@example.com'",)] == res.fetchall() 306 | 307 | 308 | @pytest.mark.parametrize("provider", ["libsql", "sqlite"]) 309 | def test_int64(provider): 310 | conn = connect(provider, ":memory:") 311 | cur = conn.cursor() 312 | cur.execute("CREATE TABLE data (id INTEGER, number INTEGER)") 313 | conn.commit() 314 | cur.execute("INSERT INTO data VALUES (1, 1099511627776)") # 1 << 40 315 | res = cur.execute("SELECT * FROM data") 316 | assert [(1, 1099511627776)] == res.fetchall() 317 | 318 | 319 | def connect(provider, database, isolation_level="DEFERRED", autocommit=-1): 320 | if provider == "libsql-remote": 321 | from urllib import request 322 | 323 | try: 324 | res = request.urlopen("http://localhost:8080/v2") 325 | except Exception as _: 326 | raise Exception("libsql-remote server is not running") 327 | if res.getcode() != 200: 328 | raise Exception("libsql-remote server is not running") 329 | return libsql_experimental.connect( 330 | database, sync_url="http://localhost:8080", auth_token="" 331 | ) 332 | if provider == "libsql": 333 | if sys.version_info < (3, 12): 334 | return libsql_experimental.connect( 335 | database, isolation_level=isolation_level 336 | ) 337 | else: 338 | if autocommit == -1: 339 | autocommit = libsql_experimental.LEGACY_TRANSACTION_CONTROL 340 | return libsql_experimental.connect( 341 | database, isolation_level=isolation_level, autocommit=autocommit 342 | ) 343 | if provider == "sqlite": 344 | if sys.version_info < (3, 12): 345 | return sqlite3.connect(database, isolation_level=isolation_level) 346 | else: 347 | if autocommit == -1: 348 | autocommit = sqlite3.LEGACY_TRANSACTION_CONTROL 349 | return sqlite3.connect( 350 | database, isolation_level=isolation_level, autocommit=autocommit 351 | ) 352 | raise Exception(f"Provider `{provider}` is not supported") 353 | --------------------------------------------------------------------------------