From a1758e141391fd85b861aaeae6796f21d4b1a7a6 Mon Sep 17 00:00:00 2001 From: Brent Rager Date: Wed, 20 May 2026 15:55:33 -0400 Subject: [PATCH 1/2] Switch PostgreSQL / CockroachDB adapter from psycopg2 to psycopg v3 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Companion to the #189 fork-fallback fix. psycopg3 delegates TLS to libpq instead of bundling libssl, so importing it doesn't pull Security.framework into the parent process on macOS — eliminating one specific child-side crash class (psycopg2 calling SSL_CTX_new inside the libpq init path after fork). The change is local to the two PostgreSQL-family adapters and the matching install metadata: - postgresql/adapter.py, cockroachdb/adapter.py: import psycopg (v3) and pass dbname= instead of database=; everything else (sslmode, sslrootcert, sslcert, sslkey, sslpassword, autocommit, extra_options) is already libpq-conninfo-compatible. - install_strategy.py: add psycopg / psycopg[binary] to the Arch package-name mapping. - pyproject.toml: replace psycopg2-binary>=2.9.0 with psycopg[binary]>=3.2.0 in the postgres, cockroachdb, and all extras. - flake.nix: pyPkgs.psycopg2 -> pyPkgs.psycopg. - README.md: driver-reference table now lists psycopg[binary]. - tests + integration fixtures: import psycopg (3) and use dbname=. The install-strategy tests still use the literal string "psycopg2-binary" because they're exercising detect_strategy with a sample package name, not the real driver. 837 unit tests pass. --- README.md | 2 +- flake.nix | 4 +- pyproject.toml | 6 +- .../connections/app/install_strategy.py | 2 + .../providers/cockroachdb/adapter.py | 16 +- .../providers/postgresql/adapter.py | 21 ++- tests/fixtures/cockroachdb.py | 16 +- tests/fixtures/postgres.py | 12 +- tests/fixtures/ssh.py | 12 +- .../test_stale_connection_reconnect.py | 8 +- tests/unit/test_extra_options_passthrough.py | 8 +- tests/unit/test_postgresql_adapter.py | 8 +- tests/unit/test_tls_adapters.py | 12 +- uv.lock | 149 ++++++++++-------- 14 files changed, 150 insertions(+), 126 deletions(-) diff --git a/README.md b/README.md index 8ca45e6b..d9fe2a29 100644 --- a/README.md +++ b/README.md @@ -279,7 +279,7 @@ Most of the time you can just run `sqlit` and connect. If a Python driver is mis | Database | Driver package | `pipx` | `pip` / venv | | :---------------------------------- | :--------------------------- | :------------------------------------------------- | :------------------------------------------------- | | SQLite | *(built-in)* | *(built-in)* | *(built-in)* | -| PostgreSQL / CockroachDB / Supabase | `psycopg2-binary` | `pipx inject sqlit-tui psycopg2-binary` | `python -m pip install psycopg2-binary` | +| PostgreSQL / CockroachDB / Supabase | `psycopg[binary]` | `pipx inject sqlit-tui 'psycopg[binary]'` | `python -m pip install 'psycopg[binary]'` | | SQL Server | `mssql-python` | `pipx inject sqlit-tui mssql-python` | `python -m pip install mssql-python` | | MySQL | `PyMySQL` | `pipx inject sqlit-tui PyMySQL` | `python -m pip install PyMySQL` | | MariaDB | `PyMySQL` | `pipx inject sqlit-tui PyMySQL` | `python -m pip install PyMySQL` | diff --git a/flake.nix b/flake.nix index c4ef7be5..da0e7f4c 100644 --- a/flake.nix +++ b/flake.nix @@ -36,8 +36,8 @@ # here; install with `pipx inject` or a custom derivation. nixpkgsExtras = { ssh = [ pyPkgs.sshtunnel pyPkgs.paramiko ]; - postgres = [ pyPkgs.psycopg2 ]; - cockroachdb = [ pyPkgs.psycopg2 ]; + postgres = [ pyPkgs.psycopg ]; + cockroachdb = [ pyPkgs.psycopg ]; mysql = [ pyPkgs.pymysql ]; duckdb = [ pyPkgs.duckdb ]; bigquery = [ pyPkgs.google-cloud-bigquery ]; diff --git a/pyproject.toml b/pyproject.toml index 7940b1ef..f8cc8c51 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -37,7 +37,7 @@ dynamic = ["version"] [project.optional-dependencies] all = [ - "psycopg2-binary>=2.9.0", + "psycopg[binary]>=3.2.0", "mssql-python>=1.1.0", "PyMySQL>=1.1.0", "oracledb>=2.0.0", @@ -63,8 +63,8 @@ all = [ "osquery>=3.0.0", "surrealdb>=1.0.0", ] -postgres = ["psycopg2-binary>=2.9.0"] -cockroachdb = ["psycopg2-binary>=2.9.0"] +postgres = ["psycopg[binary]>=3.2.0"] +cockroachdb = ["psycopg[binary]>=3.2.0"] mssql = ["mssql-python>=1.1.0"] mysql = ["PyMySQL>=1.1.0"] mariadb = ["PyMySQL>=1.1.0"] diff --git a/sqlit/domains/connections/app/install_strategy.py b/sqlit/domains/connections/app/install_strategy.py index 1e9e9aa8..4809e60e 100644 --- a/sqlit/domains/connections/app/install_strategy.py +++ b/sqlit/domains/connections/app/install_strategy.py @@ -77,6 +77,8 @@ def _get_arch_package_name(package_name: str) -> str | None: mapping = { "psycopg2-binary": "python-psycopg2", "psycopg2": "python-psycopg2", + "psycopg[binary]": "python-psycopg", + "psycopg": "python-psycopg", "mssql-python": "python-mssql", "PyMySQL": "python-pymysql", "mysql-connector-python": "python-mysql-connector", diff --git a/sqlit/domains/connections/providers/cockroachdb/adapter.py b/sqlit/domains/connections/providers/cockroachdb/adapter.py index 04e6d76b..cf6e7835 100644 --- a/sqlit/domains/connections/providers/cockroachdb/adapter.py +++ b/sqlit/domains/connections/providers/cockroachdb/adapter.py @@ -1,4 +1,4 @@ -"""CockroachDB adapter using psycopg2 (PostgreSQL wire-compatible).""" +"""CockroachDB adapter using psycopg v3 (PostgreSQL wire-compatible).""" from __future__ import annotations @@ -18,7 +18,7 @@ class CockroachDBAdapter(PostgresBaseAdapter): - """Adapter for CockroachDB using psycopg2 (PostgreSQL wire-compatible).""" + """Adapter for CockroachDB using psycopg v3 (PostgreSQL wire-compatible).""" @property def name(self) -> str: @@ -30,11 +30,11 @@ def install_extra(self) -> str: @property def install_package(self) -> str: - return "psycopg2-binary" + return "psycopg[binary]" @property def driver_import_names(self) -> tuple[str, ...]: - return ("psycopg2",) + return ("psycopg",) @property def supports_stored_procedures(self) -> bool: @@ -47,8 +47,8 @@ def supports_triggers(self) -> bool: def connect(self, config: ConnectionConfig) -> Any: """Connect to CockroachDB database.""" - psycopg2 = self._import_driver_module( - "psycopg2", + psycopg = self._import_driver_module( + "psycopg", driver_name=self.name, extra_name=self.install_extra, package_name=self.install_package, @@ -61,7 +61,7 @@ def connect(self, config: ConnectionConfig) -> Any: connect_args: dict[str, Any] = { "host": endpoint.host, "port": port, - "database": endpoint.database or "defaultdb", + "dbname": endpoint.database or "defaultdb", "user": endpoint.username, "password": endpoint.password, "connect_timeout": 10, @@ -88,7 +88,7 @@ def connect(self, config: ConnectionConfig) -> Any: connect_args["sslpassword"] = tls_key_password connect_args.update(config.extra_options) - conn = psycopg2.connect(**connect_args) + conn = psycopg.connect(**connect_args) # Enable autocommit to avoid transaction issues conn.autocommit = True return conn diff --git a/sqlit/domains/connections/providers/postgresql/adapter.py b/sqlit/domains/connections/providers/postgresql/adapter.py index cc7493eb..5c732d48 100644 --- a/sqlit/domains/connections/providers/postgresql/adapter.py +++ b/sqlit/domains/connections/providers/postgresql/adapter.py @@ -1,4 +1,4 @@ -"""PostgreSQL adapter using psycopg2.""" +"""PostgreSQL adapter using psycopg (v3).""" from __future__ import annotations @@ -18,7 +18,12 @@ class PostgreSQLAdapter(PostgresBaseAdapter): - """Adapter for PostgreSQL using psycopg2.""" + """Adapter for PostgreSQL using psycopg (v3). + + psycopg3 delegates TLS to libpq instead of bundling libssl, which avoids + pulling Security.framework into the parent process at import time on + macOS — one of the crash classes covered by issue #189. + """ @property def name(self) -> str: @@ -30,16 +35,16 @@ def install_extra(self) -> str: @property def install_package(self) -> str: - return "psycopg2-binary" + return "psycopg[binary]" @property def driver_import_names(self) -> tuple[str, ...]: - return ("psycopg2",) + return ("psycopg",) def connect(self, config: ConnectionConfig) -> Any: """Connect to PostgreSQL database.""" - psycopg2 = self._import_driver_module( - "psycopg2", + psycopg = self._import_driver_module( + "psycopg", driver_name=self.name, extra_name=self.install_extra, package_name=self.install_package, @@ -50,7 +55,7 @@ def connect(self, config: ConnectionConfig) -> Any: raise ValueError("PostgreSQL connections require a TCP-style endpoint.") connect_args: dict[str, Any] = { "connect_timeout": 10, - "database": endpoint.database or "postgres", + "dbname": endpoint.database or "postgres", } host = endpoint.host # If the user only set a port (e.g. Postgres on a non-default port @@ -82,7 +87,7 @@ def connect(self, config: ConnectionConfig) -> Any: connect_args["sslpassword"] = tls_key_password connect_args.update(config.extra_options) - conn = psycopg2.connect(**connect_args) + conn = psycopg.connect(**connect_args) # Enable autocommit to avoid "transaction aborted" errors on failed statements conn.autocommit = True return conn diff --git a/tests/fixtures/cockroachdb.py b/tests/fixtures/cockroachdb.py index 103122e5..43dc297f 100644 --- a/tests/fixtures/cockroachdb.py +++ b/tests/fixtures/cockroachdb.py @@ -39,15 +39,15 @@ def cockroachdb_db(cockroachdb_server_ready: bool) -> str: pytest.skip("CockroachDB is not available") try: - import psycopg2 + import psycopg except ImportError: - pytest.skip("psycopg2 is not installed") + pytest.skip("psycopg is not installed") try: - conn = psycopg2.connect( + conn = psycopg.connect( host=COCKROACHDB_HOST, port=COCKROACHDB_PORT, - database="defaultdb", + dbname="defaultdb", user=COCKROACHDB_USER, password=COCKROACHDB_PASSWORD or None, connect_timeout=10, @@ -60,10 +60,10 @@ def cockroachdb_db(cockroachdb_server_ready: bool) -> str: cursor.execute(f"CREATE DATABASE {COCKROACHDB_DATABASE}") conn.close() - conn = psycopg2.connect( + conn = psycopg.connect( host=COCKROACHDB_HOST, port=COCKROACHDB_PORT, - database=COCKROACHDB_DATABASE, + dbname=COCKROACHDB_DATABASE, user=COCKROACHDB_USER, password=COCKROACHDB_PASSWORD or None, connect_timeout=10, @@ -139,10 +139,10 @@ def cockroachdb_db(cockroachdb_server_ready: bool) -> str: yield COCKROACHDB_DATABASE try: - conn = psycopg2.connect( + conn = psycopg.connect( host=COCKROACHDB_HOST, port=COCKROACHDB_PORT, - database="defaultdb", + dbname="defaultdb", user=COCKROACHDB_USER, password=COCKROACHDB_PASSWORD or None, connect_timeout=10, diff --git a/tests/fixtures/postgres.py b/tests/fixtures/postgres.py index bbd1dfc3..25b11db8 100644 --- a/tests/fixtures/postgres.py +++ b/tests/fixtures/postgres.py @@ -38,15 +38,15 @@ def postgres_db(postgres_server_ready: bool) -> str: pytest.skip("PostgreSQL is not available") try: - import psycopg2 + import psycopg except ImportError: - pytest.skip("psycopg2 is not installed") + pytest.skip("psycopg is not installed") try: - conn = psycopg2.connect( + conn = psycopg.connect( host=POSTGRES_HOST, port=POSTGRES_PORT, - database=POSTGRES_DATABASE, + dbname=POSTGRES_DATABASE, user=POSTGRES_USER, password=POSTGRES_PASSWORD, connect_timeout=10, @@ -122,10 +122,10 @@ def postgres_db(postgres_server_ready: bool) -> str: yield POSTGRES_DATABASE try: - conn = psycopg2.connect( + conn = psycopg.connect( host=POSTGRES_HOST, port=POSTGRES_PORT, - database=POSTGRES_DATABASE, + dbname=POSTGRES_DATABASE, user=POSTGRES_USER, password=POSTGRES_PASSWORD, connect_timeout=10, diff --git a/tests/fixtures/ssh.py b/tests/fixtures/ssh.py index 73f2f0d8..3b95c9a8 100644 --- a/tests/fixtures/ssh.py +++ b/tests/fixtures/ssh.py @@ -41,19 +41,19 @@ def ssh_postgres_db(ssh_server_ready: bool) -> str: pytest.skip("SSH server is not available") try: - import psycopg2 + import psycopg except ImportError: - pytest.skip("psycopg2 is not installed") + pytest.skip("psycopg is not installed") # postgres-ssh container is accessible on port 5433 pg_host = os.environ.get("SSH_DIRECT_PG_HOST", "localhost") pg_port = int(os.environ.get("SSH_DIRECT_PG_PORT", "5433")) try: - conn = psycopg2.connect( + conn = psycopg.connect( host=pg_host, port=pg_port, - database=POSTGRES_DATABASE, + dbname=POSTGRES_DATABASE, user=POSTGRES_USER, password=POSTGRES_PASSWORD, connect_timeout=10, @@ -109,10 +109,10 @@ def ssh_postgres_db(ssh_server_ready: bool) -> str: yield POSTGRES_DATABASE try: - conn = psycopg2.connect( + conn = psycopg.connect( host=pg_host, port=pg_port, - database=POSTGRES_DATABASE, + dbname=POSTGRES_DATABASE, user=POSTGRES_USER, password=POSTGRES_PASSWORD, connect_timeout=10, diff --git a/tests/integration/test_stale_connection_reconnect.py b/tests/integration/test_stale_connection_reconnect.py index 46713e51..7ceae4e4 100644 --- a/tests/integration/test_stale_connection_reconnect.py +++ b/tests/integration/test_stale_connection_reconnect.py @@ -496,15 +496,15 @@ def work() -> None: if spec.key == "postgres": try: - import psycopg2 + import psycopg except ImportError: - pytest.skip("psycopg2 is not installed") + pytest.skip("psycopg is not installed") def work() -> None: - conn = psycopg2.connect( + conn = psycopg.connect( host=spec.host, port=spec.port, - database=spec.database, + dbname=spec.database, user=spec.username, password=spec.password, connect_timeout=10, diff --git a/tests/unit/test_extra_options_passthrough.py b/tests/unit/test_extra_options_passthrough.py index a3de95c9..ae164945 100644 --- a/tests/unit/test_extra_options_passthrough.py +++ b/tests/unit/test_extra_options_passthrough.py @@ -116,12 +116,12 @@ def test_postgresql_passes_extra_options(self): from sqlit.domains.connections.domain.config import ConnectionConfig, TcpEndpoint from sqlit.domains.connections.providers.postgresql.adapter import PostgreSQLAdapter - mock_psycopg2 = MagicMock() + mock_psycopg = MagicMock() mock_conn = MagicMock() mock_conn.autocommit = False - mock_psycopg2.connect.return_value = mock_conn + mock_psycopg.connect.return_value = mock_conn - with patch.dict("sys.modules", {"psycopg2": mock_psycopg2}): + with patch.dict("sys.modules", {"psycopg": mock_psycopg}): adapter = PostgreSQLAdapter() config = ConnectionConfig( name="test_pg", @@ -141,7 +141,7 @@ def test_postgresql_passes_extra_options(self): adapter.connect(config) - call_kwargs = mock_psycopg2.connect.call_args[1] + call_kwargs = mock_psycopg.connect.call_args[1] assert call_kwargs.get("application_name") == "my_app" assert call_kwargs.get("connect_timeout") == "30" diff --git a/tests/unit/test_postgresql_adapter.py b/tests/unit/test_postgresql_adapter.py index 12607afa..9af303f7 100644 --- a/tests/unit/test_postgresql_adapter.py +++ b/tests/unit/test_postgresql_adapter.py @@ -8,8 +8,8 @@ def test_postgresql_peer_auth_omits_empty_tcp_args() -> None: - mock_psycopg2 = MagicMock() - with patch.dict("sys.modules", {"psycopg2": mock_psycopg2}): + mock_psycopg = MagicMock() + with patch.dict("sys.modules", {"psycopg": mock_psycopg}): from sqlit.domains.connections.providers.postgresql.adapter import PostgreSQLAdapter adapter = PostgreSQLAdapter() @@ -25,9 +25,9 @@ def test_postgresql_peer_auth_omits_empty_tcp_args() -> None: adapter.connect(config) - kwargs = mock_psycopg2.connect.call_args.kwargs + kwargs = mock_psycopg.connect.call_args.kwargs - assert kwargs["database"] == "mydb" + assert kwargs["dbname"] == "mydb" assert "host" not in kwargs assert "port" not in kwargs assert "user" not in kwargs diff --git a/tests/unit/test_tls_adapters.py b/tests/unit/test_tls_adapters.py index e2f6118f..9b6b3552 100644 --- a/tests/unit/test_tls_adapters.py +++ b/tests/unit/test_tls_adapters.py @@ -9,8 +9,8 @@ def test_postgresql_tls_args_include_files(): - mock_psycopg2 = MagicMock() - with patch.dict("sys.modules", {"psycopg2": mock_psycopg2}): + mock_psycopg = MagicMock() + with patch.dict("sys.modules", {"psycopg": mock_psycopg}): from sqlit.domains.connections.providers.postgresql.adapter import PostgreSQLAdapter adapter = PostgreSQLAdapter() @@ -33,7 +33,7 @@ def test_postgresql_tls_args_include_files(): adapter.connect(config) - kwargs = mock_psycopg2.connect.call_args.kwargs + kwargs = mock_psycopg.connect.call_args.kwargs assert kwargs["sslmode"] == "verify-full" assert kwargs["sslrootcert"] == "/ca.pem" assert kwargs["sslcert"] == "/client.pem" @@ -42,8 +42,8 @@ def test_postgresql_tls_args_include_files(): def test_cockroachdb_defaults_to_insecure_without_tls(): - mock_psycopg2 = MagicMock() - with patch.dict("sys.modules", {"psycopg2": mock_psycopg2}): + mock_psycopg = MagicMock() + with patch.dict("sys.modules", {"psycopg": mock_psycopg}): from sqlit.domains.connections.providers.cockroachdb.adapter import CockroachDBAdapter adapter = CockroachDBAdapter() @@ -59,7 +59,7 @@ def test_cockroachdb_defaults_to_insecure_without_tls(): adapter.connect(config) - kwargs = mock_psycopg2.connect.call_args.kwargs + kwargs = mock_psycopg.connect.call_args.kwargs assert kwargs["sslmode"] == "disable" diff --git a/uv.lock b/uv.lock index 35f44b86..58eb9424 100644 --- a/uv.lock +++ b/uv.lock @@ -2986,66 +2986,83 @@ wheels = [ ] [[package]] -name = "psycopg2-binary" -version = "2.9.11" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ac/6c/8767aaa597ba424643dc87348c6f1754dd9f48e80fdc1b9f7ca5c3a7c213/psycopg2-binary-2.9.11.tar.gz", hash = "sha256:b6aed9e096bf63f9e75edf2581aa9a7e7186d97ab5c177aa6c87797cd591236c", size = 379620, upload-time = "2025-10-10T11:14:48.041Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/6a/f2/8e377d29c2ecf99f6062d35ea606b036e8800720eccfec5fe3dd672c2b24/psycopg2_binary-2.9.11-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d6fe6b47d0b42ce1c9f1fa3e35bb365011ca22e39db37074458f27921dca40f2", size = 3756506, upload-time = "2025-10-10T11:10:30.144Z" }, - { url = "https://files.pythonhosted.org/packages/24/cc/dc143ea88e4ec9d386106cac05023b69668bd0be20794c613446eaefafe5/psycopg2_binary-2.9.11-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a6c0e4262e089516603a09474ee13eabf09cb65c332277e39af68f6233911087", size = 3863943, upload-time = "2025-10-10T11:10:34.586Z" }, - { url = "https://files.pythonhosted.org/packages/8c/df/16848771155e7c419c60afeb24950b8aaa3ab09c0a091ec3ccca26a574d0/psycopg2_binary-2.9.11-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:c47676e5b485393f069b4d7a811267d3168ce46f988fa602658b8bb901e9e64d", size = 4410873, upload-time = "2025-10-10T11:10:38.951Z" }, - { url = "https://files.pythonhosted.org/packages/43/79/5ef5f32621abd5a541b89b04231fe959a9b327c874a1d41156041c75494b/psycopg2_binary-2.9.11-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:a28d8c01a7b27a1e3265b11250ba7557e5f72b5ee9e5f3a2fa8d2949c29bf5d2", size = 4468016, upload-time = "2025-10-10T11:10:43.319Z" }, - { url = "https://files.pythonhosted.org/packages/f0/9b/d7542d0f7ad78f57385971f426704776d7b310f5219ed58da5d605b1892e/psycopg2_binary-2.9.11-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5f3f2732cf504a1aa9e9609d02f79bea1067d99edf844ab92c247bbca143303b", size = 4164996, upload-time = "2025-10-10T11:10:46.705Z" }, - { url = "https://files.pythonhosted.org/packages/14/ed/e409388b537fa7414330687936917c522f6a77a13474e4238219fcfd9a84/psycopg2_binary-2.9.11-cp310-cp310-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:865f9945ed1b3950d968ec4690ce68c55019d79e4497366d36e090327ce7db14", size = 3981881, upload-time = "2025-10-30T02:54:57.182Z" }, - { url = "https://files.pythonhosted.org/packages/bf/30/50e330e63bb05efc6fa7c1447df3e08954894025ca3dcb396ecc6739bc26/psycopg2_binary-2.9.11-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:91537a8df2bde69b1c1db01d6d944c831ca793952e4f57892600e96cee95f2cd", size = 3650857, upload-time = "2025-10-10T11:10:50.112Z" }, - { url = "https://files.pythonhosted.org/packages/f0/e0/4026e4c12bb49dd028756c5b0bc4c572319f2d8f1c9008e0dad8cc9addd7/psycopg2_binary-2.9.11-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:4dca1f356a67ecb68c81a7bc7809f1569ad9e152ce7fd02c2f2036862ca9f66b", size = 3296063, upload-time = "2025-10-10T11:10:54.089Z" }, - { url = "https://files.pythonhosted.org/packages/2c/34/eb172be293c886fef5299fe5c3fcf180a05478be89856067881007934a7c/psycopg2_binary-2.9.11-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:0da4de5c1ac69d94ed4364b6cbe7190c1a70d325f112ba783d83f8440285f152", size = 3043464, upload-time = "2025-10-30T02:55:02.483Z" }, - { url = "https://files.pythonhosted.org/packages/18/1c/532c5d2cb11986372f14b798a95f2eaafe5779334f6a80589a68b5fcf769/psycopg2_binary-2.9.11-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:37d8412565a7267f7d79e29ab66876e55cb5e8e7b3bbf94f8206f6795f8f7e7e", size = 3345378, upload-time = "2025-10-10T11:11:01.039Z" }, - { url = "https://files.pythonhosted.org/packages/70/e7/de420e1cf16f838e1fa17b1120e83afff374c7c0130d088dba6286fcf8ea/psycopg2_binary-2.9.11-cp310-cp310-win_amd64.whl", hash = "sha256:c665f01ec8ab273a61c62beeb8cce3014c214429ced8a308ca1fc410ecac3a39", size = 2713904, upload-time = "2025-10-10T11:11:04.81Z" }, - { url = "https://files.pythonhosted.org/packages/c7/ae/8d8266f6dd183ab4d48b95b9674034e1b482a3f8619b33a0d86438694577/psycopg2_binary-2.9.11-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0e8480afd62362d0a6a27dd09e4ca2def6fa50ed3a4e7c09165266106b2ffa10", size = 3756452, upload-time = "2025-10-10T11:11:11.583Z" }, - { url = "https://files.pythonhosted.org/packages/4b/34/aa03d327739c1be70e09d01182619aca8ebab5970cd0cfa50dd8b9cec2ac/psycopg2_binary-2.9.11-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:763c93ef1df3da6d1a90f86ea7f3f806dc06b21c198fa87c3c25504abec9404a", size = 3863957, upload-time = "2025-10-10T11:11:16.932Z" }, - { url = "https://files.pythonhosted.org/packages/48/89/3fdb5902bdab8868bbedc1c6e6023a4e08112ceac5db97fc2012060e0c9a/psycopg2_binary-2.9.11-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2e164359396576a3cc701ba8af4751ae68a07235d7a380c631184a611220d9a4", size = 4410955, upload-time = "2025-10-10T11:11:21.21Z" }, - { url = "https://files.pythonhosted.org/packages/ce/24/e18339c407a13c72b336e0d9013fbbbde77b6fd13e853979019a1269519c/psycopg2_binary-2.9.11-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:d57c9c387660b8893093459738b6abddbb30a7eab058b77b0d0d1c7d521ddfd7", size = 4468007, upload-time = "2025-10-10T11:11:24.831Z" }, - { url = "https://files.pythonhosted.org/packages/91/7e/b8441e831a0f16c159b5381698f9f7f7ed54b77d57bc9c5f99144cc78232/psycopg2_binary-2.9.11-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2c226ef95eb2250974bf6fa7a842082b31f68385c4f3268370e3f3870e7859ee", size = 4165012, upload-time = "2025-10-10T11:11:29.51Z" }, - { url = "https://files.pythonhosted.org/packages/0d/61/4aa89eeb6d751f05178a13da95516c036e27468c5d4d2509bb1e15341c81/psycopg2_binary-2.9.11-cp311-cp311-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a311f1edc9967723d3511ea7d2708e2c3592e3405677bf53d5c7246753591fbb", size = 3981881, upload-time = "2025-10-30T02:55:07.332Z" }, - { url = "https://files.pythonhosted.org/packages/76/a1/2f5841cae4c635a9459fe7aca8ed771336e9383b6429e05c01267b0774cf/psycopg2_binary-2.9.11-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:ebb415404821b6d1c47353ebe9c8645967a5235e6d88f914147e7fd411419e6f", size = 3650985, upload-time = "2025-10-10T11:11:34.975Z" }, - { url = "https://files.pythonhosted.org/packages/84/74/4defcac9d002bca5709951b975173c8c2fa968e1a95dc713f61b3a8d3b6a/psycopg2_binary-2.9.11-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:f07c9c4a5093258a03b28fab9b4f151aa376989e7f35f855088234e656ee6a94", size = 3296039, upload-time = "2025-10-10T11:11:40.432Z" }, - { url = "https://files.pythonhosted.org/packages/6d/c2/782a3c64403d8ce35b5c50e1b684412cf94f171dc18111be8c976abd2de1/psycopg2_binary-2.9.11-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:00ce1830d971f43b667abe4a56e42c1e2d594b32da4802e44a73bacacb25535f", size = 3043477, upload-time = "2025-10-30T02:55:11.182Z" }, - { url = "https://files.pythonhosted.org/packages/c8/31/36a1d8e702aa35c38fc117c2b8be3f182613faa25d794b8aeaab948d4c03/psycopg2_binary-2.9.11-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:cffe9d7697ae7456649617e8bb8d7a45afb71cd13f7ab22af3e5c61f04840908", size = 3345842, upload-time = "2025-10-10T11:11:45.366Z" }, - { url = "https://files.pythonhosted.org/packages/6e/b4/a5375cda5b54cb95ee9b836930fea30ae5a8f14aa97da7821722323d979b/psycopg2_binary-2.9.11-cp311-cp311-win_amd64.whl", hash = "sha256:304fd7b7f97eef30e91b8f7e720b3db75fee010b520e434ea35ed1ff22501d03", size = 2713894, upload-time = "2025-10-10T11:11:48.775Z" }, - { url = "https://files.pythonhosted.org/packages/d8/91/f870a02f51be4a65987b45a7de4c2e1897dd0d01051e2b559a38fa634e3e/psycopg2_binary-2.9.11-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:be9b840ac0525a283a96b556616f5b4820e0526addb8dcf6525a0fa162730be4", size = 3756603, upload-time = "2025-10-10T11:11:52.213Z" }, - { url = "https://files.pythonhosted.org/packages/27/fa/cae40e06849b6c9a95eb5c04d419942f00d9eaac8d81626107461e268821/psycopg2_binary-2.9.11-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f090b7ddd13ca842ebfe301cd587a76a4cf0913b1e429eb92c1be5dbeb1a19bc", size = 3864509, upload-time = "2025-10-10T11:11:56.452Z" }, - { url = "https://files.pythonhosted.org/packages/2d/75/364847b879eb630b3ac8293798e380e441a957c53657995053c5ec39a316/psycopg2_binary-2.9.11-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ab8905b5dcb05bf3fb22e0cf90e10f469563486ffb6a96569e51f897c750a76a", size = 4411159, upload-time = "2025-10-10T11:12:00.49Z" }, - { url = "https://files.pythonhosted.org/packages/6f/a0/567f7ea38b6e1c62aafd58375665a547c00c608a471620c0edc364733e13/psycopg2_binary-2.9.11-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:bf940cd7e7fec19181fdbc29d76911741153d51cab52e5c21165f3262125685e", size = 4468234, upload-time = "2025-10-10T11:12:04.892Z" }, - { url = "https://files.pythonhosted.org/packages/30/da/4e42788fb811bbbfd7b7f045570c062f49e350e1d1f3df056c3fb5763353/psycopg2_binary-2.9.11-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fa0f693d3c68ae925966f0b14b8edda71696608039f4ed61b1fe9ffa468d16db", size = 4166236, upload-time = "2025-10-10T11:12:11.674Z" }, - { url = "https://files.pythonhosted.org/packages/3c/94/c1777c355bc560992af848d98216148be5f1be001af06e06fc49cbded578/psycopg2_binary-2.9.11-cp312-cp312-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a1cf393f1cdaf6a9b57c0a719a1068ba1069f022a59b8b1fe44b006745b59757", size = 3983083, upload-time = "2025-10-30T02:55:15.73Z" }, - { url = "https://files.pythonhosted.org/packages/bd/42/c9a21edf0e3daa7825ed04a4a8588686c6c14904344344a039556d78aa58/psycopg2_binary-2.9.11-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ef7a6beb4beaa62f88592ccc65df20328029d721db309cb3250b0aae0fa146c3", size = 3652281, upload-time = "2025-10-10T11:12:17.713Z" }, - { url = "https://files.pythonhosted.org/packages/12/22/dedfbcfa97917982301496b6b5e5e6c5531d1f35dd2b488b08d1ebc52482/psycopg2_binary-2.9.11-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:31b32c457a6025e74d233957cc9736742ac5a6cb196c6b68499f6bb51390bd6a", size = 3298010, upload-time = "2025-10-10T11:12:22.671Z" }, - { url = "https://files.pythonhosted.org/packages/66/ea/d3390e6696276078bd01b2ece417deac954dfdd552d2edc3d03204416c0c/psycopg2_binary-2.9.11-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:edcb3aeb11cb4bf13a2af3c53a15b3d612edeb6409047ea0b5d6a21a9d744b34", size = 3044641, upload-time = "2025-10-30T02:55:19.929Z" }, - { url = "https://files.pythonhosted.org/packages/12/9a/0402ded6cbd321da0c0ba7d34dc12b29b14f5764c2fc10750daa38e825fc/psycopg2_binary-2.9.11-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:62b6d93d7c0b61a1dd6197d208ab613eb7dcfdcca0a49c42ceb082257991de9d", size = 3347940, upload-time = "2025-10-10T11:12:26.529Z" }, - { url = "https://files.pythonhosted.org/packages/b1/d2/99b55e85832ccde77b211738ff3925a5d73ad183c0b37bcbbe5a8ff04978/psycopg2_binary-2.9.11-cp312-cp312-win_amd64.whl", hash = "sha256:b33fabeb1fde21180479b2d4667e994de7bbf0eec22832ba5d9b5e4cf65b6c6d", size = 2714147, upload-time = "2025-10-10T11:12:29.535Z" }, - { url = "https://files.pythonhosted.org/packages/ff/a8/a2709681b3ac11b0b1786def10006b8995125ba268c9a54bea6f5ae8bd3e/psycopg2_binary-2.9.11-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b8fb3db325435d34235b044b199e56cdf9ff41223a4b9752e8576465170bb38c", size = 3756572, upload-time = "2025-10-10T11:12:32.873Z" }, - { url = "https://files.pythonhosted.org/packages/62/e1/c2b38d256d0dafd32713e9f31982a5b028f4a3651f446be70785f484f472/psycopg2_binary-2.9.11-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:366df99e710a2acd90efed3764bb1e28df6c675d33a7fb40df9b7281694432ee", size = 3864529, upload-time = "2025-10-10T11:12:36.791Z" }, - { url = "https://files.pythonhosted.org/packages/11/32/b2ffe8f3853c181e88f0a157c5fb4e383102238d73c52ac6d93a5c8bffe6/psycopg2_binary-2.9.11-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8c55b385daa2f92cb64b12ec4536c66954ac53654c7f15a203578da4e78105c0", size = 4411242, upload-time = "2025-10-10T11:12:42.388Z" }, - { url = "https://files.pythonhosted.org/packages/10/04/6ca7477e6160ae258dc96f67c371157776564679aefd247b66f4661501a2/psycopg2_binary-2.9.11-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:c0377174bf1dd416993d16edc15357f6eb17ac998244cca19bc67cdc0e2e5766", size = 4468258, upload-time = "2025-10-10T11:12:48.654Z" }, - { url = "https://files.pythonhosted.org/packages/3c/7e/6a1a38f86412df101435809f225d57c1a021307dd0689f7a5e7fe83588b1/psycopg2_binary-2.9.11-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5c6ff3335ce08c75afaed19e08699e8aacf95d4a260b495a4a8545244fe2ceb3", size = 4166295, upload-time = "2025-10-10T11:12:52.525Z" }, - { url = "https://files.pythonhosted.org/packages/f2/7d/c07374c501b45f3579a9eb761cbf2604ddef3d96ad48679112c2c5aa9c25/psycopg2_binary-2.9.11-cp313-cp313-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:84011ba3109e06ac412f95399b704d3d6950e386b7994475b231cf61eec2fc1f", size = 3983133, upload-time = "2025-10-30T02:55:24.329Z" }, - { url = "https://files.pythonhosted.org/packages/82/56/993b7104cb8345ad7d4516538ccf8f0d0ac640b1ebd8c754a7b024e76878/psycopg2_binary-2.9.11-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ba34475ceb08cccbdd98f6b46916917ae6eeb92b5ae111df10b544c3a4621dc4", size = 3652383, upload-time = "2025-10-10T11:12:56.387Z" }, - { url = "https://files.pythonhosted.org/packages/2d/ac/eaeb6029362fd8d454a27374d84c6866c82c33bfc24587b4face5a8e43ef/psycopg2_binary-2.9.11-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:b31e90fdd0f968c2de3b26ab014314fe814225b6c324f770952f7d38abf17e3c", size = 3298168, upload-time = "2025-10-10T11:13:00.403Z" }, - { url = "https://files.pythonhosted.org/packages/2b/39/50c3facc66bded9ada5cbc0de867499a703dc6bca6be03070b4e3b65da6c/psycopg2_binary-2.9.11-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:d526864e0f67f74937a8fce859bd56c979f5e2ec57ca7c627f5f1071ef7fee60", size = 3044712, upload-time = "2025-10-30T02:55:27.975Z" }, - { url = "https://files.pythonhosted.org/packages/9c/8e/b7de019a1f562f72ada81081a12823d3c1590bedc48d7d2559410a2763fe/psycopg2_binary-2.9.11-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:04195548662fa544626c8ea0f06561eb6203f1984ba5b4562764fbeb4c3d14b1", size = 3347549, upload-time = "2025-10-10T11:13:03.971Z" }, - { url = "https://files.pythonhosted.org/packages/80/2d/1bb683f64737bbb1f86c82b7359db1eb2be4e2c0c13b947f80efefa7d3e5/psycopg2_binary-2.9.11-cp313-cp313-win_amd64.whl", hash = "sha256:efff12b432179443f54e230fdf60de1f6cc726b6c832db8701227d089310e8aa", size = 2714215, upload-time = "2025-10-10T11:13:07.14Z" }, - { url = "https://files.pythonhosted.org/packages/64/12/93ef0098590cf51d9732b4f139533732565704f45bdc1ffa741b7c95fb54/psycopg2_binary-2.9.11-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:92e3b669236327083a2e33ccfa0d320dd01b9803b3e14dd986a4fc54aa00f4e1", size = 3756567, upload-time = "2025-10-10T11:13:11.885Z" }, - { url = "https://files.pythonhosted.org/packages/7c/a9/9d55c614a891288f15ca4b5209b09f0f01e3124056924e17b81b9fa054cc/psycopg2_binary-2.9.11-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:e0deeb03da539fa3577fcb0b3f2554a97f7e5477c246098dbb18091a4a01c16f", size = 3864755, upload-time = "2025-10-10T11:13:17.727Z" }, - { url = "https://files.pythonhosted.org/packages/13/1e/98874ce72fd29cbde93209977b196a2edae03f8490d1bd8158e7f1daf3a0/psycopg2_binary-2.9.11-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:9b52a3f9bb540a3e4ec0f6ba6d31339727b2950c9772850d6545b7eae0b9d7c5", size = 4411646, upload-time = "2025-10-10T11:13:24.432Z" }, - { url = "https://files.pythonhosted.org/packages/5a/bd/a335ce6645334fb8d758cc358810defca14a1d19ffbc8a10bd38a2328565/psycopg2_binary-2.9.11-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:db4fd476874ccfdbb630a54426964959e58da4c61c9feba73e6094d51303d7d8", size = 4468701, upload-time = "2025-10-10T11:13:29.266Z" }, - { url = "https://files.pythonhosted.org/packages/44/d6/c8b4f53f34e295e45709b7568bf9b9407a612ea30387d35eb9fa84f269b4/psycopg2_binary-2.9.11-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:47f212c1d3be608a12937cc131bd85502954398aaa1320cb4c14421a0ffccf4c", size = 4166293, upload-time = "2025-10-10T11:13:33.336Z" }, - { url = "https://files.pythonhosted.org/packages/4b/e0/f8cc36eadd1b716ab36bb290618a3292e009867e5c97ce4aba908cb99644/psycopg2_binary-2.9.11-cp314-cp314-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e35b7abae2b0adab776add56111df1735ccc71406e56203515e228a8dc07089f", size = 3983184, upload-time = "2025-10-30T02:55:32.483Z" }, - { url = "https://files.pythonhosted.org/packages/53/3e/2a8fe18a4e61cfb3417da67b6318e12691772c0696d79434184a511906dc/psycopg2_binary-2.9.11-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:fcf21be3ce5f5659daefd2b3b3b6e4727b028221ddc94e6c1523425579664747", size = 3652650, upload-time = "2025-10-10T11:13:38.181Z" }, - { url = "https://files.pythonhosted.org/packages/76/36/03801461b31b29fe58d228c24388f999fe814dfc302856e0d17f97d7c54d/psycopg2_binary-2.9.11-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:9bd81e64e8de111237737b29d68039b9c813bdf520156af36d26819c9a979e5f", size = 3298663, upload-time = "2025-10-10T11:13:44.878Z" }, - { url = "https://files.pythonhosted.org/packages/97/77/21b0ea2e1a73aa5fa9222b2a6b8ba325c43c3a8d54272839c991f2345656/psycopg2_binary-2.9.11-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:32770a4d666fbdafab017086655bcddab791d7cb260a16679cc5a7338b64343b", size = 3044737, upload-time = "2025-10-30T02:55:35.69Z" }, - { url = "https://files.pythonhosted.org/packages/67/69/f36abe5f118c1dca6d3726ceae164b9356985805480731ac6712a63f24f0/psycopg2_binary-2.9.11-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:c3cb3a676873d7506825221045bd70e0427c905b9c8ee8d6acd70cfcbd6e576d", size = 3347643, upload-time = "2025-10-10T11:13:53.499Z" }, - { url = "https://files.pythonhosted.org/packages/e1/36/9c0c326fe3a4227953dfb29f5d0c8ae3b8eb8c1cd2967aa569f50cb3c61f/psycopg2_binary-2.9.11-cp314-cp314-win_amd64.whl", hash = "sha256:4012c9c954dfaccd28f94e84ab9f94e12df76b4afb22331b1f0d3154893a6316", size = 2803913, upload-time = "2025-10-10T11:13:57.058Z" }, +name = "psycopg" +version = "3.3.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, + { name = "tzdata", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/db/2f/cb91e5502ec9de1de6f1b76cfbf69531932725361168bb06963620c77e2e/psycopg-3.3.4.tar.gz", hash = "sha256:e21207764952cff81b6b8bdacad9a3939f2793367fdac2987b3aac36a651b5bc", size = 165799, upload-time = "2026-05-01T23:31:55.179Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5c/e0/7b3dee031daae7743609ce3c746565d4a3ed7c2c186479eb48e34e838c64/psycopg-3.3.4-py3-none-any.whl", hash = "sha256:b6bbc25ccf05c8fad3b061d9db2ef0909a555171b84b07f29458a447253d679a", size = 213001, upload-time = "2026-05-01T23:20:50.816Z" }, +] + +[package.optional-dependencies] +binary = [ + { name = "psycopg-binary", marker = "implementation_name != 'pypy'" }, +] + +[[package]] +name = "psycopg-binary" +version = "3.3.4" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/bf/70d8a60488f9955cbbcd538beae44d56bb2f1d19e673b72788f2d343ff55/psycopg_binary-3.3.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b7bfff1ca23732b488cbca3076fc11bc98d520ee122514fdb17a8e20d3338f5a", size = 4609750, upload-time = "2026-05-01T23:24:20.06Z" }, + { url = "https://files.pythonhosted.org/packages/db/b0/29e98ba210c9dbc75a6dc91e3f99b9e06ea901a62ca95804e02a1ae13e6b/psycopg_binary-3.3.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:32a6fbf8481e3a370d0d72b860d35948a693cb01281da217f7b2f307636e591a", size = 4676700, upload-time = "2026-05-01T23:25:21.727Z" }, + { url = "https://files.pythonhosted.org/packages/8e/ab/3df087b3c12bf74e47c08204172b2fabb5a144679110d5c7ad12d9201323/psycopg_binary-3.3.4-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:bdef84570ebbce1d42b4e7ea952d21c414c5f118ad02fee00c5625f35e134429", size = 5496319, upload-time = "2026-05-01T23:25:28.271Z" }, + { url = "https://files.pythonhosted.org/packages/87/9a/f088207b4cd6772f9e0d8a91807e79fa2458d4eb9eb1ae406c68415f2bec/psycopg_binary-3.3.4-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fa1cbc10768a796c96d3243656016bf4e337c81c71097270bb7b0ad6210d9765", size = 5171906, upload-time = "2026-05-01T23:25:34.004Z" }, + { url = "https://files.pythonhosted.org/packages/48/45/4523a857f253871d75c22e1c2e79fd47e599e736bcba1bad58d83e24be02/psycopg_binary-3.3.4-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cf7f73a4a792bc5db58a4b385d8a1467e8d468f7548702fb0ed1e9b7501b1c13", size = 6762621, upload-time = "2026-05-01T23:25:41.392Z" }, + { url = "https://files.pythonhosted.org/packages/7c/d1/925bf776503345bef428e6c45fb017d0139ddbe0e211814b585c4253dca8/psycopg_binary-3.3.4-cp310-cp310-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d7b4d40c153fa352ab3cca530f3a0baedf7621b2ebcbd7f084009522c21788fc", size = 5006319, upload-time = "2026-05-01T23:25:51.419Z" }, + { url = "https://files.pythonhosted.org/packages/6f/aa/99727337206fbba357ca084bf4ea8b29dc986f61842a2685859af61416db/psycopg_binary-3.3.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f9b1c2533af01cd7648378599f82b0b8ae32f293296e6eec5753a625bc97ef28", size = 4535388, upload-time = "2026-05-01T23:25:57.957Z" }, + { url = "https://files.pythonhosted.org/packages/0b/a4/567ba2c37d19d8c2f63d836385dfd2495aa5897bbee6cfab104d9ee58624/psycopg_binary-3.3.4-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:ad3bc94054876155549fdaedf4a46d1ec69d39a5bcee377148afe498e84c4b8e", size = 4224544, upload-time = "2026-05-01T23:26:03.832Z" }, + { url = "https://files.pythonhosted.org/packages/b7/23/86457f5a82731685d7701de7bfaa5eb783dd1fecbf875321897d9d9ce33a/psycopg_binary-3.3.4-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:eb4eed2079c01a4850bf467deacfab56d356d4225040170af03dc9958321242d", size = 3956282, upload-time = "2026-05-01T23:26:09.983Z" }, + { url = "https://files.pythonhosted.org/packages/a7/d8/249456df16d47de082abd9b73bce8ccdeb0293eb12e590f9150c7cbdb788/psycopg_binary-3.3.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f80e3f2b5331dbbf0901bcb658056c03eeb2c1ef31d774afb0d61598b242e744", size = 4261736, upload-time = "2026-05-01T23:26:16.798Z" }, + { url = "https://files.pythonhosted.org/packages/15/6b/c4abe228acafd8a385c1fb615d4f1e3c9b8ad7a4e4f0e84118ba3ffeed9c/psycopg_binary-3.3.4-cp310-cp310-win_amd64.whl", hash = "sha256:574ea21a9651958f1535c5a1c649c7409e9168bcbffa29a3f2f961f58b322949", size = 3570620, upload-time = "2026-05-01T23:26:22.655Z" }, + { url = "https://files.pythonhosted.org/packages/b6/82/df3312c0ca083d5b43b352f27d4dd8b1e614bd334473074715d9e0000da4/psycopg_binary-3.3.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:612a627d733f695b1de1f9b4bd511c15f999a5d8b915d444bbd7dd71cf3370da", size = 4609813, upload-time = "2026-05-01T23:26:30.612Z" }, + { url = "https://files.pythonhosted.org/packages/1f/b5/d74d542458d3e8ac0571d8a88f57ca369999b9a82f4fa528052d0d7d3e4c/psycopg_binary-3.3.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:13a7f380824c35896dcac7fe0f61440f7ca49d6dc73f3c13a9a4471e6a3b302e", size = 4676799, upload-time = "2026-05-01T23:26:38.475Z" }, + { url = "https://files.pythonhosted.org/packages/09/67/06bab9c60671999f4c6ceff1b334f3ac1f9fc5789eb467c714623ea21de9/psycopg_binary-3.3.4-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:276904e3452d6a23d474ef9a21eee19f20eed3d53ddd2576af033827e0ba0992", size = 5497050, upload-time = "2026-05-01T23:26:47.061Z" }, + { url = "https://files.pythonhosted.org/packages/72/9b/023433e2b20f970de1e22d29132a95281277646da0b2e2879dd4ee94b8c1/psycopg_binary-3.3.4-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ab8cca8ef8fb1ccf5b048ae5bd78ba55b9e4b5d472e3ce5ca39ff4d2a9c249e4", size = 5172428, upload-time = "2026-05-01T23:26:56.708Z" }, + { url = "https://files.pythonhosted.org/packages/08/cd/ae16da8fde228a38b2fe9269bbc13cf89e0186173f2265600f02d6a71e64/psycopg_binary-3.3.4-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7465bfe6087d2d5b42d4c53b9b11ca9f218e477317a4a162a10e3c19e984ba8e", size = 6762746, upload-time = "2026-05-01T23:27:07.023Z" }, + { url = "https://files.pythonhosted.org/packages/4f/81/0ba09fa5f5f88779093a2541a8e02489825721f258ab88058b11d68b3eb5/psycopg_binary-3.3.4-cp311-cp311-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:22cdbf5f91ef7bb91fe0c5757e1962d3127a8010256eefd9c61fcaf441802097", size = 5006033, upload-time = "2026-05-01T23:27:12.221Z" }, + { url = "https://files.pythonhosted.org/packages/73/6a/629136040cc3497adb442a305710b5913f2a754d4630fc3d3717c4c0df65/psycopg_binary-3.3.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:e2631da29253a98bd496e6c4813b24e09a4fe3fb2a9e88513305d6f8747cce95", size = 4534175, upload-time = "2026-05-01T23:27:18.248Z" }, + { url = "https://files.pythonhosted.org/packages/7c/32/1027f843c6dc2d5d51960ee62cc0c2cf755a4c39455aff1371173edbef7d/psycopg_binary-3.3.4-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:7f7668f30b9dd5163197e5cbf4e0efd54e00f0a859cc566ce56cfc31f4054839", size = 4224203, upload-time = "2026-05-01T23:27:24.3Z" }, + { url = "https://files.pythonhosted.org/packages/0b/e1/380a724d9093c74adb14d4fce920ea8327838abb61f760b1448586b14a8e/psycopg_binary-3.3.4-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:cffc3408d77a27973f33e5d909b624cce683db5fc25964b02fe0aae7886c1007", size = 3954509, upload-time = "2026-05-01T23:27:30.815Z" }, + { url = "https://files.pythonhosted.org/packages/db/cd/895893ae575a09c97ccfd5def070d88993d955ef34df45a881fd5ff506d6/psycopg_binary-3.3.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0579252a1202cd73e4da137a1426e2dae993ae44e757605344282af3a082848c", size = 4259551, upload-time = "2026-05-01T23:27:38.828Z" }, + { url = "https://files.pythonhosted.org/packages/dd/c6/2330a20794e37a3ec609ef2fd8522919ec7a4395a1abf979a8e2d1775cd5/psycopg_binary-3.3.4-cp311-cp311-win_amd64.whl", hash = "sha256:41f2ec0fea529832982bcb6c9415de3c86264ebe562b77a467c0fbcd7efbba8d", size = 3572054, upload-time = "2026-05-01T23:27:45.455Z" }, + { url = "https://files.pythonhosted.org/packages/95/7d/03818e13ba7f36de93573c93ee3482006d3dfa8b0f8d28df511bad0a1a92/psycopg_binary-3.3.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:5ab28a2a7649df3b72e6b674b4c190e448e8e77cf496a65bd846472048de2089", size = 4591122, upload-time = "2026-05-01T23:27:56.162Z" }, + { url = "https://files.pythonhosted.org/packages/a5/b9/11b341edf8d54e2694726b273fe9652b254d989f4f63e3ac6816ad6b55f4/psycopg_binary-3.3.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6402a9d8146cf4b3974ded3fd28a971e83dc6a0333eb7822524a3aa20b546578", size = 4669943, upload-time = "2026-05-01T23:28:04.522Z" }, + { url = "https://files.pythonhosted.org/packages/8b/18/4665bacd65e7865b4372fcd8abb8b9186ada4b0025f8c2ca691b364a556c/psycopg_binary-3.3.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:580ae30a5f95ccd90008ec697d3ed6a4a2047a516407ad904283fa42086936e9", size = 5469697, upload-time = "2026-05-01T23:28:11.337Z" }, + { url = "https://files.pythonhosted.org/packages/7c/b1/b83136c6e510593d9b0c759ba5384337bc4ad82d19fda675adc4b2703c84/psycopg_binary-3.3.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e7510c37550f91a187e3660a8cc50d4b760f8c3b8b2f89ebc5698cd2c7f2c85d", size = 5152995, upload-time = "2026-05-01T23:28:20.529Z" }, + { url = "https://files.pythonhosted.org/packages/67/8d/a9821e2a648afe6091989929982a3b0f00b2631a859cb81379728f08fb75/psycopg_binary-3.3.4-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:77df19583501ea288eaf15ac0fe7ad01e6d8091a91d5c41df5c718f307d8e31b", size = 6738180, upload-time = "2026-05-01T23:28:30.654Z" }, + { url = "https://files.pythonhosted.org/packages/7e/58/2e349e8d23905dc2317b80ac65f48fb6f821a4777a4e994a60da91c4850f/psycopg_binary-3.3.4-cp312-cp312-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:018fbed325936da502feb546642c982dcc4b9ffdea32dfef78dbf3b7f7ad4070", size = 4978828, upload-time = "2026-05-01T23:28:37.277Z" }, + { url = "https://files.pythonhosted.org/packages/45/48/57b00d03b4721878326122a1f1e6b0a90b85bcaec56b5b2f8ea6cfa45235/psycopg_binary-3.3.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:17a21953a9e5ff3a16dab692625a3676e2f101db5e40072f39dbee2250194d68", size = 4509757, upload-time = "2026-05-01T23:28:43.078Z" }, + { url = "https://files.pythonhosted.org/packages/25/37/33b47d8c007df69aec500df5889767c4d313748e8e9e27a2fef8a6dabcee/psycopg_binary-3.3.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:eb05ee1c2b817d27c537333224c9e83c7afb86fe7296ba970990068baf819b16", size = 4190546, upload-time = "2026-05-01T23:28:50.016Z" }, + { url = "https://files.pythonhosted.org/packages/ca/c6/32b0835dbc2122617902b649d76a91c1e75406e76bf3d595b0c3bb5ffad6/psycopg_binary-3.3.4-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:773d573e11f437ce0bdb95b7c18dc58390494f96d43f8b45b9760436114f7652", size = 3926197, upload-time = "2026-05-01T23:28:55.55Z" }, + { url = "https://files.pythonhosted.org/packages/cd/68/d190ef0c0c5b16ded07831dabc8ddd412f4cdab07ec6e30ed38d9bda0e1f/psycopg_binary-3.3.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:71e55ccbdfae79a2ed9c6369c3008a3025817ff9d7e27b32a2d84e2a4267e66e", size = 4236627, upload-time = "2026-05-01T23:29:05.336Z" }, + { url = "https://files.pythonhosted.org/packages/25/8f/81dcbc2e8454b74d14881275ea45f00791052dac531a9fa8be1730d1685b/psycopg_binary-3.3.4-cp312-cp312-win_amd64.whl", hash = "sha256:494ca54901be8cf9eb7e02c25b731f2317c378efa44f43e8f9bd0e1184ae7be4", size = 3560782, upload-time = "2026-05-01T23:29:11.967Z" }, + { url = "https://files.pythonhosted.org/packages/09/43/13e9c406fbbf354580476e248a16b64802a376873ebe6339e30bb655572d/psycopg_binary-3.3.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:fbd1d4ed566895ad2d3bf4ddfd8bae90026930ddf29df3b9d91d32c8c47866a7", size = 4590377, upload-time = "2026-05-01T23:29:18.782Z" }, + { url = "https://files.pythonhosted.org/packages/22/be/2923cd7c3683e7afdecf4f10796a18de02f5c5ddc0969aa2ad0a8cdd3bbd/psycopg_binary-3.3.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:75a9067e236f9b9ae3535b66fe99bddb33d39c0de10112e49b9ab11eee53dc31", size = 4669023, upload-time = "2026-05-01T23:29:25.884Z" }, + { url = "https://files.pythonhosted.org/packages/96/a0/2c913d6fe13d6a8bd13597d36739bf47af063ad9399e402cfecab16f3c1e/psycopg_binary-3.3.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:b56b603ebcea8aa10b46228b8410ba7f13e7c2ee54389d4d9be0927fd8ce2a70", size = 5467423, upload-time = "2026-05-01T23:29:33.416Z" }, + { url = "https://files.pythonhosted.org/packages/e7/38/205d10bc1ad0df4a21c5c51659126bd3ea0ef98fcad1e852f78c249bb9c3/psycopg_binary-3.3.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c677c4ad433cb7150c8cd304a0769ae3bcfbe5ea0676eb53faa7b1443b16d0d3", size = 5151137, upload-time = "2026-05-01T23:29:42.013Z" }, + { url = "https://files.pythonhosted.org/packages/36/fc/f0381ddcd45eff3bb70dbca6823a996048d7f507b2ec3fc92c6fabc0fe87/psycopg_binary-3.3.4-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:26df2717e59c0473e4465a97dfb1b7afebaa479277870fd5784d1436470db47c", size = 6736671, upload-time = "2026-05-01T23:29:51.626Z" }, + { url = "https://files.pythonhosted.org/packages/95/40/fa545ae152c24327651e5624e4902121e808270be36c10b12e9939be09bc/psycopg_binary-3.3.4-cp313-cp313-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:1dc1f79fd16bb1f3f4421417a514607539f17804d95c7ed617265369d1981cae", size = 4979601, upload-time = "2026-05-01T23:29:56.961Z" }, + { url = "https://files.pythonhosted.org/packages/86/e4/2f8a47ee97f90cd2b933d0463081d35631ff419de2b8c984a5f369857de0/psycopg_binary-3.3.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:136f199a407b5348b9b857c504aff60c77622a28482e7195839ce1b51238c4cc", size = 4510513, upload-time = "2026-05-01T23:30:07.243Z" }, + { url = "https://files.pythonhosted.org/packages/0e/0e/94e842ff4a7f98ed162580ca2e8b8864b28c1e0350f2443f8ee47f821167/psycopg_binary-3.3.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:b6f5a29e9c775b9f12a1a717aa7a2c80f9e1db6f27ba44a5b59c80ac61d2ffcf", size = 4187243, upload-time = "2026-05-01T23:30:15.352Z" }, + { url = "https://files.pythonhosted.org/packages/d0/83/fc6c174b672e29b7de996ea77b6cbddf46c891751c3355f6974292baa6b4/psycopg_binary-3.3.4-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:ee17a2cf4943cde261adfad1bbc5bf38d6b3776d7afff74c7cabcbeaeb08c260", size = 3927347, upload-time = "2026-05-01T23:30:21.186Z" }, + { url = "https://files.pythonhosted.org/packages/e9/65/768364d4a97a15b1a7f47ba52688c1686f22941d8332a8398cefc468e25f/psycopg_binary-3.3.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5c4ab71be17bdca30cb34c34c4e1496e2f5d6f20c199c12bad226070b22ef9bf", size = 4236393, upload-time = "2026-05-01T23:30:26.211Z" }, + { url = "https://files.pythonhosted.org/packages/bd/3b/218efbc9e645becd80cdf651acda05f85cfe546b7a9c0458c7cbc8fe1f74/psycopg_binary-3.3.4-cp313-cp313-win_amd64.whl", hash = "sha256:dbfdb9b6cc79f31104a7b162a2b921b765fcc62af6c00540a167a8de47e4ed38", size = 3564592, upload-time = "2026-05-01T23:30:31.764Z" }, + { url = "https://files.pythonhosted.org/packages/48/a6/828c9185701dab71b234c2a76c38a08b098ebfec5020716b4e93807492b5/psycopg_binary-3.3.4-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:28b7398fdd19db3232c884fb24550bdfe951221f510e195e233299e4c9b78f97", size = 4607292, upload-time = "2026-05-01T23:30:38.962Z" }, + { url = "https://files.pythonhosted.org/packages/92/58/5b40dbc9d839045c9dae956960e4fb6d20bcabe6c59a2aa34fc3a371913f/psycopg_binary-3.3.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1fbaa292a3c8bb61b45df1ad3da1908ccee7cb889db9425e3557d9e34e2a4829", size = 4687023, upload-time = "2026-05-01T23:30:47.227Z" }, + { url = "https://files.pythonhosted.org/packages/85/a9/793f0ac107a9003b48441d0d1f9f616d96e0f37458dd8dc12528ceff55fb/psycopg_binary-3.3.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:94596f9e7633ee3f6440711d43bb70aa31cc0a46a900ab8b4201a366ace5c9e7", size = 5486985, upload-time = "2026-05-01T23:30:55.517Z" }, + { url = "https://files.pythonhosted.org/packages/8f/26/42e8533497e2592334f68ec529cf5f840f7fa4e99575a4bb61aa184dbfbf/psycopg_binary-3.3.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8c0056529e68dbe9184cd4019a1f3d8f3a4ead2f6fc7a5afcf27d3314edd1277", size = 5168745, upload-time = "2026-05-01T23:31:01.904Z" }, + { url = "https://files.pythonhosted.org/packages/15/af/b7151776cc08d5935d45c833ec818a9beb417cf7c08239af1aafbdae78ee/psycopg_binary-3.3.4-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2c09aad7051326e7603c14e50636db9c01f78272dc54b3accff03d46370461e6", size = 6761486, upload-time = "2026-05-01T23:31:14.511Z" }, + { url = "https://files.pythonhosted.org/packages/d0/ed/c92533b9124712d592cbf1cd6c76da933a2e0acea81dfe1fbe7e735f0cff/psycopg_binary-3.3.4-cp314-cp314-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:514404ed543efd620c85602b747df2a23cf1241b4067199e1a66f2d2757aaa41", size = 4997427, upload-time = "2026-05-01T23:31:20.901Z" }, + { url = "https://files.pythonhosted.org/packages/a2/23/ccadfd0de416aa188356daa199453af24087b042e296088706d190ae0295/psycopg_binary-3.3.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:46893c26858be12cc49ca4226ed6a60b4bfccadd946b3bebb783a60b38788228", size = 4533549, upload-time = "2026-05-01T23:31:26.204Z" }, + { url = "https://files.pythonhosted.org/packages/fd/a0/c8f43cee36386f7bc891ab41a9d31ea07cf9826038e732da79f26b1e5f34/psycopg_binary-3.3.4-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:df1d567fc430f6df15c9fcf67d87685fc49bdb325adc0db5af1adfb2f44eb5c9", size = 4210256, upload-time = "2026-05-01T23:31:33.884Z" }, + { url = "https://files.pythonhosted.org/packages/4e/2c/c1547871be3790676e8868b38655496422f94f0978dfb66b74bdba2f1676/psycopg_binary-3.3.4-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:6b9016b1714da4dd5ecaaa75b82098aa5a0b87854ce9b092e21c27c4ae23e014", size = 3946204, upload-time = "2026-05-01T23:31:39.626Z" }, + { url = "https://files.pythonhosted.org/packages/c4/b1/f6670f00fa7ea601584623f6c11602ab92117d83eaff885e0210f6de7418/psycopg_binary-3.3.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:47c656a8a7ba6eb0cff1801a4caaa9c8bdc12d03080e273aff1c8ac39971a77e", size = 4255811, upload-time = "2026-05-01T23:31:44.986Z" }, + { url = "https://files.pythonhosted.org/packages/eb/e6/5fff07a70d1f945ed90ae131c3bd76cab32beff7c58c6db15ad5820b6d1f/psycopg_binary-3.3.4-cp314-cp314-win_amd64.whl", hash = "sha256:c37e024c07308cd06cf3ec51bfd0e7f6157585a4d84d1bce4a7f5f7913719bf8", size = 3666849, upload-time = "2026-05-01T23:31:51.165Z" }, ] [[package]] @@ -3900,7 +3917,7 @@ all = [ { name = "osquery" }, { name = "paramiko" }, { name = "presto-python-client" }, - { name = "psycopg2-binary" }, + { name = "psycopg", extra = ["binary"] }, { name = "pyathena" }, { name = "pymysql" }, { name = "redshift-connector" }, @@ -3921,7 +3938,7 @@ clickhouse = [ { name = "clickhouse-connect" }, ] cockroachdb = [ - { name = "psycopg2-binary" }, + { name = "psycopg", extra = ["binary"] }, ] d1 = [ { name = "requests" }, @@ -3960,7 +3977,7 @@ osquery = [ { name = "osquery" }, ] postgres = [ - { name = "psycopg2-binary" }, + { name = "psycopg", extra = ["binary"] }, ] presto = [ { name = "presto-python-client" }, @@ -4049,9 +4066,9 @@ requires-dist = [ { name = "paramiko", marker = "extra == 'ssh'", specifier = ">=2.0.0,<4.0.0" }, { name = "presto-python-client", marker = "extra == 'all'", specifier = ">=0.8.4" }, { name = "presto-python-client", marker = "extra == 'presto'", specifier = ">=0.8.4" }, - { name = "psycopg2-binary", marker = "extra == 'all'", specifier = ">=2.9.0" }, - { name = "psycopg2-binary", marker = "extra == 'cockroachdb'", specifier = ">=2.9.0" }, - { name = "psycopg2-binary", marker = "extra == 'postgres'", specifier = ">=2.9.0" }, + { name = "psycopg", extras = ["binary"], marker = "extra == 'all'", specifier = ">=3.2.0" }, + { name = "psycopg", extras = ["binary"], marker = "extra == 'cockroachdb'", specifier = ">=3.2.0" }, + { name = "psycopg", extras = ["binary"], marker = "extra == 'postgres'", specifier = ">=3.2.0" }, { name = "pyathena", marker = "extra == 'all'", specifier = ">=3.22.0" }, { name = "pyathena", marker = "extra == 'athena'", specifier = ">=3.22.0" }, { name = "pymysql", marker = "extra == 'all'", specifier = ">=1.1.0" }, From e4697cf922cda7ffcaa967fece42c51427a59d60 Mon Sep 17 00:00:00 2001 From: Brent Rager Date: Wed, 20 May 2026 17:20:40 -0400 Subject: [PATCH 2/2] Make cursor-based adapter compatible with psycopg v3 transactions and multi-statement MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Running tests/integration/test_transactions.py against a real PostgreSQL container with the psycopg3 adapter surfaced two driver-behavior differences that need handling in the shared CursorBasedAdapter: 1. autocommit-aware execute_non_query psycopg2 silently makes conn.commit() a no-op when conn.autocommit is True; psycopg v3 actively COMMITs any open transaction. The adapter's execute_non_query unconditionally called conn.commit(), which under psycopg3 meant `executor.execute("BEGIN")` immediately ended the transaction it just started — so the subsequent INSERT was committed and ROLLBACK had nothing to undo. Skipping the commit when conn.autocommit is True preserves the original intent on both drivers. 2. nextset() walk in execute_query psycopg v3 exposes each result set in a multi-statement string and leaves the cursor positioned at the first (e.g. on BEGIN, which has no columns). psycopg2 only ever surfaced the last set. Walking cursor.nextset() until it returns falsy lands on the last set; the loop is a no-op on drivers that don't implement nextset() (a number raise NotSupportedError, hence the try/except). The integration tests in tests/integration/test_transactions.py needed three fixes that are independent of the driver swap — verified to fail or hang on upstream main with psycopg2 as well: - test_multi_statement_as_single_query_works The "BEGIN; INSERT; SELECT" payload left an open transaction on the executor's persistent connection; the test's subsequent DROP TABLE (on a different connection) then blocked waiting for the lock. Added reset_tui_executor() before the cleanup so the persistent connection rolls back and releases the lock. - test_atomic_execute_rolls_back_on_error The test expected atomic_execute to raise on a failing statement. Per its docstring and current implementation, atomic_execute reports the failure via a MultiStatementResult with completed=False and the failing index in error_index. Updated the assertions accordingly. - test_atomic_execute_commits_on_success Multi-statement atomic_execute is documented to return a MultiStatementResult (one StatementResult per statement), not a single QueryResult. Asserting on result.results[-1].result picks up the trailing SELECT's QueryResult exactly as the test intended. Result against the docker-compose postgres:16 fixture: - tests/integration/test_transactions.py: 13 passed - tests/integration/test_database_browsing_flow.py::TestPostgreSQLDatabaseBrowsing: 1 passed - tests/integration/test_stale_connection_reconnect.py -k postgres: 5 passed, 1 idle-variant skipped (pre-existing) - tests/unit: 842 passed --- .../connections/providers/adapters/base.py | 22 +++++++++++++- tests/integration/test_transactions.py | 30 +++++++++++++++---- tests/unit/test_postgresql_adapter.py | 8 ++--- 3 files changed, 49 insertions(+), 11 deletions(-) diff --git a/sqlit/domains/connections/providers/adapters/base.py b/sqlit/domains/connections/providers/adapters/base.py index 109ea1da..f4bab9dc 100644 --- a/sqlit/domains/connections/providers/adapters/base.py +++ b/sqlit/domains/connections/providers/adapters/base.py @@ -492,6 +492,19 @@ def execute_query(self, conn: Any, query: str, max_rows: int | None = None) -> t """Execute a query using cursor-based approach with optional row limit.""" cursor = conn.cursor() cursor.execute(query) + # Multi-statement queries (e.g. "BEGIN; INSERT; SELECT") produce + # several result sets on drivers that expose them — psycopg v3 + # leaves the cursor positioned on the first set (the BEGIN, which + # has no columns), so advance to the last one. psycopg2 and other + # drivers either don't implement nextset() or return None for + # single-statement queries; either way this loop terminates. + while True: + try: + more = cursor.nextset() + except Exception: + more = None + if not more: + break if cursor.description: columns = [col[0] for col in cursor.description] if max_rows is not None: @@ -511,7 +524,14 @@ def execute_non_query(self, conn: Any, query: str) -> int: cursor = conn.cursor() cursor.execute(query) rowcount = int(cursor.rowcount) - conn.commit() + # When the connection is in autocommit mode, an explicit conn.commit() + # has driver-dependent semantics: psycopg2 makes it a no-op, but + # psycopg v3 actively ends any open transaction. Calling commit() + # here would unconditionally close out an explicit BEGIN before the + # caller's INSERT/ROLLBACK gets a chance to run. Skipping it when + # autocommit is on preserves the intent in both worlds. + if not getattr(conn, "autocommit", False): + conn.commit() return rowcount diff --git a/tests/integration/test_transactions.py b/tests/integration/test_transactions.py index e0a58deb..e36e3a3c 100644 --- a/tests/integration/test_transactions.py +++ b/tests/integration/test_transactions.py @@ -300,6 +300,11 @@ def test_multi_statement_as_single_query_works(self, postgres_db: str): assert len(result.columns) > 0 assert result.row_count > 0 + # The query opened a transaction (BEGIN) but never closed it. Close + # the executor so its persistent connection rolls back, otherwise + # the DROP TABLE below blocks waiting for the lock. + reset_tui_executor() + # Cleanup conn = adapter.connect(config) try: @@ -509,13 +514,19 @@ def test_atomic_execute_rolls_back_on_error(self, postgres_db: str): initial_count = result.rows[0][0] # This should fail on the second INSERT (NULL not allowed) - # and rollback the first INSERT too + # and rollback the first INSERT too. atomic_execute reports + # the failure via a MultiStatementResult with completed=False + # rather than raising. + from sqlit.domains.query.app.multi_statement import MultiStatementResult + query = """ INSERT INTO atomic_test (name) VALUES ('row1'); INSERT INTO atomic_test (name) VALUES (NULL); """ - with pytest.raises(Exception): - executor.atomic_execute(query) + atomic_result = executor.atomic_execute(query) + assert isinstance(atomic_result, MultiStatementResult) + assert atomic_result.completed is False + assert atomic_result.error_index is not None # Count should be unchanged (both inserts rolled back) result = executor.execute("SELECT COUNT(*) FROM atomic_test") @@ -556,6 +567,8 @@ def test_atomic_execute_commits_on_success(self, postgres_db: str): executor = TransactionExecutor(config, provider) try: + from sqlit.domains.query.app.multi_statement import MultiStatementResult + query = """ INSERT INTO atomic_test (name) VALUES ('row1'); INSERT INTO atomic_test (name) VALUES ('row2'); @@ -563,9 +576,14 @@ def test_atomic_execute_commits_on_success(self, postgres_db: str): """ result = executor.atomic_execute(query) - # Should return the SELECT result - assert isinstance(result, QueryResult) - assert result.row_count == 2 + # Multi-statement atomic_execute returns a MultiStatementResult + # carrying each statement's outcome. The SELECT lands as the + # last entry and exposes the rows. + assert isinstance(result, MultiStatementResult) + assert result.completed is True + select_result = result.results[-1].result + assert isinstance(select_result, QueryResult) + assert select_result.row_count == 2 finally: executor.close() conn = adapter.connect(config) diff --git a/tests/unit/test_postgresql_adapter.py b/tests/unit/test_postgresql_adapter.py index 9af303f7..4b786c5d 100644 --- a/tests/unit/test_postgresql_adapter.py +++ b/tests/unit/test_postgresql_adapter.py @@ -48,8 +48,8 @@ def test_postgresql_uses_custom_port_when_server_left_blank() -> None: psycopg2.connect so the connection actually reaches the docker container. """ - mock_psycopg2 = MagicMock() - with patch.dict("sys.modules", {"psycopg2": mock_psycopg2}): + mock_psycopg = MagicMock() + with patch.dict("sys.modules", {"psycopg": mock_psycopg}): from sqlit.domains.connections.providers.postgresql.adapter import PostgreSQLAdapter adapter = PostgreSQLAdapter() @@ -65,9 +65,9 @@ def test_postgresql_uses_custom_port_when_server_left_blank() -> None: adapter.connect(config) - kwargs = mock_psycopg2.connect.call_args.kwargs + kwargs = mock_psycopg.connect.call_args.kwargs assert kwargs.get("port") == 5433, ( - "port=5433 must reach psycopg2.connect, but the adapter " + "port=5433 must reach psycopg.connect, but the adapter " f"silently drops it when server is blank. kwargs={kwargs!r}" ) assert kwargs.get("host") == "localhost", (