]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
Document how to configure the cursor_factory on psycopg
authorFederico Caselli <cfederico87@gmail.com>
Thu, 11 Apr 2024 19:24:54 +0000 (21:24 +0200)
committerFederico Caselli <cfederico87@gmail.com>
Thu, 11 Apr 2024 19:25:55 +0000 (21:25 +0200)
Change-Id: I117a0600c31dde721c99891caaa43937458e78d9
Refereinces: #8978
(cherry picked from commit 497c4a2c22be2e5c2319acf56e11d3037a552064)

lib/sqlalchemy/dialects/postgresql/psycopg.py
test/dialect/postgresql/test_dialect.py

index 90177a43cebbe0effbc2a33bc0e7e9850d9c4470..a1ad0fc6821803e935decfc969bc3efd5d37d0b2 100644 (file)
@@ -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 <https://www.psycopg.org/psycopg3/docs/advanced/cursors.html#client-side-binding-cursors>`_
+
 """  # noqa
 from __future__ import annotations
 
index 40718ee2dff3dcedd187492f395f22fad074e6a1..eae1b55d6e99c79b69c53e85283ec4c8a98c115c 100644 (file)
@@ -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()