Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(postgres): use psycopg rather than psycopg2 #10659

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/renovate.json
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@
},
{
"addLabels": ["postgres"],
"matchPackageNames": ["/psycopg2/", "/postgres/"]
"matchPackageNames": ["/psycopg/", "/postgres/"]
},
{
"addLabels": ["druid"],
Expand All @@ -89,7 +89,7 @@
},
{
"addLabels": ["risingwave"],
"matchPackageNames": ["/risingwave/"]
"matchPackageNames": ["/psycopg/", "/risingwave/"]
},
{
"addLabels": ["snowflake"],
Expand Down
2 changes: 1 addition & 1 deletion conda/environment-arm64-flink.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ dependencies:
- pins >=0.8.2
- uv>=0.4.29
- polars >=1,<2
- psycopg2 >=2.8.4
- psycopg >= 3.2.0
- pyarrow =11.0.0
- pyarrow-tests
- pyarrow-hotfix >=0.4
Expand Down
2 changes: 1 addition & 1 deletion conda/environment-arm64.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ dependencies:
- pins >=0.8.2
- uv>=0.4.29
- polars >=1,<2
- psycopg2 >=2.8.4
- psycopg >= 3.2.0
- pyarrow >=10.0.1
- pyarrow-tests
- pyarrow-hotfix >=0.4
Expand Down
2 changes: 1 addition & 1 deletion conda/environment.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ dependencies:
- pip
- uv>=0.4.29
- polars >=1,<2
- psycopg2 >=2.8.4
- psycopg >= 3.2.0
- pyarrow >=10.0.1
- pyarrow-hotfix >=0.4
- pydata-google-auth
Expand Down
45 changes: 22 additions & 23 deletions ibis/backends/postgres/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@

import pandas as pd
import polars as pl
import psycopg2
import psycopg
import pyarrow as pa


Expand Down Expand Up @@ -90,8 +90,6 @@
return self.connect(**kwargs)

def _register_in_memory_table(self, op: ops.InMemoryTable) -> None:
from psycopg2.extras import execute_batch

schema = op.schema
if null_columns := [col for col, dtype in schema.items() if dtype.is_null()]:
raise exc.IbisTypeError(
Expand Down Expand Up @@ -129,7 +127,7 @@

with self.begin() as cur:
cur.execute(create_stmt_sql)
execute_batch(cur, sql, data, 128)
cur.executemany(sql, data)

Check warning on line 130 in ibis/backends/postgres/__init__.py

View check run for this annotation

Codecov / codecov/patch

ibis/backends/postgres/__init__.py#L130

Added line #L130 was not covered by tests

@contextlib.contextmanager
def begin(self):
Expand All @@ -145,14 +143,16 @@
finally:
cursor.close()

def _fetch_from_cursor(self, cursor, schema: sch.Schema) -> pd.DataFrame:
def _fetch_from_cursor(
self, cursor: psycopg.Cursor, schema: sch.Schema
) -> pd.DataFrame:
import pandas as pd

from ibis.backends.postgres.converter import PostgresPandasData

try:
df = pd.DataFrame.from_records(
cursor, columns=schema.names, coerce_float=True
cursor.fetchall(), columns=schema.names, coerce_float=True
)
except Exception:
# clean up the cursor if we fail to create the DataFrame
Expand All @@ -166,7 +166,7 @@

@property
def version(self):
version = f"{self.con.server_version:0>6}"
version = f"{self.con.info.server_version:0>6}"

Check warning on line 169 in ibis/backends/postgres/__init__.py

View check run for this annotation

Codecov / codecov/patch

ibis/backends/postgres/__init__.py#L169

Added line #L169 was not covered by tests
major = int(version[:2])
minor = int(version[2:4])
patch = int(version[4:])
Expand Down Expand Up @@ -233,17 +233,17 @@
year int32
month int32
"""
import psycopg2
import psycopg2.extras
import psycopg
import psycopg.types.json

Check warning on line 237 in ibis/backends/postgres/__init__.py

View check run for this annotation

Codecov / codecov/patch

ibis/backends/postgres/__init__.py#L236-L237

Added lines #L236 - L237 were not covered by tests

psycopg2.extras.register_default_json(loads=lambda x: x)
psycopg.types.json.set_json_loads(loads=lambda x: x)

Check warning on line 239 in ibis/backends/postgres/__init__.py

View check run for this annotation

Codecov / codecov/patch

ibis/backends/postgres/__init__.py#L239

Added line #L239 was not covered by tests

self.con = psycopg2.connect(
self.con = psycopg.connect(

Check warning on line 241 in ibis/backends/postgres/__init__.py

View check run for this annotation

Codecov / codecov/patch

ibis/backends/postgres/__init__.py#L241

Added line #L241 was not covered by tests
host=host,
port=port,
user=user,
password=password,
database=database,
dbname=database,
options=(f"-csearch_path={schema}" * (schema is not None)) or None,
**kwargs,
)
Expand All @@ -252,7 +252,7 @@

@util.experimental
@classmethod
def from_connection(cls, con: psycopg2.extensions.connection) -> Backend:
def from_connection(cls, con: psycopg.Connection) -> Backend:
"""Create an Ibis client from an existing connection to a PostgreSQL database.

Parameters
Expand Down Expand Up @@ -701,8 +701,9 @@
yield result

def raw_sql(self, query: str | sg.Expression, **kwargs: Any) -> Any:
import psycopg2
import psycopg2.extras
import psycopg
import psycopg.types
import psycopg.types.hstore

Check warning on line 706 in ibis/backends/postgres/__init__.py

View check run for this annotation

Codecov / codecov/patch

ibis/backends/postgres/__init__.py#L704-L706

Added lines #L704 - L706 were not covered by tests

with contextlib.suppress(AttributeError):
query = query.sql(dialect=self.dialect)
Expand All @@ -711,14 +712,12 @@
cursor = con.cursor()

try:
# try to load hstore, uuid and ipaddress extensions
with contextlib.suppress(psycopg2.ProgrammingError):
psycopg2.extras.register_hstore(cursor)
with contextlib.suppress(psycopg2.ProgrammingError):
psycopg2.extras.register_uuid(conn_or_curs=cursor)
with contextlib.suppress(psycopg2.ProgrammingError):
psycopg2.extras.register_ipaddress(cursor)
except Exception:
# try to load hstore
psycopg.types.hstore.register_hstore(

Check warning on line 716 in ibis/backends/postgres/__init__.py

View check run for this annotation

Codecov / codecov/patch

ibis/backends/postgres/__init__.py#L716

Added line #L716 was not covered by tests
psycopg.types.TypeInfo.fetch(con, "hstore"),
cursor,
)
except (psycopg.InternalError, psycopg.ProgrammingError, TypeError):

Check warning on line 720 in ibis/backends/postgres/__init__.py

View check run for this annotation

Codecov / codecov/patch

ibis/backends/postgres/__init__.py#L720

Added line #L720 was not covered by tests
cursor.close()
raise

Expand Down
2 changes: 1 addition & 1 deletion ibis/backends/postgres/tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@
supports_structs = False
rounding_method = "half_to_even"
service_name = "postgres"
deps = ("psycopg2",)
deps = ("psycopg",)

Check warning on line 51 in ibis/backends/postgres/tests/conftest.py

View check run for this annotation

Codecov / codecov/patch

ibis/backends/postgres/tests/conftest.py#L51

Added line #L51 was not covered by tests

driver_supports_multiple_statements = True

Expand Down
8 changes: 4 additions & 4 deletions ibis/backends/postgres/tests/test_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,10 @@
import ibis.common.exceptions as com
import ibis.expr.datatypes as dt
import ibis.expr.types as ir
from ibis.backends.tests.errors import PsycoPg2OperationalError
from ibis.backends.tests.errors import PsycoPgOperationalError

Check warning on line 33 in ibis/backends/postgres/tests/test_client.py

View check run for this annotation

Codecov / codecov/patch

ibis/backends/postgres/tests/test_client.py#L33

Added line #L33 was not covered by tests
from ibis.util import gen_name

pytest.importorskip("psycopg2")
pytest.importorskip("psycopg")

Check warning on line 36 in ibis/backends/postgres/tests/test_client.py

View check run for this annotation

Codecov / codecov/patch

ibis/backends/postgres/tests/test_client.py#L36

Added line #L36 was not covered by tests

POSTGRES_TEST_DB = os.environ.get("IBIS_TEST_POSTGRES_DATABASE", "ibis_testing")
IBIS_POSTGRES_HOST = os.environ.get("IBIS_TEST_POSTGRES_HOST", "localhost")
Expand Down Expand Up @@ -260,7 +260,7 @@

def test_port():
# check that we parse and use the port (and then of course fail cuz it's bogus)
with pytest.raises(PsycoPg2OperationalError):
with pytest.raises(PsycoPgOperationalError):

Check warning on line 263 in ibis/backends/postgres/tests/test_client.py

View check run for this annotation

Codecov / codecov/patch

ibis/backends/postgres/tests/test_client.py#L263

Added line #L263 was not covered by tests
ibis.connect("postgresql://postgres:postgres@localhost:1337/ibis_testing")


Expand Down Expand Up @@ -388,7 +388,7 @@
quoted_pass = quote_plus(password)
url = f"postgres://{IBIS_POSTGRES_USER}:{quoted_pass}@{IBIS_POSTGRES_HOST}:{IBIS_POSTGRES_PORT}/{POSTGRES_TEST_DB}"
with pytest.raises(
PsycoPg2OperationalError,
PsycoPgOperationalError,
match=f'password authentication failed for user "{IBIS_POSTGRES_USER}"',
):
ibis.connect(url)
Expand Down
6 changes: 3 additions & 3 deletions ibis/backends/postgres/tests/test_functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
import ibis.expr.types as ir
from ibis import literal as L

pytest.importorskip("psycopg2")
pytest.importorskip("psycopg")

Check warning on line 19 in ibis/backends/postgres/tests/test_functions.py

View check run for this annotation

Codecov / codecov/patch

ibis/backends/postgres/tests/test_functions.py#L19

Added line #L19 was not covered by tests


@pytest.mark.parametrize(
Expand Down Expand Up @@ -1195,7 +1195,7 @@
)
with con.begin() as c:
c.execute(sql_string)
raw_data = [row[0][0] for row in c.fetchall()]
raw_data = [row[0] for row in c.fetchall()]

Check warning on line 1198 in ibis/backends/postgres/tests/test_functions.py

View check run for this annotation

Codecov / codecov/patch

ibis/backends/postgres/tests/test_functions.py#L1198

Added line #L1198 was not covered by tests
expected = pd.Series(raw_data, name=name)
tm.assert_series_equal(result, expected)

Expand All @@ -1212,7 +1212,7 @@
)
with con.begin() as c:
c.execute(sql_string)
rows = [row[0] for (row,) in c.fetchall()]
rows = [row[0] for row in c.fetchall()]

Check warning on line 1215 in ibis/backends/postgres/tests/test_functions.py

View check run for this annotation

Codecov / codecov/patch

ibis/backends/postgres/tests/test_functions.py#L1215

Added line #L1215 was not covered by tests
expected = pd.Series(rows, name=name)
tm.assert_series_equal(result, expected)

Expand Down
2 changes: 1 addition & 1 deletion ibis/backends/postgres/tests/test_postgis.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import pytest
from numpy import testing

pytest.importorskip("psycopg2")
pytest.importorskip("psycopg")

Check warning on line 10 in ibis/backends/postgres/tests/test_postgis.py

View check run for this annotation

Codecov / codecov/patch

ibis/backends/postgres/tests/test_postgis.py#L10

Added line #L10 was not covered by tests
gpd = pytest.importorskip("geopandas")
pytest.importorskip("shapely")

Expand Down
2 changes: 1 addition & 1 deletion ibis/backends/postgres/tests/test_udf.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
from ibis import udf
from ibis.util import guid

pytest.importorskip("psycopg2")
pytest.importorskip("psycopg")

Check warning on line 15 in ibis/backends/postgres/tests/test_udf.py

View check run for this annotation

Codecov / codecov/patch

ibis/backends/postgres/tests/test_udf.py#L15

Added line #L15 was not covered by tests


@pytest.fixture(scope="session")
Expand Down
9 changes: 4 additions & 5 deletions ibis/backends/risingwave/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,9 @@
from operator import itemgetter
from typing import TYPE_CHECKING

import psycopg2
import psycopg
import sqlglot as sg
import sqlglot.expressions as sge
from psycopg2 import extras

import ibis
import ibis.backends.sql.compilers as sc
Expand Down Expand Up @@ -110,12 +109,12 @@
month int32
"""

self.con = psycopg2.connect(
self.con = psycopg.connect(

Check warning on line 112 in ibis/backends/risingwave/__init__.py

View check run for this annotation

Codecov / codecov/patch

ibis/backends/risingwave/__init__.py#L112

Added line #L112 was not covered by tests
host=host,
port=port,
user=user,
password=password,
database=database,
dbname=database,
options=(f"-csearch_path={schema}" * (schema is not None)) or None,
)

Expand Down Expand Up @@ -289,7 +288,7 @@
)
with self.begin() as cur:
cur.execute(create_stmt_sql)
extras.execute_batch(cur, sql, data, 128)
cur.executemany(sql, data)

Check warning on line 291 in ibis/backends/risingwave/__init__.py

View check run for this annotation

Codecov / codecov/patch

ibis/backends/risingwave/__init__.py#L291

Added line #L291 was not covered by tests

def list_databases(
self, *, like: str | None = None, catalog: str | None = None
Expand Down
2 changes: 1 addition & 1 deletion ibis/backends/risingwave/tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
supports_structs = False
rounding_method = "half_to_even"
service_name = "risingwave"
deps = ("psycopg2",)
deps = ("psycopg",)

Check warning on line 36 in ibis/backends/risingwave/tests/conftest.py

View check run for this annotation

Codecov / codecov/patch

ibis/backends/risingwave/tests/conftest.py#L36

Added line #L36 was not covered by tests

@property
def test_files(self) -> Iterable[Path]:
Expand Down
2 changes: 1 addition & 1 deletion ibis/backends/risingwave/tests/test_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
import ibis.expr.types as ir
from ibis.util import gen_name

pytest.importorskip("psycopg2")
pytest.importorskip("psycopg")

Check warning on line 15 in ibis/backends/risingwave/tests/test_client.py

View check run for this annotation

Codecov / codecov/patch

ibis/backends/risingwave/tests/test_client.py#L15

Added line #L15 was not covered by tests

RISINGWAVE_TEST_DB = os.environ.get("IBIS_TEST_RISINGWAVE_DATABASE", "dev")
IBIS_RISINGWAVE_HOST = os.environ.get("IBIS_TEST_RISINGWAVE_HOST", "localhost")
Expand Down
2 changes: 1 addition & 1 deletion ibis/backends/risingwave/tests/test_functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
import ibis.expr.datatypes as dt
from ibis import literal as L

pytest.importorskip("psycopg2")
pytest.importorskip("psycopg")

Check warning on line 17 in ibis/backends/risingwave/tests/test_functions.py

View check run for this annotation

Codecov / codecov/patch

ibis/backends/risingwave/tests/test_functions.py#L17

Added line #L17 was not covered by tests


@pytest.mark.parametrize(("value", "expected"), [(0, None), (5.5, 5.5)])
Expand Down
32 changes: 16 additions & 16 deletions ibis/backends/tests/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,23 +113,23 @@
TrinoUserError = None

try:
from psycopg2.errors import ArraySubscriptError as PsycoPg2ArraySubscriptError
from psycopg2.errors import DivisionByZero as PsycoPg2DivisionByZero
from psycopg2.errors import IndeterminateDatatype as PsycoPg2IndeterminateDatatype
from psycopg2.errors import InternalError_ as PsycoPg2InternalError
from psycopg2.errors import (
InvalidTextRepresentation as PsycoPg2InvalidTextRepresentation,
from psycopg.errors import ArraySubscriptError as PsycoPgArraySubscriptError
from psycopg.errors import DivisionByZero as PsycoPgDivisionByZero
from psycopg.errors import IndeterminateDatatype as PsycoPgIndeterminateDatatype
from psycopg.errors import InternalError_ as PsycoPgInternalError
from psycopg.errors import (
InvalidTextRepresentation as PsycoPgInvalidTextRepresentation,
)
from psycopg2.errors import OperationalError as PsycoPg2OperationalError
from psycopg2.errors import ProgrammingError as PsycoPg2ProgrammingError
from psycopg2.errors import SyntaxError as PsycoPg2SyntaxError
from psycopg2.errors import UndefinedObject as PsycoPg2UndefinedObject
except ImportError:
PsycoPg2SyntaxError = PsycoPg2IndeterminateDatatype = (
PsycoPg2InvalidTextRepresentation
) = PsycoPg2DivisionByZero = PsycoPg2InternalError = PsycoPg2ProgrammingError = (
PsycoPg2OperationalError
) = PsycoPg2UndefinedObject = PsycoPg2ArraySubscriptError = None
from psycopg.errors import OperationalError as PsycoPgOperationalError
from psycopg.errors import ProgrammingError as PsycoPgProgrammingError
from psycopg.errors import SyntaxError as PsycoPgSyntaxError
from psycopg.errors import UndefinedObject as PsycoPgUndefinedObject
except ImportError:
PsycoPgSyntaxError = PsycoPgIndeterminateDatatype = (
PsycoPgInvalidTextRepresentation
) = PsycoPgDivisionByZero = PsycoPgInternalError = PsycoPgProgrammingError = (
PsycoPgOperationalError
) = PsycoPgUndefinedObject = PsycoPgArraySubscriptError = None

try:
from MySQLdb import NotSupportedError as MySQLNotSupportedError
Expand Down
Loading
Loading