]> git.ipfire.org Git - thirdparty/psycopg.git/commitdiff
Make the row_factory attribute non-nullable
authorDaniele Varrazzo <daniele.varrazzo@gmail.com>
Wed, 24 Feb 2021 02:05:15 +0000 (03:05 +0100)
committerDaniele Varrazzo <daniele.varrazzo@gmail.com>
Wed, 24 Feb 2021 02:05:15 +0000 (03:05 +0100)
Added a `tuple_row()` factory for completeness. Note that it returns None, not
a callable, and the row_maker on the Transformer hasn't changed. The
signature of RowFactory now allows that.

This
makes simpler to specify the row_factory option on `conn.cursor()`: None
means default (the connection `row_factory`), specifying `tuple_row()`
overrides it to the normal tuplish behaviour.

docs/connection.rst
docs/row-factories.rst
psycopg3/psycopg3/connection.py
psycopg3/psycopg3/cursor.py
psycopg3/psycopg3/proto.py
psycopg3/psycopg3/server_cursor.py
tests/test_connection.py
tests/test_connection_async.py

index e3e553f212f639161b79f748fabccbf34eb28aeb..a7c25f1963c3049099b78b729703f22a8548e339 100644 (file)
@@ -31,9 +31,17 @@ The `!Connection` class
 
     .. automethod:: connect
 
