]> git.ipfire.org Git - thirdparty/psycopg.git/commitdiff
Improve row factory docs
authorDaniele Varrazzo <daniele.varrazzo@gmail.com>
Fri, 30 Apr 2021 12:44:29 +0000 (14:44 +0200)
committerDaniele Varrazzo <daniele.varrazzo@gmail.com>
Fri, 30 Apr 2021 13:26:28 +0000 (15:26 +0200)
Describe the protocol objects too. Expose them in the `psycopg3.rows`
module.

docs/advanced/rows.rst
psycopg3/psycopg3/_transform.py
psycopg3/psycopg3/connection.py
psycopg3/psycopg3/cursor.py
psycopg3/psycopg3/proto.py
psycopg3/psycopg3/rows.py
psycopg3/psycopg3/server_cursor.py
psycopg3_c/psycopg3_c/_psycopg3.pyi
psycopg3_c/psycopg3_c/_psycopg3/transform.pyx

index 80133bbbb0f37472799addbcfffed805b6e8db5b..60d95a771e1b1cdb2c0400830cbf3059397931c1 100644 (file)
@@ -8,11 +8,23 @@ Row factories
 =============
 
 Cursor's `fetch*` methods return tuples of column values by default. This can
-be changed to adapt the needs of the programmer by using custom row factories.
+be changed to adapt the needs of the programmer by using custom *row
+factories*.
 
-A row factory is a callable that accepts a cursor object and returns another
-callable accepting a `values` tuple and returning a row in the desired form.
-This can be implemented as a class, for instance:
+A row factory (formally implemented by the `~psycopg3.rows.RowFactory`
+protocol) is a callable that accepts a `Cursor` object and returns another
+callable (formally the `~psycopg3.rows.RowMaker`) accepting a `values` tuple
+and returning a row in the desired form.
+
+.. autoclass:: psycopg3.rows.RowMaker
+
+   .. automethod:: __call__
+
+.. autoclass:: psycopg3.rows.RowFactory
+
+   .. automethod:: __call__
+
+`~RowFactory` objects can be implemented as a class, for instance:
 
 .. code:: python
 
@@ -55,11 +67,6 @@ Later usages of `row_factory` override earlier definitions; for instance,
 the `row_factory` specified at `Connection.connect()` can be overridden by
 passing another value at `Connection.cursor()`.
 
-.. note:: By declaring type annotations on the row factory used in
-   `Connection` or `Cursor`, rows retrieved by `.fetch*()` calls will have the
-   correct return type (i.e. a `dict[str, Any]` in previous example) and your
-   code can be type checked with a static analyzer such as mypy.
-
 
 Available row factories
 -----------------------
@@ -69,22 +76,27 @@ The module `psycopg3.rows` provides the implementation for a few row factories:
 .. currentmodule:: psycopg3.rows
 
 .. autofunction:: tuple_row
+.. autodata:: TupleRow
+
 .. autofunction:: dict_row
+.. autodata:: DictRow
+
 .. autofunction:: namedtuple_row
 
 
 Use with a static analyzer
 --------------------------
 
-The `Connection` and `Cursor` classes are parametric types: the parameter
-`!Row` is passed by the ``row_factory`` argument (of the
-`~Connection.connect()` and the `~Connection.cursor()` method) and it controls
-what type of record is returned by the fetch methods of the cursors. The
-default `tuple_row()` returns a generic tuple as return type (`Tuple[Any,
-...]`). This information can be used for type checking using a static
-analyzer such as Mypy_.
+The `~psycopg3.Connection` and `~psycopg3.Cursor` classes are `generic
+types`__: the parameter `!Row` is passed by the ``row_factory`` argument (of
+the `~Connection.connect()` and the `~Connection.cursor()` method) and it
+controls what type of record is returned by the fetch methods of the cursors.
+The default `tuple_row()` returns a generic tuple as return type (`Tuple[Any,
+...]`). This information can be used for type checking using a static analyzer
+such as Mypy_.
 
 .. _Mypy: https://mypy.readthedocs.io/
+.. __: https://mypy.readthedocs.io/en/stable/generics.html
 
 .. code:: python
 
index b6ed255c7a3a78a33cc0faf37f7f6edf41d9d097..f6ac9c0e16d78bfd6de28cc938f51e4794db2a63 100644 (file)
@@ -11,7 +11,8 @@ from collections import defaultdict
 from . import pq
 from . import errors as e
 from .oids import INVALID_OID
-from .proto import LoadFunc, AdaptContext, Row, RowMaker
+from .rows import Row, RowMaker
+from .proto import LoadFunc, AdaptContext
 from ._enums import Format
 
 if TYPE_CHECKING:
index fff67965cc38b09507500f686e2566a940205e5e..d5472437ac2122238f2c8fe5f9e88be8732cc515 100644 (file)
@@ -23,9 +23,9 @@ from . import waiting
 from . import encodings
 from .pq import ConnStatus, ExecStatus, TransactionStatus, Format
 from .sql import Composable
