From 2109272035cd06a68cc5d680838d84c21784d73e Mon Sep 17 00:00:00 2001 From: "chandr-andr (Kiselev Aleksandr)" Date: Wed, 16 Oct 2024 22:03:02 +0200 Subject: [PATCH 1/6] Bumped version to 0.8.2 Signed-off-by: chandr-andr (Kiselev Aleksandr) --- Cargo.lock | 2 +- Cargo.toml | 2 +- tox.ini | 2 ++ 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f8669a02..629b9f22 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -994,7 +994,7 @@ dependencies = [ [[package]] name = "psqlpy" -version = "0.8.1" +version = "0.8.2" dependencies = [ "byteorder", "bytes", diff --git a/Cargo.toml b/Cargo.toml index b7adfbd7..68c20942 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "psqlpy" -version = "0.8.1" +version = "0.8.2" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/tox.ini b/tox.ini index 3f11c9fa..fb207ade 100644 --- a/tox.ini +++ b/tox.ini @@ -1,6 +1,7 @@ [tox] isolated_build = true env_list = + py313 py312 py311 py310 @@ -9,6 +10,7 @@ env_list = [gh] python = + 3.13 = py313 3.12 = py312 3.11 = py311 3.10 = py310 From 3ad8e9b4a24112cf601a46d75270a0d331fcc78c Mon Sep 17 00:00:00 2001 From: "chandr-andr (Kiselev Aleksandr)" Date: Wed, 16 Oct 2024 22:04:45 +0200 Subject: [PATCH 2/6] Added Python 3.13 support Signed-off-by: chandr-andr (Kiselev Aleksandr) --- .github/workflows/test.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 7c1ec37d..b364d543 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -54,7 +54,7 @@ jobs: name: ${{matrix.job.os}}-${{matrix.py_version}} strategy: matrix: - py_version: ["3.8", "3.9", "3.10", "3.11", "3.12"] + py_version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13"] job: - os: ubuntu-latest ssl_cmd: sudo apt-get update && sudo apt-get install libssl-dev openssl From 22a569440d52be2d8379795380b0ab19568ec0dd Mon Sep 17 00:00:00 2001 From: "chandr-andr (Kiselev Aleksandr)" Date: Wed, 16 Oct 2024 22:17:29 +0200 Subject: [PATCH 3/6] Added Python 3.13 support Signed-off-by: chandr-andr (Kiselev Aleksandr) --- python/tests/test_binary_copy.py | 112 +++++++++++++++++++++++++++++++ python/tests/test_connection.py | 56 ---------------- python/tests/test_transaction.py | 56 ---------------- tox.ini | 15 +++++ 4 files changed, 127 insertions(+), 112 deletions(-) create mode 100644 python/tests/test_binary_copy.py diff --git a/python/tests/test_binary_copy.py b/python/tests/test_binary_copy.py new file mode 100644 index 00000000..a54bc665 --- /dev/null +++ b/python/tests/test_binary_copy.py @@ -0,0 +1,112 @@ +import os +import typing +from io import BytesIO + +from pgpq import ArrowToPostgresBinaryEncoder +from pyarrow import parquet + +from psqlpy import ConnectionPool + + +async def test_binary_copy_to_table_in_connection( + psql_pool: ConnectionPool, +) -> None: + """Test binary copy in connection.""" + table_name: typing.Final = "cars" + await psql_pool.execute(f"DROP TABLE IF EXISTS {table_name}") + await psql_pool.execute( + """ +CREATE TABLE IF NOT EXISTS cars ( + model VARCHAR, + mpg FLOAT8, + cyl INTEGER, + disp FLOAT8, + hp INTEGER, + drat FLOAT8, + wt FLOAT8, + qsec FLOAT8, + vs INTEGER, + am INTEGER, + gear INTEGER, + carb INTEGER +); +""", + ) + + arrow_table = parquet.read_table( + f"{os.path.dirname(os.path.abspath(__file__))}/test_data/MTcars.parquet", # noqa: PTH120, PTH100 + ) + encoder = ArrowToPostgresBinaryEncoder(arrow_table.schema) + buf = BytesIO() + buf.write(encoder.write_header()) + for batch in arrow_table.to_batches(): + buf.write(encoder.write_batch(batch)) + buf.write(encoder.finish()) + buf.seek(0) + + async with psql_pool.acquire() as connection: + inserted_rows = await connection.binary_copy_to_table( + source=buf, + table_name=table_name, + ) + + expected_inserted_row: typing.Final = 32 + + assert inserted_rows == expected_inserted_row + + real_table_rows: typing.Final = await psql_pool.execute( + f"SELECT COUNT(*) AS rows_count FROM {table_name}", + ) + assert real_table_rows.result()[0]["rows_count"] == expected_inserted_row + + +async def test_binary_copy_to_table_in_transaction( + psql_pool: ConnectionPool, +) -> None: + """Test binary copy in transaction.""" + table_name: typing.Final = "cars" + await psql_pool.execute(f"DROP TABLE IF EXISTS {table_name}") + await psql_pool.execute( + """ +CREATE TABLE IF NOT EXISTS cars ( + model VARCHAR, + mpg FLOAT8, + cyl INTEGER, + disp FLOAT8, + hp INTEGER, + drat FLOAT8, + wt FLOAT8, + qsec FLOAT8, + vs INTEGER, + am INTEGER, + gear INTEGER, + carb INTEGER +); +""", + ) + + arrow_table = parquet.read_table( + f"{os.path.dirname(os.path.abspath(__file__))}/test_data/MTcars.parquet", # noqa: PTH120, PTH100 + ) + encoder = ArrowToPostgresBinaryEncoder(arrow_table.schema) + buf = BytesIO() + buf.write(encoder.write_header()) + for batch in arrow_table.to_batches(): + buf.write(encoder.write_batch(batch)) + buf.write(encoder.finish()) + buf.seek(0) + + async with psql_pool.acquire() as connection: + inserted_rows = await connection.binary_copy_to_table( + source=buf, + table_name=table_name, + ) + + expected_inserted_row: typing.Final = 32 + + assert inserted_rows == expected_inserted_row + + real_table_rows: typing.Final = await psql_pool.execute( + f"SELECT COUNT(*) AS rows_count FROM {table_name}", + ) + assert real_table_rows.result()[0]["rows_count"] == expected_inserted_row diff --git a/python/tests/test_connection.py b/python/tests/test_connection.py index 9efcb419..3d25556d 100644 --- a/python/tests/test_connection.py +++ b/python/tests/test_connection.py @@ -1,12 +1,8 @@ from __future__ import annotations -import os import typing -from io import BytesIO import pytest -from pgpq import ArrowToPostgresBinaryEncoder -from pyarrow import parquet from tests.helpers import count_rows_in_test_table from psqlpy import ConnectionPool, Cursor, QueryResult, Transaction @@ -186,58 +182,6 @@ async def test_closed_connection_error( await connection.execute("SELECT 1") -async def test_binary_copy_to_table( - psql_pool: ConnectionPool, -) -> None: - """Test binary copy in connection.""" - table_name: typing.Final = "cars" - await psql_pool.execute(f"DROP TABLE IF EXISTS {table_name}") - await psql_pool.execute( - """ -CREATE TABLE IF NOT EXISTS cars ( - model VARCHAR, - mpg FLOAT8, - cyl INTEGER, - disp FLOAT8, - hp INTEGER, - drat FLOAT8, - wt FLOAT8, - qsec FLOAT8, - vs INTEGER, - am INTEGER, - gear INTEGER, - carb INTEGER -); -""", - ) - - arrow_table = parquet.read_table( - f"{os.path.dirname(os.path.abspath(__file__))}/test_data/MTcars.parquet", # noqa: PTH120, PTH100 - ) - encoder = ArrowToPostgresBinaryEncoder(arrow_table.schema) - buf = BytesIO() - buf.write(encoder.write_header()) - for batch in arrow_table.to_batches(): - buf.write(encoder.write_batch(batch)) - buf.write(encoder.finish()) - buf.seek(0) - - async with psql_pool.acquire() as connection: - inserted_rows = await connection.binary_copy_to_table( - source=buf, - table_name=table_name, - ) - - expected_inserted_row: typing.Final = 32 - - assert inserted_rows == expected_inserted_row - - real_table_rows: typing.Final = await psql_pool.execute( - f"SELECT COUNT(*) AS rows_count FROM {table_name}", - ) - assert real_table_rows.result()[0]["rows_count"] == expected_inserted_row - - async def test_execute_batch_method(psql_pool: ConnectionPool) -> None: """Test `execute_batch` method.""" await psql_pool.execute(querystring="DROP TABLE IF EXISTS execute_batch") diff --git a/python/tests/test_transaction.py b/python/tests/test_transaction.py index b01302e4..6e34a3d6 100644 --- a/python/tests/test_transaction.py +++ b/python/tests/test_transaction.py @@ -1,12 +1,8 @@ from __future__ import annotations -import os import typing -from io import BytesIO import pytest -from pgpq import ArrowToPostgresBinaryEncoder -from pyarrow import parquet from tests.helpers import count_rows_in_test_table from psqlpy import ( @@ -346,58 +342,6 @@ async def test_transaction_send_underlying_connection_to_pool_manually( assert psql_pool.status().available == 1 -async def test_binary_copy_to_table( - psql_pool: ConnectionPool, -) -> None: - """Test binary copy in transaction.""" - table_name: typing.Final = "cars" - await psql_pool.execute(f"DROP TABLE IF EXISTS {table_name}") - await psql_pool.execute( - """ -CREATE TABLE IF NOT EXISTS cars ( - model VARCHAR, - mpg FLOAT8, - cyl INTEGER, - disp FLOAT8, - hp INTEGER, - drat FLOAT8, - wt FLOAT8, - qsec FLOAT8, - vs INTEGER, - am INTEGER, - gear INTEGER, - carb INTEGER -); -""", - ) - - arrow_table = parquet.read_table( - f"{os.path.dirname(os.path.abspath(__file__))}/test_data/MTcars.parquet", # noqa: PTH120, PTH100 - ) - encoder = ArrowToPostgresBinaryEncoder(arrow_table.schema) - buf = BytesIO() - buf.write(encoder.write_header()) - for batch in arrow_table.to_batches(): - buf.write(encoder.write_batch(batch)) - buf.write(encoder.finish()) - buf.seek(0) - - async with psql_pool.acquire() as connection: - inserted_rows = await connection.binary_copy_to_table( - source=buf, - table_name=table_name, - ) - - expected_inserted_row: typing.Final = 32 - - assert inserted_rows == expected_inserted_row - - real_table_rows: typing.Final = await psql_pool.execute( - f"SELECT COUNT(*) AS rows_count FROM {table_name}", - ) - assert real_table_rows.result()[0]["rows_count"] == expected_inserted_row - - async def test_execute_batch_method(psql_pool: ConnectionPool) -> None: """Test `execute_batch` method.""" await psql_pool.execute(querystring="DROP TABLE IF EXISTS execute_batch") diff --git a/tox.ini b/tox.ini index fb207ade..f5cdadf3 100644 --- a/tox.ini +++ b/tox.ini @@ -31,3 +31,18 @@ commands_pre = maturin develop commands = pytest -vv + +[testenv:py313] +skip_install = true +deps = + pytest>=7,<8 + anyio>=3,<4 + maturin>=1,<2 + pydantic>=2 + pyarrow>=17 + pgpq>=0.9 +allowlist_externals = maturin +commands_pre = + maturin develop +commands = + pytest -vv --ignore="./python/tests/test_binary_copy.py" From b511f5d10cafb4a282705ad5dd25a93377651936 Mon Sep 17 00:00:00 2001 From: "chandr-andr (Kiselev Aleksandr)" Date: Wed, 16 Oct 2024 22:18:56 +0200 Subject: [PATCH 4/6] Added Python 3.13 support Signed-off-by: chandr-andr (Kiselev Aleksandr) --- tox.ini | 1 - 1 file changed, 1 deletion(-) diff --git a/tox.ini b/tox.ini index f5cdadf3..37f2e422 100644 --- a/tox.ini +++ b/tox.ini @@ -39,7 +39,6 @@ deps = anyio>=3,<4 maturin>=1,<2 pydantic>=2 - pyarrow>=17 pgpq>=0.9 allowlist_externals = maturin commands_pre = From 0b38248f241f6ed418a40b6d27a2d0b8b945a838 Mon Sep 17 00:00:00 2001 From: "chandr-andr (Kiselev Aleksandr)" Date: Wed, 16 Oct 2024 22:21:27 +0200 Subject: [PATCH 5/6] Added Python 3.13 support Signed-off-by: chandr-andr (Kiselev Aleksandr) --- tox.ini | 1 - 1 file changed, 1 deletion(-) diff --git a/tox.ini b/tox.ini index 37f2e422..6c22fea2 100644 --- a/tox.ini +++ b/tox.ini @@ -39,7 +39,6 @@ deps = anyio>=3,<4 maturin>=1,<2 pydantic>=2 - pgpq>=0.9 allowlist_externals = maturin commands_pre = maturin develop From 3ba42e61b5ae46896c1568c4173f3548c05170cd Mon Sep 17 00:00:00 2001 From: "chandr-andr (Kiselev Aleksandr)" Date: Thu, 17 Oct 2024 11:15:41 +0200 Subject: [PATCH 6/6] Upgraded pyo3 deps Signed-off-by: chandr-andr (Kiselev Aleksandr) --- Cargo.lock | 76 ++++++++++++++----------------- Cargo.toml | 6 ++- python/tests/test_binary_copy.py | 3 ++ python/tests/test_connection.py | 19 ++++---- src/driver/common_options.rs | 19 ++++---- src/driver/connection.rs | 26 ++++++++++- src/driver/connection_pool.rs | 60 +++++++++++++++++++++++- src/driver/cursor.rs | 4 +- src/driver/transaction.rs | 16 ++++++- src/driver/transaction_options.rs | 12 ++--- src/lib.rs | 6 ++- src/query_result.rs | 4 ++ src/runtime.rs | 5 +- src/value_converter.rs | 25 ++++++++-- 14 files changed, 204 insertions(+), 77 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 629b9f22..d8ffb516 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -370,9 +370,9 @@ checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" [[package]] name = "futures" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" dependencies = [ "futures-channel", "futures-core", @@ -385,9 +385,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" dependencies = [ "futures-core", "futures-sink", @@ -395,15 +395,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" [[package]] name = "futures-executor" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" dependencies = [ "futures-core", "futures-task", @@ -412,15 +412,15 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" [[package]] name = "futures-macro" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", @@ -429,21 +429,21 @@ dependencies = [ [[package]] name = "futures-sink" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" [[package]] name = "futures-task" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" [[package]] name = "futures-util" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" dependencies = [ "futures-channel", "futures-core", @@ -512,12 +512,6 @@ version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" -[[package]] -name = "heck" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" - [[package]] name = "heck" version = "0.5.0" @@ -880,7 +874,7 @@ name = "postgres-derive" version = "0.4.5" source = "git+https://github.com/chandr-andr/rust-postgres.git?branch=psqlpy#e4e1047e701318b31c61330e428ebd8ade7ed1cb" dependencies = [ - "heck 0.5.0", + "heck", "proc-macro2", "quote", "syn 2.0.72", @@ -1014,7 +1008,7 @@ dependencies = [ "postgres-types", "postgres_array", "pyo3", - "pyo3-asyncio", + "pyo3-async-runtimes", "rust_decimal 1.36.0", "serde", "serde_json", @@ -1046,16 +1040,16 @@ dependencies = [ [[package]] name = "pyo3" -version = "0.21.2" +version = "0.22.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5e00b96a521718e08e03b1a622f01c8a8deb50719335de3f60b3b3950f069d8" +checksum = "3d922163ba1f79c04bc49073ba7b32fd5a8d3b76a87c955921234b8e77333c51" dependencies = [ "cfg-if", "chrono", "indoc", "libc", "memoffset", - "parking_lot", + "once_cell", "portable-atomic", "pyo3-build-config", "pyo3-ffi", @@ -1065,9 +1059,9 @@ dependencies = [ ] [[package]] -name = "pyo3-asyncio" -version = "0.20.0" -source = "git+https://github.com/chandr-andr/pyo3-asyncio.git#15752a639b1948b03768d4a3e016ec55feaca1b7" +name = "pyo3-async-runtimes" +version = "0.21.0" +source = "git+https://github.com/chandr-andr/pyo3-async-runtimes.git?branch=main#284bd36d0426a988026f878cae22abdb179795e6" dependencies = [ "futures", "once_cell", @@ -1078,9 +1072,9 @@ dependencies = [ [[package]] name = "pyo3-build-config" -version = "0.21.2" +version = "0.22.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7883df5835fafdad87c0d888b266c8ec0f4c9ca48a5bed6bbb592e8dedee1b50" +checksum = "bc38c5feeb496c8321091edf3d63e9a6829eab4b863b4a6a65f26f3e9cc6b179" dependencies = [ "once_cell", "target-lexicon", @@ -1088,9 +1082,9 @@ dependencies = [ [[package]] name = "pyo3-ffi" -version = "0.21.2" +version = "0.22.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01be5843dc60b916ab4dad1dca6d20b9b4e6ddc8e15f50c47fe6d85f1fb97403" +checksum = "94845622d88ae274d2729fcefc850e63d7a3ddff5e3ce11bd88486db9f1d357d" dependencies = [ "libc", "pyo3-build-config", @@ -1098,9 +1092,9 @@ dependencies = [ [[package]] name = "pyo3-macros" -version = "0.21.2" +version = "0.22.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77b34069fc0682e11b31dbd10321cbf94808394c56fd996796ce45217dfac53c" +checksum = "e655aad15e09b94ffdb3ce3d217acf652e26bbc37697ef012f5e5e348c716e5e" dependencies = [ "proc-macro2", "pyo3-macros-backend", @@ -1110,11 +1104,11 @@ dependencies = [ [[package]] name = "pyo3-macros-backend" -version = "0.21.2" +version = "0.22.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08260721f32db5e1a5beae69a55553f56b99bd0e1c3e6e0a5e8851a9d0f5a85c" +checksum = "ae1e3f09eecd94618f60a455a23def79f79eba4dc561a97324bf9ac8c6df30ce" dependencies = [ - "heck 0.4.1", + "heck", "proc-macro2", "pyo3-build-config", "quote", diff --git a/Cargo.toml b/Cargo.toml index 68c20942..87e0b191 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,10 +14,14 @@ pyo3 = { version = "*", features = [ "chrono", "experimental-async", "rust_decimal", + "py-clone", + "gil-refs", + "macros", ] } -pyo3-asyncio = { git = "https://github.com/chandr-andr/pyo3-asyncio.git", version = "0.20.0", features = [ +pyo3-async-runtimes = { git = "https://github.com/chandr-andr/pyo3-async-runtimes.git", branch = "main", features = [ "tokio-runtime", ] } + tokio = { version = "1.35.1", features = ["full"] } thiserror = "1.0.56" bytes = "1.5.0" diff --git a/python/tests/test_binary_copy.py b/python/tests/test_binary_copy.py index a54bc665..e1c3d473 100644 --- a/python/tests/test_binary_copy.py +++ b/python/tests/test_binary_copy.py @@ -2,11 +2,14 @@ import typing from io import BytesIO +import pytest from pgpq import ArrowToPostgresBinaryEncoder from pyarrow import parquet from psqlpy import ConnectionPool +pytestmark = pytest.mark.anyio + async def test_binary_copy_to_table_in_connection( psql_pool: ConnectionPool, diff --git a/python/tests/test_connection.py b/python/tests/test_connection.py index 3d25556d..f9f72d9a 100644 --- a/python/tests/test_connection.py +++ b/python/tests/test_connection.py @@ -142,15 +142,16 @@ async def test_connection_cursor( """Test cursor from Connection.""" connection = await psql_pool.connection() cursor: Cursor - all_results: list[dict[typing.Any, typing.Any]] = [] - - async with connection.transaction(), connection.cursor( - querystring=f"SELECT * FROM {table_name}", - ) as cursor: - async for cur_res in cursor: - all_results.extend(cur_res.result()) - - assert len(all_results) == number_database_records + transaction = connection.transaction() + await transaction.begin() + cursor = connection.cursor(querystring=f"SELECT * FROM {table_name}") + await cursor.start() + await cursor.close() + await transaction.commit() + + # async with connection.transaction(), connection.cursor( + # ) as cursor: + # async for cur_res in cursor: async def test_connection_async_context_manager( diff --git a/src/driver/common_options.rs b/src/driver/common_options.rs index aaf9329a..aebc5837 100644 --- a/src/driver/common_options.rs +++ b/src/driver/common_options.rs @@ -3,8 +3,8 @@ use std::time::Duration; use deadpool_postgres::RecyclingMethod; use pyo3::{pyclass, pymethods}; -#[pyclass] -#[derive(Clone, Copy)] +#[pyclass(eq, eq_int)] +#[derive(Clone, Copy, PartialEq)] pub enum ConnRecyclingMethod { Fast, Verified, @@ -22,8 +22,8 @@ impl ConnRecyclingMethod { } } -#[pyclass] -#[derive(Clone, Copy)] +#[pyclass(eq, eq_int)] +#[derive(Clone, Copy, PartialEq)] pub enum LoadBalanceHosts { /// Make connection attempts to hosts in the order provided. Disable, @@ -41,8 +41,8 @@ impl LoadBalanceHosts { } } -#[pyclass] -#[derive(Clone, Copy)] +#[pyclass(eq, eq_int)] +#[derive(Clone, Copy, PartialEq)] pub enum TargetSessionAttrs { /// No special properties are required. Any, @@ -63,7 +63,7 @@ impl TargetSessionAttrs { } } -#[pyclass] +#[pyclass(eq, eq_int)] #[derive(Clone, Copy, PartialEq)] pub enum SslMode { /// Do not use TLS. @@ -110,6 +110,7 @@ pub struct KeepaliveConfig { #[pymethods] impl KeepaliveConfig { #[new] + #[pyo3(signature = (idle, interval=None, retries=None))] fn build_config(idle: u64, interval: Option, retries: Option) -> Self { let interval_internal = interval.map(Duration::from_secs); KeepaliveConfig { @@ -120,8 +121,8 @@ impl KeepaliveConfig { } } -#[pyclass] -#[derive(Clone, Copy)] +#[pyclass(eq, eq_int)] +#[derive(Clone, Copy, PartialEq)] pub enum CopyCommandFormat { TEXT, CSV, diff --git a/src/driver/connection.rs b/src/driver/connection.rs index e0df0b14..23e86a44 100644 --- a/src/driver/connection.rs +++ b/src/driver/connection.rs @@ -112,7 +112,7 @@ pub fn _format_copy_opts( } } -#[pyclass] +#[pyclass(subclass)] pub struct Connection { db_client: Option>, db_pool: Option, @@ -198,6 +198,7 @@ impl Connection { /// 1) Cannot convert incoming parameters /// 2) Cannot prepare statement /// 3) Cannot execute query + #[pyo3(signature = (querystring, parameters=None, prepared=None))] pub async fn execute( self_: pyo3::Py, querystring: String, @@ -295,6 +296,7 @@ impl Connection { /// May return Err Result if: /// 1) Cannot convert python parameters /// 2) Cannot execute querystring. + #[pyo3(signature = (querystring, parameters=None, prepared=None))] pub async fn execute_many<'a>( self_: pyo3::Py, querystring: String, @@ -371,6 +373,7 @@ impl Connection { /// 1) Cannot convert incoming parameters /// 2) Cannot prepare statement /// 3) Cannot execute query + #[pyo3(signature = (querystring, parameters=None, prepared=None))] pub async fn fetch( self_: pyo3::Py, querystring: String, @@ -447,6 +450,7 @@ impl Connection { /// 3) Can not create/retrieve prepared statement /// 4) Can not execute statement /// 5) Query returns more than one row + #[pyo3(signature = (querystring, parameters=None, prepared=None))] pub async fn fetch_row( self_: pyo3::Py, querystring: String, @@ -520,6 +524,7 @@ impl Connection { /// 1) Cannot convert python parameters /// 2) Cannot execute querystring. /// 3) Query returns more than one row + #[pyo3(signature = (querystring, parameters=None, prepared=None))] pub async fn fetch_val<'a>( self_: pyo3::Py, querystring: String, @@ -589,6 +594,12 @@ impl Connection { /// /// # Errors /// May return Err Result if db_client is None. + #[pyo3(signature = ( + isolation_level=None, + read_variant=None, + deferrable=None, + synchronous_commit=None, + ))] pub fn transaction( &self, isolation_level: Option, @@ -616,6 +627,13 @@ impl Connection { /// /// # Errors /// May return Err Result if db_client is None. + #[pyo3(signature = ( + querystring, + parameters=None, + fetch_number=None, + scroll=None, + prepared=None, + ))] pub fn cursor( &self, querystring: String, @@ -655,6 +673,12 @@ impl Connection { /// May return Err Result if cannot get bytes, /// cannot perform request to the database, /// cannot write bytes to the database. + #[pyo3(signature = ( + source, + table_name, + columns=None, + schema_name=None, + ))] pub async fn binary_copy_to_table( self_: pyo3::Py, source: Py, diff --git a/src/driver/connection_pool.rs b/src/driver/connection_pool.rs index f0f33fa3..8f5ea984 100644 --- a/src/driver/connection_pool.rs +++ b/src/driver/connection_pool.rs @@ -23,6 +23,34 @@ use super::{ /// # Errors /// May return error if cannot build new connection pool. #[pyfunction] +#[pyo3(signature = ( + dsn=None, + username=None, + password=None, + host=None, + hosts=None, + port=None, + ports=None, + db_name=None, + target_session_attrs=None, + options=None, + application_name=None, + connect_timeout_sec=None, + connect_timeout_nanosec=None, + tcp_user_timeout_sec=None, + tcp_user_timeout_nanosec=None, + keepalives=None, + keepalives_idle_sec=None, + keepalives_idle_nanosec=None, + keepalives_interval_sec=None, + keepalives_interval_nanosec=None, + keepalives_retries=None, + load_balance_hosts=None, + ssl_mode=None, + ca_file=None, + max_db_pool_size=None, + conn_recycling_method=None, +))] #[allow(clippy::too_many_arguments)] pub fn connect( dsn: Option, @@ -184,7 +212,7 @@ impl ConnectionPoolStatus { } } -#[pyclass] +#[pyclass(subclass)] pub struct ConnectionPool(pub Pool); #[pymethods] @@ -194,6 +222,34 @@ impl ConnectionPool { /// # Errors /// May return error if cannot build new connection pool. #[new] + #[pyo3(signature = ( + dsn=None, + username=None, + password=None, + host=None, + hosts=None, + port=None, + ports=None, + db_name=None, + target_session_attrs=None, + options=None, + application_name=None, + connect_timeout_sec=None, + connect_timeout_nanosec=None, + tcp_user_timeout_sec=None, + tcp_user_timeout_nanosec=None, + keepalives=None, + keepalives_idle_sec=None, + keepalives_idle_nanosec=None, + keepalives_interval_sec=None, + keepalives_interval_nanosec=None, + keepalives_retries=None, + load_balance_hosts=None, + max_db_pool_size=None, + conn_recycling_method=None, + ssl_mode=None, + ca_file=None, + ))] #[allow(clippy::too_many_arguments)] pub fn new( dsn: Option, @@ -298,6 +354,7 @@ impl ConnectionPool { /// # Errors /// May return Err Result if cannot retrieve new connection /// or prepare statement or execute statement. + #[pyo3(signature = (querystring, parameters=None, prepared=None))] pub async fn execute<'a>( self_: pyo3::Py, querystring: String, @@ -366,6 +423,7 @@ impl ConnectionPool { /// # Errors /// May return Err Result if cannot retrieve new connection /// or prepare statement or execute statement. + #[pyo3(signature = (querystring, parameters=None, prepared=None))] pub async fn fetch<'a>( self_: pyo3::Py, querystring: String, diff --git a/src/driver/cursor.rs b/src/driver/cursor.rs index cc684610..ee424f5f 100644 --- a/src/driver/cursor.rs +++ b/src/driver/cursor.rs @@ -85,7 +85,7 @@ impl CursorObjectTrait for Object { } } -#[pyclass] +#[pyclass(subclass)] pub struct Cursor { db_transaction: Option>, querystring: String, @@ -213,7 +213,6 @@ impl Cursor { let db_transaction = self.db_transaction.clone(); let fetch_number = self.fetch_number; let cursor_name = self.cursor_name.clone(); - let py_future = Python::with_gil(move |gil| { rustdriver_future(gil, async move { if let Some(db_transaction) = db_transaction { @@ -295,6 +294,7 @@ impl Cursor { /// /// # Errors /// May return Err Result if cannot execute query. + #[pyo3(signature = (fetch_number=None))] pub async fn fetch<'a>( slf: Py, fetch_number: Option, diff --git a/src/driver/transaction.rs b/src/driver/transaction.rs index 472d623d..bc992c68 100644 --- a/src/driver/transaction.rs +++ b/src/driver/transaction.rs @@ -104,7 +104,7 @@ impl TransactionObjectTrait for Object { } } -#[pyclass] +#[pyclass(subclass)] pub struct Transaction { pub db_client: Option>, is_started: bool, @@ -315,6 +315,7 @@ impl Transaction { /// May return Err Result if: /// 1) Cannot convert python parameters /// 2) Cannot execute querystring. + #[pyo3(signature = (querystring, parameters=None, prepared=None))] pub async fn execute( self_: Py, querystring: String, @@ -370,6 +371,7 @@ impl Transaction { /// May return Err Result if: /// 1) Cannot convert python parameters /// 2) Cannot execute querystring. + #[pyo3(signature = (querystring, parameters=None, prepared=None))] pub async fn fetch( self_: Py, querystring: String, @@ -404,6 +406,7 @@ impl Transaction { /// 3) Can not create/retrieve prepared statement /// 4) Can not execute statement /// 5) Query returns more than one row + #[pyo3(signature = (querystring, parameters=None, prepared=None))] pub async fn fetch_row( self_: Py, querystring: String, @@ -462,6 +465,7 @@ impl Transaction { /// 1) Cannot convert python parameters /// 2) Cannot execute querystring. /// 3) Query returns more than one row + #[pyo3(signature = (querystring, parameters=None, prepared=None))] pub async fn fetch_val( self_: Py, querystring: String, @@ -521,6 +525,7 @@ impl Transaction { /// May return Err Result if: /// 1) Cannot convert python parameters /// 2) Cannot execute querystring. + #[pyo3(signature = (querystring, parameters=None, prepared=None))] pub async fn execute_many( self_: Py, querystring: String, @@ -787,6 +792,7 @@ impl Transaction { /// May return Err Result if: /// 1) Cannot convert python parameters /// 2) Cannot execute any of querystring. + #[pyo3(signature = (queries=None, prepared=None))] pub async fn pipeline<'py>( self_: Py, queries: Option>, @@ -839,6 +845,13 @@ impl Transaction { /// /// # Errors /// May return Err Result if db_client is None + #[pyo3(signature = ( + querystring, + parameters=None, + fetch_number=None, + scroll=None, + prepared=None, + ))] pub fn cursor( &self, querystring: String, @@ -868,6 +881,7 @@ impl Transaction { /// May return Err Result if cannot get bytes, /// cannot perform request to the database, /// cannot write bytes to the database. + #[pyo3(signature = (source, table_name, columns=None, schema_name=None))] pub async fn binary_copy_to_table( self_: pyo3::Py, source: Py, diff --git a/src/driver/transaction_options.rs b/src/driver/transaction_options.rs index cdbfcb34..51467761 100644 --- a/src/driver/transaction_options.rs +++ b/src/driver/transaction_options.rs @@ -1,7 +1,7 @@ use pyo3::pyclass; -#[pyclass] -#[derive(Clone, Copy)] +#[pyclass(eq, eq_int)] +#[derive(Clone, Copy, PartialEq)] pub enum IsolationLevel { ReadUncommitted, ReadCommitted, @@ -22,15 +22,15 @@ impl IsolationLevel { } } -#[pyclass] -#[derive(Clone, Copy)] +#[pyclass(eq, eq_int)] +#[derive(Clone, Copy, PartialEq)] pub enum ReadVariant { ReadOnly, ReadWrite, } -#[pyclass] -#[derive(Clone, Copy)] +#[pyclass(eq, eq_int)] +#[derive(Clone, Copy, PartialEq)] pub enum SynchronousCommit { /// As the name indicates, the commit acknowledgment can come before /// flushing the records to disk. diff --git a/src/lib.rs b/src/lib.rs index 6ead48ec..edda3119 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -12,7 +12,11 @@ pub mod value_converter; use common::add_module; use exceptions::python_errors::python_exceptions_module; use extra_types::extra_types_module; -use pyo3::{pymodule, types::PyModule, wrap_pyfunction, Bound, PyResult, Python}; +use pyo3::{ + pymodule, + types::{PyModule, PyModuleMethods}, + wrap_pyfunction, Bound, PyResult, Python, +}; use row_factories::row_factories_module; #[pymodule] diff --git a/src/query_result.rs b/src/query_result.rs index 3b2bdbc7..545705f9 100644 --- a/src/query_result.rs +++ b/src/query_result.rs @@ -55,6 +55,7 @@ impl PSQLDriverPyQueryResult { /// May return Err Result if can not convert /// postgres type to python or set new key-value pair /// in python dict. + #[pyo3(signature = (custom_decoders=None))] #[allow(clippy::needless_pass_by_value)] pub fn result( &self, @@ -97,6 +98,7 @@ impl PSQLDriverPyQueryResult { /// May return Err Result if can not convert /// postgres type with custom function. #[allow(clippy::needless_pass_by_value)] + #[pyo3(signature = (row_factory, custom_decoders=None))] pub fn row_factory<'a>( &'a self, py: Python<'a>, @@ -144,6 +146,7 @@ impl PSQLDriverSinglePyQueryResult { /// postgres type to python, can not set new key-value pair /// in python dict or there are no result. #[allow(clippy::needless_pass_by_value)] + #[pyo3(signature = (custom_decoders=None))] pub fn result( &self, py: Python<'_>, @@ -176,6 +179,7 @@ impl PSQLDriverSinglePyQueryResult { /// May return Err Result if can not convert /// postgres type with custom function #[allow(clippy::needless_pass_by_value)] + #[pyo3(signature = (row_factory, custom_decoders=None))] pub fn row_factory<'a>( &'a self, py: Python<'a>, diff --git a/src/runtime.rs b/src/runtime.rs index 32440e91..21365d4f 100644 --- a/src/runtime.rs +++ b/src/runtime.rs @@ -23,7 +23,8 @@ where F: Future> + Send + 'static, T: IntoPy, { - let res = pyo3_asyncio::tokio::future_into_py(py, async { future.await.map_err(Into::into) }) - .map(Into::into)?; + let res = + pyo3_async_runtimes::tokio::future_into_py(py, async { future.await.map_err(Into::into) }) + .map(Into::into)?; Ok(res) } diff --git a/src/value_converter.rs b/src/value_converter.rs index f26f00a3..93edb290 100644 --- a/src/value_converter.rs +++ b/src/value_converter.rs @@ -71,7 +71,16 @@ fn get_timedelta_cls(py: Python<'_>) -> PyResult<&Bound<'_, PyType>> { #[derive(Clone, Copy)] pub struct InternalUuid(Uuid); -impl<'a> FromPyObject<'a> for InternalUuid {} +impl<'a> FromPyObject<'a> for InternalUuid { + fn extract_bound(obj: &Bound<'a, PyAny>) -> PyResult { + let uuid_value = Uuid::parse_str(obj.str()?.extract::<&str>()?).map_err(|_| { + RustPSQLDriverError::PyToRustValueConversionError( + "Cannot convert UUID Array to inner rust type, check you parameters.".into(), + ) + })?; + Ok(InternalUuid(uuid_value)) + } +} impl ToPyObject for InternalUuid { fn to_object(&self, py: Python<'_>) -> PyObject { @@ -99,7 +108,13 @@ impl<'a> FromSql<'a> for InternalUuid { #[derive(Clone)] pub struct InternalSerdeValue(Value); -impl<'a> FromPyObject<'a> for InternalSerdeValue {} +impl<'a> FromPyObject<'a> for InternalSerdeValue { + fn extract_bound(ob: &Bound<'a, PyAny>) -> PyResult { + let serde_value = build_serde_value(ob.clone().unbind())?; + + Ok(InternalSerdeValue(serde_value)) + } +} impl ToPyObject for InternalSerdeValue { fn to_object(&self, py: Python<'_>) -> PyObject { @@ -731,6 +746,8 @@ pub fn py_sequence_into_postgres_array( /// or value of the type is incorrect. #[allow(clippy::too_many_lines)] pub fn py_to_rust(parameter: &pyo3::Bound<'_, PyAny>) -> RustPSQLDriverPyResult { + println!("{:?}", parameter.get_type().name()?); + if parameter.is_none() { return Ok(PythonDTO::PyNone); } @@ -912,7 +929,9 @@ pub fn py_to_rust(parameter: &pyo3::Bound<'_, PyAny>) -> RustPSQLDriverPyResult< )?)); } - if parameter.get_type().name()? == "decimal.Decimal" { + if parameter.get_type().name()? == "decimal.Decimal" + || parameter.get_type().name()? == "Decimal" + { return Ok(PythonDTO::PyDecimal(Decimal::from_str_exact( parameter.str()?.extract::<&str>()?, )?));