-        Connection parameters can be passed either as a `conninfo string`__ (a
-        ``postgresql://`` url or a list of ``key=value pairs``) or as keywords.
-        Keyword parameters override the ones specified in the connection string.
+        :param conninfo: The `connection string`__ (a ``postgresql://`` url or
+                         a list of ``key=value pairs``) to specify where and
+                         how to connect.
+        :param kwargs: Further parameters specifying the connection string.
+                       They override the ones specified in *conninfo*.
+        :param autocommit: If `!True` don't start transactions automatically.
+                           See `transactions` for details.
+        :param row_factory: The row factory specifying what type of records
+                            to create fetching data (default:
+                            `~psycopg3.rows.tuple_row()`). See
+                            :ref:`row-factories` for details.
 
         .. __: https://www.postgresql.org/docs/current/libpq-connect.html
             #LIBPQ-CONNSTRING
@@ -56,11 +64,21 @@ The `!Connection` class
     .. autoattribute:: closed
         :annotation: bool
 
-    .. automethod:: cursor
 
-        Calling the method without a *name* creates a client-side cursor,
-        specifying a *name* crates a server-side cursor. See
-        :ref:`cursor-types` for the details.
+    .. method:: cursor(*, binary: bool = False, row_factory: Optional[RowFactory] = None) -> Cursor
+    .. method:: cursor(name: str, *, binary: bool = False, row_factory: Optional[RowFactory] = None) -> ServerCursor
+        :noindex:
+
+        Return a new cursor to send commands and queries to the connection.
+
+        :param name: If not specified create a client-side cursor, if
+                     specified create a server-side cursor. See
+                     :ref:`cursor-types` for details.
+        :param binary: If `!True` return binary values from the database. All
+                       the types returned by the query must have a binary
+                       loader. See :ref:`binary-data` for details.
+        :param row_factory: If specified override the `row_factory` set on the
+                            connection. See :ref:`row-factories` for details.
 
         .. note:: You can use :ref:`with conn.cursor(): ...<usage>`
             to close the cursor automatically when the block is exited.
@@ -85,7 +103,7 @@ The `!Connection` class
     .. autoattribute:: row_factory
         :annotation: RowFactory
 
-        Writable attribute to control how result's rows are formed.
+        Writable attribute to control how result rows are formed.
         See :ref:`row-factories` for details.
 
     .. rubric:: Transaction management methods
@@ -203,7 +221,9 @@ The `!AsyncConnection` class
             automatically when the block is exited, but be careful about
             the async quirkness: see :ref:`async-with` for details.
 
-    .. automethod:: cursor
+    .. method:: cursor(*, binary: bool = False, row_factory: Optional[RowFactory] = None) -> AsyncCursor
+    .. method:: cursor(name: str, *, binary: bool = False, row_factory: Optional[RowFactory] = None) -> AsyncServerCursor
+        :noindex:
 
         .. note:: You can use ``async with conn.cursor() as cur: ...`` to
             close the cursor automatically when the block is exited.
index 7b8919ac22ab07e12ad780684b647dfb1561f371..ec504bcd984ea2fe6f675699ab06d52e25505983 100644 (file)
@@ -53,9 +53,10 @@ passing another value at `Connection.cursor()`.
 Available row factories
 -----------------------
 
-Module `psycopg3.rows` contains available row factories:
+The module `psycopg3.rows` provides the implementation for a few row factories:
 
 .. currentmodule:: psycopg3.rows
 
+.. autofunction:: tuple_row
 .. autofunction:: dict_row
 .. autofunction:: namedtuple_row
index 3daa08b66c2bf4fd9711540d2b49275b10d46e8d..9c701fdd382d948a8778dfadd95b3ef79ef90294 100644 (file)
@@ -28,6 +28,7 @@ from . import waiting
 from . import encodings
 from .pq import ConnStatus, ExecStatus, TransactionStatus, Format
 from .sql import Composable
+from .rows import tuple_row
 from .proto import PQGen, PQGenConn, RV, RowFactory, Query, Params
 from .proto import AdaptContext, ConnectionType
 from .cursor import Cursor, AsyncCursor
@@ -78,9 +79,6 @@ NoticeHandler = Callable[[e.Diagnostic], None]
 NotifyHandler = Callable[[Notify], None]
 
 
-_null_row_factory: RowFactory = object()  # type: ignore[assignment]
-
-
 class BaseConnection(AdaptContext):
     """
     Base class for different types of connections.
@@ -105,7 +103,7 @@ class BaseConnection(AdaptContext):
     ConnStatus = pq.ConnStatus
     TransactionStatus = pq.TransactionStatus
 
-    row_factory: Optional[RowFactory] = None
+    row_factory: RowFactory = tuple_row
 
     def __init__(self, pgconn: "PGconn"):
         self.pgconn = pgconn  # TODO: document this
@@ -317,7 +315,7 @@ class BaseConnection(AdaptContext):
         conninfo: str = "",
         *,
         autocommit: bool = False,
-        row_factory: Optional[RowFactory] = None,
+        row_factory: RowFactory,
         **kwargs: Any,
     ) -> PQGenConn[ConnectionType]:
         """Generator to connect to the database and create a new instance."""
@@ -416,7 +414,7 @@ class Connection(BaseConnection):
         conninfo: str = "",
         *,
         autocommit: bool = False,
-        row_factory: Optional[RowFactory] = None,
+        row_factory: RowFactory = tuple_row,
         **kwargs: Any,
     ) -> "Connection":
         """
@@ -482,13 +480,13 @@ class Connection(BaseConnection):
         name: str = "",
         *,
         binary: bool = False,
-        row_factory: Optional[RowFactory] = _null_row_factory,
+        row_factory: Optional[RowFactory] = None,
     ) -> Union[Cursor, ServerCursor]:
         """
         Return a new cursor to send commands and queries to the connection.
         """
         format = Format.BINARY if binary else Format.TEXT
-        if row_factory is _null_row_factory:
+        if not row_factory:
             row_factory = self.row_factory
         if name:
             return ServerCursor(
@@ -591,7 +589,7 @@ class AsyncConnection(BaseConnection):
         conninfo: str = "",
         *,
         autocommit: bool = False,
-        row_factory: Optional[RowFactory] = None,
+        row_factory: RowFactory = tuple_row,
         **kwargs: Any,
     ) -> "AsyncConnection":
         return await cls._wait_conn(
@@ -651,13 +649,13 @@ class AsyncConnection(BaseConnection):
         name: str = "",
         *,
         binary: bool = False,
-        row_factory: Optional[RowFactory] = _null_row_factory,
+        row_factory: Optional[RowFactory] = None,
     ) -> Union[AsyncCursor, AsyncServerCursor]:
         """
         Return a new `AsyncCursor` to send commands and queries to the connection.
         """
         format = Format.BINARY if binary else Format.TEXT
