From 2e6b5ba7e7018c90191bf072729c487ca680e6bf Mon Sep 17 00:00:00 2001 From: Katherine Bargar Date: Thu, 11 Sep 2025 12:49:45 -0400 Subject: [PATCH 1/7] Use psycopg2-binary + psycopg3 and keep internal connections separate (#40) I want to use psycopg3 features (the sql module and the copy write_record) but don't want to break backwards compatibility just yet. This introduces psycopg3 while leaving in psycopg2 and switching it for the binary version to make it easier for users to install the library. While messing with the connection methods I've isolated the connection object that is returned from ldlite and the one that ldlite uses internally. This makes sure that the connections aren't broken or closed unexepectedly. --- CHANGELOG.md | 5 ++ pylock.maximal.toml | 50 ++++++++++++++-- pylock.minimal.toml | 66 +++++++++++++++++---- pylock.toml | 131 +++++++++++++++++++++++++++++++++-------- pyproject.toml | 3 +- src/ldlite/__init__.py | 23 +++++--- src/ldlite/_jsonx.py | 26 ++++---- src/ldlite/_sqlx.py | 8 +-- 8 files changed, 248 insertions(+), 64 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index efd5912..303d86b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,10 +11,15 @@ Please see [MIGRATING.md](./MIGRATING.md) for information on breaking changes. ### Added +- Connections returned from the LDLite.connect_db* methods are now isolated from the ones used internally. + ### Fixed ### Changed +- psycopg3 is now used for internal operations. LDLite.connect_db_postgres will return a psycopg3 connection instead of psycopg2 in the next major release. +- psycopg2 is now installed using the binary version. + ### Removed ## [3.1.4] - September 2025 diff --git a/pylock.maximal.toml b/pylock.maximal.toml index 43cd6d9..d04a769 100644 --- a/pylock.maximal.toml +++ b/pylock.maximal.toml @@ -210,12 +210,39 @@ marker = "python_version >= \"3.13\" and \"default\" in dependency_groups" dependencies = [] [[packages]] -name = "psycopg2" -version = "2.9.10" +name = "psycopg" +version = "3.2.10" requires-python = ">=3.8" -sdist = {name = "psycopg2-2.9.10.tar.gz", url = "https://files.pythonhosted.org/packages/62/51/2007ea29e605957a17ac6357115d0c1a1b60c8c984951c19419b3474cdfd/psycopg2-2.9.10.tar.gz", hashes = {sha256 = "12ec0b40b0273f95296233e8750441339298e6a572f7039da5b260e3c8b60e11"}} +sdist = {name = "psycopg-3.2.10.tar.gz", url = "https://files.pythonhosted.org/packages/a9/f1/0258a123c045afaf3c3b60c22ccff077bceeb24b8dc2c593270899353bd0/psycopg-3.2.10.tar.gz", hashes = {sha256 = "0bce99269d16ed18401683a8569b2c5abd94f72f8364856d56c0389bcd50972a"}} wheels = [ - {name = "psycopg2-2.9.10-cp313-cp313-win_amd64.whl",url = "https://files.pythonhosted.org/packages/ae/49/a6cfc94a9c483b1fa401fbcb23aca7892f60c7269c5ffa2ac408364f80dc/psycopg2-2.9.10-cp313-cp313-win_amd64.whl",hashes = {sha256 = "91fd603a2155da8d0cfcdbf8ab24a2d54bca72795b90d2a3ed2b6da8d979dee2"}}, + {name = "psycopg-3.2.10-py3-none-any.whl",url = "https://files.pythonhosted.org/packages/4a/90/422ffbbeeb9418c795dae2a768db860401446af0c6768bc061ce22325f58/psycopg-3.2.10-py3-none-any.whl",hashes = {sha256 = "ab5caf09a9ec42e314a21f5216dbcceac528e0e05142e42eea83a3b28b320ac3"}}, +] +marker = "python_version >= \"3.13\" and \"default\" in dependency_groups" + +[packages.tool.pdm] +dependencies = [ + "backports-zoneinfo>=0.2.0; python_version < \"3.9\"", + "typing-extensions>=4.6; python_version < \"3.13\"", + "tzdata; sys_platform == \"win32\"", +] + +[[packages]] +name = "psycopg2-binary" +version = "2.9.10" +requires-python = ">=3.8" +sdist = {name = "psycopg2-binary-2.9.10.tar.gz", url = "https://files.pythonhosted.org/packages/cb/0e/bdc8274dc0585090b4e3432267d7be4dfbfd8971c0fa59167c711105a6bf/psycopg2-binary-2.9.10.tar.gz", hashes = {sha256 = "4b3df0e6990aa98acda57d983942eff13d824135fe2250e6522edaa782a06de2"}} +wheels = [ + {name = "psycopg2_binary-2.9.10-cp313-cp313-macosx_12_0_x86_64.whl",url = "https://files.pythonhosted.org/packages/3e/30/d41d3ba765609c0763505d565c4d12d8f3c79793f0d0f044ff5a28bf395b/psycopg2_binary-2.9.10-cp313-cp313-macosx_12_0_x86_64.whl",hashes = {sha256 = "26540d4a9a4e2b096f1ff9cce51253d0504dca5a85872c7f7be23be5a53eb18d"}}, + {name = "psycopg2_binary-2.9.10-cp313-cp313-macosx_14_0_arm64.whl",url = "https://files.pythonhosted.org/packages/35/44/257ddadec7ef04536ba71af6bc6a75ec05c5343004a7ec93006bee66c0bc/psycopg2_binary-2.9.10-cp313-cp313-macosx_14_0_arm64.whl",hashes = {sha256 = "e217ce4d37667df0bc1c397fdcd8de5e81018ef305aed9415c3b093faaeb10fb"}}, + {name = "psycopg2_binary-2.9.10-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl",url = "https://files.pythonhosted.org/packages/1b/11/48ea1cd11de67f9efd7262085588790a95d9dfcd9b8a687d46caf7305c1a/psycopg2_binary-2.9.10-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl",hashes = {sha256 = "245159e7ab20a71d989da00f280ca57da7641fa2cdcf71749c193cea540a74f7"}}, + {name = "psycopg2_binary-2.9.10-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl",url = "https://files.pythonhosted.org/packages/62/e0/62ce5ee650e6c86719d621a761fe4bc846ab9eff8c1f12b1ed5741bf1c9b/psycopg2_binary-2.9.10-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl",hashes = {sha256 = "3c4ded1a24b20021ebe677b7b08ad10bf09aac197d6943bfe6fec70ac4e4690d"}}, + {name = "psycopg2_binary-2.9.10-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl",url = "https://files.pythonhosted.org/packages/27/ce/63f946c098611f7be234c0dd7cb1ad68b0b5744d34f68062bb3c5aa510c8/psycopg2_binary-2.9.10-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl",hashes = {sha256 = "3abb691ff9e57d4a93355f60d4f4c1dd2d68326c968e7db17ea96df3c023ef73"}}, + {name = "psycopg2_binary-2.9.10-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",url = "https://files.pythonhosted.org/packages/43/25/c603cd81402e69edf7daa59b1602bd41eb9859e2824b8c0855d748366ac9/psycopg2_binary-2.9.10-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",hashes = {sha256 = "8608c078134f0b3cbd9f89b34bd60a943b23fd33cc5f065e8d5f840061bd0673"}}, + {name = "psycopg2_binary-2.9.10-cp313-cp313-musllinux_1_2_aarch64.whl",url = "https://files.pythonhosted.org/packages/5f/d6/8708d8c6fca531057fa170cdde8df870e8b6a9b136e82b361c65e42b841e/psycopg2_binary-2.9.10-cp313-cp313-musllinux_1_2_aarch64.whl",hashes = {sha256 = "230eeae2d71594103cd5b93fd29d1ace6420d0b86f4778739cb1a5a32f607d1f"}}, + {name = "psycopg2_binary-2.9.10-cp313-cp313-musllinux_1_2_i686.whl",url = "https://files.pythonhosted.org/packages/ce/ac/5b1ea50fc08a9df82de7e1771537557f07c2632231bbab652c7e22597908/psycopg2_binary-2.9.10-cp313-cp313-musllinux_1_2_i686.whl",hashes = {sha256 = "bb89f0a835bcfc1d42ccd5f41f04870c1b936d8507c6df12b7737febc40f0909"}}, + {name = "psycopg2_binary-2.9.10-cp313-cp313-musllinux_1_2_ppc64le.whl",url = "https://files.pythonhosted.org/packages/c4/fc/504d4503b2abc4570fac3ca56eb8fed5e437bf9c9ef13f36b6621db8ef00/psycopg2_binary-2.9.10-cp313-cp313-musllinux_1_2_ppc64le.whl",hashes = {sha256 = "f0c2d907a1e102526dd2986df638343388b94c33860ff3bbe1384130828714b1"}}, + {name = "psycopg2_binary-2.9.10-cp313-cp313-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/b2/d1/323581e9273ad2c0dbd1902f3fb50c441da86e894b6e25a73c3fda32c57e/psycopg2_binary-2.9.10-cp313-cp313-musllinux_1_2_x86_64.whl",hashes = {sha256 = "f8157bed2f51db683f31306aa497311b560f2265998122abe1dce6428bd86567"}}, + {name = "psycopg2_binary-2.9.10-cp313-cp313-win_amd64.whl",url = "https://files.pythonhosted.org/packages/08/50/d13ea0a054189ae1bc21af1d85b6f8bb9bbc5572991055d70ad9006fe2d6/psycopg2_binary-2.9.10-cp313-cp313-win_amd64.whl",hashes = {sha256 = "27422aa5f11fbcd9b18da48373eb67081243662f9b46e6fd07c3eb46e4535142"}}, ] marker = "python_version >= \"3.13\" and \"default\" in dependency_groups" @@ -633,8 +660,21 @@ marker = "python_version >= \"3.13\" and \"types\" in dependency_groups" [packages.tool.pdm] dependencies = [] +[[packages]] +name = "tzdata" +version = "2025.2" +requires-python = ">=2" +sdist = {name = "tzdata-2025.2.tar.gz", url = "https://files.pythonhosted.org/packages/95/32/1a225d6164441be760d75c2c42e2780dc0873fe382da3e98a2e1e48361e5/tzdata-2025.2.tar.gz", hashes = {sha256 = "b60a638fcc0daffadf82fe0f57e53d06bdec2f36c4df66280ae79bce6bd6f2b9"}} +wheels = [ + {name = "tzdata-2025.2-py2.py3-none-any.whl",url = "https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl",hashes = {sha256 = "1a403fada01ff9221ca8044d701868fa132215d84beb92242d9acd2147f667a8"}}, +] +marker = "sys_platform == \"win32\" and python_version >= \"3.13\" and \"default\" in dependency_groups" + +[packages.tool.pdm] +dependencies = [] + [tool.pdm] -hashes = {sha256 = "8f79d3a116fb2aeff8263d46e00c1f48dbba637f607c3da738af719783d85afe"} +hashes = {sha256 = "993642e8053f231239acbb7c445ace66bc6705d078056cf64d5ce3729d4d43c3"} strategy = ["inherit_metadata", "static_urls"] [[tool.pdm.targets]] diff --git a/pylock.minimal.toml b/pylock.minimal.toml index e6aa2bd..66189f3 100644 --- a/pylock.minimal.toml +++ b/pylock.minimal.toml @@ -251,17 +251,63 @@ marker = "\"default\" in dependency_groups" dependencies = [] [[packages]] -name = "psycopg2" -version = "2.9.5" +name = "psycopg" +version = "3.0" requires-python = ">=3.6" -sdist = {name = "psycopg2-2.9.5.tar.gz", url = "https://files.pythonhosted.org/packages/89/d6/cd8c46417e0f7a16b4b0fc321f4ab676a59250d08fce5b64921897fb07cc/psycopg2-2.9.5.tar.gz", hashes = {sha256 = "a5246d2e683a972e2187a8714b5c2cf8156c064629f9a9b1a873c1730d9e245a"}} +sdist = {name = "psycopg-3.0.tar.gz", url = "https://files.pythonhosted.org/packages/2a/1d/77fd95c963b8e066de8b49e592e7e912ae53a24275f04a4d5279f67e454a/psycopg-3.0.tar.gz", hashes = {sha256 = "82bce906431f44e81d72fbe0b924a71eebec09189bcf0dd16003e33960e36efa"}} wheels = [ - {name = "psycopg2-2.9.5-cp311-cp311-win32.whl",url = "https://files.pythonhosted.org/packages/9f/1f/df4c9ab12634dc89c71c2184b2524adccd804af2bacc8968a7c831642338/psycopg2-2.9.5-cp311-cp311-win32.whl",hashes = {sha256 = "093e3894d2d3c592ab0945d9eba9d139c139664dcf83a1c440b8a7aa9bb21955"}}, - {name = "psycopg2-2.9.5-cp311-cp311-win_amd64.whl",url = "https://files.pythonhosted.org/packages/23/b2/679ba212dd7fc5f726913053756b23344d5520b4fa49c2a93af31d0f4508/psycopg2-2.9.5-cp311-cp311-win_amd64.whl",hashes = {sha256 = "920bf418000dd17669d2904472efeab2b20546efd0548139618f8fa305d1d7ad"}}, - {name = "psycopg2-2.9.5-cp310-cp310-win32.whl",url = "https://files.pythonhosted.org/packages/04/51/00ed720de223485a56c28b404747b96806679e720f4c97a75bdd556a01f4/psycopg2-2.9.5-cp310-cp310-win32.whl",hashes = {sha256 = "d3ef67e630b0de0779c42912fe2cbae3805ebaba30cda27fea2a3de650a9414f"}}, - {name = "psycopg2-2.9.5-cp310-cp310-win_amd64.whl",url = "https://files.pythonhosted.org/packages/af/4f/3abf262dc49cb70c2fbb6d38a9a8956a0c79ba2d102e457958bc4f21660f/psycopg2-2.9.5-cp310-cp310-win_amd64.whl",hashes = {sha256 = "4cb9936316d88bfab614666eb9e32995e794ed0f8f6b3b718666c22819c1d7ee"}}, - {name = "psycopg2-2.9.5-cp39-cp39-win32.whl",url = "https://files.pythonhosted.org/packages/c8/80/9c4a7f453fbef2acc65a66f79def6b048787f5ff82005682a98252311e68/psycopg2-2.9.5-cp39-cp39-win32.whl",hashes = {sha256 = "322fd5fca0b1113677089d4ebd5222c964b1760e361f151cbb2706c4912112c5"}}, - {name = "psycopg2-2.9.5-cp39-cp39-win_amd64.whl",url = "https://files.pythonhosted.org/packages/10/db/a34baee6275fcc371ecdb30040042ed2a548f18c29339ea9ca2406a35cba/psycopg2-2.9.5-cp39-cp39-win_amd64.whl",hashes = {sha256 = "190d51e8c1b25a47484e52a79638a8182451d6f6dff99f26ad9bd81e5359a0fa"}}, + {name = "psycopg-3.0-py3-none-any.whl",url = "https://files.pythonhosted.org/packages/d8/31/9ce982570ebb20d092ba4d029ed2702634789bc0cc8deb0c09ba6e22e4db/psycopg-3.0-py3-none-any.whl",hashes = {sha256 = "65b9fb8838dae61040ad3e0cfc184d4ffd17f740ef4c0353d76050a6eb061a9c"}}, +] +marker = "\"default\" in dependency_groups" + +[packages.tool.pdm] +dependencies = [ + "typing-extensions; python_version < \"3.8\"", + "backports-zoneinfo; python_version < \"3.9\"", +] + +[[packages]] +name = "psycopg2-binary" +version = "2.9.5" +requires-python = ">=3.6" +sdist = {name = "psycopg2-binary-2.9.5.tar.gz", url = "https://files.pythonhosted.org/packages/8c/45/77147700f5088efaf9235a3a62b611b594d477a5c5613b5316d0ebd18be0/psycopg2-binary-2.9.5.tar.gz", hashes = {sha256 = "33e632d0885b95a8b97165899006c40e9ecdc634a529dca7b991eb7de4ece41c"}} +wheels = [ + {name = "psycopg2_binary-2.9.5-cp311-cp311-macosx_10_9_universal2.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl",url = "https://files.pythonhosted.org/packages/71/00/bcdc61fb9c96f122fa8d1afb0608160b5b7fbba935ce8058e6b74520cade/psycopg2_binary-2.9.5-cp311-cp311-macosx_10_9_universal2.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl",hashes = {sha256 = "426c2ae999135d64e6a18849a7d1ad0e1bd007277e4a8f4752eaa40a96b550ff"}}, + {name = "psycopg2_binary-2.9.5-cp311-cp311-macosx_11_0_arm64.whl",url = "https://files.pythonhosted.org/packages/ce/0b/71b366318c88ce6517561048b3310388c12cfe6bd038e3531cda9fe30327/psycopg2_binary-2.9.5-cp311-cp311-macosx_11_0_arm64.whl",hashes = {sha256 = "7cf1d44e710ca3a9ce952bda2855830fe9f9017ed6259e01fcd71ea6287565f5"}}, + {name = "psycopg2_binary-2.9.5-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl",url = "https://files.pythonhosted.org/packages/e1/9b/da86cea11053996728cf473d02d63a2137a74834a0e93af4ee4c42c993ae/psycopg2_binary-2.9.5-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl",hashes = {sha256 = "024030b13bdcbd53d8a93891a2cf07719715724fc9fee40243f3bd78b4264b8f"}}, + {name = "psycopg2_binary-2.9.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",url = "https://files.pythonhosted.org/packages/36/af/a9f06e2469e943364b2383b45b3209b40350c105281948df62153394b4a9/psycopg2_binary-2.9.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",hashes = {sha256 = "bcda1c84a1c533c528356da5490d464a139b6e84eb77cc0b432e38c5c6dd7882"}}, + {name = "psycopg2_binary-2.9.5-cp311-cp311-manylinux_2_24_aarch64.whl",url = "https://files.pythonhosted.org/packages/b9/97/61d8794442b50456f3263ca263af9fde2a1fde93811b74fe0c5a9519f6ca/psycopg2_binary-2.9.5-cp311-cp311-manylinux_2_24_aarch64.whl",hashes = {sha256 = "2ef892cabdccefe577088a79580301f09f2a713eb239f4f9f62b2b29cafb0577"}}, + {name = "psycopg2_binary-2.9.5-cp311-cp311-manylinux_2_24_ppc64le.whl",url = "https://files.pythonhosted.org/packages/62/ed/2b7b8727677e2d4b5d53a962a2c9e11d7d141b9a5cb8681b37110d953341/psycopg2_binary-2.9.5-cp311-cp311-manylinux_2_24_ppc64le.whl",hashes = {sha256 = "af0516e1711995cb08dc19bbd05bec7dbdebf4185f68870595156718d237df3e"}}, + {name = "psycopg2_binary-2.9.5-cp311-cp311-musllinux_1_1_aarch64.whl",url = "https://files.pythonhosted.org/packages/b0/82/27c5e2442ba10ab4f01de9c3bf2043581aa8e80e2f64fb64d9cc2eb847d6/psycopg2_binary-2.9.5-cp311-cp311-musllinux_1_1_aarch64.whl",hashes = {sha256 = "e72c91bda9880f097c8aa3601a2c0de6c708763ba8128006151f496ca9065935"}}, + {name = "psycopg2_binary-2.9.5-cp311-cp311-musllinux_1_1_i686.whl",url = "https://files.pythonhosted.org/packages/66/e7/4764b10034b1323d33758823883034c1347f6ebeaf2b45b60f076749d360/psycopg2_binary-2.9.5-cp311-cp311-musllinux_1_1_i686.whl",hashes = {sha256 = "e67b3c26e9b6d37b370c83aa790bbc121775c57bfb096c2e77eacca25fd0233b"}}, + {name = "psycopg2_binary-2.9.5-cp311-cp311-musllinux_1_1_ppc64le.whl",url = "https://files.pythonhosted.org/packages/fa/52/ecd23580763212731eec723313ee3678484ed7de0d22214f6b70120aaa29/psycopg2_binary-2.9.5-cp311-cp311-musllinux_1_1_ppc64le.whl",hashes = {sha256 = "5fc447058d083b8c6ac076fc26b446d44f0145308465d745fba93a28c14c9e32"}}, + {name = "psycopg2_binary-2.9.5-cp311-cp311-musllinux_1_1_x86_64.whl",url = "https://files.pythonhosted.org/packages/e3/57/7b776b14f68532be17659a20741f17457e0583c800ddcc3610270cec584c/psycopg2_binary-2.9.5-cp311-cp311-musllinux_1_1_x86_64.whl",hashes = {sha256 = "d892bfa1d023c3781a3cab8dd5af76b626c483484d782e8bd047c180db590e4c"}}, + {name = "psycopg2_binary-2.9.5-cp311-cp311-win32.whl",url = "https://files.pythonhosted.org/packages/32/85/8a6dbcf799136e6085e0cfb869babb8e19843df23c405a65840bc22f6dbf/psycopg2_binary-2.9.5-cp311-cp311-win32.whl",hashes = {sha256 = "2abccab84d057723d2ca8f99ff7b619285d40da6814d50366f61f0fc385c3903"}}, + {name = "psycopg2_binary-2.9.5-cp311-cp311-win_amd64.whl",url = "https://files.pythonhosted.org/packages/89/ca/4eb68b87bb664a6f6c56b72ac876626c8c036b086892fb6cc803c8d38d2b/psycopg2_binary-2.9.5-cp311-cp311-win_amd64.whl",hashes = {sha256 = "bef7e3f9dc6f0c13afdd671008534be5744e0e682fb851584c8c3a025ec09720"}}, + {name = "psycopg2_binary-2.9.5-cp310-cp310-macosx_10_15_x86_64.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl",url = "https://files.pythonhosted.org/packages/53/4f/8216992bcb530c6e599f8276a27b87b5cbbc06423e68eb2668413481214c/psycopg2_binary-2.9.5-cp310-cp310-macosx_10_15_x86_64.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl",hashes = {sha256 = "0775d6252ccb22b15da3b5d7adbbf8cfe284916b14b6dc0ff503a23edb01ee85"}}, + {name = "psycopg2_binary-2.9.5-cp310-cp310-macosx_11_0_arm64.whl",url = "https://files.pythonhosted.org/packages/c7/4e/c94d534a29f17b4b54b5a7e4020e9e4dc51641a9cdce806323df68d0f5ca/psycopg2_binary-2.9.5-cp310-cp310-macosx_11_0_arm64.whl",hashes = {sha256 = "2ec46ed947801652c9643e0b1dc334cfb2781232e375ba97312c2fc256597632"}}, + {name = "psycopg2_binary-2.9.5-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl",url = "https://files.pythonhosted.org/packages/03/b8/f85694de7d17b37bc4bdc31e8e623b9727c4b7491b1e7b752f2d8b5cc8e0/psycopg2_binary-2.9.5-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl",hashes = {sha256 = "3520d7af1ebc838cc6084a3281145d5cd5bdd43fdef139e6db5af01b92596cb7"}}, + {name = "psycopg2_binary-2.9.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",url = "https://files.pythonhosted.org/packages/72/29/e16ac3d2722f2a3bc20823e6acc30d9ad56136652cce1324502adf94da1f/psycopg2_binary-2.9.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",hashes = {sha256 = "5cbc554ba47ecca8cd3396ddaca85e1ecfe3e48dd57dc5e415e59551affe568e"}}, + {name = "psycopg2_binary-2.9.5-cp310-cp310-manylinux_2_24_aarch64.whl",url = "https://files.pythonhosted.org/packages/67/8d/aab7e25dd2961eb355eb889e1a668cd49635243e9b9051ac5b2f5fb7a84f/psycopg2_binary-2.9.5-cp310-cp310-manylinux_2_24_aarch64.whl",hashes = {sha256 = "5d28ecdf191db558d0c07d0f16524ee9d67896edf2b7990eea800abeb23ebd61"}}, + {name = "psycopg2_binary-2.9.5-cp310-cp310-manylinux_2_24_ppc64le.whl",url = "https://files.pythonhosted.org/packages/25/62/b73e3441e5a291b0ea3559face0ad70ee53b10d20ebe8bf01e48006abd3e/psycopg2_binary-2.9.5-cp310-cp310-manylinux_2_24_ppc64le.whl",hashes = {sha256 = "b9c33d4aef08dfecbd1736ceab8b7b3c4358bf10a0121483e5cd60d3d308cc64"}}, + {name = "psycopg2_binary-2.9.5-cp310-cp310-musllinux_1_1_aarch64.whl",url = "https://files.pythonhosted.org/packages/5c/26/5000a9f311162dc560aecc8c141bee498e061140c22b0a301a84bc5d0e0f/psycopg2_binary-2.9.5-cp310-cp310-musllinux_1_1_aarch64.whl",hashes = {sha256 = "05b3d479425e047c848b9782cd7aac9c6727ce23181eb9647baf64ffdfc3da41"}}, + {name = "psycopg2_binary-2.9.5-cp310-cp310-musllinux_1_1_i686.whl",url = "https://files.pythonhosted.org/packages/95/d5/bbaf6ea0fb7d555e86656b67e03c7bf6506ef3fdfd73470e93ce7073db19/psycopg2_binary-2.9.5-cp310-cp310-musllinux_1_1_i686.whl",hashes = {sha256 = "1e491e6489a6cb1d079df8eaa15957c277fdedb102b6a68cfbf40c4994412fd0"}}, + {name = "psycopg2_binary-2.9.5-cp310-cp310-musllinux_1_1_ppc64le.whl",url = "https://files.pythonhosted.org/packages/17/e7/58947b0f3e6f7ed244bbc7831e42fcf1ef582e8a6c13364cb2cdf19724a3/psycopg2_binary-2.9.5-cp310-cp310-musllinux_1_1_ppc64le.whl",hashes = {sha256 = "9e32cedc389bcb76d9f24ea8a012b3cb8385ee362ea437e1d012ffaed106c17d"}}, + {name = "psycopg2_binary-2.9.5-cp310-cp310-musllinux_1_1_x86_64.whl",url = "https://files.pythonhosted.org/packages/91/f2/556cc32bbf1271310a6cc74f3f2852b2b8dd3231789d942bda4f4625231e/psycopg2_binary-2.9.5-cp310-cp310-musllinux_1_1_x86_64.whl",hashes = {sha256 = "46850a640df62ae940e34a163f72e26aca1f88e2da79148e1862faaac985c302"}}, + {name = "psycopg2_binary-2.9.5-cp310-cp310-win32.whl",url = "https://files.pythonhosted.org/packages/4a/49/ba8b695e63f49e09331347731253909d853dc6e5316740ef7cf0389460d9/psycopg2_binary-2.9.5-cp310-cp310-win32.whl",hashes = {sha256 = "3d790f84201c3698d1bfb404c917f36e40531577a6dda02e45ba29b64d539867"}}, + {name = "psycopg2_binary-2.9.5-cp310-cp310-win_amd64.whl",url = "https://files.pythonhosted.org/packages/e6/a9/00b93fee5384a6e0d5fef8fecc753c9729d1f911eea90f8628a4a6310855/psycopg2_binary-2.9.5-cp310-cp310-win_amd64.whl",hashes = {sha256 = "1764546ffeaed4f9428707be61d68972eb5ede81239b46a45843e0071104d0dd"}}, + {name = "psycopg2_binary-2.9.5-cp39-cp39-macosx_10_15_x86_64.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl",url = "https://files.pythonhosted.org/packages/fa/88/e44363a9c68abfab4f596850636b4be496d479845877a507a73a651ef623/psycopg2_binary-2.9.5-cp39-cp39-macosx_10_15_x86_64.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl",hashes = {sha256 = "25382c7d174c679ce6927c16b6fbb68b10e56ee44b1acb40671e02d29f2fce7c"}}, + {name = "psycopg2_binary-2.9.5-cp39-cp39-macosx_11_0_arm64.whl",url = "https://files.pythonhosted.org/packages/63/db/67c2a94c3d89c48a2a4efc2029f6dc082faa3bc3dd14de49346c126c9600/psycopg2_binary-2.9.5-cp39-cp39-macosx_11_0_arm64.whl",hashes = {sha256 = "9c38d3869238e9d3409239bc05bc27d6b7c99c2a460ea337d2814b35fb4fea1b"}}, + {name = "psycopg2_binary-2.9.5-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl",url = "https://files.pythonhosted.org/packages/db/c8/9f58d89b864f052a2e99cb3a9fd3e64c20984b1319b5d207eb629b4f9c40/psycopg2_binary-2.9.5-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl",hashes = {sha256 = "5c6527c8efa5226a9e787507652dd5ba97b62d29b53c371a85cd13f957fe4d42"}}, + {name = "psycopg2_binary-2.9.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",url = "https://files.pythonhosted.org/packages/62/25/5b84c484aefa78f91366eea8b778de797681a5bc626b569555011874de58/psycopg2_binary-2.9.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",hashes = {sha256 = "e59137cdb970249ae60be2a49774c6dfb015bd0403f05af1fe61862e9626642d"}}, + {name = "psycopg2_binary-2.9.5-cp39-cp39-manylinux_2_24_aarch64.whl",url = "https://files.pythonhosted.org/packages/31/48/e7fe28aca3e2bf3b5fd6f73f681c0be26837b680c815b7cf53b0311a5a28/psycopg2_binary-2.9.5-cp39-cp39-manylinux_2_24_aarch64.whl",hashes = {sha256 = "d4c7b3a31502184e856df1f7bbb2c3735a05a8ce0ade34c5277e1577738a5c91"}}, + {name = "psycopg2_binary-2.9.5-cp39-cp39-manylinux_2_24_ppc64le.whl",url = "https://files.pythonhosted.org/packages/d7/4a/96d3d343ba3d7f1408f091e43bc6265267b625421e1a4aa5f4ea210013e1/psycopg2_binary-2.9.5-cp39-cp39-manylinux_2_24_ppc64le.whl",hashes = {sha256 = "b9a794cef1d9c1772b94a72eec6da144c18e18041d294a9ab47669bc77a80c1d"}}, + {name = "psycopg2_binary-2.9.5-cp39-cp39-musllinux_1_1_aarch64.whl",url = "https://files.pythonhosted.org/packages/0e/07/c689d0c42e86da7e055e3298a3854931c38e8332e571e6cdf71cedec4aa3/psycopg2_binary-2.9.5-cp39-cp39-musllinux_1_1_aarch64.whl",hashes = {sha256 = "c5254cbd4f4855e11cebf678c1a848a3042d455a22a4ce61349c36aafd4c2267"}}, + {name = "psycopg2_binary-2.9.5-cp39-cp39-musllinux_1_1_i686.whl",url = "https://files.pythonhosted.org/packages/ff/e2/e82829bbf209d9fa4ca5df95b244eab30ee170d6c3ba2bf8f0a8a02c64bf/psycopg2_binary-2.9.5-cp39-cp39-musllinux_1_1_i686.whl",hashes = {sha256 = "c5e65c6ac0ae4bf5bef1667029f81010b6017795dcb817ba5c7b8a8d61fab76f"}}, + {name = "psycopg2_binary-2.9.5-cp39-cp39-musllinux_1_1_ppc64le.whl",url = "https://files.pythonhosted.org/packages/29/e5/9dc756e4eb496764199ca1fbee235e0b6b935c987922af2243e6e0001d24/psycopg2_binary-2.9.5-cp39-cp39-musllinux_1_1_ppc64le.whl",hashes = {sha256 = "74eddec4537ab1f701a1647214734bc52cee2794df748f6ae5908e00771f180a"}}, + {name = "psycopg2_binary-2.9.5-cp39-cp39-musllinux_1_1_x86_64.whl",url = "https://files.pythonhosted.org/packages/f1/39/6e2d51b2f35b4e272f9380797e1b838a1d2086f06414ea837f62a3505243/psycopg2_binary-2.9.5-cp39-cp39-musllinux_1_1_x86_64.whl",hashes = {sha256 = "01ad49d68dd8c5362e4bfb4158f2896dc6e0c02e87b8a3770fc003459f1a4425"}}, + {name = "psycopg2_binary-2.9.5-cp39-cp39-win32.whl",url = "https://files.pythonhosted.org/packages/8f/c6/cc416da1edbeaf8e9c69eb811d4495920a6dc7fe71b689ce67db1e84bfce/psycopg2_binary-2.9.5-cp39-cp39-win32.whl",hashes = {sha256 = "937880290775033a743f4836aa253087b85e62784b63fd099ee725d567a48aa1"}}, + {name = "psycopg2_binary-2.9.5-cp39-cp39-win_amd64.whl",url = "https://files.pythonhosted.org/packages/3e/97/d8bda00145b96d79dc9ff6790640e6b1c503c2ccadd76a45fef8dc342b95/psycopg2_binary-2.9.5-cp39-cp39-win_amd64.whl",hashes = {sha256 = "484405b883630f3e74ed32041a87456c5e0e63a8e3429aa93e8714c366d62bd1"}}, ] marker = "\"default\" in dependency_groups" @@ -801,7 +847,7 @@ dependencies = [ ] [tool.pdm] -hashes = {sha256 = "8f79d3a116fb2aeff8263d46e00c1f48dbba637f607c3da738af719783d85afe"} +hashes = {sha256 = "993642e8053f231239acbb7c445ace66bc6705d078056cf64d5ce3729d4d43c3"} strategy = ["direct_minimal_versions", "inherit_metadata", "static_urls"] [[tool.pdm.targets]] diff --git a/pylock.toml b/pylock.toml index d274855..7b801c5 100644 --- a/pylock.toml +++ b/pylock.toml @@ -360,20 +360,86 @@ marker = "\"default\" in dependency_groups" dependencies = [] [[packages]] -name = "psycopg2" +name = "psycopg" +version = "3.2.10" +requires-python = ">=3.8" +sdist = {name = "psycopg-3.2.10.tar.gz", url = "https://files.pythonhosted.org/packages/a9/f1/0258a123c045afaf3c3b60c22ccff077bceeb24b8dc2c593270899353bd0/psycopg-3.2.10.tar.gz", hashes = {sha256 = "0bce99269d16ed18401683a8569b2c5abd94f72f8364856d56c0389bcd50972a"}} +wheels = [ + {name = "psycopg-3.2.10-py3-none-any.whl",url = "https://files.pythonhosted.org/packages/4a/90/422ffbbeeb9418c795dae2a768db860401446af0c6768bc061ce22325f58/psycopg-3.2.10-py3-none-any.whl",hashes = {sha256 = "ab5caf09a9ec42e314a21f5216dbcceac528e0e05142e42eea83a3b28b320ac3"}}, +] +marker = "\"default\" in dependency_groups" + +[packages.tool.pdm] +dependencies = [ + "backports-zoneinfo>=0.2.0; python_version < \"3.9\"", + "typing-extensions>=4.6; python_version < \"3.13\"", + "tzdata; sys_platform == \"win32\"", +] + +[[packages]] +name = "psycopg2-binary" version = "2.9.10" requires-python = ">=3.8" -sdist = {name = "psycopg2-2.9.10.tar.gz", url = "https://files.pythonhosted.org/packages/62/51/2007ea29e605957a17ac6357115d0c1a1b60c8c984951c19419b3474cdfd/psycopg2-2.9.10.tar.gz", hashes = {sha256 = "12ec0b40b0273f95296233e8750441339298e6a572f7039da5b260e3c8b60e11"}} -wheels = [ - {name = "psycopg2-2.9.10-cp313-cp313-win_amd64.whl",url = "https://files.pythonhosted.org/packages/ae/49/a6cfc94a9c483b1fa401fbcb23aca7892f60c7269c5ffa2ac408364f80dc/psycopg2-2.9.10-cp313-cp313-win_amd64.whl",hashes = {sha256 = "91fd603a2155da8d0cfcdbf8ab24a2d54bca72795b90d2a3ed2b6da8d979dee2"}}, - {name = "psycopg2-2.9.10-cp312-cp312-win32.whl",url = "https://files.pythonhosted.org/packages/3d/16/4623fad6076448df21c1a870c93a9774ad8a7b4dd1660223b59082dd8fec/psycopg2-2.9.10-cp312-cp312-win32.whl",hashes = {sha256 = "65a63d7ab0e067e2cdb3cf266de39663203d38d6a8ed97f5ca0cb315c73fe067"}}, - {name = "psycopg2-2.9.10-cp312-cp312-win_amd64.whl",url = "https://files.pythonhosted.org/packages/66/de/baed128ae0fc07460d9399d82e631ea31a1f171c0c4ae18f9808ac6759e3/psycopg2-2.9.10-cp312-cp312-win_amd64.whl",hashes = {sha256 = "4a579d6243da40a7b3182e0430493dbd55950c493d8c68f4eec0b302f6bbf20e"}}, - {name = "psycopg2-2.9.10-cp311-cp311-win32.whl",url = "https://files.pythonhosted.org/packages/20/a2/c51ca3e667c34e7852157b665e3d49418e68182081060231d514dd823225/psycopg2-2.9.10-cp311-cp311-win32.whl",hashes = {sha256 = "47c4f9875125344f4c2b870e41b6aad585901318068acd01de93f3677a6522c2"}}, - {name = "psycopg2-2.9.10-cp311-cp311-win_amd64.whl",url = "https://files.pythonhosted.org/packages/33/39/5a9a229bb5414abeb86e33b8fc8143ab0aecce5a7f698a53e31367d30caa/psycopg2-2.9.10-cp311-cp311-win_amd64.whl",hashes = {sha256 = "0435034157049f6846e95103bd8f5a668788dd913a7c30162ca9503fdf542cb4"}}, - {name = "psycopg2-2.9.10-cp310-cp310-win32.whl",url = "https://files.pythonhosted.org/packages/0a/a9/146b6bdc0d33539a359f5e134ee6dda9173fb8121c5b96af33fa299e50c4/psycopg2-2.9.10-cp310-cp310-win32.whl",hashes = {sha256 = "5df2b672140f95adb453af93a7d669d7a7bf0a56bcd26f1502329166f4a61716"}}, - {name = "psycopg2-2.9.10-cp310-cp310-win_amd64.whl",url = "https://files.pythonhosted.org/packages/47/50/c509e56f725fd2572b59b69bd964edaf064deebf1c896b2452f6b46fdfb3/psycopg2-2.9.10-cp310-cp310-win_amd64.whl",hashes = {sha256 = "c6f7b8561225f9e711a9c47087388a97fdc948211c10a4bccbf0ba68ab7b3b5a"}}, - {name = "psycopg2-2.9.10-cp39-cp39-win32.whl",url = "https://files.pythonhosted.org/packages/5f/29/bc9639b9c50abd93a8274fd2deffbf70b2a65aa9e7881e63ea6bc4319e84/psycopg2-2.9.10-cp39-cp39-win32.whl",hashes = {sha256 = "9d5b3b94b79a844a986d029eee38998232451119ad653aea42bb9220a8c5066b"}}, - {name = "psycopg2-2.9.10-cp39-cp39-win_amd64.whl",url = "https://files.pythonhosted.org/packages/2c/f8/0be7d99d24656b689d83ac167240c3527efb0b161d814fb1dd58329ddf75/psycopg2-2.9.10-cp39-cp39-win_amd64.whl",hashes = {sha256 = "88138c8dedcbfa96408023ea2b0c369eda40fe5d75002c0964c78f46f11fa442"}}, +sdist = {name = "psycopg2-binary-2.9.10.tar.gz", url = "https://files.pythonhosted.org/packages/cb/0e/bdc8274dc0585090b4e3432267d7be4dfbfd8971c0fa59167c711105a6bf/psycopg2-binary-2.9.10.tar.gz", hashes = {sha256 = "4b3df0e6990aa98acda57d983942eff13d824135fe2250e6522edaa782a06de2"}} +wheels = [ + {name = "psycopg2_binary-2.9.10-cp313-cp313-macosx_12_0_x86_64.whl",url = "https://files.pythonhosted.org/packages/3e/30/d41d3ba765609c0763505d565c4d12d8f3c79793f0d0f044ff5a28bf395b/psycopg2_binary-2.9.10-cp313-cp313-macosx_12_0_x86_64.whl",hashes = {sha256 = "26540d4a9a4e2b096f1ff9cce51253d0504dca5a85872c7f7be23be5a53eb18d"}}, + {name = "psycopg2_binary-2.9.10-cp313-cp313-macosx_14_0_arm64.whl",url = "https://files.pythonhosted.org/packages/35/44/257ddadec7ef04536ba71af6bc6a75ec05c5343004a7ec93006bee66c0bc/psycopg2_binary-2.9.10-cp313-cp313-macosx_14_0_arm64.whl",hashes = {sha256 = "e217ce4d37667df0bc1c397fdcd8de5e81018ef305aed9415c3b093faaeb10fb"}}, + {name = "psycopg2_binary-2.9.10-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl",url = "https://files.pythonhosted.org/packages/1b/11/48ea1cd11de67f9efd7262085588790a95d9dfcd9b8a687d46caf7305c1a/psycopg2_binary-2.9.10-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl",hashes = {sha256 = "245159e7ab20a71d989da00f280ca57da7641fa2cdcf71749c193cea540a74f7"}}, + {name = "psycopg2_binary-2.9.10-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl",url = "https://files.pythonhosted.org/packages/62/e0/62ce5ee650e6c86719d621a761fe4bc846ab9eff8c1f12b1ed5741bf1c9b/psycopg2_binary-2.9.10-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl",hashes = {sha256 = "3c4ded1a24b20021ebe677b7b08ad10bf09aac197d6943bfe6fec70ac4e4690d"}}, + {name = "psycopg2_binary-2.9.10-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl",url = "https://files.pythonhosted.org/packages/27/ce/63f946c098611f7be234c0dd7cb1ad68b0b5744d34f68062bb3c5aa510c8/psycopg2_binary-2.9.10-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl",hashes = {sha256 = "3abb691ff9e57d4a93355f60d4f4c1dd2d68326c968e7db17ea96df3c023ef73"}}, + {name = "psycopg2_binary-2.9.10-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",url = "https://files.pythonhosted.org/packages/43/25/c603cd81402e69edf7daa59b1602bd41eb9859e2824b8c0855d748366ac9/psycopg2_binary-2.9.10-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",hashes = {sha256 = "8608c078134f0b3cbd9f89b34bd60a943b23fd33cc5f065e8d5f840061bd0673"}}, + {name = "psycopg2_binary-2.9.10-cp313-cp313-musllinux_1_2_aarch64.whl",url = "https://files.pythonhosted.org/packages/5f/d6/8708d8c6fca531057fa170cdde8df870e8b6a9b136e82b361c65e42b841e/psycopg2_binary-2.9.10-cp313-cp313-musllinux_1_2_aarch64.whl",hashes = {sha256 = "230eeae2d71594103cd5b93fd29d1ace6420d0b86f4778739cb1a5a32f607d1f"}}, + {name = "psycopg2_binary-2.9.10-cp313-cp313-musllinux_1_2_i686.whl",url = "https://files.pythonhosted.org/packages/ce/ac/5b1ea50fc08a9df82de7e1771537557f07c2632231bbab652c7e22597908/psycopg2_binary-2.9.10-cp313-cp313-musllinux_1_2_i686.whl",hashes = {sha256 = "bb89f0a835bcfc1d42ccd5f41f04870c1b936d8507c6df12b7737febc40f0909"}}, + {name = "psycopg2_binary-2.9.10-cp313-cp313-musllinux_1_2_ppc64le.whl",url = "https://files.pythonhosted.org/packages/c4/fc/504d4503b2abc4570fac3ca56eb8fed5e437bf9c9ef13f36b6621db8ef00/psycopg2_binary-2.9.10-cp313-cp313-musllinux_1_2_ppc64le.whl",hashes = {sha256 = "f0c2d907a1e102526dd2986df638343388b94c33860ff3bbe1384130828714b1"}}, + {name = "psycopg2_binary-2.9.10-cp313-cp313-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/b2/d1/323581e9273ad2c0dbd1902f3fb50c441da86e894b6e25a73c3fda32c57e/psycopg2_binary-2.9.10-cp313-cp313-musllinux_1_2_x86_64.whl",hashes = {sha256 = "f8157bed2f51db683f31306aa497311b560f2265998122abe1dce6428bd86567"}}, + {name = "psycopg2_binary-2.9.10-cp313-cp313-win_amd64.whl",url = "https://files.pythonhosted.org/packages/08/50/d13ea0a054189ae1bc21af1d85b6f8bb9bbc5572991055d70ad9006fe2d6/psycopg2_binary-2.9.10-cp313-cp313-win_amd64.whl",hashes = {sha256 = "27422aa5f11fbcd9b18da48373eb67081243662f9b46e6fd07c3eb46e4535142"}}, + {name = "psycopg2_binary-2.9.10-cp312-cp312-macosx_12_0_x86_64.whl",url = "https://files.pythonhosted.org/packages/49/7d/465cc9795cf76f6d329efdafca74693714556ea3891813701ac1fee87545/psycopg2_binary-2.9.10-cp312-cp312-macosx_12_0_x86_64.whl",hashes = {sha256 = "880845dfe1f85d9d5f7c412efea7a08946a46894537e4e5d091732eb1d34d9a0"}}, + {name = "psycopg2_binary-2.9.10-cp312-cp312-macosx_14_0_arm64.whl",url = "https://files.pythonhosted.org/packages/8b/31/6d225b7b641a1a2148e3ed65e1aa74fc86ba3fee850545e27be9e1de893d/psycopg2_binary-2.9.10-cp312-cp312-macosx_14_0_arm64.whl",hashes = {sha256 = "9440fa522a79356aaa482aa4ba500b65f28e5d0e63b801abf6aa152a29bd842a"}}, + {name = "psycopg2_binary-2.9.10-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl",url = "https://files.pythonhosted.org/packages/30/b7/a68c2b4bff1cbb1728e3ec864b2d92327c77ad52edcd27922535a8366f68/psycopg2_binary-2.9.10-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl",hashes = {sha256 = "e3923c1d9870c49a2d44f795df0c889a22380d36ef92440ff618ec315757e539"}}, + {name = "psycopg2_binary-2.9.10-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl",url = "https://files.pythonhosted.org/packages/0b/b1/cfedc0e0e6f9ad61f8657fd173b2f831ce261c02a08c0b09c652b127d813/psycopg2_binary-2.9.10-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl",hashes = {sha256 = "7b2c956c028ea5de47ff3a8d6b3cc3330ab45cf0b7c3da35a2d6ff8420896526"}}, + {name = "psycopg2_binary-2.9.10-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl",url = "https://files.pythonhosted.org/packages/18/ed/0a8e4153c9b769f59c02fb5e7914f20f0b2483a19dae7bf2db54b743d0d0/psycopg2_binary-2.9.10-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl",hashes = {sha256 = "f758ed67cab30b9a8d2833609513ce4d3bd027641673d4ebc9c067e4d208eec1"}}, + {name = "psycopg2_binary-2.9.10-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",url = "https://files.pythonhosted.org/packages/10/db/d09da68c6a0cdab41566b74e0a6068a425f077169bed0946559b7348ebe9/psycopg2_binary-2.9.10-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",hashes = {sha256 = "8cd9b4f2cfab88ed4a9106192de509464b75a906462fb846b936eabe45c2063e"}}, + {name = "psycopg2_binary-2.9.10-cp312-cp312-musllinux_1_2_aarch64.whl",url = "https://files.pythonhosted.org/packages/94/28/4d6f8c255f0dfffb410db2b3f9ac5218d959a66c715c34cac31081e19b95/psycopg2_binary-2.9.10-cp312-cp312-musllinux_1_2_aarch64.whl",hashes = {sha256 = "6dc08420625b5a20b53551c50deae6e231e6371194fa0651dbe0fb206452ae1f"}}, + {name = "psycopg2_binary-2.9.10-cp312-cp312-musllinux_1_2_i686.whl",url = "https://files.pythonhosted.org/packages/05/f7/20d7bf796593c4fea95e12119d6cc384ff1f6141a24fbb7df5a668d29d29/psycopg2_binary-2.9.10-cp312-cp312-musllinux_1_2_i686.whl",hashes = {sha256 = "d7cd730dfa7c36dbe8724426bf5612798734bff2d3c3857f36f2733f5bfc7c00"}}, + {name = "psycopg2_binary-2.9.10-cp312-cp312-musllinux_1_2_ppc64le.whl",url = "https://files.pythonhosted.org/packages/4d/e4/0c407ae919ef626dbdb32835a03b6737013c3cc7240169843965cada2bdf/psycopg2_binary-2.9.10-cp312-cp312-musllinux_1_2_ppc64le.whl",hashes = {sha256 = "155e69561d54d02b3c3209545fb08938e27889ff5a10c19de8d23eb5a41be8a5"}}, + {name = "psycopg2_binary-2.9.10-cp312-cp312-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/2d/70/aa69c9f69cf09a01da224909ff6ce8b68faeef476f00f7ec377e8f03be70/psycopg2_binary-2.9.10-cp312-cp312-musllinux_1_2_x86_64.whl",hashes = {sha256 = "c3cc28a6fd5a4a26224007712e79b81dbaee2ffb90ff406256158ec4d7b52b47"}}, + {name = "psycopg2_binary-2.9.10-cp312-cp312-win32.whl",url = "https://files.pythonhosted.org/packages/d3/bd/213e59854fafe87ba47814bf413ace0dcee33a89c8c8c814faca6bc7cf3c/psycopg2_binary-2.9.10-cp312-cp312-win32.whl",hashes = {sha256 = "ec8a77f521a17506a24a5f626cb2aee7850f9b69a0afe704586f63a464f3cd64"}}, + {name = "psycopg2_binary-2.9.10-cp312-cp312-win_amd64.whl",url = "https://files.pythonhosted.org/packages/92/29/06261ea000e2dc1e22907dbbc483a1093665509ea586b29b8986a0e56733/psycopg2_binary-2.9.10-cp312-cp312-win_amd64.whl",hashes = {sha256 = "18c5ee682b9c6dd3696dad6e54cc7ff3a1a9020df6a5c0f861ef8bfd338c3ca0"}}, + {name = "psycopg2_binary-2.9.10-cp311-cp311-macosx_12_0_x86_64.whl",url = "https://files.pythonhosted.org/packages/9c/8f/9feb01291d0d7a0a4c6a6bab24094135c2b59c6a81943752f632c75896d6/psycopg2_binary-2.9.10-cp311-cp311-macosx_12_0_x86_64.whl",hashes = {sha256 = "04392983d0bb89a8717772a193cfaac58871321e3ec69514e1c4e0d4957b5aff"}}, + {name = "psycopg2_binary-2.9.10-cp311-cp311-macosx_14_0_arm64.whl",url = "https://files.pythonhosted.org/packages/15/30/346e4683532011561cd9c8dfeac6a8153dd96452fee0b12666058ab7893c/psycopg2_binary-2.9.10-cp311-cp311-macosx_14_0_arm64.whl",hashes = {sha256 = "1a6784f0ce3fec4edc64e985865c17778514325074adf5ad8f80636cd029ef7c"}}, + {name = "psycopg2_binary-2.9.10-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl",url = "https://files.pythonhosted.org/packages/66/6e/4efebe76f76aee7ec99166b6c023ff8abdc4e183f7b70913d7c047701b79/psycopg2_binary-2.9.10-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl",hashes = {sha256 = "b5f86c56eeb91dc3135b3fd8a95dc7ae14c538a2f3ad77a19645cf55bab1799c"}}, + {name = "psycopg2_binary-2.9.10-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl",url = "https://files.pythonhosted.org/packages/7f/fd/ff83313f86b50f7ca089b161b8e0a22bb3c319974096093cd50680433fdb/psycopg2_binary-2.9.10-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl",hashes = {sha256 = "2b3d2491d4d78b6b14f76881905c7a8a8abcf974aad4a8a0b065273a0ed7a2cb"}}, + {name = "psycopg2_binary-2.9.10-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl",url = "https://files.pythonhosted.org/packages/e6/c4/bfadd202dcda8333a7ccafdc51c541dbdfce7c2c7cda89fa2374455d795f/psycopg2_binary-2.9.10-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl",hashes = {sha256 = "2286791ececda3a723d1910441c793be44625d86d1a4e79942751197f4d30341"}}, + {name = "psycopg2_binary-2.9.10-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",url = "https://files.pythonhosted.org/packages/5d/f1/09f45ac25e704ac954862581f9f9ae21303cc5ded3d0b775532b407f0e90/psycopg2_binary-2.9.10-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",hashes = {sha256 = "512d29bb12608891e349af6a0cccedce51677725a921c07dba6342beaf576f9a"}}, + {name = "psycopg2_binary-2.9.10-cp311-cp311-musllinux_1_2_aarch64.whl",url = "https://files.pythonhosted.org/packages/9e/2e/9beaea078095cc558f215e38f647c7114987d9febfc25cb2beed7c3582a5/psycopg2_binary-2.9.10-cp311-cp311-musllinux_1_2_aarch64.whl",hashes = {sha256 = "5a507320c58903967ef7384355a4da7ff3f28132d679aeb23572753cbf2ec10b"}}, + {name = "psycopg2_binary-2.9.10-cp311-cp311-musllinux_1_2_i686.whl",url = "https://files.pythonhosted.org/packages/01/9e/ef93c5d93f3dc9fc92786ffab39e323b9aed066ba59fdc34cf85e2722271/psycopg2_binary-2.9.10-cp311-cp311-musllinux_1_2_i686.whl",hashes = {sha256 = "6d4fa1079cab9018f4d0bd2db307beaa612b0d13ba73b5c6304b9fe2fb441ff7"}}, + {name = "psycopg2_binary-2.9.10-cp311-cp311-musllinux_1_2_ppc64le.whl",url = "https://files.pythonhosted.org/packages/a5/f0/049e9631e3268fe4c5a387f6fc27e267ebe199acf1bc1bc9cbde4bd6916c/psycopg2_binary-2.9.10-cp311-cp311-musllinux_1_2_ppc64le.whl",hashes = {sha256 = "851485a42dbb0bdc1edcdabdb8557c09c9655dfa2ca0460ff210522e073e319e"}}, + {name = "psycopg2_binary-2.9.10-cp311-cp311-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/dc/9a/bcb8773b88e45fb5a5ea8339e2104d82c863a3b8558fbb2aadfe66df86b3/psycopg2_binary-2.9.10-cp311-cp311-musllinux_1_2_x86_64.whl",hashes = {sha256 = "35958ec9e46432d9076286dda67942ed6d968b9c3a6a2fd62b48939d1d78bf68"}}, + {name = "psycopg2_binary-2.9.10-cp311-cp311-win32.whl",url = "https://files.pythonhosted.org/packages/e2/6b/144336a9bf08a67d217b3af3246abb1d027095dab726f0687f01f43e8c03/psycopg2_binary-2.9.10-cp311-cp311-win32.whl",hashes = {sha256 = "ecced182e935529727401b24d76634a357c71c9275b356efafd8a2a91ec07392"}}, + {name = "psycopg2_binary-2.9.10-cp311-cp311-win_amd64.whl",url = "https://files.pythonhosted.org/packages/61/69/3b3d7bd583c6d3cbe5100802efa5beacaacc86e37b653fc708bf3d6853b8/psycopg2_binary-2.9.10-cp311-cp311-win_amd64.whl",hashes = {sha256 = "ee0e8c683a7ff25d23b55b11161c2663d4b099770f6085ff0a20d4505778d6b4"}}, + {name = "psycopg2_binary-2.9.10-cp310-cp310-macosx_12_0_x86_64.whl",url = "https://files.pythonhosted.org/packages/7a/81/331257dbf2801cdb82105306042f7a1637cc752f65f2bb688188e0de5f0b/psycopg2_binary-2.9.10-cp310-cp310-macosx_12_0_x86_64.whl",hashes = {sha256 = "0ea8e3d0ae83564f2fc554955d327fa081d065c8ca5cc6d2abb643e2c9c1200f"}}, + {name = "psycopg2_binary-2.9.10-cp310-cp310-macosx_14_0_arm64.whl",url = "https://files.pythonhosted.org/packages/e7/9a/7f4f2f031010bbfe6a02b4a15c01e12eb6b9b7b358ab33229f28baadbfc1/psycopg2_binary-2.9.10-cp310-cp310-macosx_14_0_arm64.whl",hashes = {sha256 = "3e9c76f0ac6f92ecfc79516a8034a544926430f7b080ec5a0537bca389ee0906"}}, + {name = "psycopg2_binary-2.9.10-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl",url = "https://files.pythonhosted.org/packages/e5/57/8ddd4b374fa811a0b0a0f49b6abad1cde9cb34df73ea3348cc283fcd70b4/psycopg2_binary-2.9.10-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl",hashes = {sha256 = "2ad26b467a405c798aaa1458ba09d7e2b6e5f96b1ce0ac15d82fd9f95dc38a92"}}, + {name = "psycopg2_binary-2.9.10-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl",url = "https://files.pythonhosted.org/packages/f9/66/d1e52c20d283f1f3a8e7e5c1e06851d432f123ef57b13043b4f9b21ffa1f/psycopg2_binary-2.9.10-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl",hashes = {sha256 = "270934a475a0e4b6925b5f804e3809dd5f90f8613621d062848dd82f9cd62007"}}, + {name = "psycopg2_binary-2.9.10-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl",url = "https://files.pythonhosted.org/packages/a0/cb/592d44a9546aba78f8a1249021fe7c59d3afb8a0ba51434d6610cc3462b6/psycopg2_binary-2.9.10-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl",hashes = {sha256 = "48b338f08d93e7be4ab2b5f1dbe69dc5e9ef07170fe1f86514422076d9c010d0"}}, + {name = "psycopg2_binary-2.9.10-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",url = "https://files.pythonhosted.org/packages/64/33/c8548560b94b7617f203d7236d6cdf36fe1a5a3645600ada6efd79da946f/psycopg2_binary-2.9.10-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",hashes = {sha256 = "7f4152f8f76d2023aac16285576a9ecd2b11a9895373a1f10fd9db54b3ff06b4"}}, + {name = "psycopg2_binary-2.9.10-cp310-cp310-musllinux_1_2_aarch64.whl",url = "https://files.pythonhosted.org/packages/b0/0e/c2da0db5bea88a3be52307f88b75eec72c4de62814cbe9ee600c29c06334/psycopg2_binary-2.9.10-cp310-cp310-musllinux_1_2_aarch64.whl",hashes = {sha256 = "32581b3020c72d7a421009ee1c6bf4a131ef5f0a968fab2e2de0c9d2bb4577f1"}}, + {name = "psycopg2_binary-2.9.10-cp310-cp310-musllinux_1_2_i686.whl",url = "https://files.pythonhosted.org/packages/15/d7/774afa1eadb787ddf41aab52d4c62785563e29949613c958955031408ae6/psycopg2_binary-2.9.10-cp310-cp310-musllinux_1_2_i686.whl",hashes = {sha256 = "2ce3e21dc3437b1d960521eca599d57408a695a0d3c26797ea0f72e834c7ffe5"}}, + {name = "psycopg2_binary-2.9.10-cp310-cp310-musllinux_1_2_ppc64le.whl",url = "https://files.pythonhosted.org/packages/5e/ed/440dc3f5991a8c6172a1cde44850ead0e483a375277a1aef7cfcec00af07/psycopg2_binary-2.9.10-cp310-cp310-musllinux_1_2_ppc64le.whl",hashes = {sha256 = "e984839e75e0b60cfe75e351db53d6db750b00de45644c5d1f7ee5d1f34a1ce5"}}, + {name = "psycopg2_binary-2.9.10-cp310-cp310-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/03/be/2cc8f4282898306732d2ae7b7378ae14e8df3c1231b53579efa056aae887/psycopg2_binary-2.9.10-cp310-cp310-musllinux_1_2_x86_64.whl",hashes = {sha256 = "3c4745a90b78e51d9ba06e2088a2fe0c693ae19cc8cb051ccda44e8df8a6eb53"}}, + {name = "psycopg2_binary-2.9.10-cp310-cp310-win32.whl",url = "https://files.pythonhosted.org/packages/d0/12/fb8e4f485d98c570e00dad5800e9a2349cfe0f71a767c856857160d343a5/psycopg2_binary-2.9.10-cp310-cp310-win32.whl",hashes = {sha256 = "e5720a5d25e3b99cd0dc5c8a440570469ff82659bb09431c1439b92caf184d3b"}}, + {name = "psycopg2_binary-2.9.10-cp310-cp310-win_amd64.whl",url = "https://files.pythonhosted.org/packages/22/4f/217cd2471ecf45d82905dd09085e049af8de6cfdc008b6663c3226dc1c98/psycopg2_binary-2.9.10-cp310-cp310-win_amd64.whl",hashes = {sha256 = "3c18f74eb4386bf35e92ab2354a12c17e5eb4d9798e4c0ad3a00783eae7cd9f1"}}, + {name = "psycopg2_binary-2.9.10-cp39-cp39-macosx_12_0_x86_64.whl",url = "https://files.pythonhosted.org/packages/a2/bc/e77648009b6e61af327c607543f65fdf25bcfb4100f5a6f3bdb62ddac03c/psycopg2_binary-2.9.10-cp39-cp39-macosx_12_0_x86_64.whl",hashes = {sha256 = "7a813c8bdbaaaab1f078014b9b0b13f5de757e2b5d9be6403639b298a04d218b"}}, + {name = "psycopg2_binary-2.9.10-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl",url = "https://files.pythonhosted.org/packages/e0/e8/5a12211a1f5b959f3e3ccd342eace60c1f26422f53e06d687821dc268780/psycopg2_binary-2.9.10-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl",hashes = {sha256 = "d00924255d7fc916ef66e4bf22f354a940c67179ad3fd7067d7a0a9c84d2fbfc"}}, + {name = "psycopg2_binary-2.9.10-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl",url = "https://files.pythonhosted.org/packages/47/ed/5932b0458a7fc61237b653df050513c8d18a6f4083cc7f90dcef967f7bce/psycopg2_binary-2.9.10-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl",hashes = {sha256 = "7559bce4b505762d737172556a4e6ea8a9998ecac1e39b5233465093e8cee697"}}, + {name = "psycopg2_binary-2.9.10-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl",url = "https://files.pythonhosted.org/packages/71/df/8047d85c3d23864aca4613c3be1ea0fe61dbe4e050a89ac189f9dce4403e/psycopg2_binary-2.9.10-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl",hashes = {sha256 = "e8b58f0a96e7a1e341fc894f62c1177a7c83febebb5ff9123b579418fdc8a481"}}, + {name = "psycopg2_binary-2.9.10-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",url = "https://files.pythonhosted.org/packages/f3/de/6157e4ef242920e8f2749f7708d5cc8815414bdd4a27a91996e7cd5c80df/psycopg2_binary-2.9.10-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",hashes = {sha256 = "6b269105e59ac96aba877c1707c600ae55711d9dcd3fc4b5012e4af68e30c648"}}, + {name = "psycopg2_binary-2.9.10-cp39-cp39-musllinux_1_2_aarch64.whl",url = "https://files.pythonhosted.org/packages/25/f9/0fc49efd2d4d6db3a8d0a3f5749b33a0d3fdd872cad49fbf5bfce1c50027/psycopg2_binary-2.9.10-cp39-cp39-musllinux_1_2_aarch64.whl",hashes = {sha256 = "79625966e176dc97ddabc142351e0409e28acf4660b88d1cf6adb876d20c490d"}}, + {name = "psycopg2_binary-2.9.10-cp39-cp39-musllinux_1_2_i686.whl",url = "https://files.pythonhosted.org/packages/57/bc/2ed1bd182219065692ed458d218d311b0b220b20662d25d913bc4e8d3549/psycopg2_binary-2.9.10-cp39-cp39-musllinux_1_2_i686.whl",hashes = {sha256 = "8aabf1c1a04584c168984ac678a668094d831f152859d06e055288fa515e4d30"}}, + {name = "psycopg2_binary-2.9.10-cp39-cp39-musllinux_1_2_ppc64le.whl",url = "https://files.pythonhosted.org/packages/71/2a/43f77a9b8ee0b10e2de784d97ddc099d9fe0d9eec462a006e4d2cc74756d/psycopg2_binary-2.9.10-cp39-cp39-musllinux_1_2_ppc64le.whl",hashes = {sha256 = "19721ac03892001ee8fdd11507e6a2e01f4e37014def96379411ca99d78aeb2c"}}, + {name = "psycopg2_binary-2.9.10-cp39-cp39-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/57/86/d2943df70469e6afab3b5b8e1367fccc61891f46de436b24ddee6f2c8404/psycopg2_binary-2.9.10-cp39-cp39-musllinux_1_2_x86_64.whl",hashes = {sha256 = "7f5d859928e635fa3ce3477704acee0f667b3a3d3e4bb109f2b18d4005f38287"}}, + {name = "psycopg2_binary-2.9.10-cp39-cp39-win32.whl",url = "https://files.pythonhosted.org/packages/85/21/195d69371330983aa16139e60ba855d0a18164c9295f3a3696be41bbcd54/psycopg2_binary-2.9.10-cp39-cp39-win32.whl",hashes = {sha256 = "3216ccf953b3f267691c90c6fe742e45d890d8272326b4a8b20850a03d05b7b8"}}, + {name = "psycopg2_binary-2.9.10-cp39-cp39-win_amd64.whl",url = "https://files.pythonhosted.org/packages/ad/53/73196ebc19d6fbfc22427b982fbc98698b7b9c361e5e7707e3a3247cf06d/psycopg2_binary-2.9.10-cp39-cp39-win_amd64.whl",hashes = {sha256 = "30e34c4e97964805f715206c7b789d54a78b70f3ff19fbe590104b71c45600e5"}}, ] marker = "\"default\" in dependency_groups" @@ -530,6 +596,19 @@ marker = "\"test\" in dependency_groups" [packages.tool.pdm] dependencies = [] +[[packages]] +name = "typing-extensions" +version = "4.15.0" +requires-python = ">=3.9" +sdist = {name = "typing_extensions-4.15.0.tar.gz", url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hashes = {sha256 = "0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466"}} +wheels = [ + {name = "typing_extensions-4.15.0-py3-none-any.whl",url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl",hashes = {sha256 = "f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548"}}, +] +marker = "\"default\" in dependency_groups or \"lint\" in dependency_groups or \"test\" in dependency_groups" + +[packages.tool.pdm] +dependencies = [] + [[packages]] name = "colorama" version = "0.4.6" @@ -558,19 +637,6 @@ dependencies = [ "typing-extensions>=4.6.0; python_version < \"3.13\"", ] -[[packages]] -name = "typing-extensions" -version = "4.15.0" -requires-python = ">=3.9" -sdist = {name = "typing_extensions-4.15.0.tar.gz", url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hashes = {sha256 = "0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466"}} -wheels = [ - {name = "typing_extensions-4.15.0-py3-none-any.whl",url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl",hashes = {sha256 = "f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548"}}, -] -marker = "\"default\" in dependency_groups or \"lint\" in dependency_groups or \"test\" in dependency_groups" - -[packages.tool.pdm] -dependencies = [] - [[packages]] name = "httpx" version = "0.28.1" @@ -885,8 +951,21 @@ marker = "\"types\" in dependency_groups" [packages.tool.pdm] dependencies = [] +[[packages]] +name = "tzdata" +version = "2025.2" +requires-python = ">=2" +sdist = {name = "tzdata-2025.2.tar.gz", url = "https://files.pythonhosted.org/packages/95/32/1a225d6164441be760d75c2c42e2780dc0873fe382da3e98a2e1e48361e5/tzdata-2025.2.tar.gz", hashes = {sha256 = "b60a638fcc0daffadf82fe0f57e53d06bdec2f36c4df66280ae79bce6bd6f2b9"}} +wheels = [ + {name = "tzdata-2025.2-py2.py3-none-any.whl",url = "https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl",hashes = {sha256 = "1a403fada01ff9221ca8044d701868fa132215d84beb92242d9acd2147f667a8"}}, +] +marker = "sys_platform == \"win32\" and \"default\" in dependency_groups" + +[packages.tool.pdm] +dependencies = [] + [tool.pdm] -hashes = {sha256 = "8f79d3a116fb2aeff8263d46e00c1f48dbba637f607c3da738af719783d85afe"} +hashes = {sha256 = "993642e8053f231239acbb7c445ace66bc6705d078056cf64d5ce3729d4d43c3"} strategy = ["inherit_metadata", "static_urls"] [[tool.pdm.targets]] diff --git a/pyproject.toml b/pyproject.toml index 3b10813..e6651d6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,11 +12,12 @@ authors = [ ] dependencies = [ "duckdb>=0.6.1", - "psycopg2>=2.9.5", + "psycopg2-binary>=2.9.5", "tqdm>=4.64.1", "XlsxWriter>=3.0.6", "httpx-folio>=0.2.3", "orjson>=2.2.1", + "psycopg>=3.0", ] requires-python = ">=3.9" readme = "README.md" diff --git a/src/ldlite/__init__.py b/src/ldlite/__init__.py index d1b8dc2..d506d76 100644 --- a/src/ldlite/__init__.py +++ b/src/ldlite/__init__.py @@ -41,6 +41,7 @@ from typing import TYPE_CHECKING, NoReturn, cast import duckdb +import psycopg import psycopg2 from httpx_folio.auth import FolioParams from tqdm import tqdm @@ -122,8 +123,8 @@ def _connect_db_duckdb( self.dbtype = DBType.DUCKDB fn = filename if filename is not None else ":memory:" db = duckdb.connect(database=fn) - self.db = cast("dbapi.DBAPIConnection", db) - return db + self.db = cast("dbapi.DBAPIConnection", db.cursor()) + return db.cursor() def connect_db_postgresql(self, dsn: str) -> psycopg2.extensions.connection: """Connects to a PostgreSQL database for storing data. @@ -132,15 +133,21 @@ def connect_db_postgresql(self, dsn: str) -> psycopg2.extensions.connection: connection to the database which can be used to submit SQL queries. The returned connection defaults to autocommit mode. + This will return a psycopg3 connection in the next major release of LDLite. + Example: db = ld.connect_db_postgresql(dsn='dbname=ld host=localhost user=ldlite') """ self.dbtype = DBType.POSTGRES - db = psycopg2.connect(dsn) + db = psycopg.connect(dsn) self.db = cast("dbapi.DBAPIConnection", db) autocommit(self.db, self.dbtype, True) - return db + + ret_db = psycopg2.connect(dsn) + ret_db.rollback() + ret_db.set_session(autocommit=True) + return ret_db def experimental_connect_db_sqlite( self, @@ -163,9 +170,11 @@ def experimental_connect_db_sqlite( """ self.dbtype = DBType.SQLITE - fn = filename if filename is not None else ":memory:" + fn = filename if filename is not None else "file::memory:?cache=shared" self.db = sqlite3.connect(fn) - autocommit(self.db, self.dbtype, True) + + db = sqlite3.connect(fn) + autocommit(db, self.dbtype, True) return self.db def _check_folio(self) -> None: @@ -466,7 +475,7 @@ def query( # noqa: C901, PLR0912, PLR0913, PLR0915 cur.execute( "CREATE INDEX ON " + sqlid(t) + " (" + sqlid(attr.name) + ")", ) - except (RuntimeError, psycopg2.Error): + except (RuntimeError, psycopg.Error): pass finally: cur.close() diff --git a/src/ldlite/_jsonx.py b/src/ldlite/_jsonx.py index 89a1a04..efce0d3 100644 --- a/src/ldlite/_jsonx.py +++ b/src/ldlite/_jsonx.py @@ -6,7 +6,7 @@ from typing import TYPE_CHECKING, Literal, Union import duckdb -import psycopg2 +import psycopg from tqdm import tqdm from ._camelcase import decode_camel_case @@ -106,12 +106,12 @@ def _old_drop_json_tables(db: dbapi.DBAPIConnection, table: str) -> None: cur2 = db.cursor() try: cur2.execute("DROP TABLE " + sqlid(t)) - except (psycopg2.Error, duckdb.CatalogException, sqlite3.OperationalError): + except (psycopg.Error, duckdb.CatalogException, sqlite3.OperationalError): continue finally: cur2.close() except ( - psycopg2.Error, + psycopg.errors.UndefinedTable, sqlite3.OperationalError, duckdb.CatalogException, ): @@ -122,7 +122,7 @@ def _old_drop_json_tables(db: dbapi.DBAPIConnection, table: str) -> None: try: cur.execute("DROP TABLE " + jtable_sql) except ( - psycopg2.Error, + psycopg.errors.UndefinedTable, duckdb.CatalogException, sqlite3.OperationalError, ): @@ -143,12 +143,16 @@ def drop_json_tables(db: dbapi.DBAPIConnection, table: str) -> None: cur2 = db.cursor() try: cur2.execute("DROP TABLE " + sqlid(t)) - except (psycopg2.Error, duckdb.CatalogException, sqlite3.OperationalError): + except ( + psycopg.errors.UndefinedTable, + duckdb.CatalogException, + sqlite3.OperationalError, + ): continue finally: cur2.close() except ( - psycopg2.Error, + psycopg.errors.UndefinedTable, duckdb.CatalogException, sqlite3.OperationalError, ): @@ -159,7 +163,7 @@ def drop_json_tables(db: dbapi.DBAPIConnection, table: str) -> None: try: cur.execute("DROP TABLE " + tcatalog_sql) except ( - psycopg2.Error, + psycopg.errors.UndefinedTable, duckdb.CatalogException, sqlite3.OperationalError, ): @@ -361,7 +365,7 @@ def _transform_array_data( # noqa: PLR0913 q += "," + str(i + 1) + "," + encode_sql(dbtype, value) + ")" try: cur.execute(q) - except (RuntimeError, psycopg2.Error) as e: + except (RuntimeError, psycopg.Error) as e: raise RuntimeError("error executing SQL: " + q) from e row_ids[table] += 1 @@ -477,7 +481,7 @@ def _transform_data( # noqa: PLR0913 q += ")" try: cur.execute(q) - except (RuntimeError, psycopg2.Error) as e: + except (RuntimeError, psycopg.Error) as e: raise RuntimeError("error executing SQL: " + q) from e row_ids[table] += 1 @@ -656,7 +660,7 @@ def transform_json( # noqa: C901, PLR0912, PLR0913, PLR0915 pbar.close() except ( RuntimeError, - psycopg2.Error, + psycopg.Error, sqlite3.OperationalError, duckdb.CatalogException, ) as e: @@ -684,7 +688,7 @@ def transform_json( # noqa: C901, PLR0912, PLR0913, PLR0915 ) except ( RuntimeError, - psycopg2.Error, + psycopg.Error, sqlite3.OperationalError, duckdb.CatalogException, ) as e: diff --git a/src/ldlite/_sqlx.py b/src/ldlite/_sqlx.py index 562a51f..34615f0 100644 --- a/src/ldlite/_sqlx.py +++ b/src/ldlite/_sqlx.py @@ -8,7 +8,7 @@ import sqlite3 import duckdb - import psycopg2 + import psycopg from _typeshed import dbapi from ._jsonx import JsonValue @@ -34,11 +34,11 @@ def as_duckdb( def as_postgres( db: dbapi.DBAPIConnection, dbtype: DBType, -) -> psycopg2.extensions.connection | None: +) -> psycopg.Connection | None: if dbtype != DBType.POSTGRES: return None - return cast("psycopg2.extensions.connection", db) + return cast("psycopg.Connection", db) def as_sqlite( @@ -63,7 +63,7 @@ def strip_schema(table: str) -> str: def autocommit(db: dbapi.DBAPIConnection, dbtype: DBType, enable: bool) -> None: if (pgdb := as_postgres(db, dbtype)) is not None: pgdb.rollback() - pgdb.set_session(autocommit=enable) + pgdb.set_autocommit(enable) if (sql3db := as_sqlite(db, dbtype)) is not None: sql3db.rollback() From 462a9cef0111acff042b27f86e83b8a745c760aa Mon Sep 17 00:00:00 2001 From: Katherine Bargar Date: Thu, 11 Sep 2025 13:30:16 -0400 Subject: [PATCH 2/7] Abstract ingestion database interactions (#41) LDlite has been playing fast and loose with concatenating sql strings. It's probably fine but psycopg's sql module is intended to make this safe. While refactoring the existing code to implementing the sql module I've abstracted it into a database class intended to be overridden. In the first implementation we're still using the DBType enum and a single implementation, this will change in future into separate postgres and duckdb implementations without the DBType enum. --- CHANGELOG.md | 1 + pylock.maximal.toml | 2 +- pylock.minimal.toml | 54 ++++++++++++++-------- pylock.toml | 2 +- pyproject.toml | 2 +- src/ldlite/__init__.py | 100 +++++++++++++--------------------------- src/ldlite/_database.py | 98 +++++++++++++++++++++++++++++++++++++++ src/ldlite/_sqlx.py | 53 ++++++++++++++------- 8 files changed, 205 insertions(+), 107 deletions(-) create mode 100644 src/ldlite/_database.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 303d86b..42beae1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ Please see [MIGRATING.md](./MIGRATING.md) for information on breaking changes. - psycopg3 is now used for internal operations. LDLite.connect_db_postgres will return a psycopg3 connection instead of psycopg2 in the next major release. - psycopg2 is now installed using the binary version. +- Refactored internal database handling logic ### Removed diff --git a/pylock.maximal.toml b/pylock.maximal.toml index d04a769..171af54 100644 --- a/pylock.maximal.toml +++ b/pylock.maximal.toml @@ -674,7 +674,7 @@ marker = "sys_platform == \"win32\" and python_version >= \"3.13\" and \"default dependencies = [] [tool.pdm] -hashes = {sha256 = "993642e8053f231239acbb7c445ace66bc6705d078056cf64d5ce3729d4d43c3"} +hashes = {sha256 = "9cd31bd06db4ed0781a4f5b9f507c9fb109b9e48b412ee0b76a1115f841b603b"} strategy = ["inherit_metadata", "static_urls"] [[tool.pdm.targets]] diff --git a/pylock.minimal.toml b/pylock.minimal.toml index 66189f3..218eb67 100644 --- a/pylock.minimal.toml +++ b/pylock.minimal.toml @@ -252,18 +252,19 @@ dependencies = [] [[packages]] name = "psycopg" -version = "3.0" -requires-python = ">=3.6" -sdist = {name = "psycopg-3.0.tar.gz", url = "https://files.pythonhosted.org/packages/2a/1d/77fd95c963b8e066de8b49e592e7e912ae53a24275f04a4d5279f67e454a/psycopg-3.0.tar.gz", hashes = {sha256 = "82bce906431f44e81d72fbe0b924a71eebec09189bcf0dd16003e33960e36efa"}} +version = "3.2.0" +requires-python = ">=3.8" +sdist = {name = "psycopg-3.2.0.tar.gz", url = "https://files.pythonhosted.org/packages/10/8e/6b14afef7d09f2769cded48422dbb46699612573092334274e56e4a989b5/psycopg-3.2.0.tar.gz", hashes = {sha256 = "f93c5376598da868a5f761a44f920b84ec937a15a46e85df2454328f13d7009a"}} wheels = [ - {name = "psycopg-3.0-py3-none-any.whl",url = "https://files.pythonhosted.org/packages/d8/31/9ce982570ebb20d092ba4d029ed2702634789bc0cc8deb0c09ba6e22e4db/psycopg-3.0-py3-none-any.whl",hashes = {sha256 = "65b9fb8838dae61040ad3e0cfc184d4ffd17f740ef4c0353d76050a6eb061a9c"}}, + {name = "psycopg-3.2.0-py3-none-any.whl",url = "https://files.pythonhosted.org/packages/e4/72/d7f826af204e7d826e00c5fb28ce6fca7ee4b66f784ea8d9184c5fd0a9e4/psycopg-3.2.0-py3-none-any.whl",hashes = {sha256 = "48a069cfe0ae852a331e1eb48e4da64a1a47723a6303559935801f0e0245ba22"}}, ] marker = "\"default\" in dependency_groups" [packages.tool.pdm] dependencies = [ - "typing-extensions; python_version < \"3.8\"", - "backports-zoneinfo; python_version < \"3.9\"", + "typing-extensions>=4.4", + "backports-zoneinfo>=0.2.0; python_version < \"3.9\"", + "tzdata; sys_platform == \"win32\"", ] [[packages]] @@ -463,6 +464,19 @@ marker = "\"test\" in dependency_groups" [packages.tool.pdm] dependencies = [] +[[packages]] +name = "typing-extensions" +version = "4.15.0" +requires-python = ">=3.9" +sdist = {name = "typing_extensions-4.15.0.tar.gz", url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hashes = {sha256 = "0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466"}} +wheels = [ + {name = "typing_extensions-4.15.0-py3-none-any.whl",url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl",hashes = {sha256 = "f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548"}}, +] +marker = "\"default\" in dependency_groups or \"lint\" in dependency_groups or \"test\" in dependency_groups" + +[packages.tool.pdm] +dependencies = [] + [[packages]] name = "colorama" version = "0.4.6" @@ -491,19 +505,6 @@ dependencies = [ "typing-extensions>=4.6.0; python_version < \"3.13\"", ] -[[packages]] -name = "typing-extensions" -version = "4.15.0" -requires-python = ">=3.9" -sdist = {name = "typing_extensions-4.15.0.tar.gz", url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hashes = {sha256 = "0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466"}} -wheels = [ - {name = "typing_extensions-4.15.0-py3-none-any.whl",url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl",hashes = {sha256 = "f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548"}}, -] -marker = "\"default\" in dependency_groups or \"lint\" in dependency_groups or \"test\" in dependency_groups" - -[packages.tool.pdm] -dependencies = [] - [[packages]] name = "httpx" version = "0.28.1" @@ -846,8 +847,21 @@ dependencies = [ "enum34; python_version < \"3.4\"", ] +[[packages]] +name = "tzdata" +version = "2025.2" +requires-python = ">=2" +sdist = {name = "tzdata-2025.2.tar.gz", url = "https://files.pythonhosted.org/packages/95/32/1a225d6164441be760d75c2c42e2780dc0873fe382da3e98a2e1e48361e5/tzdata-2025.2.tar.gz", hashes = {sha256 = "b60a638fcc0daffadf82fe0f57e53d06bdec2f36c4df66280ae79bce6bd6f2b9"}} +wheels = [ + {name = "tzdata-2025.2-py2.py3-none-any.whl",url = "https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl",hashes = {sha256 = "1a403fada01ff9221ca8044d701868fa132215d84beb92242d9acd2147f667a8"}}, +] +marker = "sys_platform == \"win32\" and \"default\" in dependency_groups" + +[packages.tool.pdm] +dependencies = [] + [tool.pdm] -hashes = {sha256 = "993642e8053f231239acbb7c445ace66bc6705d078056cf64d5ce3729d4d43c3"} +hashes = {sha256 = "9cd31bd06db4ed0781a4f5b9f507c9fb109b9e48b412ee0b76a1115f841b603b"} strategy = ["direct_minimal_versions", "inherit_metadata", "static_urls"] [[tool.pdm.targets]] diff --git a/pylock.toml b/pylock.toml index 7b801c5..513acd0 100644 --- a/pylock.toml +++ b/pylock.toml @@ -965,7 +965,7 @@ marker = "sys_platform == \"win32\" and \"default\" in dependency_groups" dependencies = [] [tool.pdm] -hashes = {sha256 = "993642e8053f231239acbb7c445ace66bc6705d078056cf64d5ce3729d4d43c3"} +hashes = {sha256 = "9cd31bd06db4ed0781a4f5b9f507c9fb109b9e48b412ee0b76a1115f841b603b"} strategy = ["inherit_metadata", "static_urls"] [[tool.pdm.targets]] diff --git a/pyproject.toml b/pyproject.toml index e6651d6..b23a302 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -17,7 +17,7 @@ dependencies = [ "XlsxWriter>=3.0.6", "httpx-folio>=0.2.3", "orjson>=2.2.1", - "psycopg>=3.0", + "psycopg>=3.2.0", ] requires-python = ">=3.9" readme = "README.md" diff --git a/src/ldlite/__init__.py b/src/ldlite/__init__.py index d506d76..61c6081 100644 --- a/src/ldlite/__init__.py +++ b/src/ldlite/__init__.py @@ -38,6 +38,7 @@ import sqlite3 import sys +from itertools import count from typing import TYPE_CHECKING, NoReturn, cast import duckdb @@ -47,10 +48,17 @@ from tqdm import tqdm from ._csv import to_csv +from ._database import Prefix from ._folio import FolioClient from ._jsonx import Attr, drop_json_tables, transform_json from ._select import select -from ._sqlx import DBType, as_postgres, autocommit, encode_sql_str, json_type, sqlid +from ._sqlx import ( + DBType, + DBTypeDatabase, + as_postgres, + autocommit, + sqlid, +) from ._xlsx import to_xlsx if TYPE_CHECKING: @@ -341,21 +349,6 @@ def query( # noqa: C901, PLR0912, PLR0913, PLR0915 drop_json_tables(self.db, table) autocommit(self.db, self.dbtype, False) try: - cur = self.db.cursor() - try: - if len(schema_table) == 2: - cur.execute("CREATE SCHEMA IF NOT EXISTS " + sqlid(schema_table[0])) - cur.execute("DROP TABLE IF EXISTS " + sqlid(table)) - cur.execute( - "CREATE TABLE " - + sqlid(table) - + "(__id integer, jsonb " - + json_type(self.dbtype) - + ")", - ) - finally: - cur.close() - self.db.commit() # First get total number of records records = self._folio.iterate_records( path, @@ -364,62 +357,37 @@ def query( # noqa: C901, PLR0912, PLR0913, PLR0915 self.page_size, query=cast("QueryType", query), ) - (total_records, _) = next(records) - total = total_records if total_records is not None else 0 + total = min(total_records, limit or total_records) if self._verbose: print("ldlite: estimated row count: " + str(total), file=sys.stderr) - # Read result pages + + processed = count(0) pbar = None - pbartotal = 0 if not self._quiet: - if total == -1: - pbar = tqdm( - desc="reading", - leave=False, - mininterval=3, - smoothing=0, - colour="#A9A9A9", - bar_format="{desc} {elapsed} {bar}{postfix}", - ) - else: - pbar = tqdm( - desc="reading", - total=total, - leave=False, - mininterval=3, - smoothing=0, - colour="#A9A9A9", - bar_format="{desc} {bar}{postfix}", - ) + pbar = tqdm( + desc="reading", + total=total, + leave=False, + mininterval=3, + smoothing=0, + colour="#A9A9A9", + bar_format="{desc} {bar}{postfix}", + ) + + def on_processed() -> bool: + if pbar is not None: + pbar.update(1) + p = next(processed) + return limit is None or p >= limit + cur = self.db.cursor() - try: - count = 0 - for pkey, d in records: - cur.execute( - "INSERT INTO " - + sqlid(table) - + " VALUES(" - + str(pkey) - + "," - + encode_sql_str(self.dbtype, d) - + ")", - ) - count += 1 - if pbar is not None: - if pbartotal + 1 > total: - pbartotal = total - pbar.update(total - pbartotal) - else: - pbartotal += 1 - pbar.update(1) - if limit is not None and count == limit: - break - finally: - cur.close() + db = DBTypeDatabase(self.dbtype, self.db) + db.ingest_records(self.db, Prefix(table), on_processed, records) + self.db.commit() if pbar is not None: pbar.close() - self.db.commit() + newtables = [table] newattrs = {} if json_depth > 0: @@ -427,7 +395,7 @@ def query( # noqa: C901, PLR0912, PLR0913, PLR0915 self.db, self.dbtype, table, - count, + next(processed) - 1, self._quiet, json_depth, ) @@ -468,7 +436,6 @@ def query( # noqa: C901, PLR0912, PLR0913, PLR0915 colour="#A9A9A9", bar_format="{desc} {bar}{postfix}", ) - pbartotal = 0 for t, attr in indexable_attrs: cur = self.db.cursor() try: @@ -480,7 +447,6 @@ def query( # noqa: C901, PLR0912, PLR0913, PLR0915 finally: cur.close() if pbar is not None: - pbartotal += 1 pbar.update(1) if pbar is not None: pbar.close() diff --git a/src/ldlite/_database.py b/src/ldlite/_database.py new file mode 100644 index 0000000..f1988de --- /dev/null +++ b/src/ldlite/_database.py @@ -0,0 +1,98 @@ +from __future__ import annotations + +from abc import ABC, abstractmethod +from contextlib import closing +from typing import TYPE_CHECKING, Callable, Generic, TypeVar + +from psycopg import sql + +if TYPE_CHECKING: + from collections.abc import Iterator + + from _typeshed import dbapi + +DB = TypeVar("DB", bound="dbapi.DBAPIConnection") + + +class Prefix: + def __init__(self, table: str): + self._schema: str | None = None + sandt = table.split(".") + if len(sandt) == 1: + (self._prefix,) = sandt + else: + (self._schema, self._prefix) = sandt + + @property + def schema_name(self) -> sql.Identifier | None: + return None if self._schema is None else sql.Identifier(self._schema) + + @property + def raw_table_name(self) -> sql.Identifier: + return ( + sql.Identifier(self._schema, self._prefix) + if self._schema is not None + else sql.Identifier(self._prefix) + ) + + +class Database(ABC, Generic[DB]): + def __init__(self, conn_factory: Callable[[], DB]): + self._conn_factory = conn_factory + + @property + @abstractmethod + def _truncate_raw_table_sql(self) -> sql.SQL: ... + @property + @abstractmethod + def _create_raw_table_sql(self) -> sql.SQL: ... + @property + @abstractmethod + def _insert_record_sql(self) -> sql.SQL: ... + + def _prepare_raw_table( + self, + conn: DB, + prefix: Prefix, + ) -> None: + with closing(conn.cursor()) as cur: + if prefix.schema_name is not None: + cur.execute( + sql.SQL("CREATE SCHEMA IF NOT EXISTS {schema};") + .format(schema=prefix.schema_name) + .as_string(), + ) + + cur.execute( + self._create_raw_table_sql.format( + table=prefix.raw_table_name, + ).as_string(), + ) + cur.execute( + self._truncate_raw_table_sql.format( + table=prefix.raw_table_name, + ).as_string(), + ) + + def ingest_records( + self, + conn: DB, + prefix: Prefix, + on_processed: Callable[[], bool], + records: Iterator[tuple[int, str | bytes]], + ) -> None: + # the only implementation right now is a hack + # the db connection is managed outside of the factory + # for now it's taken as a parameter + # with self._conn_factory() as conn: + self._prepare_raw_table(conn, prefix) + with closing(conn.cursor()) as cur: + for pkey, d in records: + cur.execute( + self._insert_record_sql.format( + table=prefix.raw_table_name, + ).as_string(), + [pkey, d if isinstance(d, str) else d.decode("utf-8")], + ) + if not on_processed(): + return diff --git a/src/ldlite/_sqlx.py b/src/ldlite/_sqlx.py index 34615f0..6277fda 100644 --- a/src/ldlite/_sqlx.py +++ b/src/ldlite/_sqlx.py @@ -4,6 +4,10 @@ from enum import Enum from typing import TYPE_CHECKING, cast +from psycopg import sql + +from ._database import Database + if TYPE_CHECKING: import sqlite3 @@ -21,6 +25,38 @@ class DBType(Enum): SQLITE = 4 +class DBTypeDatabase(Database["dbapi.DBAPIConnection"]): + def __init__(self, dbtype: DBType, db: dbapi.DBAPIConnection): + self._dbtype = dbtype + super().__init__(lambda: db) + + @property + def _create_raw_table_sql(self) -> sql.SQL: + create_sql = "CREATE TABLE IF NOT EXISTS {table} (__id integer, jsonb text);" + if self._dbtype == DBType.POSTGRES: + create_sql = ( + "CREATE TABLE IF NOT EXISTS {table} (__id integer, jsonb jsonb);" + ) + + return sql.SQL(create_sql) + + @property + def _truncate_raw_table_sql(self) -> sql.SQL: + truncate_sql = "TRUNCATE TABLE {table};" + if self._dbtype == DBType.SQLITE: + truncate_sql = "DELETE FROM {table};" + + return sql.SQL(truncate_sql) + + @property + def _insert_record_sql(self) -> sql.SQL: + insert_sql = "INSERT INTO {table} VALUES(?, ?);" + if self._dbtype == DBType.POSTGRES: + insert_sql = "INSERT INTO {table} VALUES(%s, %s);" + + return sql.SQL(insert_sql) + + def as_duckdb( db: dbapi.DBAPIConnection, dbtype: DBType, @@ -51,15 +87,6 @@ def as_sqlite( return cast("sqlite3.Connection", db) -def strip_schema(table: str) -> str: - st = table.split(".") - if len(st) == 1: - return table - if len(st) == 2: - return st[1] - raise ValueError("invalid table name: " + table) - - def autocommit(db: dbapi.DBAPIConnection, dbtype: DBType, enable: bool) -> None: if (pgdb := as_postgres(db, dbtype)) is not None: pgdb.rollback() @@ -101,14 +128,6 @@ def varchar_type(dbtype: DBType) -> str: return "varchar" -def json_type(dbtype: DBType) -> str: - if dbtype == DBType.POSTGRES: - return "jsonb" - if dbtype == DBType.SQLITE: - return "text" - return "varchar" - - def encode_sql_str(dbtype: DBType, s: str | bytes) -> str: # noqa: C901, PLR0912 if isinstance(s, bytes): s = s.decode("utf-8") From 858f307e5cc406b1f05dbbdeee1f38898684b385 Mon Sep 17 00:00:00 2001 From: Katherine Bargar Date: Thu, 11 Sep 2025 13:55:46 -0400 Subject: [PATCH 3/7] Abstract drop_tables database interactions (#42) The drop tables logic was always a little weird and required autocommit transaction hacks for postgres. This refactors the existing drop tables logic into the database abstraction. Any place that is dropping tables also uses the same logic where is was duplicated before. I don't know when the jtable was used but it doesn't hurt to leave it in I guess? This also lets the abstraction manage its own connection as is intended by the abstraction. There is maybe a behavior change here, where a table in the catalog doesn't actually exist in the database. Before that wouldn't cause an error but now it will. I'm ok with this edge case as the behavior wasn't really specified before. Lastly, the on_progress function was doing multiple checks which adds up over a million invocations. Maybe python optimizes these code paths at runtime but it doesn't hurt to do a little less work by default. --- src/ldlite/__init__.py | 98 ++++++++++++++----------- src/ldlite/_database.py | 158 +++++++++++++++++++++++++++++++--------- src/ldlite/_jsonx.py | 83 --------------------- src/ldlite/_sqlx.py | 27 +++++-- 4 files changed, 201 insertions(+), 165 deletions(-) diff --git a/src/ldlite/__init__.py b/src/ldlite/__init__.py index 61c6081..063bea9 100644 --- a/src/ldlite/__init__.py +++ b/src/ldlite/__init__.py @@ -50,7 +50,7 @@ from ._csv import to_csv from ._database import Prefix from ._folio import FolioClient -from ._jsonx import Attr, drop_json_tables, transform_json +from ._jsonx import Attr, transform_json from ._select import select from ._sqlx import ( DBType, @@ -82,6 +82,7 @@ def __init__(self) -> None: self._quiet = False self.dbtype: DBType = DBType.UNDEFINED self.db: dbapi.DBAPIConnection | None = None + self._db: DBTypeDatabase | None = None self._folio: FolioClient | None = None self.page_size = 1000 self._okapi_timeout = 60 @@ -132,6 +133,11 @@ def _connect_db_duckdb( fn = filename if filename is not None else ":memory:" db = duckdb.connect(database=fn) self.db = cast("dbapi.DBAPIConnection", db.cursor()) + self._db = DBTypeDatabase( + DBType.DUCKDB, + lambda: cast("dbapi.DBAPIConnection", db.cursor()), + ) + return db.cursor() def connect_db_postgresql(self, dsn: str) -> psycopg2.extensions.connection: @@ -150,7 +156,10 @@ def connect_db_postgresql(self, dsn: str) -> psycopg2.extensions.connection: self.dbtype = DBType.POSTGRES db = psycopg.connect(dsn) self.db = cast("dbapi.DBAPIConnection", db) - autocommit(self.db, self.dbtype, True) + self._db = DBTypeDatabase( + DBType.POSTGRES, + lambda: cast("dbapi.DBAPIConnection", psycopg.connect(dsn)), + ) ret_db = psycopg2.connect(dsn) ret_db.rollback() @@ -180,6 +189,10 @@ def experimental_connect_db_sqlite( self.dbtype = DBType.SQLITE fn = filename if filename is not None else "file::memory:?cache=shared" self.db = sqlite3.connect(fn) + self._db = DBTypeDatabase( + DBType.SQLITE, + lambda: cast("dbapi.DBAPIConnection", sqlite3.connect(fn)), + ) db = sqlite3.connect(fn) autocommit(db, self.dbtype, True) @@ -223,22 +236,16 @@ def drop_tables(self, table: str) -> None: ld.drop_tables('g') """ - if self.db is None: + if self.db is None or self._db is None: self._check_db() return - autocommit(self.db, self.dbtype, True) schema_table = table.strip().split(".") - if len(schema_table) < 1 or len(schema_table) > 2: + if len(schema_table) != 1 and len(schema_table) != 2: raise ValueError("invalid table name: " + table) - self._check_db() - cur = self.db.cursor() - try: - cur.execute("DROP TABLE IF EXISTS " + sqlid(table)) - except (RuntimeError, psycopg2.Error): - pass - finally: - cur.close() - drop_json_tables(self.db, table) + if len(schema_table) == 2 and self.dbtype == DBType.SQLITE: + table = schema_table[0] + "_" + schema_table[1] + prefix = Prefix(table) + self._db.drop_prefix(prefix) def set_folio_max_retries(self, max_retries: int) -> None: """Sets the maximum number of retries for FOLIO requests. @@ -338,16 +345,14 @@ def query( # noqa: C901, PLR0912, PLR0913, PLR0915 if self._folio is None: self._check_folio() return [] - if self.db is None: + if self.db is None or self._db is None: self._check_db() return [] if len(schema_table) == 2 and self.dbtype == DBType.SQLITE: table = schema_table[0] + "_" + schema_table[1] - schema_table = [table] + prefix = Prefix(table) if not self._quiet: print("ldlite: querying: " + path, file=sys.stderr) - drop_json_tables(self.db, table) - autocommit(self.db, self.dbtype, False) try: # First get total number of records records = self._folio.iterate_records( @@ -362,8 +367,9 @@ def query( # noqa: C901, PLR0912, PLR0913, PLR0915 if self._verbose: print("ldlite: estimated row count: " + str(total), file=sys.stderr) - processed = count(0) - pbar = None + p_count = count(0) + processed = 0 + pbar: tqdm | PbarNoop # type:ignore[type-arg] if not self._quiet: pbar = tqdm( desc="reading", @@ -374,28 +380,43 @@ def query( # noqa: C901, PLR0912, PLR0913, PLR0915 colour="#A9A9A9", bar_format="{desc} {bar}{postfix}", ) + else: + + class PbarNoop: + def update(self, _: int) -> None: ... + def close(self) -> None: ... + + pbar = PbarNoop() def on_processed() -> bool: - if pbar is not None: - pbar.update(1) - p = next(processed) - return limit is None or p >= limit - - cur = self.db.cursor() - db = DBTypeDatabase(self.dbtype, self.db) - db.ingest_records(self.db, Prefix(table), on_processed, records) - self.db.commit() - if pbar is not None: - pbar.close() + pbar.update(1) + nonlocal processed + processed = next(p_count) + return True + + def on_processed_limit() -> bool: + pbar.update(1) + nonlocal processed + processed = next(p_count) + return limit is None or processed >= limit + + self._db.ingest_records( + prefix, + on_processed_limit if limit is not None else on_processed, + records, + ) + pbar.close() + self._db.drop_extracted_tables(prefix) newtables = [table] newattrs = {} if json_depth > 0: + autocommit(self.db, self.dbtype, False) jsontables, jsonattrs = transform_json( self.db, self.dbtype, table, - next(processed) - 1, + processed, self._quiet, json_depth, ) @@ -406,12 +427,7 @@ def on_processed() -> bool: newattrs[table] = {"__id": Attr("__id", "bigint")} if not keep_raw: - cur = self.db.cursor() - try: - cur.execute("DROP TABLE " + sqlid(table)) - self.db.commit() - finally: - cur.close() + self._db.drop_raw_table(prefix) finally: autocommit(self.db, self.dbtype, True) @@ -446,10 +462,8 @@ def on_processed() -> bool: pass finally: cur.close() - if pbar is not None: - pbar.update(1) - if pbar is not None: - pbar.close() + pbar.update(1) + pbar.close() # Return table names if not self._quiet: print("ldlite: created tables: " + ", ".join(newtables), file=sys.stderr) diff --git a/src/ldlite/_database.py b/src/ldlite/_database.py index f1988de..0024e4f 100644 --- a/src/ldlite/_database.py +++ b/src/ldlite/_database.py @@ -2,12 +2,12 @@ from abc import ABC, abstractmethod from contextlib import closing -from typing import TYPE_CHECKING, Callable, Generic, TypeVar +from typing import TYPE_CHECKING, Any, Callable, Generic, TypeVar, cast from psycopg import sql if TYPE_CHECKING: - from collections.abc import Iterator + from collections.abc import Iterator, Sequence from _typeshed import dbapi @@ -27,29 +27,125 @@ def __init__(self, table: str): def schema_name(self) -> sql.Identifier | None: return None if self._schema is None else sql.Identifier(self._schema) + def identifier(self, table: str) -> sql.Identifier: + if self._schema is None: + return sql.Identifier(table) + return sql.Identifier(self._schema, table) + @property def raw_table_name(self) -> sql.Identifier: - return ( - sql.Identifier(self._schema, self._prefix) - if self._schema is not None - else sql.Identifier(self._prefix) - ) + return self.identifier(self._prefix) + + @property + def catalog_table_name(self) -> sql.Identifier: + return self.identifier(f"{self._prefix}__tcatalog") + + @property + def legacy_jtable(self) -> sql.Identifier: + return self.identifier(f"{self._prefix}_jtable") class Database(ABC, Generic[DB]): def __init__(self, conn_factory: Callable[[], DB]): self._conn_factory = conn_factory + @abstractmethod + def _rollback(self, conn: DB) -> None: ... + + def drop_prefix( + self, + prefix: Prefix, + ) -> None: + with closing(self._conn_factory()) as conn: + self._drop_extracted_tables(conn, prefix) + self._drop_raw_table(conn, prefix) + conn.commit() + + def drop_raw_table( + self, + prefix: Prefix, + ) -> None: + with closing(self._conn_factory()) as conn: + self._drop_raw_table(conn, prefix) + conn.commit() + + def _drop_raw_table( + self, + conn: DB, + prefix: Prefix, + ) -> None: + with closing(conn.cursor()) as cur: + cur.execute( + sql.SQL("DROP TABLE IF EXISTS {table};") + .format(table=prefix.raw_table_name) + .as_string(), + ) + + def drop_extracted_tables( + self, + prefix: Prefix, + ) -> None: + with closing(self._conn_factory()) as conn: + self._drop_extracted_tables(conn, prefix) + conn.commit() + @property @abstractmethod - def _truncate_raw_table_sql(self) -> sql.SQL: ... + def _missing_table_error(self) -> tuple[type[Exception], ...]: ... + def _drop_extracted_tables( + self, + conn: DB, + prefix: Prefix, + ) -> None: + tables: list[Sequence[Sequence[Any]]] = [] + with closing(conn.cursor()) as cur: + try: + cur.execute( + sql.SQL("SELECT table_name FROM {catalog};") + .format(catalog=prefix.catalog_table_name) + .as_string(), + ) + except self._missing_table_error: + self._rollback(conn) + else: + tables.extend(cur.fetchall()) + + with closing(conn.cursor()) as cur: + try: + cur.execute( + sql.SQL("SELECT table_name FROM {catalog};") + .format(catalog=prefix.legacy_jtable) + .as_string(), + ) + except self._missing_table_error: + self._rollback(conn) + else: + tables.extend(cur.fetchall()) + + with closing(conn.cursor()) as cur: + for (et,) in tables: + cur.execute( + sql.SQL("DROP TABLE IF EXISTS {table};") + .format(table=sql.Identifier(cast("str", et))) + .as_string(), + ) + cur.execute( + sql.SQL("DROP TABLE IF EXISTS {catalog};") + .format(catalog=prefix.catalog_table_name) + .as_string(), + ) + cur.execute( + sql.SQL("DROP TABLE IF EXISTS {catalog};") + .format(catalog=prefix.legacy_jtable) + .as_string(), + ) + @property @abstractmethod - def _create_raw_table_sql(self) -> sql.SQL: ... + def _truncate_raw_table_sql(self) -> sql.SQL: ... @property @abstractmethod - def _insert_record_sql(self) -> sql.SQL: ... - + def _create_raw_table_sql(self) -> sql.SQL: ... def _prepare_raw_table( self, conn: DB, @@ -62,37 +158,33 @@ def _prepare_raw_table( .format(schema=prefix.schema_name) .as_string(), ) - + self._drop_raw_table(conn, prefix) + with closing(conn.cursor()) as cur: cur.execute( self._create_raw_table_sql.format( table=prefix.raw_table_name, ).as_string(), ) - cur.execute( - self._truncate_raw_table_sql.format( - table=prefix.raw_table_name, - ).as_string(), - ) + @property + @abstractmethod + def _insert_record_sql(self) -> sql.SQL: ... def ingest_records( self, - conn: DB, prefix: Prefix, on_processed: Callable[[], bool], records: Iterator[tuple[int, str | bytes]], ) -> None: - # the only implementation right now is a hack - # the db connection is managed outside of the factory - # for now it's taken as a parameter - # with self._conn_factory() as conn: - self._prepare_raw_table(conn, prefix) - with closing(conn.cursor()) as cur: - for pkey, d in records: - cur.execute( - self._insert_record_sql.format( - table=prefix.raw_table_name, - ).as_string(), - [pkey, d if isinstance(d, str) else d.decode("utf-8")], - ) - if not on_processed(): - return + with closing(self._conn_factory()) as conn: + self._prepare_raw_table(conn, prefix) + with closing(conn.cursor()) as cur: + for pkey, d in records: + cur.execute( + self._insert_record_sql.format( + table=prefix.raw_table_name, + ).as_string(), + [pkey, d if isinstance(d, str) else d.decode("utf-8")], + ) + if not on_processed(): + return + conn.commit() diff --git a/src/ldlite/_jsonx.py b/src/ldlite/_jsonx.py index efce0d3..4983e94 100644 --- a/src/ldlite/_jsonx.py +++ b/src/ldlite/_jsonx.py @@ -86,93 +86,10 @@ def __repr__(self) -> str: ) -def _old_jtable(table: str) -> str: - return table + "_jtable" - - def _tcatalog(table: str) -> str: return table + "__tcatalog" -# noinspection DuplicatedCode -def _old_drop_json_tables(db: dbapi.DBAPIConnection, table: str) -> None: - jtable_sql = sqlid(_old_jtable(table)) - cur = db.cursor() - try: - cur.execute("SELECT table_name FROM " + jtable_sql) - rows = list(cur.fetchall()) - for row in rows: - t = row[0] - cur2 = db.cursor() - try: - cur2.execute("DROP TABLE " + sqlid(t)) - except (psycopg.Error, duckdb.CatalogException, sqlite3.OperationalError): - continue - finally: - cur2.close() - except ( - psycopg.errors.UndefinedTable, - sqlite3.OperationalError, - duckdb.CatalogException, - ): - pass - finally: - cur.close() - cur = db.cursor() - try: - cur.execute("DROP TABLE " + jtable_sql) - except ( - psycopg.errors.UndefinedTable, - duckdb.CatalogException, - sqlite3.OperationalError, - ): - pass - finally: - cur.close() - - -# noinspection DuplicatedCode -def drop_json_tables(db: dbapi.DBAPIConnection, table: str) -> None: - tcatalog_sql = sqlid(_tcatalog(table)) - cur = db.cursor() - try: - cur.execute("SELECT table_name FROM " + tcatalog_sql) - rows = list(cur.fetchall()) - for row in rows: - t = row[0] - cur2 = db.cursor() - try: - cur2.execute("DROP TABLE " + sqlid(t)) - except ( - psycopg.errors.UndefinedTable, - duckdb.CatalogException, - sqlite3.OperationalError, - ): - continue - finally: - cur2.close() - except ( - psycopg.errors.UndefinedTable, - duckdb.CatalogException, - sqlite3.OperationalError, - ): - pass - finally: - cur.close() - cur = db.cursor() - try: - cur.execute("DROP TABLE " + tcatalog_sql) - except ( - psycopg.errors.UndefinedTable, - duckdb.CatalogException, - sqlite3.OperationalError, - ): - pass - finally: - cur.close() - _old_drop_json_tables(db, table) - - def _table_name(parents: list[tuple[int, str]]) -> str: j = len(parents) while j > 0 and parents[j - 1][0] == 0: diff --git a/src/ldlite/_sqlx.py b/src/ldlite/_sqlx.py index 6277fda..e87a38f 100644 --- a/src/ldlite/_sqlx.py +++ b/src/ldlite/_sqlx.py @@ -1,18 +1,17 @@ from __future__ import annotations import secrets +import sqlite3 from enum import Enum -from typing import TYPE_CHECKING, cast +from typing import TYPE_CHECKING, Callable, cast +import duckdb +import psycopg from psycopg import sql from ._database import Database if TYPE_CHECKING: - import sqlite3 - - import duckdb - import psycopg from _typeshed import dbapi from ._jsonx import JsonValue @@ -26,9 +25,23 @@ class DBType(Enum): class DBTypeDatabase(Database["dbapi.DBAPIConnection"]): - def __init__(self, dbtype: DBType, db: dbapi.DBAPIConnection): + def __init__(self, dbtype: DBType, factory: Callable[[], dbapi.DBAPIConnection]): self._dbtype = dbtype - super().__init__(lambda: db) + super().__init__(factory) + + @property + def _missing_table_error(self) -> tuple[type[Exception], ...]: + return ( + psycopg.errors.UndefinedTable, + sqlite3.OperationalError, + duckdb.CatalogException, + ) + + def _rollback(self, conn: dbapi.DBAPIConnection) -> None: + if sql3db := as_sqlite(conn, self._dbtype): + sql3db.rollback() + if pgdb := as_postgres(conn, self._dbtype): + pgdb.rollback() @property def _create_raw_table_sql(self) -> sql.SQL: From 63e9c88cc97e0f157c618df5a6c4d17c608d39bf Mon Sep 17 00:00:00 2001 From: Katherine Bargar Date: Wed, 17 Sep 2025 09:48:20 -0400 Subject: [PATCH 4/7] Switch from INSERT INTO to COPY FROM for postgres (#43) Using COPY FROM is an order of magnitude faster for bulk insertions for postgres. This is the last low-hanging fruit for download performance until async/await is supported and optimized for in a future future release. DuckDB can kind of sort of do bulk operations but it isn't a priority right now, especially with sqlite still supported in this release. Getting bytes do go directly into the database was a bit of a struggle but postgres expects a "version" byte at the beginning of any record. By doing the byte munging ourselves we skip a whole lot of conversion in ldlite and conversion/logic in psycopg which would have added points of failure and slowness. I'm going to be testing to make sure it works across 5C FOLIO data before releasing the change. --- CHANGELOG.md | 1 + src/ldlite/__init__.py | 6 +++-- src/ldlite/_database.py | 33 ++++++++++++++++++-------- src/ldlite/_folio.py | 6 ++--- src/ldlite/_sqlx.py | 52 +++++++++++++++++++++++++++++++++++++++++ 5 files changed, 83 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 42beae1..494909c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ Please see [MIGRATING.md](./MIGRATING.md) for information on breaking changes. - psycopg3 is now used for internal operations. LDLite.connect_db_postgres will return a psycopg3 connection instead of psycopg2 in the next major release. - psycopg2 is now installed using the binary version. - Refactored internal database handling logic +- Ingesting data into postgres now uses COPY FROM which significantly improves the download performance. ### Removed diff --git a/src/ldlite/__init__.py b/src/ldlite/__init__.py index 063bea9..59381f4 100644 --- a/src/ldlite/__init__.py +++ b/src/ldlite/__init__.py @@ -62,6 +62,8 @@ from ._xlsx import to_xlsx if TYPE_CHECKING: + from collections.abc import Iterator + from _typeshed import dbapi from httpx_folio.query import QueryType @@ -362,7 +364,7 @@ def query( # noqa: C901, PLR0912, PLR0913, PLR0915 self.page_size, query=cast("QueryType", query), ) - (total_records, _) = next(records) + total_records = cast("int", next(records)) total = min(total_records, limit or total_records) if self._verbose: print("ldlite: estimated row count: " + str(total), file=sys.stderr) @@ -403,7 +405,7 @@ def on_processed_limit() -> bool: self._db.ingest_records( prefix, on_processed_limit if limit is not None else on_processed, - records, + cast("Iterator[tuple[bytes, bytes] | tuple[int, str]]", records), ) pbar.close() diff --git a/src/ldlite/_database.py b/src/ldlite/_database.py index 0024e4f..a18a0bf 100644 --- a/src/ldlite/_database.py +++ b/src/ldlite/_database.py @@ -173,18 +173,31 @@ def ingest_records( self, prefix: Prefix, on_processed: Callable[[], bool], - records: Iterator[tuple[int, str | bytes]], + records: Iterator[tuple[bytes, bytes] | tuple[int, str]], ) -> None: with closing(self._conn_factory()) as conn: self._prepare_raw_table(conn, prefix) + insert_sql = self._insert_record_sql.format( + table=prefix.raw_table_name, + ).as_string() with closing(conn.cursor()) as cur: - for pkey, d in records: - cur.execute( - self._insert_record_sql.format( - table=prefix.raw_table_name, - ).as_string(), - [pkey, d if isinstance(d, str) else d.decode("utf-8")], - ) - if not on_processed(): - return + fr = next(records) + if isinstance(fr[0], bytes): + record = fr + while record is not None: + (pkey, rb) = record + cur.execute( + insert_sql, + (int.from_bytes(pkey, "big"), rb.decode()), + ) + if not on_processed(): + break + record = cast("tuple[bytes, bytes]", next(records, None)) + else: + cur.execute(insert_sql, fr) + for r in records: + cur.execute(insert_sql, r) + if not on_processed(): + break + conn.commit() diff --git a/src/ldlite/_folio.py b/src/ldlite/_folio.py index cea5d7d..b6e4cd4 100644 --- a/src/ldlite/_folio.py +++ b/src/ldlite/_folio.py @@ -28,7 +28,7 @@ def iterate_records( retries: int, page_size: int, query: QueryType | None = None, - ) -> Iterator[tuple[int, str | bytes]]: + ) -> Iterator[int | tuple[bytes, bytes] | tuple[int, str]]: """Iterates all records for a given path. Returns: @@ -54,7 +54,7 @@ def iterate_records( res.raise_for_status() j = orjson.loads(res.text) r = int(j["totalRecords"]) - yield (r, b"") + yield r if r == 0: return @@ -103,7 +103,7 @@ def iterate_records( last = None for r in (o for o in orjson.loads(res.text)[key] if o is not None): last = r - yield (next(pkey), orjson.dumps(r)) + yield (next(pkey).to_bytes(4, "big"), orjson.dumps(r)) if last is None: return diff --git a/src/ldlite/_sqlx.py b/src/ldlite/_sqlx.py index e87a38f..18743d1 100644 --- a/src/ldlite/_sqlx.py +++ b/src/ldlite/_sqlx.py @@ -2,6 +2,7 @@ import secrets import sqlite3 +from contextlib import closing from enum import Enum from typing import TYPE_CHECKING, Callable, cast @@ -12,8 +13,11 @@ from ._database import Database if TYPE_CHECKING: + from collections.abc import Iterator + from _typeshed import dbapi + from ._database import Prefix from ._jsonx import JsonValue @@ -69,6 +73,54 @@ def _insert_record_sql(self) -> sql.SQL: return sql.SQL(insert_sql) + def ingest_records( + self, + prefix: Prefix, + on_processed: Callable[[], bool], + records: Iterator[tuple[bytes, bytes] | tuple[int, str]], + ) -> None: + if self._dbtype != DBType.POSTGRES: + super().ingest_records(prefix, on_processed, records) + return + + with closing(self._conn_factory()) as conn: + self._prepare_raw_table(conn, prefix) + + fr = next(records) + copy_from = "COPY {table} (__id, jsonb) FROM STDIN" + if is_bytes := isinstance(fr[0], bytes): + copy_from += " (FORMAT BINARY)" + + if pgconn := as_postgres(conn, self._dbtype): + with ( + pgconn.cursor() as cur, + cur.copy( + sql.SQL(copy_from).format(table=prefix.raw_table_name), + ) as copy, + ): + if is_bytes: + # postgres jsonb is always version 1 + # and it always goes in front + jver = (1).to_bytes(1, "big") + record = fr + while record is not None: + pkey, rb = record + rbpg = bytearray() + rbpg.extend(jver) + rbpg.extend(cast("bytes", rb)) + copy.write_row((pkey, rbpg)) + if not on_processed(): + break + record = cast("tuple[bytes, bytes]", next(records, None)) + else: + copy.write_row(fr) + for r in records: + copy.write_row(r) + if not on_processed(): + break + + pgconn.commit() + def as_duckdb( db: dbapi.DBAPIConnection, From e9108de11aec58594f22232627a5aa2dc6a34363 Mon Sep 17 00:00:00 2001 From: Katherine Bargar Date: Thu, 18 Sep 2025 10:32:08 -0400 Subject: [PATCH 5/7] Increase consistency of behavior around streaming srs endpoints (#44) I wasn't super happy with the weird type shenanigans happening in the last MR #43 but thought that doing a loads/dumps on all the source records to keep it consistent would be too slow. I discovered orjson.Fragment which means we can just use dumps and treat both srs and non-srs as bytes. This simplified the signatures and type handling quite a bit. Streaming support for SRS was rushed because non-streaming became even more unstable under Ramsons. There was a chance (though probably small) that someone was loading source-storage endpoints that weren't the source-records one and they would have broken. This adds more consistency around which endpoints we do support with streaming and fixes any accidental breaks. --- CHANGELOG.md | 2 + pylock.maximal.toml | 58 ++++++++++---------- pylock.minimal.toml | 118 ++++++++++++++++++++++++---------------- pylock.toml | 114 +++++++++++++++++++------------------- pyproject.toml | 4 +- src/ldlite/__init__.py | 6 +- src/ldlite/_database.py | 25 ++------- src/ldlite/_folio.py | 34 ++++++++---- src/ldlite/_sqlx.py | 41 +++++--------- tests/test_endtoend.py | 12 +++- 10 files changed, 217 insertions(+), 197 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 494909c..2373343 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,8 @@ Please see [MIGRATING.md](./MIGRATING.md) for information on breaking changes. ### Fixed +- Source Storage endpoints now stream only if streaming is available. + ### Changed - psycopg3 is now used for internal operations. LDLite.connect_db_postgres will return a psycopg3 connection instead of psycopg2 in the next major release. diff --git a/pylock.maximal.toml b/pylock.maximal.toml index 171af54..ba50b76 100644 --- a/pylock.maximal.toml +++ b/pylock.maximal.toml @@ -79,6 +79,25 @@ marker = "python_version >= \"3.13\" and \"lint\" in dependency_groups" [packages.tool.pdm] dependencies = [] +[[packages]] +name = "duckdb" +version = "1.3.2" +requires-python = ">=3.7.0" +sdist = {name = "duckdb-1.3.2.tar.gz", url = "https://files.pythonhosted.org/packages/47/24/a2e7fb78fba577641c286fe33185789ab1e1569ccdf4d142e005995991d2/duckdb-1.3.2.tar.gz", hashes = {sha256 = "c658df8a1bc78704f702ad0d954d82a1edd4518d7a04f00027ec53e40f591ff5"}} +wheels = [ + {name = "duckdb-1.3.2-cp313-cp313-macosx_12_0_arm64.whl",url = "https://files.pythonhosted.org/packages/f5/f0/8cac9713735864899e8abc4065bbdb3d1617f2130006d508a80e1b1a6c53/duckdb-1.3.2-cp313-cp313-macosx_12_0_arm64.whl",hashes = {sha256 = "a3418c973b06ac4e97f178f803e032c30c9a9f56a3e3b43a866f33223dfbf60b"}}, + {name = "duckdb-1.3.2-cp313-cp313-macosx_12_0_universal2.whl",url = "https://files.pythonhosted.org/packages/c5/26/6698bbb30b7bce8b8b17697599f1517611c61e4bd68b37eaeaf4f5ddd915/duckdb-1.3.2-cp313-cp313-macosx_12_0_universal2.whl",hashes = {sha256 = "2a741eae2cf110fd2223eeebe4151e22c0c02803e1cfac6880dbe8a39fecab6a"}}, + {name = "duckdb-1.3.2-cp313-cp313-macosx_12_0_x86_64.whl",url = "https://files.pythonhosted.org/packages/10/75/8ab4da3099a2fac7335ecebce4246706d19bdd5dad167aa436b5b27c43c4/duckdb-1.3.2-cp313-cp313-macosx_12_0_x86_64.whl",hashes = {sha256 = "51e62541341ea1a9e31f0f1ade2496a39b742caf513bebd52396f42ddd6525a0"}}, + {name = "duckdb-1.3.2-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl",url = "https://files.pythonhosted.org/packages/d1/46/af81b10d4a66a0f27c248df296d1b41ff2a305a235ed8488f93240f6f8b5/duckdb-1.3.2-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl",hashes = {sha256 = "b3e519de5640e5671f1731b3ae6b496e0ed7e4de4a1c25c7a2f34c991ab64d71"}}, + {name = "duckdb-1.3.2-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl",url = "https://files.pythonhosted.org/packages/68/fc/259a54fc22111a847981927aa58528d766e8b228c6d41deb0ad8a1959f9f/duckdb-1.3.2-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl",hashes = {sha256 = "4732fb8cc60566b60e7e53b8c19972cb5ed12d285147a3063b16cc64a79f6d9f"}}, + {name = "duckdb-1.3.2-cp313-cp313-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/ab/dc/5d5140383e40661173dacdceaddee2a97c3f6721a5e8d76e08258110595e/duckdb-1.3.2-cp313-cp313-musllinux_1_2_x86_64.whl",hashes = {sha256 = "97f7a22dcaa1cca889d12c3dc43a999468375cdb6f6fe56edf840e062d4a8293"}}, + {name = "duckdb-1.3.2-cp313-cp313-win_amd64.whl",url = "https://files.pythonhosted.org/packages/51/c9/2fcd86ab7530a5b6caff42dbe516ce7a86277e12c499d1c1f5acd266ffb2/duckdb-1.3.2-cp313-cp313-win_amd64.whl",hashes = {sha256 = "cd3d717bf9c49ef4b1016c2216517572258fa645c2923e91c5234053defa3fb5"}}, +] +marker = "python_version >= \"3.13\" and \"default\" in dependency_groups" + +[packages.tool.pdm] +dependencies = [] + [[packages]] name = "coverage" version = "7.10.6" @@ -136,25 +155,6 @@ marker = "python_version >= \"3.13\" and \"test\" in dependency_groups" [packages.tool.pdm] dependencies = [] -[[packages]] -name = "duckdb" -version = "1.3.2" -requires-python = ">=3.7.0" -sdist = {name = "duckdb-1.3.2.tar.gz", url = "https://files.pythonhosted.org/packages/47/24/a2e7fb78fba577641c286fe33185789ab1e1569ccdf4d142e005995991d2/duckdb-1.3.2.tar.gz", hashes = {sha256 = "c658df8a1bc78704f702ad0d954d82a1edd4518d7a04f00027ec53e40f591ff5"}} -wheels = [ - {name = "duckdb-1.3.2-cp313-cp313-macosx_12_0_arm64.whl",url = "https://files.pythonhosted.org/packages/f5/f0/8cac9713735864899e8abc4065bbdb3d1617f2130006d508a80e1b1a6c53/duckdb-1.3.2-cp313-cp313-macosx_12_0_arm64.whl",hashes = {sha256 = "a3418c973b06ac4e97f178f803e032c30c9a9f56a3e3b43a866f33223dfbf60b"}}, - {name = "duckdb-1.3.2-cp313-cp313-macosx_12_0_universal2.whl",url = "https://files.pythonhosted.org/packages/c5/26/6698bbb30b7bce8b8b17697599f1517611c61e4bd68b37eaeaf4f5ddd915/duckdb-1.3.2-cp313-cp313-macosx_12_0_universal2.whl",hashes = {sha256 = "2a741eae2cf110fd2223eeebe4151e22c0c02803e1cfac6880dbe8a39fecab6a"}}, - {name = "duckdb-1.3.2-cp313-cp313-macosx_12_0_x86_64.whl",url = "https://files.pythonhosted.org/packages/10/75/8ab4da3099a2fac7335ecebce4246706d19bdd5dad167aa436b5b27c43c4/duckdb-1.3.2-cp313-cp313-macosx_12_0_x86_64.whl",hashes = {sha256 = "51e62541341ea1a9e31f0f1ade2496a39b742caf513bebd52396f42ddd6525a0"}}, - {name = "duckdb-1.3.2-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl",url = "https://files.pythonhosted.org/packages/d1/46/af81b10d4a66a0f27c248df296d1b41ff2a305a235ed8488f93240f6f8b5/duckdb-1.3.2-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl",hashes = {sha256 = "b3e519de5640e5671f1731b3ae6b496e0ed7e4de4a1c25c7a2f34c991ab64d71"}}, - {name = "duckdb-1.3.2-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl",url = "https://files.pythonhosted.org/packages/68/fc/259a54fc22111a847981927aa58528d766e8b228c6d41deb0ad8a1959f9f/duckdb-1.3.2-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl",hashes = {sha256 = "4732fb8cc60566b60e7e53b8c19972cb5ed12d285147a3063b16cc64a79f6d9f"}}, - {name = "duckdb-1.3.2-cp313-cp313-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/ab/dc/5d5140383e40661173dacdceaddee2a97c3f6721a5e8d76e08258110595e/duckdb-1.3.2-cp313-cp313-musllinux_1_2_x86_64.whl",hashes = {sha256 = "97f7a22dcaa1cca889d12c3dc43a999468375cdb6f6fe56edf840e062d4a8293"}}, - {name = "duckdb-1.3.2-cp313-cp313-win_amd64.whl",url = "https://files.pythonhosted.org/packages/51/c9/2fcd86ab7530a5b6caff42dbe516ce7a86277e12c499d1c1f5acd266ffb2/duckdb-1.3.2-cp313-cp313-win_amd64.whl",hashes = {sha256 = "cd3d717bf9c49ef4b1016c2216517572258fa645c2923e91c5234053defa3fb5"}}, -] -marker = "python_version >= \"3.13\" and \"default\" in dependency_groups" - -[packages.tool.pdm] -dependencies = [] - [[packages]] name = "httpx-folio" version = "0.2.3" @@ -304,11 +304,11 @@ dependencies = [ [[packages]] name = "types-psycopg2" -version = "2.9.21.20250809" +version = "2.9.21.20250915" requires-python = ">=3.9" -sdist = {name = "types_psycopg2-2.9.21.20250809.tar.gz", url = "https://files.pythonhosted.org/packages/17/d0/66f3f04bab48bfdb2c8b795b2b3e75eb20c7d1fb0516916db3be6aa4a683/types_psycopg2-2.9.21.20250809.tar.gz", hashes = {sha256 = "b7c2cbdcf7c0bd16240f59ba694347329b0463e43398de69784ea4dee45f3c6d"}} +sdist = {name = "types_psycopg2-2.9.21.20250915.tar.gz", url = "https://files.pythonhosted.org/packages/8f/20/3dcb89df8d1661cf6c4c2d9f84d4ba94dde48559cdcf7b536a380a9c387f/types_psycopg2-2.9.21.20250915.tar.gz", hashes = {sha256 = "bfeb8f54c32490e7b5edc46215ab4163693192bc90407b4a023822de9239f5c8"}} wheels = [ - {name = "types_psycopg2-2.9.21.20250809-py3-none-any.whl",url = "https://files.pythonhosted.org/packages/7b/98/182497602921c47fadc8470d51a32e5c75343c8931c0b572a5c4ae3b948b/types_psycopg2-2.9.21.20250809-py3-none-any.whl",hashes = {sha256 = "59b7b0ed56dcae9efae62b8373497274fc1a0484bdc5135cdacbe5a8f44e1d7b"}}, + {name = "types_psycopg2-2.9.21.20250915-py3-none-any.whl",url = "https://files.pythonhosted.org/packages/93/4d/ebf1c72809a30150ad142074e1ad5101304f7569c0df2fa872906d76d0af/types_psycopg2-2.9.21.20250915-py3-none-any.whl",hashes = {sha256 = "eefe5ccdc693fc086146e84c9ba437bb278efe1ef330b299a0cb71169dc6c55f"}}, ] marker = "python_version >= \"3.13\" and \"types\" in dependency_groups" @@ -332,11 +332,11 @@ dependencies = [ [[packages]] name = "xlsxwriter" -version = "3.2.5" +version = "3.2.9" requires-python = ">=3.8" -sdist = {name = "xlsxwriter-3.2.5.tar.gz", url = "https://files.pythonhosted.org/packages/a7/47/7704bac42ac6fe1710ae099b70e6a1e68ed173ef14792b647808c357da43/xlsxwriter-3.2.5.tar.gz", hashes = {sha256 = "7e88469d607cdc920151c0ab3ce9cf1a83992d4b7bc730c5ffdd1a12115a7dbe"}} +sdist = {name = "xlsxwriter-3.2.9.tar.gz", url = "https://files.pythonhosted.org/packages/46/2c/c06ef49dc36e7954e55b802a8b231770d286a9758b3d936bd1e04ce5ba88/xlsxwriter-3.2.9.tar.gz", hashes = {sha256 = "254b1c37a368c444eac6e2f867405cc9e461b0ed97a3233b2ac1e574efb4140c"}} wheels = [ - {name = "xlsxwriter-3.2.5-py3-none-any.whl",url = "https://files.pythonhosted.org/packages/fa/34/a22e6664211f0c8879521328000bdcae9bf6dbafa94a923e531f6d5b3f73/xlsxwriter-3.2.5-py3-none-any.whl",hashes = {sha256 = "4f4824234e1eaf9d95df9a8fe974585ff91d0f5e3d3f12ace5b71e443c1c6abd"}}, + {name = "xlsxwriter-3.2.9-py3-none-any.whl",url = "https://files.pythonhosted.org/packages/3a/0c/3662f4a66880196a590b202f0db82d919dd2f89e99a27fadef91c4a33d41/xlsxwriter-3.2.9-py3-none-any.whl",hashes = {sha256 = "9a5db42bc5dff014806c58a20b9eae7322a134abb6fce3c92c181bfb275ec5b3"}}, ] marker = "python_version >= \"3.13\" and \"default\" in dependency_groups" @@ -634,11 +634,11 @@ dependencies = [ [[packages]] name = "types-requests" -version = "2.32.4.20250809" +version = "2.32.4.20250913" requires-python = ">=3.9" -sdist = {name = "types_requests-2.32.4.20250809.tar.gz", url = "https://files.pythonhosted.org/packages/ed/b0/9355adb86ec84d057fea765e4c49cce592aaf3d5117ce5609a95a7fc3dac/types_requests-2.32.4.20250809.tar.gz", hashes = {sha256 = "d8060de1c8ee599311f56ff58010fb4902f462a1470802cf9f6ed27bc46c4df3"}} +sdist = {name = "types_requests-2.32.4.20250913.tar.gz", url = "https://files.pythonhosted.org/packages/36/27/489922f4505975b11de2b5ad07b4fe1dca0bca9be81a703f26c5f3acfce5/types_requests-2.32.4.20250913.tar.gz", hashes = {sha256 = "abd6d4f9ce3a9383f269775a9835a4c24e5cd6b9f647d64f88aa4613c33def5d"}} wheels = [ - {name = "types_requests-2.32.4.20250809-py3-none-any.whl",url = "https://files.pythonhosted.org/packages/2b/6f/ec0012be842b1d888d46884ac5558fd62aeae1f0ec4f7a581433d890d4b5/types_requests-2.32.4.20250809-py3-none-any.whl",hashes = {sha256 = "f73d1832fb519ece02c85b1f09d5f0dd3108938e7d47e7f94bbfa18a6782b163"}}, + {name = "types_requests-2.32.4.20250913-py3-none-any.whl",url = "https://files.pythonhosted.org/packages/2a/20/9a227ea57c1285986c4cf78400d0a91615d25b24e257fd9e2969606bdfae/types_requests-2.32.4.20250913-py3-none-any.whl",hashes = {sha256 = "78c9c1fffebbe0fa487a418e0fa5252017e9c60d1a2da394077f1780f655d7e1"}}, ] marker = "python_version >= \"3.13\" and \"types\" in dependency_groups" @@ -674,7 +674,7 @@ marker = "sys_platform == \"win32\" and python_version >= \"3.13\" and \"default dependencies = [] [tool.pdm] -hashes = {sha256 = "9cd31bd06db4ed0781a4f5b9f507c9fb109b9e48b412ee0b76a1115f841b603b"} +hashes = {sha256 = "9edcf2127050cd04b1c55f2c5d0ef0c306790635417a4e7c2a0ed6e601c76e78"} strategy = ["inherit_metadata", "static_urls"] [[tool.pdm.targets]] diff --git a/pylock.minimal.toml b/pylock.minimal.toml index 218eb67..893d114 100644 --- a/pylock.minimal.toml +++ b/pylock.minimal.toml @@ -103,6 +103,46 @@ marker = "\"lint\" in dependency_groups" [packages.tool.pdm] dependencies = [] +[[packages]] +name = "duckdb" +version = "0.6.1" +sdist = {name = "duckdb-0.6.1.tar.gz", url = "https://files.pythonhosted.org/packages/48/80/0dee966cde56d2e1b3ea8e3389b2e93288d662bc0b85b6b0508f3151f3c0/duckdb-0.6.1.tar.gz", hashes = {sha256 = "6d26e9f1afcb924a6057785e506810d48332d4764ddc4a5b414d0f2bf0cacfb4"}} +wheels = [ + {name = "duckdb-0.6.1-cp311-cp311-macosx_10_9_universal2.whl",url = "https://files.pythonhosted.org/packages/2e/d7/0320924f9e817442afad06a5e428e8f5fb55b6cb20bfd2b0be4b3975ec08/duckdb-0.6.1-cp311-cp311-macosx_10_9_universal2.whl",hashes = {sha256 = "b4bbe2f6c1b109c626f9318eee80934ad2a5b81a51409c6b5083c6c5f9bdb125"}}, + {name = "duckdb-0.6.1-cp311-cp311-macosx_10_9_x86_64.whl",url = "https://files.pythonhosted.org/packages/79/a7/c01998a05ed41fd3bf124079de9508ee6002e5b120a4ba353817535eaa7a/duckdb-0.6.1-cp311-cp311-macosx_10_9_x86_64.whl",hashes = {sha256 = "cfea36b58928ce778d17280d4fb3bf0a2d7cff407667baedd69c5b41463ac0fd"}}, + {name = "duckdb-0.6.1-cp311-cp311-macosx_11_0_arm64.whl",url = "https://files.pythonhosted.org/packages/bc/1b/a0b13a6e985ff1024e0d52b372f43e385ce1538773ecac250e82543572e2/duckdb-0.6.1-cp311-cp311-macosx_11_0_arm64.whl",hashes = {sha256 = "0b64eb53d0d0695814bf1b65c0f91ab7ed66b515f89c88038f65ad5e0762571c"}}, + {name = "duckdb-0.6.1-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl",url = "https://files.pythonhosted.org/packages/1e/5e/8b7472fdaedce2a3eed50da24335bfa81ca5dbca66d6b684a1646ab52727/duckdb-0.6.1-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl",hashes = {sha256 = "35b01bc724e1933293f4c34f410d2833bfbb56d5743b515d805bbfed0651476e"}}, + {name = "duckdb-0.6.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",url = "https://files.pythonhosted.org/packages/09/4d/5baca267898d4d290a9dabe3d6d023cba38fb1774234f7fa4cc069a02638/duckdb-0.6.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",hashes = {sha256 = "fec2c2466654ce786843bda2bfba71e0e4719106b41d36b17ceb1901e130aa71"}}, + {name = "duckdb-0.6.1-cp311-cp311-musllinux_1_1_i686.whl",url = "https://files.pythonhosted.org/packages/55/d9/d80159f4304e351db0459a16e9c8d139c87a495300007cc105b75452c30f/duckdb-0.6.1-cp311-cp311-musllinux_1_1_i686.whl",hashes = {sha256 = "82cd30f5cf368658ef879b1c60276bc8650cf67cfe3dc3e3009438ba39251333"}}, + {name = "duckdb-0.6.1-cp311-cp311-musllinux_1_1_x86_64.whl",url = "https://files.pythonhosted.org/packages/5c/ad/1186693bc6d73f5ba1ac4e13772ba57abfcc970aa03ccdfb8c7405b039f1/duckdb-0.6.1-cp311-cp311-musllinux_1_1_x86_64.whl",hashes = {sha256 = "a782bbfb7f5e97d4a9c834c9e78f023fb8b3f6687c22ca99841e6ed944b724da"}}, + {name = "duckdb-0.6.1-cp311-cp311-win32.whl",url = "https://files.pythonhosted.org/packages/8a/54/4210ce0b26a010fd2755f8d78dcaeceeb4d1ddda76d2e884c0b130fae6dc/duckdb-0.6.1-cp311-cp311-win32.whl",hashes = {sha256 = "e3702d4a9ade54c6403f6615a98bbec2020a76a60f5db7fcf085df1bd270e66e"}}, + {name = "duckdb-0.6.1-cp311-cp311-win_amd64.whl",url = "https://files.pythonhosted.org/packages/78/cc/508a045cb18164d0890eb09f38bc7f8346aa03c387217c71aeeab1be61ec/duckdb-0.6.1-cp311-cp311-win_amd64.whl",hashes = {sha256 = "93b074f473d68c944b0eeb2edcafd91ad11da8432b484836efaaab4e26351d48"}}, + {name = "duckdb-0.6.1-cp310-cp310-macosx_10_9_universal2.whl",url = "https://files.pythonhosted.org/packages/94/68/c633dcd576dd63e627619a6274c17b6ddc8e2637fa9b037a5241b687c40b/duckdb-0.6.1-cp310-cp310-macosx_10_9_universal2.whl",hashes = {sha256 = "e566514f9327f89264e98ac14ee7a84fbd9857328028258422c3e8375ee19d25"}}, + {name = "duckdb-0.6.1-cp310-cp310-macosx_10_9_x86_64.whl",url = "https://files.pythonhosted.org/packages/c5/ef/ff74822cbd8792138edad7da762fc1d371723761fe9362a44df85b09774c/duckdb-0.6.1-cp310-cp310-macosx_10_9_x86_64.whl",hashes = {sha256 = "b31c2883de5b19591a2852165e6b3f9821f77af649835f27bc146b26e4aa30cb"}}, + {name = "duckdb-0.6.1-cp310-cp310-macosx_11_0_arm64.whl",url = "https://files.pythonhosted.org/packages/98/74/069f636ba410c7055847cd202bb8fb7e8ea1b0e0a2ae6b131ba40e0d4322/duckdb-0.6.1-cp310-cp310-macosx_11_0_arm64.whl",hashes = {sha256 = "998165b2fb1f1d2b0ad742096015ea70878f7d40304643c7424c3ed3ddf07bfc"}}, + {name = "duckdb-0.6.1-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl",url = "https://files.pythonhosted.org/packages/a2/02/a36ca6545c81cb6ec339fcff178b931b0079cdd9d39806da2d440dcdab32/duckdb-0.6.1-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl",hashes = {sha256 = "3941b3a1e8a1cdb7b90ab3917b87af816e71f9692e5ada7f19b6b60969f731e5"}}, + {name = "duckdb-0.6.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",url = "https://files.pythonhosted.org/packages/7f/fa/d79b39cec15fd227c7174108f24541bd69fc7f1751ec2cf02099055e2592/duckdb-0.6.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",hashes = {sha256 = "143611bd1b7c13343f087d4d423a7a8a4f33a114c5326171e867febf3f0fcfe1"}}, + {name = "duckdb-0.6.1-cp310-cp310-musllinux_1_1_i686.whl",url = "https://files.pythonhosted.org/packages/35/53/d85036800c1672f15e178a623b2fcb3ac203664cbfa54180c39c74ad4102/duckdb-0.6.1-cp310-cp310-musllinux_1_1_i686.whl",hashes = {sha256 = "125ba45e8b08f28858f918ec9cbd3a19975e5d8d9e8275ef4ad924028a616e14"}}, + {name = "duckdb-0.6.1-cp310-cp310-musllinux_1_1_x86_64.whl",url = "https://files.pythonhosted.org/packages/30/b4/5f7f1d46392bbf0d95ebc53762b18228fd7331c7f9eebb72469469419672/duckdb-0.6.1-cp310-cp310-musllinux_1_1_x86_64.whl",hashes = {sha256 = "e609a65b31c92f2f7166831f74b56f5ed54b33d8c2c4b4c3974c26fdc50464c5"}}, + {name = "duckdb-0.6.1-cp310-cp310-win32.whl",url = "https://files.pythonhosted.org/packages/e6/2d/b43e5864efde07dce1d20a07cf2a238efb71878e002a14b0234da4a60936/duckdb-0.6.1-cp310-cp310-win32.whl",hashes = {sha256 = "b39045074fb9a3f068496475a5d627ad4fa572fa3b4980e3b479c11d0b706f2d"}}, + {name = "duckdb-0.6.1-cp310-cp310-win_amd64.whl",url = "https://files.pythonhosted.org/packages/47/8d/140845f6f61f9fe936f5743fedb6625c0ff4781f3be928e4d3a20d551fc7/duckdb-0.6.1-cp310-cp310-win_amd64.whl",hashes = {sha256 = "16fa96ffaa3d842a9355a633fb8bc092d119be08d4bc02013946d8594417bc14"}}, + {name = "duckdb-0.6.1-cp39-cp39-macosx_10_9_universal2.whl",url = "https://files.pythonhosted.org/packages/1d/77/6bc4ebcc95986ce57c74e94066f9a668b8508d32b0a07a427eb92b11e19f/duckdb-0.6.1-cp39-cp39-macosx_10_9_universal2.whl",hashes = {sha256 = "c35ff4b1117096ef72d101524df0079da36c3735d52fcf1d907ccffa63bd6202"}}, + {name = "duckdb-0.6.1-cp39-cp39-macosx_10_9_x86_64.whl",url = "https://files.pythonhosted.org/packages/ef/a0/42dc092e852b3fc577445a53119d954d5b0c50815782b91a0cc66af4a1a1/duckdb-0.6.1-cp39-cp39-macosx_10_9_x86_64.whl",hashes = {sha256 = "5c54910fbb6de0f21d562e18a5c91540c19876db61b862fc9ffc8e31be8b3f03"}}, + {name = "duckdb-0.6.1-cp39-cp39-macosx_11_0_arm64.whl",url = "https://files.pythonhosted.org/packages/b6/ae/ea1b6860227f0bb175d8a331a235c66a24883c746ee308a9391c5e697613/duckdb-0.6.1-cp39-cp39-macosx_11_0_arm64.whl",hashes = {sha256 = "99a7172563a3ae67d867572ce27cf3962f58e76f491cb7f602f08c2af39213b3"}}, + {name = "duckdb-0.6.1-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl",url = "https://files.pythonhosted.org/packages/7b/dd/3439c82a6bc61c677098ba0d1216665321d0624ef6d0e3bb2cc79c3c9616/duckdb-0.6.1-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl",hashes = {sha256 = "7363ffe857d00216b659116647fbf1e925cb3895699015d4a4e50b746de13041"}}, + {name = "duckdb-0.6.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",url = "https://files.pythonhosted.org/packages/84/8f/72a0be1eb246471b58162eb319ac003ac5ac09a47aec72755a91743d3954/duckdb-0.6.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",hashes = {sha256 = "06c1cef25f896b2284ba048108f645c72fab5c54aa5a6f62f95663f44ff8a79b"}}, + {name = "duckdb-0.6.1-cp39-cp39-musllinux_1_1_i686.whl",url = "https://files.pythonhosted.org/packages/74/ad/271475c8b9c4bf42b4c59946eb9e1f1a0177966f02850804f62c4e81657d/duckdb-0.6.1-cp39-cp39-musllinux_1_1_i686.whl",hashes = {sha256 = "e92dd6aad7e8c29d002947376b6f5ce28cae29eb3b6b58a64a46cdbfc5cb7943"}}, + {name = "duckdb-0.6.1-cp39-cp39-musllinux_1_1_x86_64.whl",url = "https://files.pythonhosted.org/packages/2a/d8/aa2facdb198cab383221c5e13a7738f09830e3989908b02abd1cab682a0b/duckdb-0.6.1-cp39-cp39-musllinux_1_1_x86_64.whl",hashes = {sha256 = "4b280b2d8a01ecd4fe2feab041df70233c534fafbe33a38565b52c1e017529c7"}}, + {name = "duckdb-0.6.1-cp39-cp39-win32.whl",url = "https://files.pythonhosted.org/packages/92/7c/5879a645e7a3abc13781c368a0ca550fe64e049f5c2e73b8d5834bb2a010/duckdb-0.6.1-cp39-cp39-win32.whl",hashes = {sha256 = "d9212d76e90b8469743924a4d22bef845be310d0d193d54ae17d9ef1f753cfa7"}}, + {name = "duckdb-0.6.1-cp39-cp39-win_amd64.whl",url = "https://files.pythonhosted.org/packages/8d/3d/2c5e19de3eb44213cb860d095e597eaa0e264ce34c918bb8a239b3b2c03d/duckdb-0.6.1-cp39-cp39-win_amd64.whl",hashes = {sha256 = "00b7be8f67ec1a8edaa8844f521267baa1a795f4c482bfad56c72c26e1862ab2"}}, +] +marker = "\"default\" in dependency_groups" + +[packages.tool.pdm] +dependencies = [ + "numpy>=1.14", +] + [[packages]] name = "coverage" version = "7.9.2" @@ -181,46 +221,6 @@ marker = "\"test\" in dependency_groups" [packages.tool.pdm] dependencies = [] -[[packages]] -name = "duckdb" -version = "0.6.1" -sdist = {name = "duckdb-0.6.1.tar.gz", url = "https://files.pythonhosted.org/packages/48/80/0dee966cde56d2e1b3ea8e3389b2e93288d662bc0b85b6b0508f3151f3c0/duckdb-0.6.1.tar.gz", hashes = {sha256 = "6d26e9f1afcb924a6057785e506810d48332d4764ddc4a5b414d0f2bf0cacfb4"}} -wheels = [ - {name = "duckdb-0.6.1-cp311-cp311-macosx_10_9_universal2.whl",url = "https://files.pythonhosted.org/packages/2e/d7/0320924f9e817442afad06a5e428e8f5fb55b6cb20bfd2b0be4b3975ec08/duckdb-0.6.1-cp311-cp311-macosx_10_9_universal2.whl",hashes = {sha256 = "b4bbe2f6c1b109c626f9318eee80934ad2a5b81a51409c6b5083c6c5f9bdb125"}}, - {name = "duckdb-0.6.1-cp311-cp311-macosx_10_9_x86_64.whl",url = "https://files.pythonhosted.org/packages/79/a7/c01998a05ed41fd3bf124079de9508ee6002e5b120a4ba353817535eaa7a/duckdb-0.6.1-cp311-cp311-macosx_10_9_x86_64.whl",hashes = {sha256 = "cfea36b58928ce778d17280d4fb3bf0a2d7cff407667baedd69c5b41463ac0fd"}}, - {name = "duckdb-0.6.1-cp311-cp311-macosx_11_0_arm64.whl",url = "https://files.pythonhosted.org/packages/bc/1b/a0b13a6e985ff1024e0d52b372f43e385ce1538773ecac250e82543572e2/duckdb-0.6.1-cp311-cp311-macosx_11_0_arm64.whl",hashes = {sha256 = "0b64eb53d0d0695814bf1b65c0f91ab7ed66b515f89c88038f65ad5e0762571c"}}, - {name = "duckdb-0.6.1-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl",url = "https://files.pythonhosted.org/packages/1e/5e/8b7472fdaedce2a3eed50da24335bfa81ca5dbca66d6b684a1646ab52727/duckdb-0.6.1-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl",hashes = {sha256 = "35b01bc724e1933293f4c34f410d2833bfbb56d5743b515d805bbfed0651476e"}}, - {name = "duckdb-0.6.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",url = "https://files.pythonhosted.org/packages/09/4d/5baca267898d4d290a9dabe3d6d023cba38fb1774234f7fa4cc069a02638/duckdb-0.6.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",hashes = {sha256 = "fec2c2466654ce786843bda2bfba71e0e4719106b41d36b17ceb1901e130aa71"}}, - {name = "duckdb-0.6.1-cp311-cp311-musllinux_1_1_i686.whl",url = "https://files.pythonhosted.org/packages/55/d9/d80159f4304e351db0459a16e9c8d139c87a495300007cc105b75452c30f/duckdb-0.6.1-cp311-cp311-musllinux_1_1_i686.whl",hashes = {sha256 = "82cd30f5cf368658ef879b1c60276bc8650cf67cfe3dc3e3009438ba39251333"}}, - {name = "duckdb-0.6.1-cp311-cp311-musllinux_1_1_x86_64.whl",url = "https://files.pythonhosted.org/packages/5c/ad/1186693bc6d73f5ba1ac4e13772ba57abfcc970aa03ccdfb8c7405b039f1/duckdb-0.6.1-cp311-cp311-musllinux_1_1_x86_64.whl",hashes = {sha256 = "a782bbfb7f5e97d4a9c834c9e78f023fb8b3f6687c22ca99841e6ed944b724da"}}, - {name = "duckdb-0.6.1-cp311-cp311-win32.whl",url = "https://files.pythonhosted.org/packages/8a/54/4210ce0b26a010fd2755f8d78dcaeceeb4d1ddda76d2e884c0b130fae6dc/duckdb-0.6.1-cp311-cp311-win32.whl",hashes = {sha256 = "e3702d4a9ade54c6403f6615a98bbec2020a76a60f5db7fcf085df1bd270e66e"}}, - {name = "duckdb-0.6.1-cp311-cp311-win_amd64.whl",url = "https://files.pythonhosted.org/packages/78/cc/508a045cb18164d0890eb09f38bc7f8346aa03c387217c71aeeab1be61ec/duckdb-0.6.1-cp311-cp311-win_amd64.whl",hashes = {sha256 = "93b074f473d68c944b0eeb2edcafd91ad11da8432b484836efaaab4e26351d48"}}, - {name = "duckdb-0.6.1-cp310-cp310-macosx_10_9_universal2.whl",url = "https://files.pythonhosted.org/packages/94/68/c633dcd576dd63e627619a6274c17b6ddc8e2637fa9b037a5241b687c40b/duckdb-0.6.1-cp310-cp310-macosx_10_9_universal2.whl",hashes = {sha256 = "e566514f9327f89264e98ac14ee7a84fbd9857328028258422c3e8375ee19d25"}}, - {name = "duckdb-0.6.1-cp310-cp310-macosx_10_9_x86_64.whl",url = "https://files.pythonhosted.org/packages/c5/ef/ff74822cbd8792138edad7da762fc1d371723761fe9362a44df85b09774c/duckdb-0.6.1-cp310-cp310-macosx_10_9_x86_64.whl",hashes = {sha256 = "b31c2883de5b19591a2852165e6b3f9821f77af649835f27bc146b26e4aa30cb"}}, - {name = "duckdb-0.6.1-cp310-cp310-macosx_11_0_arm64.whl",url = "https://files.pythonhosted.org/packages/98/74/069f636ba410c7055847cd202bb8fb7e8ea1b0e0a2ae6b131ba40e0d4322/duckdb-0.6.1-cp310-cp310-macosx_11_0_arm64.whl",hashes = {sha256 = "998165b2fb1f1d2b0ad742096015ea70878f7d40304643c7424c3ed3ddf07bfc"}}, - {name = "duckdb-0.6.1-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl",url = "https://files.pythonhosted.org/packages/a2/02/a36ca6545c81cb6ec339fcff178b931b0079cdd9d39806da2d440dcdab32/duckdb-0.6.1-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl",hashes = {sha256 = "3941b3a1e8a1cdb7b90ab3917b87af816e71f9692e5ada7f19b6b60969f731e5"}}, - {name = "duckdb-0.6.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",url = "https://files.pythonhosted.org/packages/7f/fa/d79b39cec15fd227c7174108f24541bd69fc7f1751ec2cf02099055e2592/duckdb-0.6.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",hashes = {sha256 = "143611bd1b7c13343f087d4d423a7a8a4f33a114c5326171e867febf3f0fcfe1"}}, - {name = "duckdb-0.6.1-cp310-cp310-musllinux_1_1_i686.whl",url = "https://files.pythonhosted.org/packages/35/53/d85036800c1672f15e178a623b2fcb3ac203664cbfa54180c39c74ad4102/duckdb-0.6.1-cp310-cp310-musllinux_1_1_i686.whl",hashes = {sha256 = "125ba45e8b08f28858f918ec9cbd3a19975e5d8d9e8275ef4ad924028a616e14"}}, - {name = "duckdb-0.6.1-cp310-cp310-musllinux_1_1_x86_64.whl",url = "https://files.pythonhosted.org/packages/30/b4/5f7f1d46392bbf0d95ebc53762b18228fd7331c7f9eebb72469469419672/duckdb-0.6.1-cp310-cp310-musllinux_1_1_x86_64.whl",hashes = {sha256 = "e609a65b31c92f2f7166831f74b56f5ed54b33d8c2c4b4c3974c26fdc50464c5"}}, - {name = "duckdb-0.6.1-cp310-cp310-win32.whl",url = "https://files.pythonhosted.org/packages/e6/2d/b43e5864efde07dce1d20a07cf2a238efb71878e002a14b0234da4a60936/duckdb-0.6.1-cp310-cp310-win32.whl",hashes = {sha256 = "b39045074fb9a3f068496475a5d627ad4fa572fa3b4980e3b479c11d0b706f2d"}}, - {name = "duckdb-0.6.1-cp310-cp310-win_amd64.whl",url = "https://files.pythonhosted.org/packages/47/8d/140845f6f61f9fe936f5743fedb6625c0ff4781f3be928e4d3a20d551fc7/duckdb-0.6.1-cp310-cp310-win_amd64.whl",hashes = {sha256 = "16fa96ffaa3d842a9355a633fb8bc092d119be08d4bc02013946d8594417bc14"}}, - {name = "duckdb-0.6.1-cp39-cp39-macosx_10_9_universal2.whl",url = "https://files.pythonhosted.org/packages/1d/77/6bc4ebcc95986ce57c74e94066f9a668b8508d32b0a07a427eb92b11e19f/duckdb-0.6.1-cp39-cp39-macosx_10_9_universal2.whl",hashes = {sha256 = "c35ff4b1117096ef72d101524df0079da36c3735d52fcf1d907ccffa63bd6202"}}, - {name = "duckdb-0.6.1-cp39-cp39-macosx_10_9_x86_64.whl",url = "https://files.pythonhosted.org/packages/ef/a0/42dc092e852b3fc577445a53119d954d5b0c50815782b91a0cc66af4a1a1/duckdb-0.6.1-cp39-cp39-macosx_10_9_x86_64.whl",hashes = {sha256 = "5c54910fbb6de0f21d562e18a5c91540c19876db61b862fc9ffc8e31be8b3f03"}}, - {name = "duckdb-0.6.1-cp39-cp39-macosx_11_0_arm64.whl",url = "https://files.pythonhosted.org/packages/b6/ae/ea1b6860227f0bb175d8a331a235c66a24883c746ee308a9391c5e697613/duckdb-0.6.1-cp39-cp39-macosx_11_0_arm64.whl",hashes = {sha256 = "99a7172563a3ae67d867572ce27cf3962f58e76f491cb7f602f08c2af39213b3"}}, - {name = "duckdb-0.6.1-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl",url = "https://files.pythonhosted.org/packages/7b/dd/3439c82a6bc61c677098ba0d1216665321d0624ef6d0e3bb2cc79c3c9616/duckdb-0.6.1-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl",hashes = {sha256 = "7363ffe857d00216b659116647fbf1e925cb3895699015d4a4e50b746de13041"}}, - {name = "duckdb-0.6.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",url = "https://files.pythonhosted.org/packages/84/8f/72a0be1eb246471b58162eb319ac003ac5ac09a47aec72755a91743d3954/duckdb-0.6.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",hashes = {sha256 = "06c1cef25f896b2284ba048108f645c72fab5c54aa5a6f62f95663f44ff8a79b"}}, - {name = "duckdb-0.6.1-cp39-cp39-musllinux_1_1_i686.whl",url = "https://files.pythonhosted.org/packages/74/ad/271475c8b9c4bf42b4c59946eb9e1f1a0177966f02850804f62c4e81657d/duckdb-0.6.1-cp39-cp39-musllinux_1_1_i686.whl",hashes = {sha256 = "e92dd6aad7e8c29d002947376b6f5ce28cae29eb3b6b58a64a46cdbfc5cb7943"}}, - {name = "duckdb-0.6.1-cp39-cp39-musllinux_1_1_x86_64.whl",url = "https://files.pythonhosted.org/packages/2a/d8/aa2facdb198cab383221c5e13a7738f09830e3989908b02abd1cab682a0b/duckdb-0.6.1-cp39-cp39-musllinux_1_1_x86_64.whl",hashes = {sha256 = "4b280b2d8a01ecd4fe2feab041df70233c534fafbe33a38565b52c1e017529c7"}}, - {name = "duckdb-0.6.1-cp39-cp39-win32.whl",url = "https://files.pythonhosted.org/packages/92/7c/5879a645e7a3abc13781c368a0ca550fe64e049f5c2e73b8d5834bb2a010/duckdb-0.6.1-cp39-cp39-win32.whl",hashes = {sha256 = "d9212d76e90b8469743924a4d22bef845be310d0d193d54ae17d9ef1f753cfa7"}}, - {name = "duckdb-0.6.1-cp39-cp39-win_amd64.whl",url = "https://files.pythonhosted.org/packages/8d/3d/2c5e19de3eb44213cb860d095e597eaa0e264ce34c918bb8a239b3b2c03d/duckdb-0.6.1-cp39-cp39-win_amd64.whl",hashes = {sha256 = "00b7be8f67ec1a8edaa8844f521267baa1a795f4c482bfad56c72c26e1862ab2"}}, -] -marker = "\"default\" in dependency_groups" - -[packages.tool.pdm] -dependencies = [ - "numpy>=1.14", -] - [[packages]] name = "httpx-folio" version = "0.2.3" @@ -239,11 +239,37 @@ dependencies = [ [[packages]] name = "orjson" -version = "2.2.1" -requires-python = ">=3.6" -sdist = {name = "orjson-2.2.1.tar.gz", url = "https://files.pythonhosted.org/packages/cd/58/e8526187aad924e760e6e4ca41e990fccba7ce61c501dc6c59162507ea19/orjson-2.2.1.tar.gz", hashes = {sha256 = "0636c348ef55ba2d910ef562708621a5f80ef7cdafc3b831ef85ad48447ee70d"}} -wheels = [ - {name = "orjson-2.2.1-cp39-cp39-manylinux1_x86_64.whl",url = "https://files.pythonhosted.org/packages/c5/63/12aaf2d15665a10799d375fe7fef5ce85e2cff97577d1ce9fa89285c5cbc/orjson-2.2.1-cp39-cp39-manylinux1_x86_64.whl",hashes = {sha256 = "e599298798a98239f7fe3abdfec4264dfe98b1b8855df6b3813f7562ed68c512"}}, +version = "3.9.0" +requires-python = ">=3.7" +sdist = {name = "orjson-3.9.0.tar.gz", url = "https://files.pythonhosted.org/packages/ef/00/f3beb032641547b01c5850a4ff02bf6dbc318207c10b116d405f071321a0/orjson-3.9.0.tar.gz", hashes = {sha256 = "f6dd27c71cd6e146795f876449a8eae74f67ae1e4e244dfc1203489103eb2d94"}} +wheels = [ + {name = "orjson-3.9.0-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl",url = "https://files.pythonhosted.org/packages/d5/64/9e81da69bb5dfc0a30d7ce2682f1a0aa4637a82fde9fd56e2e188f040898/orjson-3.9.0-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl",hashes = {sha256 = "47d7e4a3effc0e9314bd5b06e7431f2490a5e64dcdcbbc4d60e713786fec327d"}}, + {name = "orjson-3.9.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl",url = "https://files.pythonhosted.org/packages/e5/e4/b48906b617a14e8e9b4d99d5b5f470002674ba434bc10770f17511255f1f/orjson-3.9.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl",hashes = {sha256 = "c41d1ef6ec308e9e3701764b3de889ed8c1c126eceaea881dd1027bffbed89fe"}}, + {name = "orjson-3.9.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl",url = "https://files.pythonhosted.org/packages/ec/2b/a2530675913d060f7ee760efcb88dd94b4d2b4bb1dedf3b09c401a0b82f9/orjson-3.9.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl",hashes = {sha256 = "86da00836029b2a071229c8aecab998a2f316c1bc7de10ae020d7311de3a6d0d"}}, + {name = "orjson-3.9.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl",url = "https://files.pythonhosted.org/packages/b1/e6/6389d8ea0a3d9981f13c61be638715c8f616d1231574131081d35195b9fe/orjson-3.9.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl",hashes = {sha256 = "d4fcf598bd5a99a94caa7ec92ce657939f12491e4753ea7e4d6c03faf5f7912e"}}, + {name = "orjson-3.9.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl",url = "https://files.pythonhosted.org/packages/15/a3/7520991c2e46ad7d9c2a9076aac4c3ca2bbe6820fc166b184f49ccbcea81/orjson-3.9.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl",hashes = {sha256 = "09522937479bd39d5bb32d11a5ecdf6926fda43ac2cbde21cc1a9508b4e4ea29"}}, + {name = "orjson-3.9.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",url = "https://files.pythonhosted.org/packages/87/15/f643ee5e696ab43a690c4745310553d2c3e8180c8cac59bab4e50dccd22a/orjson-3.9.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",hashes = {sha256 = "d2fbf34667a8be48ec89d5ef479a00d4e7b3acda62d722c97377702da0c30ffd"}}, + {name = "orjson-3.9.0-cp311-cp311-musllinux_1_1_aarch64.whl",url = "https://files.pythonhosted.org/packages/a9/d0/c3341cc7d1818dc80df738e3879275a8236da26cd17823ba4a66d12d577b/orjson-3.9.0-cp311-cp311-musllinux_1_1_aarch64.whl",hashes = {sha256 = "edd77183c154cbedaa6dac32fee9cb770b04e2a7f367a5864f444578554cc946"}}, + {name = "orjson-3.9.0-cp311-cp311-musllinux_1_1_x86_64.whl",url = "https://files.pythonhosted.org/packages/2a/9f/e6e15918c7ef6fc38e3c009aa704937dfb31ba3916a7783eaf365b7a257f/orjson-3.9.0-cp311-cp311-musllinux_1_1_x86_64.whl",hashes = {sha256 = "2af7dff1c7ddb0c83eb5773acf6566b153f8cd32e4ba782ae9ccd6d0f324efd3"}}, + {name = "orjson-3.9.0-cp311-none-win_amd64.whl",url = "https://files.pythonhosted.org/packages/c3/cb/683a60de5c1820412979aeca04e43cdb6c01a282edf1583bf84054d22458/orjson-3.9.0-cp311-none-win_amd64.whl",hashes = {sha256 = "44fa74b497e608a8cdca1ee37fe3533a30f17163c7e2872ab1b854900cf0dfcf"}}, + {name = "orjson-3.9.0-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl",url = "https://files.pythonhosted.org/packages/ce/19/ae05dd3d72f8036ad4f7ab6ff5e41d4dd44e81fb62aa6a04d6c1f53d051f/orjson-3.9.0-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl",hashes = {sha256 = "128b1cd0f00a37ba64a12cceeba4e8070655d4400edd55a737513ee663c1ed5a"}}, + {name = "orjson-3.9.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl",url = "https://files.pythonhosted.org/packages/95/1c/be392b9e88f568a67fe546008a1d46cb955798c5bb02f11f9d0485dfa8ed/orjson-3.9.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl",hashes = {sha256 = "7a3693fde44b2eeb80074ecbe8c504b25baf71e66c080af2a574193a5ba81960"}}, + {name = "orjson-3.9.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl",url = "https://files.pythonhosted.org/packages/e8/7a/fffa0e31dde392a9116718e371e9deb27ac35b6f089644a1fd9ffdb6b3fe/orjson-3.9.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl",hashes = {sha256 = "3f1193417b5a93deb41bcb8db27b61179b9b3e299b337b578c31f19159664da3"}}, + {name = "orjson-3.9.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl",url = "https://files.pythonhosted.org/packages/0c/75/e41f01986e5ee56362c93ac5c92149f8fb6b6c8123c5aa5afe8a8b96746e/orjson-3.9.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl",hashes = {sha256 = "88626d898c408450c57664899831cf072787898af4847fa4466607ad2a83f454"}}, + {name = "orjson-3.9.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl",url = "https://files.pythonhosted.org/packages/2e/ee/2b2ca51b317e975ef8fbc67b7909269c97591bc921bb5946174905b71d16/orjson-3.9.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl",hashes = {sha256 = "1e3bde77c1e0061eb34bae6fea44818b2198e043ee10a16ad7b160921fee26ea"}}, + {name = "orjson-3.9.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",url = "https://files.pythonhosted.org/packages/44/1e/cc7c47dcf5ce59a4b9b1fd873d3813a4bf8e3f18fbda3c402e70e07fdd87/orjson-3.9.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",hashes = {sha256 = "45df5bf6531ffda518331cc93cdcd4c84f4a4a0507d72af8fb698c7131a440a0"}}, + {name = "orjson-3.9.0-cp310-cp310-musllinux_1_1_aarch64.whl",url = "https://files.pythonhosted.org/packages/49/a1/de519d9901d92ef086d8ddfc80d63a2ee51871887af5397ed4896cf06202/orjson-3.9.0-cp310-cp310-musllinux_1_1_aarch64.whl",hashes = {sha256 = "2536a7f30fd4d77532769ea9285cd20c69bd2b40acf980de94bbc79b1c6fad5a"}}, + {name = "orjson-3.9.0-cp310-cp310-musllinux_1_1_x86_64.whl",url = "https://files.pythonhosted.org/packages/e3/96/fa927d1c19866b11dcfaf56d4de9d29b4ec4c14786b4a8c816cb58bb7e95/orjson-3.9.0-cp310-cp310-musllinux_1_1_x86_64.whl",hashes = {sha256 = "21f6a6fdfbc13cd715c61e9fa9daeff732df6401ab7d6a2ebad0042313a40bd1"}}, + {name = "orjson-3.9.0-cp310-none-win_amd64.whl",url = "https://files.pythonhosted.org/packages/df/8c/1184f03df5233823f88a1d3c5c1878f985a9958d06512d875fc9f8f5340b/orjson-3.9.0-cp310-none-win_amd64.whl",hashes = {sha256 = "46c9733330b75c116438f555c0b971a2388b5f502e2dd4ec3bf6bacb96f82741"}}, + {name = "orjson-3.9.0-cp39-cp39-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl",url = "https://files.pythonhosted.org/packages/5c/ca/4d7a76381df4119c9ccfdaae1ab62004ed909f24519673118c3ad651a857/orjson-3.9.0-cp39-cp39-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl",hashes = {sha256 = "a901c432828c191332d75f358142736c433d4a192f7794123e1d30d68193de86"}}, + {name = "orjson-3.9.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl",url = "https://files.pythonhosted.org/packages/b2/95/707184b9b991a58bc8f290aed0c3942f642e85c089f0228168af41e54cf3/orjson-3.9.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl",hashes = {sha256 = "271b6f1018757fc6bca40ae72e6cdb6cf84584dde2d1e5eaac30e387a13d9e72"}}, + {name = "orjson-3.9.0-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl",url = "https://files.pythonhosted.org/packages/8b/8a/59611e84d2d76f5eb23ffaa9c7c8aaa728174b5e1cb86c6a29b8418ec349/orjson-3.9.0-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl",hashes = {sha256 = "949698bdddb1daff986d73e6bbe6cd68833cd80c4adc6b69fafbd46634d4672c"}}, + {name = "orjson-3.9.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl",url = "https://files.pythonhosted.org/packages/ad/c3/bdc121a246921d0699c695eb73cf56417ab487767c4f761f9425dd1bf59c/orjson-3.9.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl",hashes = {sha256 = "108c58d2c7648c991f82f9b2217c50981ad7cf6aaee3efbfaa9d807e49cd69b8"}}, + {name = "orjson-3.9.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl",url = "https://files.pythonhosted.org/packages/ee/45/47bcbad2c90b2846428bcd3a0a4623e37bb74ae3583bb96929b2e625d095/orjson-3.9.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl",hashes = {sha256 = "08cb43569198c1f5c89ecafcbfc62414f6115d894ff908d8cf8e5e24801364e6"}}, + {name = "orjson-3.9.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",url = "https://files.pythonhosted.org/packages/d7/c8/a604ad0c7f0ffb5897328e0ca4a81697c2ffe1fac3f72bbaee59b12629cb/orjson-3.9.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",hashes = {sha256 = "09ee828572fadcd58bf356d2c1bad99a95c7c9c1f182b407abbc7dec1810f542"}}, + {name = "orjson-3.9.0-cp39-cp39-musllinux_1_1_aarch64.whl",url = "https://files.pythonhosted.org/packages/61/ac/1923bf83c0e36a9ac35d4c7347f715ce87b807c8692403b66047c7ac11e1/orjson-3.9.0-cp39-cp39-musllinux_1_1_aarch64.whl",hashes = {sha256 = "0e7fe5d603ee9177ff2e45858b4fc47fea2da0688f23d9773654889d56dfbc82"}}, + {name = "orjson-3.9.0-cp39-cp39-musllinux_1_1_x86_64.whl",url = "https://files.pythonhosted.org/packages/10/1f/2915887049c72a195a7991ec8e5cf0344fcd6a940a2e50abee860752b7bf/orjson-3.9.0-cp39-cp39-musllinux_1_1_x86_64.whl",hashes = {sha256 = "9ee5f1ba82146a50d61fb58d310a37c0f406eda898172f9c98673b5d6f9461c3"}}, + {name = "orjson-3.9.0-cp39-none-win_amd64.whl",url = "https://files.pythonhosted.org/packages/c9/d9/0382e682322a996476ece62f92cc90cb067da3266a6a9ed71da9b49cc442/orjson-3.9.0-cp39-none-win_amd64.whl",hashes = {sha256 = "3235c31d0fe674f6e3433e9ddfed212aa840c83a9b6ef5ae128950e2c808c303"}}, ] marker = "\"default\" in dependency_groups" @@ -861,7 +887,7 @@ marker = "sys_platform == \"win32\" and \"default\" in dependency_groups" dependencies = [] [tool.pdm] -hashes = {sha256 = "9cd31bd06db4ed0781a4f5b9f507c9fb109b9e48b412ee0b76a1115f841b603b"} +hashes = {sha256 = "9edcf2127050cd04b1c55f2c5d0ef0c306790635417a4e7c2a0ed6e601c76e78"} strategy = ["direct_minimal_versions", "inherit_metadata", "static_urls"] [[tool.pdm.targets]] diff --git a/pylock.toml b/pylock.toml index 513acd0..6cf66b9 100644 --- a/pylock.toml +++ b/pylock.toml @@ -103,6 +103,53 @@ marker = "\"lint\" in dependency_groups" [packages.tool.pdm] dependencies = [] +[[packages]] +name = "duckdb" +version = "1.3.2" +requires-python = ">=3.7.0" +sdist = {name = "duckdb-1.3.2.tar.gz", url = "https://files.pythonhosted.org/packages/47/24/a2e7fb78fba577641c286fe33185789ab1e1569ccdf4d142e005995991d2/duckdb-1.3.2.tar.gz", hashes = {sha256 = "c658df8a1bc78704f702ad0d954d82a1edd4518d7a04f00027ec53e40f591ff5"}} +wheels = [ + {name = "duckdb-1.3.2-cp313-cp313-macosx_12_0_arm64.whl",url = "https://files.pythonhosted.org/packages/f5/f0/8cac9713735864899e8abc4065bbdb3d1617f2130006d508a80e1b1a6c53/duckdb-1.3.2-cp313-cp313-macosx_12_0_arm64.whl",hashes = {sha256 = "a3418c973b06ac4e97f178f803e032c30c9a9f56a3e3b43a866f33223dfbf60b"}}, + {name = "duckdb-1.3.2-cp313-cp313-macosx_12_0_universal2.whl",url = "https://files.pythonhosted.org/packages/c5/26/6698bbb30b7bce8b8b17697599f1517611c61e4bd68b37eaeaf4f5ddd915/duckdb-1.3.2-cp313-cp313-macosx_12_0_universal2.whl",hashes = {sha256 = "2a741eae2cf110fd2223eeebe4151e22c0c02803e1cfac6880dbe8a39fecab6a"}}, + {name = "duckdb-1.3.2-cp313-cp313-macosx_12_0_x86_64.whl",url = "https://files.pythonhosted.org/packages/10/75/8ab4da3099a2fac7335ecebce4246706d19bdd5dad167aa436b5b27c43c4/duckdb-1.3.2-cp313-cp313-macosx_12_0_x86_64.whl",hashes = {sha256 = "51e62541341ea1a9e31f0f1ade2496a39b742caf513bebd52396f42ddd6525a0"}}, + {name = "duckdb-1.3.2-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl",url = "https://files.pythonhosted.org/packages/d1/46/af81b10d4a66a0f27c248df296d1b41ff2a305a235ed8488f93240f6f8b5/duckdb-1.3.2-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl",hashes = {sha256 = "b3e519de5640e5671f1731b3ae6b496e0ed7e4de4a1c25c7a2f34c991ab64d71"}}, + {name = "duckdb-1.3.2-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl",url = "https://files.pythonhosted.org/packages/68/fc/259a54fc22111a847981927aa58528d766e8b228c6d41deb0ad8a1959f9f/duckdb-1.3.2-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl",hashes = {sha256 = "4732fb8cc60566b60e7e53b8c19972cb5ed12d285147a3063b16cc64a79f6d9f"}}, + {name = "duckdb-1.3.2-cp313-cp313-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/ab/dc/5d5140383e40661173dacdceaddee2a97c3f6721a5e8d76e08258110595e/duckdb-1.3.2-cp313-cp313-musllinux_1_2_x86_64.whl",hashes = {sha256 = "97f7a22dcaa1cca889d12c3dc43a999468375cdb6f6fe56edf840e062d4a8293"}}, + {name = "duckdb-1.3.2-cp313-cp313-win_amd64.whl",url = "https://files.pythonhosted.org/packages/51/c9/2fcd86ab7530a5b6caff42dbe516ce7a86277e12c499d1c1f5acd266ffb2/duckdb-1.3.2-cp313-cp313-win_amd64.whl",hashes = {sha256 = "cd3d717bf9c49ef4b1016c2216517572258fa645c2923e91c5234053defa3fb5"}}, + {name = "duckdb-1.3.2-cp312-cp312-macosx_12_0_arm64.whl",url = "https://files.pythonhosted.org/packages/6c/5d/77f15528857c2b186ebec07778dc199ccc04aafb69fb7b15227af4f19ac9/duckdb-1.3.2-cp312-cp312-macosx_12_0_arm64.whl",hashes = {sha256 = "2455b1ffef4e3d3c7ef8b806977c0e3973c10ec85aa28f08c993ab7f2598e8dd"}}, + {name = "duckdb-1.3.2-cp312-cp312-macosx_12_0_universal2.whl",url = "https://files.pythonhosted.org/packages/78/67/7e4964f688b846676c813a4acc527cd3454be8a9cafa10f3a9aa78d0d165/duckdb-1.3.2-cp312-cp312-macosx_12_0_universal2.whl",hashes = {sha256 = "9d0ae509713da3461c000af27496d5413f839d26111d2a609242d9d17b37d464"}}, + {name = "duckdb-1.3.2-cp312-cp312-macosx_12_0_x86_64.whl",url = "https://files.pythonhosted.org/packages/95/3d/2d7f8078194130dbf30b5ae154ce454bfc208c91aa5f3e802531a3e09bca/duckdb-1.3.2-cp312-cp312-macosx_12_0_x86_64.whl",hashes = {sha256 = "72ca6143d23c0bf6426396400f01fcbe4785ad9ceec771bd9a4acc5b5ef9a075"}}, + {name = "duckdb-1.3.2-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl",url = "https://files.pythonhosted.org/packages/cd/05/36ff9000b9c6d2a68c1b248f133ee316fcac10c0ff817112cbf5214dbe91/duckdb-1.3.2-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl",hashes = {sha256 = "b49a11afba36b98436db83770df10faa03ebded06514cb9b180b513d8be7f392"}}, + {name = "duckdb-1.3.2-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl",url = "https://files.pythonhosted.org/packages/ac/73/f85acbb3ac319a86abbf6b46103d58594d73529123377219980f11b388e9/duckdb-1.3.2-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl",hashes = {sha256 = "36abdfe0d1704fe09b08d233165f312dad7d7d0ecaaca5fb3bb869f4838a2d0b"}}, + {name = "duckdb-1.3.2-cp312-cp312-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/32/40/9aa3267f3631ae06b30fb1045a48628f4dba7beb2efb485c0282b4a73367/duckdb-1.3.2-cp312-cp312-musllinux_1_2_x86_64.whl",hashes = {sha256 = "3380aae1c4f2af3f37b0bf223fabd62077dd0493c84ef441e69b45167188e7b6"}}, + {name = "duckdb-1.3.2-cp312-cp312-win_amd64.whl",url = "https://files.pythonhosted.org/packages/8c/8d/47bf95f6999b327cf4da677e150cfce802abf9057b61a93a1f91e89d748c/duckdb-1.3.2-cp312-cp312-win_amd64.whl",hashes = {sha256 = "11af73963ae174aafd90ea45fb0317f1b2e28a7f1d9902819d47c67cc957d49c"}}, + {name = "duckdb-1.3.2-cp311-cp311-macosx_12_0_arm64.whl",url = "https://files.pythonhosted.org/packages/38/16/4cde40c37dd1f48d2f9ffa63027e8b668391c5cc32cbb59f7ca8b1cec6e2/duckdb-1.3.2-cp311-cp311-macosx_12_0_arm64.whl",hashes = {sha256 = "e1872cf63aae28c3f1dc2e19b5e23940339fc39fb3425a06196c5d00a8d01040"}}, + {name = "duckdb-1.3.2-cp311-cp311-macosx_12_0_universal2.whl",url = "https://files.pythonhosted.org/packages/22/ca/9ca65db51868604007114a27cc7d44864d89328ad6a934668626618147ff/duckdb-1.3.2-cp311-cp311-macosx_12_0_universal2.whl",hashes = {sha256 = "db256c206056468ae6a9e931776bdf7debaffc58e19a0ff4fa9e7e1e82d38b3b"}}, + {name = "duckdb-1.3.2-cp311-cp311-macosx_12_0_x86_64.whl",url = "https://files.pythonhosted.org/packages/9e/ca/7f7cf01dd7731d358632fb516521f2962070a627558fb6fc3137e594bbaa/duckdb-1.3.2-cp311-cp311-macosx_12_0_x86_64.whl",hashes = {sha256 = "1d57df2149d6e4e0bd5198689316c5e2ceec7f6ac0a9ec11bc2b216502a57b34"}}, + {name = "duckdb-1.3.2-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl",url = "https://files.pythonhosted.org/packages/4c/7f/38e518b8f51299410dcad9f1e99f1c99f3592516581467a2da344d3b5951/duckdb-1.3.2-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl",hashes = {sha256 = "54f76c8b1e2a19dfe194027894209ce9ddb073fd9db69af729a524d2860e4680"}}, + {name = "duckdb-1.3.2-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl",url = "https://files.pythonhosted.org/packages/90/a3/41f3d42fddd9629846aac328eb295170e76782d8dfc5e58b3584b96fa296/duckdb-1.3.2-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl",hashes = {sha256 = "45bea70b3e93c6bf766ce2f80fc3876efa94c4ee4de72036417a7bd1e32142fe"}}, + {name = "duckdb-1.3.2-cp311-cp311-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/11/8e/c5444b6890ae7f00836fd0cd17799abbcc3066bbab32e90b04aa8a8a5087/duckdb-1.3.2-cp311-cp311-musllinux_1_2_x86_64.whl",hashes = {sha256 = "003f7d36f0d8a430cb0e00521f18b7d5ee49ec98aaa541914c6d0e008c306f1a"}}, + {name = "duckdb-1.3.2-cp311-cp311-win_amd64.whl",url = "https://files.pythonhosted.org/packages/87/a1/e240bd07671542ddf2084962e68a7d5c9b068d8da3f938e935af69441355/duckdb-1.3.2-cp311-cp311-win_amd64.whl",hashes = {sha256 = "0eb210cedf08b067fa90c666339688f1c874844a54708562282bc54b0189aac6"}}, + {name = "duckdb-1.3.2-cp310-cp310-macosx_12_0_arm64.whl",url = "https://files.pythonhosted.org/packages/6a/a0/13f45e67565800826ce0af12a0ab68fe9502dcac0e39bc03bf8a8cba61da/duckdb-1.3.2-cp310-cp310-macosx_12_0_arm64.whl",hashes = {sha256 = "14676651b86f827ea10bf965eec698b18e3519fdc6266d4ca849f5af7a8c315e"}}, + {name = "duckdb-1.3.2-cp310-cp310-macosx_12_0_universal2.whl",url = "https://files.pythonhosted.org/packages/ec/28/daf9c01b5cb4058fc80070c74284c52f11581c888db2b0e73ca48f9bae23/duckdb-1.3.2-cp310-cp310-macosx_12_0_universal2.whl",hashes = {sha256 = "e584f25892450757919639b148c2410402b17105bd404017a57fa9eec9c98919"}}, + {name = "duckdb-1.3.2-cp310-cp310-macosx_12_0_x86_64.whl",url = "https://files.pythonhosted.org/packages/77/e0/5b50014d92eb6c879608183f6184186ab2cf324dd33e432174af93d19a44/duckdb-1.3.2-cp310-cp310-macosx_12_0_x86_64.whl",hashes = {sha256 = "84a19f185ee0c5bc66d95908c6be19103e184b743e594e005dee6f84118dc22c"}}, + {name = "duckdb-1.3.2-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl",url = "https://files.pythonhosted.org/packages/a2/ff/291d74f8b4c988b2a7ee5f65d3073fe0cf4c6a4505aa1a6f28721bb2ebe2/duckdb-1.3.2-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl",hashes = {sha256 = "186fc3f98943e97f88a1e501d5720b11214695571f2c74745d6e300b18bef80e"}}, + {name = "duckdb-1.3.2-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl",url = "https://files.pythonhosted.org/packages/65/50/9a1289619447d93a8c63b08f6ab22e1e6ce73a681e0dceb0cd0ea7558613/duckdb-1.3.2-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl",hashes = {sha256 = "6b7e6bb613b73745f03bff4bb412f362d4a1e158bdcb3946f61fd18e9e1a8ddf"}}, + {name = "duckdb-1.3.2-cp310-cp310-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/e0/d1/8dc959e3ca16c4c32ab34e28ceea189edc9bf32523aaa976080fd2101835/duckdb-1.3.2-cp310-cp310-musllinux_1_2_x86_64.whl",hashes = {sha256 = "1c90646b52a0eccda1f76b10ac98b502deb9017569e84073da00a2ab97763578"}}, + {name = "duckdb-1.3.2-cp310-cp310-win_amd64.whl",url = "https://files.pythonhosted.org/packages/7b/e8/126767fe5acbe01230f7431d999a2c2ef028ffdaebda8fe32ddb57628815/duckdb-1.3.2-cp310-cp310-win_amd64.whl",hashes = {sha256 = "4cdffb1e60defbfa75407b7f2ccc322f535fd462976940731dfd1644146f90c6"}}, + {name = "duckdb-1.3.2-cp39-cp39-macosx_12_0_arm64.whl",url = "https://files.pythonhosted.org/packages/e5/e1/2e98d78eebcf405f1900e22c4ec3f5f7e2d4ed889693f95103255f6a1452/duckdb-1.3.2-cp39-cp39-macosx_12_0_arm64.whl",hashes = {sha256 = "18862e3b8a805f2204543d42d5f103b629cb7f7f2e69f5188eceb0b8a023f0af"}}, + {name = "duckdb-1.3.2-cp39-cp39-macosx_12_0_universal2.whl",url = "https://files.pythonhosted.org/packages/f7/73/ee28ba97b5dd2da5d1bb4e592e79384d54288d82ec34e75c068012b36f53/duckdb-1.3.2-cp39-cp39-macosx_12_0_universal2.whl",hashes = {sha256 = "75ed129761b6159f0b8eca4854e496a3c4c416e888537ec47ff8eb35fda2b667"}}, + {name = "duckdb-1.3.2-cp39-cp39-macosx_12_0_x86_64.whl",url = "https://files.pythonhosted.org/packages/a6/0b/67f938499c6c52df90c821a8a3f25699274ce7fbf46fa9227bc4c0bd92fe/duckdb-1.3.2-cp39-cp39-macosx_12_0_x86_64.whl",hashes = {sha256 = "875193ae9f718bc80ab5635435de5b313e3de3ec99420a9b25275ddc5c45ff58"}}, + {name = "duckdb-1.3.2-cp39-cp39-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl",url = "https://files.pythonhosted.org/packages/6c/2d/373665ef567ef0d6bcf9caf9803b697168f9e6904aff99d5782a1c5e91d1/duckdb-1.3.2-cp39-cp39-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl",hashes = {sha256 = "09b5fd8a112301096668903781ad5944c3aec2af27622bd80eae54149de42b42"}}, + {name = "duckdb-1.3.2-cp39-cp39-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl",url = "https://files.pythonhosted.org/packages/b1/18/9a89fa02689db8496d414f96d2e0ea56a24910c546c126c8a4626f3a51ee/duckdb-1.3.2-cp39-cp39-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl",hashes = {sha256 = "10cb87ad964b989175e7757d7ada0b1a7264b401a79be2f828cf8f7c366f7f95"}}, + {name = "duckdb-1.3.2-cp39-cp39-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/2e/97/2b09ad149081d75534fe063ff6a1b4b91fffe7e17816a7d9261aa7456788/duckdb-1.3.2-cp39-cp39-musllinux_1_2_x86_64.whl",hashes = {sha256 = "4389fc3812e26977034fe3ff08d1f7dbfe6d2d8337487b4686f2b50e254d7ee3"}}, + {name = "duckdb-1.3.2-cp39-cp39-win_amd64.whl",url = "https://files.pythonhosted.org/packages/6d/78/8c096f1ef46205f561e7e62d1aff749a079cf57f5c433485f55e15463041/duckdb-1.3.2-cp39-cp39-win_amd64.whl",hashes = {sha256 = "07952ec6f45dd3c7db0f825d231232dc889f1f2490b97a4e9b7abb6830145a19"}}, +] +marker = "\"default\" in dependency_groups" + +[packages.tool.pdm] +dependencies = [] + [[packages]] name = "coverage" version = "7.10.6" @@ -202,53 +249,6 @@ marker = "\"test\" in dependency_groups" [packages.tool.pdm] dependencies = [] -[[packages]] -name = "duckdb" -version = "1.3.2" -requires-python = ">=3.7.0" -sdist = {name = "duckdb-1.3.2.tar.gz", url = "https://files.pythonhosted.org/packages/47/24/a2e7fb78fba577641c286fe33185789ab1e1569ccdf4d142e005995991d2/duckdb-1.3.2.tar.gz", hashes = {sha256 = "c658df8a1bc78704f702ad0d954d82a1edd4518d7a04f00027ec53e40f591ff5"}} -wheels = [ - {name = "duckdb-1.3.2-cp313-cp313-macosx_12_0_arm64.whl",url = "https://files.pythonhosted.org/packages/f5/f0/8cac9713735864899e8abc4065bbdb3d1617f2130006d508a80e1b1a6c53/duckdb-1.3.2-cp313-cp313-macosx_12_0_arm64.whl",hashes = {sha256 = "a3418c973b06ac4e97f178f803e032c30c9a9f56a3e3b43a866f33223dfbf60b"}}, - {name = "duckdb-1.3.2-cp313-cp313-macosx_12_0_universal2.whl",url = "https://files.pythonhosted.org/packages/c5/26/6698bbb30b7bce8b8b17697599f1517611c61e4bd68b37eaeaf4f5ddd915/duckdb-1.3.2-cp313-cp313-macosx_12_0_universal2.whl",hashes = {sha256 = "2a741eae2cf110fd2223eeebe4151e22c0c02803e1cfac6880dbe8a39fecab6a"}}, - {name = "duckdb-1.3.2-cp313-cp313-macosx_12_0_x86_64.whl",url = "https://files.pythonhosted.org/packages/10/75/8ab4da3099a2fac7335ecebce4246706d19bdd5dad167aa436b5b27c43c4/duckdb-1.3.2-cp313-cp313-macosx_12_0_x86_64.whl",hashes = {sha256 = "51e62541341ea1a9e31f0f1ade2496a39b742caf513bebd52396f42ddd6525a0"}}, - {name = "duckdb-1.3.2-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl",url = "https://files.pythonhosted.org/packages/d1/46/af81b10d4a66a0f27c248df296d1b41ff2a305a235ed8488f93240f6f8b5/duckdb-1.3.2-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl",hashes = {sha256 = "b3e519de5640e5671f1731b3ae6b496e0ed7e4de4a1c25c7a2f34c991ab64d71"}}, - {name = "duckdb-1.3.2-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl",url = "https://files.pythonhosted.org/packages/68/fc/259a54fc22111a847981927aa58528d766e8b228c6d41deb0ad8a1959f9f/duckdb-1.3.2-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl",hashes = {sha256 = "4732fb8cc60566b60e7e53b8c19972cb5ed12d285147a3063b16cc64a79f6d9f"}}, - {name = "duckdb-1.3.2-cp313-cp313-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/ab/dc/5d5140383e40661173dacdceaddee2a97c3f6721a5e8d76e08258110595e/duckdb-1.3.2-cp313-cp313-musllinux_1_2_x86_64.whl",hashes = {sha256 = "97f7a22dcaa1cca889d12c3dc43a999468375cdb6f6fe56edf840e062d4a8293"}}, - {name = "duckdb-1.3.2-cp313-cp313-win_amd64.whl",url = "https://files.pythonhosted.org/packages/51/c9/2fcd86ab7530a5b6caff42dbe516ce7a86277e12c499d1c1f5acd266ffb2/duckdb-1.3.2-cp313-cp313-win_amd64.whl",hashes = {sha256 = "cd3d717bf9c49ef4b1016c2216517572258fa645c2923e91c5234053defa3fb5"}}, - {name = "duckdb-1.3.2-cp312-cp312-macosx_12_0_arm64.whl",url = "https://files.pythonhosted.org/packages/6c/5d/77f15528857c2b186ebec07778dc199ccc04aafb69fb7b15227af4f19ac9/duckdb-1.3.2-cp312-cp312-macosx_12_0_arm64.whl",hashes = {sha256 = "2455b1ffef4e3d3c7ef8b806977c0e3973c10ec85aa28f08c993ab7f2598e8dd"}}, - {name = "duckdb-1.3.2-cp312-cp312-macosx_12_0_universal2.whl",url = "https://files.pythonhosted.org/packages/78/67/7e4964f688b846676c813a4acc527cd3454be8a9cafa10f3a9aa78d0d165/duckdb-1.3.2-cp312-cp312-macosx_12_0_universal2.whl",hashes = {sha256 = "9d0ae509713da3461c000af27496d5413f839d26111d2a609242d9d17b37d464"}}, - {name = "duckdb-1.3.2-cp312-cp312-macosx_12_0_x86_64.whl",url = "https://files.pythonhosted.org/packages/95/3d/2d7f8078194130dbf30b5ae154ce454bfc208c91aa5f3e802531a3e09bca/duckdb-1.3.2-cp312-cp312-macosx_12_0_x86_64.whl",hashes = {sha256 = "72ca6143d23c0bf6426396400f01fcbe4785ad9ceec771bd9a4acc5b5ef9a075"}}, - {name = "duckdb-1.3.2-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl",url = "https://files.pythonhosted.org/packages/cd/05/36ff9000b9c6d2a68c1b248f133ee316fcac10c0ff817112cbf5214dbe91/duckdb-1.3.2-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl",hashes = {sha256 = "b49a11afba36b98436db83770df10faa03ebded06514cb9b180b513d8be7f392"}}, - {name = "duckdb-1.3.2-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl",url = "https://files.pythonhosted.org/packages/ac/73/f85acbb3ac319a86abbf6b46103d58594d73529123377219980f11b388e9/duckdb-1.3.2-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl",hashes = {sha256 = "36abdfe0d1704fe09b08d233165f312dad7d7d0ecaaca5fb3bb869f4838a2d0b"}}, - {name = "duckdb-1.3.2-cp312-cp312-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/32/40/9aa3267f3631ae06b30fb1045a48628f4dba7beb2efb485c0282b4a73367/duckdb-1.3.2-cp312-cp312-musllinux_1_2_x86_64.whl",hashes = {sha256 = "3380aae1c4f2af3f37b0bf223fabd62077dd0493c84ef441e69b45167188e7b6"}}, - {name = "duckdb-1.3.2-cp312-cp312-win_amd64.whl",url = "https://files.pythonhosted.org/packages/8c/8d/47bf95f6999b327cf4da677e150cfce802abf9057b61a93a1f91e89d748c/duckdb-1.3.2-cp312-cp312-win_amd64.whl",hashes = {sha256 = "11af73963ae174aafd90ea45fb0317f1b2e28a7f1d9902819d47c67cc957d49c"}}, - {name = "duckdb-1.3.2-cp311-cp311-macosx_12_0_arm64.whl",url = "https://files.pythonhosted.org/packages/38/16/4cde40c37dd1f48d2f9ffa63027e8b668391c5cc32cbb59f7ca8b1cec6e2/duckdb-1.3.2-cp311-cp311-macosx_12_0_arm64.whl",hashes = {sha256 = "e1872cf63aae28c3f1dc2e19b5e23940339fc39fb3425a06196c5d00a8d01040"}}, - {name = "duckdb-1.3.2-cp311-cp311-macosx_12_0_universal2.whl",url = "https://files.pythonhosted.org/packages/22/ca/9ca65db51868604007114a27cc7d44864d89328ad6a934668626618147ff/duckdb-1.3.2-cp311-cp311-macosx_12_0_universal2.whl",hashes = {sha256 = "db256c206056468ae6a9e931776bdf7debaffc58e19a0ff4fa9e7e1e82d38b3b"}}, - {name = "duckdb-1.3.2-cp311-cp311-macosx_12_0_x86_64.whl",url = "https://files.pythonhosted.org/packages/9e/ca/7f7cf01dd7731d358632fb516521f2962070a627558fb6fc3137e594bbaa/duckdb-1.3.2-cp311-cp311-macosx_12_0_x86_64.whl",hashes = {sha256 = "1d57df2149d6e4e0bd5198689316c5e2ceec7f6ac0a9ec11bc2b216502a57b34"}}, - {name = "duckdb-1.3.2-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl",url = "https://files.pythonhosted.org/packages/4c/7f/38e518b8f51299410dcad9f1e99f1c99f3592516581467a2da344d3b5951/duckdb-1.3.2-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl",hashes = {sha256 = "54f76c8b1e2a19dfe194027894209ce9ddb073fd9db69af729a524d2860e4680"}}, - {name = "duckdb-1.3.2-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl",url = "https://files.pythonhosted.org/packages/90/a3/41f3d42fddd9629846aac328eb295170e76782d8dfc5e58b3584b96fa296/duckdb-1.3.2-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl",hashes = {sha256 = "45bea70b3e93c6bf766ce2f80fc3876efa94c4ee4de72036417a7bd1e32142fe"}}, - {name = "duckdb-1.3.2-cp311-cp311-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/11/8e/c5444b6890ae7f00836fd0cd17799abbcc3066bbab32e90b04aa8a8a5087/duckdb-1.3.2-cp311-cp311-musllinux_1_2_x86_64.whl",hashes = {sha256 = "003f7d36f0d8a430cb0e00521f18b7d5ee49ec98aaa541914c6d0e008c306f1a"}}, - {name = "duckdb-1.3.2-cp311-cp311-win_amd64.whl",url = "https://files.pythonhosted.org/packages/87/a1/e240bd07671542ddf2084962e68a7d5c9b068d8da3f938e935af69441355/duckdb-1.3.2-cp311-cp311-win_amd64.whl",hashes = {sha256 = "0eb210cedf08b067fa90c666339688f1c874844a54708562282bc54b0189aac6"}}, - {name = "duckdb-1.3.2-cp310-cp310-macosx_12_0_arm64.whl",url = "https://files.pythonhosted.org/packages/6a/a0/13f45e67565800826ce0af12a0ab68fe9502dcac0e39bc03bf8a8cba61da/duckdb-1.3.2-cp310-cp310-macosx_12_0_arm64.whl",hashes = {sha256 = "14676651b86f827ea10bf965eec698b18e3519fdc6266d4ca849f5af7a8c315e"}}, - {name = "duckdb-1.3.2-cp310-cp310-macosx_12_0_universal2.whl",url = "https://files.pythonhosted.org/packages/ec/28/daf9c01b5cb4058fc80070c74284c52f11581c888db2b0e73ca48f9bae23/duckdb-1.3.2-cp310-cp310-macosx_12_0_universal2.whl",hashes = {sha256 = "e584f25892450757919639b148c2410402b17105bd404017a57fa9eec9c98919"}}, - {name = "duckdb-1.3.2-cp310-cp310-macosx_12_0_x86_64.whl",url = "https://files.pythonhosted.org/packages/77/e0/5b50014d92eb6c879608183f6184186ab2cf324dd33e432174af93d19a44/duckdb-1.3.2-cp310-cp310-macosx_12_0_x86_64.whl",hashes = {sha256 = "84a19f185ee0c5bc66d95908c6be19103e184b743e594e005dee6f84118dc22c"}}, - {name = "duckdb-1.3.2-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl",url = "https://files.pythonhosted.org/packages/a2/ff/291d74f8b4c988b2a7ee5f65d3073fe0cf4c6a4505aa1a6f28721bb2ebe2/duckdb-1.3.2-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl",hashes = {sha256 = "186fc3f98943e97f88a1e501d5720b11214695571f2c74745d6e300b18bef80e"}}, - {name = "duckdb-1.3.2-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl",url = "https://files.pythonhosted.org/packages/65/50/9a1289619447d93a8c63b08f6ab22e1e6ce73a681e0dceb0cd0ea7558613/duckdb-1.3.2-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl",hashes = {sha256 = "6b7e6bb613b73745f03bff4bb412f362d4a1e158bdcb3946f61fd18e9e1a8ddf"}}, - {name = "duckdb-1.3.2-cp310-cp310-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/e0/d1/8dc959e3ca16c4c32ab34e28ceea189edc9bf32523aaa976080fd2101835/duckdb-1.3.2-cp310-cp310-musllinux_1_2_x86_64.whl",hashes = {sha256 = "1c90646b52a0eccda1f76b10ac98b502deb9017569e84073da00a2ab97763578"}}, - {name = "duckdb-1.3.2-cp310-cp310-win_amd64.whl",url = "https://files.pythonhosted.org/packages/7b/e8/126767fe5acbe01230f7431d999a2c2ef028ffdaebda8fe32ddb57628815/duckdb-1.3.2-cp310-cp310-win_amd64.whl",hashes = {sha256 = "4cdffb1e60defbfa75407b7f2ccc322f535fd462976940731dfd1644146f90c6"}}, - {name = "duckdb-1.3.2-cp39-cp39-macosx_12_0_arm64.whl",url = "https://files.pythonhosted.org/packages/e5/e1/2e98d78eebcf405f1900e22c4ec3f5f7e2d4ed889693f95103255f6a1452/duckdb-1.3.2-cp39-cp39-macosx_12_0_arm64.whl",hashes = {sha256 = "18862e3b8a805f2204543d42d5f103b629cb7f7f2e69f5188eceb0b8a023f0af"}}, - {name = "duckdb-1.3.2-cp39-cp39-macosx_12_0_universal2.whl",url = "https://files.pythonhosted.org/packages/f7/73/ee28ba97b5dd2da5d1bb4e592e79384d54288d82ec34e75c068012b36f53/duckdb-1.3.2-cp39-cp39-macosx_12_0_universal2.whl",hashes = {sha256 = "75ed129761b6159f0b8eca4854e496a3c4c416e888537ec47ff8eb35fda2b667"}}, - {name = "duckdb-1.3.2-cp39-cp39-macosx_12_0_x86_64.whl",url = "https://files.pythonhosted.org/packages/a6/0b/67f938499c6c52df90c821a8a3f25699274ce7fbf46fa9227bc4c0bd92fe/duckdb-1.3.2-cp39-cp39-macosx_12_0_x86_64.whl",hashes = {sha256 = "875193ae9f718bc80ab5635435de5b313e3de3ec99420a9b25275ddc5c45ff58"}}, - {name = "duckdb-1.3.2-cp39-cp39-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl",url = "https://files.pythonhosted.org/packages/6c/2d/373665ef567ef0d6bcf9caf9803b697168f9e6904aff99d5782a1c5e91d1/duckdb-1.3.2-cp39-cp39-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl",hashes = {sha256 = "09b5fd8a112301096668903781ad5944c3aec2af27622bd80eae54149de42b42"}}, - {name = "duckdb-1.3.2-cp39-cp39-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl",url = "https://files.pythonhosted.org/packages/b1/18/9a89fa02689db8496d414f96d2e0ea56a24910c546c126c8a4626f3a51ee/duckdb-1.3.2-cp39-cp39-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl",hashes = {sha256 = "10cb87ad964b989175e7757d7ada0b1a7264b401a79be2f828cf8f7c366f7f95"}}, - {name = "duckdb-1.3.2-cp39-cp39-musllinux_1_2_x86_64.whl",url = "https://files.pythonhosted.org/packages/2e/97/2b09ad149081d75534fe063ff6a1b4b91fffe7e17816a7d9261aa7456788/duckdb-1.3.2-cp39-cp39-musllinux_1_2_x86_64.whl",hashes = {sha256 = "4389fc3812e26977034fe3ff08d1f7dbfe6d2d8337487b4686f2b50e254d7ee3"}}, - {name = "duckdb-1.3.2-cp39-cp39-win_amd64.whl",url = "https://files.pythonhosted.org/packages/6d/78/8c096f1ef46205f561e7e62d1aff749a079cf57f5c433485f55e15463041/duckdb-1.3.2-cp39-cp39-win_amd64.whl",hashes = {sha256 = "07952ec6f45dd3c7db0f825d231232dc889f1f2490b97a4e9b7abb6830145a19"}}, -] -marker = "\"default\" in dependency_groups" - -[packages.tool.pdm] -dependencies = [] - [[packages]] name = "httpx-folio" version = "0.2.3" @@ -501,11 +501,11 @@ dependencies = [ [[packages]] name = "types-psycopg2" -version = "2.9.21.20250809" +version = "2.9.21.20250915" requires-python = ">=3.9" -sdist = {name = "types_psycopg2-2.9.21.20250809.tar.gz", url = "https://files.pythonhosted.org/packages/17/d0/66f3f04bab48bfdb2c8b795b2b3e75eb20c7d1fb0516916db3be6aa4a683/types_psycopg2-2.9.21.20250809.tar.gz", hashes = {sha256 = "b7c2cbdcf7c0bd16240f59ba694347329b0463e43398de69784ea4dee45f3c6d"}} +sdist = {name = "types_psycopg2-2.9.21.20250915.tar.gz", url = "https://files.pythonhosted.org/packages/8f/20/3dcb89df8d1661cf6c4c2d9f84d4ba94dde48559cdcf7b536a380a9c387f/types_psycopg2-2.9.21.20250915.tar.gz", hashes = {sha256 = "bfeb8f54c32490e7b5edc46215ab4163693192bc90407b4a023822de9239f5c8"}} wheels = [ - {name = "types_psycopg2-2.9.21.20250809-py3-none-any.whl",url = "https://files.pythonhosted.org/packages/7b/98/182497602921c47fadc8470d51a32e5c75343c8931c0b572a5c4ae3b948b/types_psycopg2-2.9.21.20250809-py3-none-any.whl",hashes = {sha256 = "59b7b0ed56dcae9efae62b8373497274fc1a0484bdc5135cdacbe5a8f44e1d7b"}}, + {name = "types_psycopg2-2.9.21.20250915-py3-none-any.whl",url = "https://files.pythonhosted.org/packages/93/4d/ebf1c72809a30150ad142074e1ad5101304f7569c0df2fa872906d76d0af/types_psycopg2-2.9.21.20250915-py3-none-any.whl",hashes = {sha256 = "eefe5ccdc693fc086146e84c9ba437bb278efe1ef330b299a0cb71169dc6c55f"}}, ] marker = "\"types\" in dependency_groups" @@ -529,11 +529,11 @@ dependencies = [ [[packages]] name = "xlsxwriter" -version = "3.2.5" +version = "3.2.9" requires-python = ">=3.8" -sdist = {name = "xlsxwriter-3.2.5.tar.gz", url = "https://files.pythonhosted.org/packages/a7/47/7704bac42ac6fe1710ae099b70e6a1e68ed173ef14792b647808c357da43/xlsxwriter-3.2.5.tar.gz", hashes = {sha256 = "7e88469d607cdc920151c0ab3ce9cf1a83992d4b7bc730c5ffdd1a12115a7dbe"}} +sdist = {name = "xlsxwriter-3.2.9.tar.gz", url = "https://files.pythonhosted.org/packages/46/2c/c06ef49dc36e7954e55b802a8b231770d286a9758b3d936bd1e04ce5ba88/xlsxwriter-3.2.9.tar.gz", hashes = {sha256 = "254b1c37a368c444eac6e2f867405cc9e461b0ed97a3233b2ac1e574efb4140c"}} wheels = [ - {name = "xlsxwriter-3.2.5-py3-none-any.whl",url = "https://files.pythonhosted.org/packages/fa/34/a22e6664211f0c8879521328000bdcae9bf6dbafa94a923e531f6d5b3f73/xlsxwriter-3.2.5-py3-none-any.whl",hashes = {sha256 = "4f4824234e1eaf9d95df9a8fe974585ff91d0f5e3d3f12ace5b71e443c1c6abd"}}, + {name = "xlsxwriter-3.2.9-py3-none-any.whl",url = "https://files.pythonhosted.org/packages/3a/0c/3662f4a66880196a590b202f0db82d919dd2f89e99a27fadef91c4a33d41/xlsxwriter-3.2.9-py3-none-any.whl",hashes = {sha256 = "9a5db42bc5dff014806c58a20b9eae7322a134abb6fce3c92c181bfb275ec5b3"}}, ] marker = "\"default\" in dependency_groups" @@ -925,11 +925,11 @@ dependencies = [ [[packages]] name = "types-requests" -version = "2.32.4.20250809" +version = "2.32.4.20250913" requires-python = ">=3.9" -sdist = {name = "types_requests-2.32.4.20250809.tar.gz", url = "https://files.pythonhosted.org/packages/ed/b0/9355adb86ec84d057fea765e4c49cce592aaf3d5117ce5609a95a7fc3dac/types_requests-2.32.4.20250809.tar.gz", hashes = {sha256 = "d8060de1c8ee599311f56ff58010fb4902f462a1470802cf9f6ed27bc46c4df3"}} +sdist = {name = "types_requests-2.32.4.20250913.tar.gz", url = "https://files.pythonhosted.org/packages/36/27/489922f4505975b11de2b5ad07b4fe1dca0bca9be81a703f26c5f3acfce5/types_requests-2.32.4.20250913.tar.gz", hashes = {sha256 = "abd6d4f9ce3a9383f269775a9835a4c24e5cd6b9f647d64f88aa4613c33def5d"}} wheels = [ - {name = "types_requests-2.32.4.20250809-py3-none-any.whl",url = "https://files.pythonhosted.org/packages/2b/6f/ec0012be842b1d888d46884ac5558fd62aeae1f0ec4f7a581433d890d4b5/types_requests-2.32.4.20250809-py3-none-any.whl",hashes = {sha256 = "f73d1832fb519ece02c85b1f09d5f0dd3108938e7d47e7f94bbfa18a6782b163"}}, + {name = "types_requests-2.32.4.20250913-py3-none-any.whl",url = "https://files.pythonhosted.org/packages/2a/20/9a227ea57c1285986c4cf78400d0a91615d25b24e257fd9e2969606bdfae/types_requests-2.32.4.20250913-py3-none-any.whl",hashes = {sha256 = "78c9c1fffebbe0fa487a418e0fa5252017e9c60d1a2da394077f1780f655d7e1"}}, ] marker = "\"types\" in dependency_groups" @@ -965,7 +965,7 @@ marker = "sys_platform == \"win32\" and \"default\" in dependency_groups" dependencies = [] [tool.pdm] -hashes = {sha256 = "9cd31bd06db4ed0781a4f5b9f507c9fb109b9e48b412ee0b76a1115f841b603b"} +hashes = {sha256 = "9edcf2127050cd04b1c55f2c5d0ef0c306790635417a4e7c2a0ed6e601c76e78"} strategy = ["inherit_metadata", "static_urls"] [[tool.pdm.targets]] diff --git a/pyproject.toml b/pyproject.toml index b23a302..5800ba1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -11,12 +11,12 @@ authors = [ {name = "Nassib Nassar", email = "nassib@indexdata.com"}, ] dependencies = [ - "duckdb>=0.6.1", + "duckdb>=0.6.1,<1.4", "psycopg2-binary>=2.9.5", "tqdm>=4.64.1", "XlsxWriter>=3.0.6", "httpx-folio>=0.2.3", - "orjson>=2.2.1", + "orjson>=3.9", "psycopg>=3.2.0", ] requires-python = ">=3.9" diff --git a/src/ldlite/__init__.py b/src/ldlite/__init__.py index 59381f4..063bea9 100644 --- a/src/ldlite/__init__.py +++ b/src/ldlite/__init__.py @@ -62,8 +62,6 @@ from ._xlsx import to_xlsx if TYPE_CHECKING: - from collections.abc import Iterator - from _typeshed import dbapi from httpx_folio.query import QueryType @@ -364,7 +362,7 @@ def query( # noqa: C901, PLR0912, PLR0913, PLR0915 self.page_size, query=cast("QueryType", query), ) - total_records = cast("int", next(records)) + (total_records, _) = next(records) total = min(total_records, limit or total_records) if self._verbose: print("ldlite: estimated row count: " + str(total), file=sys.stderr) @@ -405,7 +403,7 @@ def on_processed_limit() -> bool: self._db.ingest_records( prefix, on_processed_limit if limit is not None else on_processed, - cast("Iterator[tuple[bytes, bytes] | tuple[int, str]]", records), + records, ) pbar.close() diff --git a/src/ldlite/_database.py b/src/ldlite/_database.py index a18a0bf..6f7ad14 100644 --- a/src/ldlite/_database.py +++ b/src/ldlite/_database.py @@ -173,31 +173,18 @@ def ingest_records( self, prefix: Prefix, on_processed: Callable[[], bool], - records: Iterator[tuple[bytes, bytes] | tuple[int, str]], + records: Iterator[tuple[int, bytes]], ) -> None: with closing(self._conn_factory()) as conn: self._prepare_raw_table(conn, prefix) + insert_sql = self._insert_record_sql.format( table=prefix.raw_table_name, ).as_string() with closing(conn.cursor()) as cur: - fr = next(records) - if isinstance(fr[0], bytes): - record = fr - while record is not None: - (pkey, rb) = record - cur.execute( - insert_sql, - (int.from_bytes(pkey, "big"), rb.decode()), - ) - if not on_processed(): - break - record = cast("tuple[bytes, bytes]", next(records, None)) - else: - cur.execute(insert_sql, fr) - for r in records: - cur.execute(insert_sql, r) - if not on_processed(): - break + for pkey, r in records: + cur.execute(insert_sql, (pkey, r.decode())) + if not on_processed(): + break conn.commit() diff --git a/src/ldlite/_folio.py b/src/ldlite/_folio.py index b6e4cd4..c7f8046 100644 --- a/src/ldlite/_folio.py +++ b/src/ldlite/_folio.py @@ -16,6 +16,21 @@ if TYPE_CHECKING: from collections.abc import Iterator +_SOURCESTATS = { + "/source-storage/records": "/source-storage/records", + "/source-storage/stream/records": "/source-storage/records", + "/source-storage/source-records": "/source-storage/source-records", + "/source-storage/stream/source-records": "/source-storage/source-records", + # This endpoint is in the docs but not actually in FOLIO? + # "/source-storage/stream/marc-record-identifiers": "???", +} +_SOURCESTREAM = { + "/source-storage/records": "/source-storage/stream/records", + "/source-storage/stream/records": "/source-storage/stream/records", + "/source-storage/source-records": "/source-storage/stream/source-records", + "/source-storage/stream/source-records": "/source-storage/stream/source-records", +} + class FolioClient: def __init__(self, params: FolioParams): @@ -28,14 +43,14 @@ def iterate_records( retries: int, page_size: int, query: QueryType | None = None, - ) -> Iterator[int | tuple[bytes, bytes] | tuple[int, str]]: + ) -> Iterator[tuple[int, bytes]]: """Iterates all records for a given path. Returns: A tuple of the autoincrementing key + the json for each record. The first result will be the total record count. """ - is_srs = path.startswith("/source-storage") + is_srs = path.lower() in _SOURCESTATS # this is Java's max size of int because we want all the source records params = QueryParams(query, 2_147_483_647 - 1 if is_srs else page_size) @@ -46,26 +61,23 @@ def iterate_records( ), ) as client: res = client.get( - # Hardcode the source storage endpoint that returns stats - # even if the user passes in the stream endpoint - path if not is_srs else "/source-storage/source-records", + path if not is_srs else _SOURCESTATS[path.lower()], params=params.stats(), ) res.raise_for_status() j = orjson.loads(res.text) r = int(j["totalRecords"]) - yield r + yield (r, b"") if r == 0: return pkey = count(start=1) if is_srs: - # this is a more stable endpoint for srs - # we want it to be transparent so if the user wants srs we just use it + # streaming is a more stable endpoint for source records with client.stream( "GET", - "/source-storage/stream/source-records", + _SOURCESTREAM[path.lower()], params=params.normalized(), ) as res: res.raise_for_status() @@ -76,7 +88,7 @@ def iterate_records( record += f if len(f) == 0 or f[-1] != "}": continue - yield (next(pkey), record) + yield (next(pkey), orjson.dumps(orjson.Fragment(record))) record = "" return @@ -103,7 +115,7 @@ def iterate_records( last = None for r in (o for o in orjson.loads(res.text)[key] if o is not None): last = r - yield (next(pkey).to_bytes(4, "big"), orjson.dumps(r)) + yield (next(pkey), orjson.dumps(r)) if last is None: return diff --git a/src/ldlite/_sqlx.py b/src/ldlite/_sqlx.py index 18743d1..828bb34 100644 --- a/src/ldlite/_sqlx.py +++ b/src/ldlite/_sqlx.py @@ -77,7 +77,7 @@ def ingest_records( self, prefix: Prefix, on_processed: Callable[[], bool], - records: Iterator[tuple[bytes, bytes] | tuple[int, str]], + records: Iterator[tuple[int, bytes]], ) -> None: if self._dbtype != DBType.POSTGRES: super().ingest_records(prefix, on_processed, records) @@ -86,38 +86,25 @@ def ingest_records( with closing(self._conn_factory()) as conn: self._prepare_raw_table(conn, prefix) - fr = next(records) - copy_from = "COPY {table} (__id, jsonb) FROM STDIN" - if is_bytes := isinstance(fr[0], bytes): - copy_from += " (FORMAT BINARY)" - if pgconn := as_postgres(conn, self._dbtype): with ( pgconn.cursor() as cur, cur.copy( - sql.SQL(copy_from).format(table=prefix.raw_table_name), + sql.SQL( + "COPY {table} (__id, jsonb) FROM STDIN (FORMAT BINARY)", + ).format(table=prefix.raw_table_name), ) as copy, ): - if is_bytes: - # postgres jsonb is always version 1 - # and it always goes in front - jver = (1).to_bytes(1, "big") - record = fr - while record is not None: - pkey, rb = record - rbpg = bytearray() - rbpg.extend(jver) - rbpg.extend(cast("bytes", rb)) - copy.write_row((pkey, rbpg)) - if not on_processed(): - break - record = cast("tuple[bytes, bytes]", next(records, None)) - else: - copy.write_row(fr) - for r in records: - copy.write_row(r) - if not on_processed(): - break + # postgres jsonb is always version 1 + # and it always goes in front + jver = (1).to_bytes(1, "big") + for pkey, r in records: + rb = bytearray() + rb.extend(jver) + rb.extend(r) + copy.write_row((pkey.to_bytes(4, "big"), rb)) + if not on_processed(): + break pgconn.commit() diff --git a/tests/test_endtoend.py b/tests/test_endtoend.py index 6bd6951..d8aada2 100644 --- a/tests/test_endtoend.py +++ b/tests/test_endtoend.py @@ -54,14 +54,22 @@ def test_endtoend( assert actual == expected -def test_endtoend_srs(folio_params: tuple[bool, FolioParams]) -> None: +@parametrize( + srs=[ + "/source-storage/records", + "/source-storage/stream/records", + "/source-storage/source-records", + "/source-storage/stream/source-records", + ], +) +def test_endtoend_srs(folio_params: tuple[bool, FolioParams], srs: str) -> None: from ldlite import LDLite as uut ld = uut() db = ld.connect_db() ld.connect_folio(*astuple(folio_params[1])) - ld.query(table="test", path="/source-storage/source-records", limit=10) + ld.query(table="test", path=srs, limit=10) db.execute("SELECT COUNT(DISTINCT COLUMNS(*)) FROM test__t;") actual = cast("tuple[int]", db.fetchone())[0] From bf148993e1246c346a4c5ba1c564b9d32b9d6643 Mon Sep 17 00:00:00 2001 From: Katherine Bargar Date: Thu, 18 Sep 2025 16:33:28 +0000 Subject: [PATCH 6/7] Version bump and update changelog --- CHANGELOG.md | 15 +++++++++++---- pyproject.toml | 10 +++++++++- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2373343..0b958f7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,17 +11,24 @@ Please see [MIGRATING.md](./MIGRATING.md) for information on breaking changes. ### Added -- Connections returned from the LDLite.connect_db* methods are now isolated from the ones used internally. - ### Fixed +### Changed + +### Removed + +## [3.2.0] - September 2025 + +### Added + - Source Storage endpoints now stream only if streaming is available. +- Connections returned from the LDLite.connect_db methods are now isolated from the ones used internally. ### Changed - psycopg3 is now used for internal operations. LDLite.connect_db_postgres will return a psycopg3 connection instead of psycopg2 in the next major release. -- psycopg2 is now installed using the binary version. -- Refactored internal database handling logic +- psycopg2 is now installed as the binary version. +- Refactored internal table management to be safer and more resilient. - Ingesting data into postgres now uses COPY FROM which significantly improves the download performance. ### Removed diff --git a/pyproject.toml b/pyproject.toml index 5800ba1..1de1d21 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,19 +4,27 @@ build-backend = "pdm.backend" [project] name = "ldlite" -version = "3.1.4" +version = "3.2.0" description = "Lightweight analytics tool for FOLIO services" authors = [ {name = "Katherine Bargar", email = "kbargar@fivecolleges.edu"}, {name = "Nassib Nassar", email = "nassib@indexdata.com"}, ] dependencies = [ + # 0.6.1 was the original pinned dependency version + # 1.4 slightly changes data formats and fails tests "duckdb>=0.6.1,<1.4", + # 2.9.5 was the original pinned dependency version "psycopg2-binary>=2.9.5", + # 4.64.1 was the original pinned dependency version "tqdm>=4.64.1", + # 3.0.6 was the original pinned dependency version "XlsxWriter>=3.0.6", + # 0.2 has query parameter handling, 0.2.3 is a required bug fix version "httpx-folio>=0.2.3", + # 3.9 introduces orjson.Fragment "orjson>=3.9", + # 3.2 changes the sql.SQL.format signature "psycopg>=3.2.0", ] requires-python = ">=3.9" From f6f523a9f9d0e0d92d0749db392575ec5c4c16cb Mon Sep 17 00:00:00 2001 From: Katherine Bargar Date: Fri, 19 Sep 2025 16:25:39 +0000 Subject: [PATCH 7/7] Fix limit in query method --- src/ldlite/__init__.py | 6 +++--- tests/test___init__.py | 16 ++++++++++++++++ 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/src/ldlite/__init__.py b/src/ldlite/__init__.py index 063bea9..4f619d7 100644 --- a/src/ldlite/__init__.py +++ b/src/ldlite/__init__.py @@ -367,7 +367,7 @@ def query( # noqa: C901, PLR0912, PLR0913, PLR0915 if self._verbose: print("ldlite: estimated row count: " + str(total), file=sys.stderr) - p_count = count(0) + p_count = count(1) processed = 0 pbar: tqdm | PbarNoop # type:ignore[type-arg] if not self._quiet: @@ -396,9 +396,9 @@ def on_processed() -> bool: def on_processed_limit() -> bool: pbar.update(1) - nonlocal processed + nonlocal processed, limit processed = next(p_count) - return limit is None or processed >= limit + return limit is None or processed < limit self._db.ingest_records( prefix, diff --git a/tests/test___init__.py b/tests/test___init__.py index 88303b5..e880ebf 100644 --- a/tests/test___init__.py +++ b/tests/test___init__.py @@ -1,4 +1,5 @@ from dataclasses import astuple, dataclass +from typing import cast import httpx import pytest @@ -16,6 +17,21 @@ def test_ok_legacy(folio_params: tuple[bool, FolioParams]) -> None: ld.select(table="g__t") +def test_ok_limit(folio_params: tuple[bool, FolioParams]) -> None: + from ldlite import LDLite as uut + + ld = uut() + db = ld.connect_db() + + ld.connect_folio(*astuple(folio_params[1])) + ld.page_size = 2 + ld.query(table="g", path="/groups", query="cql.allRecords=1 sortby id", limit=5) + + db.execute("SELECT COUNT(DISTINCT COLUMNS(*)) FROM g__t;") + actual = cast("tuple[int]", db.fetchone())[0] + assert actual == 5 + + def test_ok_trailing_slash(folio_params: tuple[bool, FolioParams]) -> None: if folio_params[0]: pytest.skip("Specify an okapi environment with --folio-base-url to run")