-from .rows import tuple_row, TupleRow
+from .rows import Row, RowFactory, tuple_row, TupleRow
 from .proto import AdaptContext, ConnectionType, Params, PQGen, PQGenConn
-from .proto import Query, Row, RowFactory, RV
+from .proto import Query, RV
 from .cursor import Cursor, AsyncCursor
 from .conninfo import make_conninfo, ConnectionInfo
 from .generators import notifies
index 11fbd4a7a8209c112d5bf806826b32ae5954f9f6..0bee499a904a3122dda67517730f9ca2970225e6 100644 (file)
@@ -17,8 +17,8 @@ from . import generators
 
 from .pq import ExecStatus, Format
 from .copy import Copy, AsyncCopy
+from .rows import Row, RowFactory
 from .proto import ConnectionType, Query, Params, PQGen
-from .proto import Row, RowFactory
 from ._column import Column
 from ._queries import PostgresQuery
 from ._preparing import Prepare
index 6f6652a982b7650ead5afeb0b98e0939deeca84a..4bf1e7633e0476ce6165d11987f106e88a8bdb8b 100644 (file)
@@ -13,11 +13,11 @@ from . import pq
 from ._enums import Format
 
 if TYPE_CHECKING:
-    from .connection import BaseConnection
-    from .cursor import AnyCursor
+    from .sql import Composable
+    from .rows import Row, RowMaker
     from .adapt import Dumper, Loader, AdaptersMap
     from .waiting import Wait, Ready
-    from .sql import Composable
+    from .connection import BaseConnection
 
 # An object implementing the buffer protocol
 Buffer = Union[bytes, bytearray, memoryview]
@@ -45,22 +45,6 @@ Wait states.
 """
 
 
-# Row factories
-
-Row = TypeVar("Row")
-Row_co = TypeVar("Row_co", covariant=True)
-
-
-class RowMaker(Protocol[Row_co]):
-    def __call__(self, __values: Sequence[Any]) -> Row_co:
-        ...
-
-
-class RowFactory(Protocol[Row]):
-    def __call__(self, __cursor: "AnyCursor[Row]") -> RowMaker[Row]:
-        ...
-
-
 # Adaptation types
 
 DumpFunc = Callable[[Any], bytes]
@@ -121,11 +105,11 @@ class Transformer(Protocol):
         ...
 
     def load_rows(
-        self, row0: int, row1: int, make_row: RowMaker[Row]
-    ) -> List[Row]:
+        self, row0: int, row1: int, make_row: "RowMaker[Row]"
+    ) -> List["Row"]:
         ...
 
-    def load_row(self, row: int, make_row: RowMaker[Row]) -> Optional[Row]:
+    def load_row(self, row: int, make_row: "RowMaker[Row]") -> Optional["Row"]:
         ...
 
     def load_sequence(
index 1aec4d9ed581117147dba270f82e9cb89afeccf8..b9f977a5c15935b82166148924a64937768c177b 100644 (file)
@@ -8,23 +8,74 @@ import functools
 import re
 from collections import namedtuple
 from typing import Any, Callable, Dict, NamedTuple, Sequence, Tuple, Type
-from typing import TYPE_CHECKING
+from typing import TypeVar, TYPE_CHECKING
+from typing_extensions import Protocol
 
 from . import errors as e
 
 if TYPE_CHECKING:
     from .cursor import AnyCursor
 
+# Row factories
+
+Row = TypeVar("Row")
+Row_co = TypeVar("Row_co", covariant=True)
+
+
+class RowMaker(Protocol[Row_co]):
+    """
+    Callable protocol taking a sequence of value and returning an object.
+
+    The sequence of value is what is returned from a database query, already
+    adapted to the right Python types. Tye return value is the object that your
+    program would like to receive: By defaut (`tuple_row()`) it is a simple
+    tuple, but it may be any type of object.
+
+    Typically, `~RowMaker` functions are returned by `RowFactory`.
+    """
+
+    def __call__(self, __values: Sequence[Any]) -> Row_co:
+        """
+        Convert a sequence of values from the database to a finished object.
+        """
+        ...
+
+
+class RowFactory(Protocol[Row]):
+    """
+    Callable protocol taking a `~psycopg3.Cursor` and returning a `RowMaker`.
+
+    A `!RowFactory` is typically called when a `!Cursor` receives a result.
+    This way it can inspect the cursor state (for instance the
+    `~psycopg3.Cursor.description` attribute) and help a `!RowMaker` to create
+    a complete object.
+
+    For instance the `dict_row()` `!RowFactory` uses the names of the column to
+    define the dictionary key and returns a `!RowMaker` function which would
+    use the values to create a dictionary for each record.
+    """
+
+    def __call__(self, __cursor: "AnyCursor[Row]") -> RowMaker[Row]:
+        """
+        Inspect the result on a cursor and return a `RowMaker` to convert rows.
+        """
+        ...
+
 
 TupleRow = Tuple[Any, ...]
+"""
+An alias for the type returned by `tuple_row()` (i.e. a tuple of any content).
+"""
 
 
 def tuple_row(
     cursor: "AnyCursor[TupleRow]",
 ) -> Callable[[Sequence[Any]], TupleRow]:
-    """Row factory to represent rows as simple tuples.
+    r"""Row factory to represent rows as simple tuples.
 
     This is the default factory.
+
+    :rtype: `RowMaker`\ [`TupleRow`]
     """
     # Implementation detail: make sure this is the tuple type itself, not an
     # equivalent function, because the C code fast-paths on it.
