From: Federico Caselli Date: Thu, 11 Apr 2024 19:24:54 +0000 (+0200) Subject: Document how to configure the cursor_factory on psycopg X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=497c4a2c22be2e5c2319acf56e11d3037a552064;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git Document how to configure the cursor_factory on psycopg Change-Id: I117a0600c31dde721c99891caaa43937458e78d9 Refereinces: #8978 --- diff --git a/lib/sqlalchemy/dialects/postgresql/psycopg.py b/lib/sqlalchemy/dialects/postgresql/psycopg.py index 88ad13d408..5bdae1703a 100644 --- a/lib/sqlalchemy/dialects/postgresql/psycopg.py +++ b/lib/sqlalchemy/dialects/postgresql/psycopg.py @@ -50,6 +50,38 @@ The asyncio version of the dialect may also be specified explicitly using the dialect shares most of its behavior with the ``psycopg2`` dialect. Further documentation is available there. +Using a different Cursor class +------------------------------ + +One of the differences between ``psycopg`` and the older ``psycopg2`` +is how bound parameters are handled: ``psycopg2`` would bind them +client side, while ``psycopg`` by default will bind them server side. + +It's possible to configure ``psycopg`` to do client side binding by +specifying the ``cursor_factory`` to be ``ClientCursor`` when creating +the engine:: + + from psycopg import ClientCursor + + client_side_engine = create_engine( + "postgresql+psycopg://...", + connect_args={"cursor_factory": ClientCursor}, + ) + +Similarly when using an async engine the ``AsyncClientCursor`` can be +specified:: + + from psycopg import AsyncClientCursor + + client_side_engine = create_async_engine( + "postgresql+psycopg://...", + connect_args={"cursor_factory": AsyncClientCursor}, + ) + +.. seealso:: + + `Client-side-binding cursors `_ + """ # noqa from __future__ import annotations diff --git a/test/dialect/postgresql/test_dialect.py b/test/dialect/postgresql/test_dialect.py index 40718ee2df..eae1b55d6e 100644 --- a/test/dialect/postgresql/test_dialect.py +++ b/test/dialect/postgresql/test_dialect.py @@ -1376,6 +1376,7 @@ $$ LANGUAGE plpgsql; conn.exec_driver_sql("SELECT note('another note')") finally: trans.rollback() + conn.close() finally: log.removeHandler(buf) log.setLevel(lev) @@ -1720,3 +1721,37 @@ class Psycopg3Test(fixtures.TestBase): def test_async_version(self): e = create_engine("postgresql+psycopg_async://") is_true(isinstance(e.dialect, psycopg_dialect.PGDialectAsync_psycopg)) + + @testing.skip_if(lambda c: c.db.dialect.is_async) + def test_client_side_cursor(self, testing_engine): + from psycopg import ClientCursor + + engine = testing_engine( + options={"connect_args": {"cursor_factory": ClientCursor}} + ) + + with engine.connect() as c: + res = c.execute(select(1, 2, 3)).one() + eq_(res, (1, 2, 3)) + with c.connection.driver_connection.cursor() as cursor: + is_true(isinstance(cursor, ClientCursor)) + + @config.async_test + @testing.skip_if(lambda c: not c.db.dialect.is_async) + async def test_async_client_side_cursor(self, testing_engine): + from psycopg import AsyncClientCursor + + engine = testing_engine( + options={"connect_args": {"cursor_factory": AsyncClientCursor}}, + asyncio=True, + ) + + async with engine.connect() as c: + res = (await c.execute(select(1, 2, 3))).one() + eq_(res, (1, 2, 3)) + async with ( + await c.get_raw_connection() + ).driver_connection.cursor() as cursor: + is_true(isinstance(cursor, AsyncClientCursor)) + + await engine.dispose()