-        if row_factory is _null_row_factory:
+        if not row_factory:
             row_factory = self.row_factory
         if name:
             return AsyncServerCursor(
index e281cb6692f0a5d70a1ac7598cf78bd245920ff6..dd6e2661b5201f71f0d9ad3bcba896fede64149e 100644 (file)
@@ -17,6 +17,7 @@ from . import generators
 
 from .pq import ExecStatus, Format
 from .copy import Copy, AsyncCopy
+from .rows import tuple_row
 from .proto import ConnectionType, Query, Params, PQGen
 from .proto import Row, RowFactory
 from ._column import Column
@@ -64,7 +65,7 @@ class BaseCursor(Generic[ConnectionType]):
         connection: ConnectionType,
         *,
         format: Format = Format.TEXT,
-        row_factory: Optional[RowFactory] = None,
+        row_factory: RowFactory = tuple_row,
     ):
         self._conn = connection
         self.format = format
@@ -177,8 +178,7 @@ class BaseCursor(Generic[ConnectionType]):
         if self._iresult < len(self._results):
             self.pgresult = self._results[self._iresult]
             self._tx.set_pgresult(self._results[self._iresult])
-            if self._row_factory:
-                self._tx.make_row = self._row_factory(self)
+            self._tx.make_row = self._row_factory(self)
             self._pos = 0
             nrows = self.pgresult.command_tuples
             self._rowcount = nrows if nrows is not None else -1
@@ -280,7 +280,7 @@ class BaseCursor(Generic[ConnectionType]):
         elif res.status == ExecStatus.SINGLE_TUPLE:
             self.pgresult = res
             self._tx.set_pgresult(res, set_loaders=first)
-            if first and self._row_factory:
+            if first:
                 self._tx.make_row = self._row_factory(self)
             return res
 
@@ -384,8 +384,7 @@ class BaseCursor(Generic[ConnectionType]):
         self._results = list(results)
         self.pgresult = results[0]
         self._tx.set_pgresult(results[0])
-        if self._row_factory:
-            self._tx.make_row = self._row_factory(self)
+        self._tx.make_row = self._row_factory(self)
         nrows = self.pgresult.command_tuples
         if nrows is not None:
             if self._rowcount < 0:
index a86d9ac25edec7bf7f89803d4bcbc6549430400e..8786de0506c7201ef34cbc2b55295b7c3e9bc266 100644 (file)
@@ -56,7 +56,9 @@ class RowMaker(Protocol):
 
 
 class RowFactory(Protocol):
-    def __call__(self, __cursor: "BaseCursor[ConnectionType]") -> RowMaker:
+    def __call__(
+        self, __cursor: "BaseCursor[ConnectionType]"
+    ) -> Optional[RowMaker]:
         ...
 
 
index 851839ec79a1b4e8111fb948bdc702b7c22ae1e1..6ea9b8819750004f5604efc1b7984015dc5431d9 100644 (file)
@@ -12,6 +12,7 @@ from typing import Sequence, Type, Tuple, TYPE_CHECKING
 from . import pq
 from . import sql
 from . import errors as e
+from .rows import tuple_row
 from .cursor import BaseCursor, execute
 from .proto import ConnectionType, Query, Params, PQGen, Row, RowFactory
 
@@ -173,12 +174,11 @@ class ServerCursor(BaseCursor["Connection"]):
         name: str,
         *,
         format: pq.Format = pq.Format.TEXT,
-        row_factory: Optional[RowFactory] = None,
+        row_factory: RowFactory = tuple_row,
     ):
         super().__init__(connection, format=format, row_factory=row_factory)