@@ -32,15 +83,23 @@ def tuple_row(
 
 
 DictRow = Dict[str, Any]
+"""
+An alias for the type returned by `dict_row()`
+
+A `!DictRow` is a dictionary with keys as string and any value returned by the
+database.
+"""
 
 
 def dict_row(
     cursor: "AnyCursor[DictRow]",
 ) -> Callable[[Sequence[Any]], DictRow]:
-    """Row factory to represent rows as dicts.
+    r"""Row factory to represent rows as dicts.
 
     Note that this is not compatible with the DBAPI, which expects the records
     to be sequences.
+
+    :rtype: `RowMaker`\ [`DictRow`]
     """
 
     def make_row(values: Sequence[Any]) -> Dict[str, Any]:
@@ -56,7 +115,10 @@ def dict_row(
 def namedtuple_row(
     cursor: "AnyCursor[NamedTuple]",
 ) -> Callable[[Sequence[Any]], NamedTuple]:
-    """Row factory to represent rows as `~collections.namedtuple`."""
+    r"""Row factory to represent rows as `~collections.namedtuple`.
+
+    :rtype: `RowMaker`\ [`NamedTuple`]
+    """
 
     def make_row(values: Sequence[Any]) -> NamedTuple:
         desc = cursor.description
index 099563bac64612c89999ea601c0c3327993c84d0..5031b5beed4535d5fc904574420d54287eba64f9 100644 (file)
@@ -12,8 +12,9 @@ from typing import Sequence, Type, TYPE_CHECKING
 from . import pq
 from . import sql
 from . import errors as e
+from .rows import Row, RowFactory
+from .proto import ConnectionType, Query, Params, PQGen
 from .cursor import BaseCursor, execute
-from .proto import ConnectionType, Query, Params, PQGen, Row, RowFactory
 
 if TYPE_CHECKING:
     from typing import Any  # noqa: F401
index 899feadfde6eb3d0325e359880e4697b8cf57ddb..32beb2fb69531139e276cca937b839b142cb9d1b 100644 (file)
@@ -9,11 +9,12 @@ information. Will submit a bug.
 
 from typing import Any, Iterable, List, Optional, Sequence, Tuple
 
+from psycopg3 import pq
 from psycopg3 import proto
+from psycopg3.rows import Row, RowMaker
 from psycopg3.adapt import Dumper, Loader, AdaptersMap, Format
-from psycopg3.connection import BaseConnection
-from psycopg3 import pq
 from psycopg3.pq.proto import PGconn, PGresult
+from psycopg3.connection import BaseConnection
 
 class Transformer(proto.AdaptContext):
     def __init__(self, context: Optional[proto.AdaptContext] = None): ...
@@ -34,11 +35,9 @@ class Transformer(proto.AdaptContext):
     ) -> Tuple[List[Any], Tuple[int, ...], Sequence[pq.Format]]: ...
     def get_dumper(self, obj: Any, format: Format) -> Dumper: ...
     def load_rows(
-        self, row0: int, row1: int, make_row: proto.RowMaker[proto.Row]
-    ) -> List[proto.Row]: ...
-    def load_row(
-        self, row: int, make_row: proto.RowMaker[proto.Row]
-    ) -> Optional[proto.Row]: ...
+        self, row0: int, row1: int, make_row: RowMaker[Row]
+    ) -> List[Row]: ...
+    def load_row(self, row: int, make_row: RowMaker[Row]) -> Optional[Row]: ...
     def load_sequence(
         self, record: Sequence[Optional[bytes]]
     ) -> Tuple[Any, ...]: ...
index d52d60e5de6b0e620f14877c72f5fa119b975bb8..15bd996080003f037594a7040fe9764541276b26 100644 (file)
@@ -24,7 +24,7 @@ from typing import Any, Dict, Iterable, List, Optional, Sequence, Tuple
 from psycopg3 import errors as e
 from psycopg3._enums import Format as Pg3Format
 from psycopg3.pq import Format as PqFormat
-from psycopg3.proto import Row, RowMaker
+from psycopg3.rows import Row, RowMaker
 
 # internal structure: you are not supposed to know this. But it's worth some
 # 10% of the innermost loop, so I'm willing to ask for forgiveness later...