-        self._helper: ServerCursorHelper["Connection"] = ServerCursorHelper(
-            name
-        )
+        self._helper: ServerCursorHelper["Connection"]
+        self._helper = ServerCursorHelper(name)
         self.itersize = DEFAULT_ITERSIZE
 
     def __del__(self) -> None:
@@ -295,7 +295,7 @@ class AsyncServerCursor(BaseCursor["AsyncConnection"]):
         name: str,
         *,
         format: pq.Format = pq.Format.TEXT,
-        row_factory: Optional[RowFactory] = None,
+        row_factory: RowFactory = tuple_row,
     ):
         super().__init__(connection, format=format, row_factory=row_factory)
         self._helper: ServerCursorHelper["AsyncConnection"]
index 45b24dbb2a6fd081c066adee521b7a8c839b9ddd..8ab62ee77c3dd3c9097eb729ebe7f49e55fdd04e 100644 (file)
@@ -10,6 +10,7 @@ from threading import Thread
 import psycopg3
 from psycopg3 import encodings
 from psycopg3 import Connection, Notify
+from psycopg3.rows import tuple_row
 from psycopg3.errors import UndefinedTable
 from psycopg3.conninfo import conninfo_to_dict
 from .test_cursor import my_row_factory
@@ -484,8 +485,11 @@ def test_execute(conn):
 
 
 def test_row_factory(dsn):
+    conn = Connection.connect(dsn)
+    assert conn.row_factory is tuple_row
+
     conn = Connection.connect(dsn, row_factory=my_row_factory)
-    assert conn.row_factory
+    assert conn.row_factory is my_row_factory
 
     cur = conn.execute("select 'a' as ve")
     assert cur.fetchone() == ["Ave"]
@@ -494,11 +498,11 @@ def test_row_factory(dsn):
         cur.execute("select 1, 1, 2")
         assert cur.fetchall() == [{1, 2}]
 
-    with conn.cursor(row_factory=None) as cur:
+    with conn.cursor(row_factory=tuple_row) as cur:
         cur.execute("select 1, 1, 2")
         assert cur.fetchall() == [(1, 1, 2)]
 
-    conn.row_factory = None
+    conn.row_factory = tuple_row
     cur = conn.execute("select 'vale'")
     assert cur.fetchone() == ("vale",)
 
index 4ba04e5ff9db4ab350abfed6c86f48a458147233..9ec28560e2e0af8827f473baa83a1b91066c2ad5 100644 (file)
@@ -9,6 +9,7 @@ import weakref
 import psycopg3
 from psycopg3 import encodings
 from psycopg3 import AsyncConnection, Notify
+from psycopg3.rows import tuple_row
 from psycopg3.errors import UndefinedTable
 from psycopg3.conninfo import conninfo_to_dict
 from .test_cursor import my_row_factory
@@ -502,8 +503,11 @@ async def test_execute(aconn):
 
 
 async def test_row_factory(dsn):
+    conn = await AsyncConnection.connect(dsn)
+    assert conn.row_factory is tuple_row
+
     conn = await AsyncConnection.connect(dsn, row_factory=my_row_factory)
-    assert conn.row_factory
+    assert conn.row_factory is my_row_factory
 
     cur = await conn.execute("select 'a' as ve")
     assert await cur.fetchone() == ["Ave"]
@@ -512,11 +516,11 @@ async def test_row_factory(dsn):
         await cur.execute("select 1, 1, 2")
         assert await cur.fetchall() == [{1, 2}]
 
-    async with conn.cursor(row_factory=None) as cur:
+    async with conn.cursor(row_factory=tuple_row) as cur:
         await cur.execute("select 1, 1, 2")
         assert await cur.fetchall() == [(1, 1, 2)]
 
-    conn.row_factory = None
+    conn.row_factory = tuple_row
     cur = await conn.execute("select 'vale'")
     assert await cur.fetchone() == ("vale",)