]> git.ipfire.org Git - thirdparty/psycopg.git/commitdiff
Improve rows docs
authorDaniele Varrazzo <daniele.varrazzo@gmail.com>
Sun, 1 Aug 2021 19:55:56 +0000 (21:55 +0200)
committerDaniele Varrazzo <daniele.varrazzo@gmail.com>
Sun, 1 Aug 2021 20:37:31 +0000 (22:37 +0200)
docs/advanced/rows.rst
docs/api/connections.rst

index 3b4c500185508daf58ee79597122061f55bb8a09..5781429c4098ee264037dd6f9b5d7a8002d664bc 100644 (file)
@@ -46,10 +46,10 @@ want to use the same row factory for both sync and async cursors.
 .. code:: python
 
    from typing import Any, Sequence
-   from psycopg import AnyCursor
+   from psycopg import Cursor
 
    class DictRowFactory:
-       def __init__(self, cursor: AnyCursor[dict[str, Any]]):
+       def __init__(self, cursor: Cursor[dict[str, Any]]):
            self.fields = [c.name for c in cursor.description]
 
        def __call__(self, values: Sequence[Any]) -> dict[str, Any]:
@@ -59,9 +59,7 @@ or as a plain function:
 
 .. code:: python
 
-   def dict_row_factory(
-       cursor: AnyCursor[dict[str, Any]]
-   ) -> Callable[[Sequence[Any]], dict[str, Any]]:
+   def dict_row_factory(cursor: Cursor[dict[str, Any]]) -> RowMaker[dict[str, Any]]:
        fields = [c.name for c in cursor.description]
 
        def make_row(values: Sequence[Any]) -> dict[str, Any]:
@@ -110,9 +108,9 @@ 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_.
+such as mypy_.
 
-.. _Mypy: https://mypy.readthedocs.io/
+.. _mypy: https://mypy.readthedocs.io/
 .. __: https://mypy.readthedocs.io/en/stable/generics.html
 
 .. code:: python
@@ -141,7 +139,7 @@ Example: returning records as Pydantic models
 ---------------------------------------------
 
 Using Pydantic_ it is possible to enforce static typing at runtime. Using a
-Pydantic model factory the code can be checked statically using Mypy and
+Pydantic model factory the code can be checked statically using mypy and
 querying the database will raise an exception if the resultset is not
 compatible with the model.
 
@@ -166,7 +164,7 @@ any issue. Pydantic will also raise a runtime error in case the
         dob: Optional[date]
 
     class PersonFactory:
-        def __init__(self, cur: psycopg.AnyCursor[Person]):
+        def __init__(self, cur: psycopg.Cursor[Person]):
             assert cur.description
             self.fields = [c.name for c in cur.description]
 
@@ -198,3 +196,56 @@ any issue. Pydantic will also raise a runtime error in case the
             print(f"{p.first_name} was born in {p.dob.year}")
         else:
             print(f"Who knows when {p.first_name} was born")
+
+
+Another level of generic
+^^^^^^^^^^^^^^^^^^^^^^^^
+
+Note that, in the example above, the `!PersonFactory` implementation has
+nothing specific to the `!Person` class, apart from the returned type itself.
+This suggests that it's actually possible to create a... factory of factories:
+a function that, given a Pydantic model, returns a RowFactory that can be used
+to annotate connections and cursor statically.
+
+In the example above, the `!PersonFactory` class can be implemented as a
+function:
+
+.. code:: python
+
+    def person_factory(cursor: Cursor[Person]) -> RowMaker[Person]:
+        assert cursor.description
+        fields = [c.name for c in cursor.description]
+
+        def person_factory_(values: Sequence[Any]) -> Person:
+            return Person(**dict(zip(fields, values)))
+
+        return person_factory_
+
+The function `!person_factory()` is a `!RowFactory[Person]`. We can introduce
+a generic `M`, which can be any Pydantic model, and write a function returning
+`!RowFactory[M]`:
+
+.. code:: python
+
+    M = TypeVar("M", bound=BaseModel)
+
+    def model_factory(model: Type[M]) -> RowFactory[M]:
+        def model_factory_(cursor: Cursor[M]) -> RowMaker[M]:
+            assert cursor.description
+            fields = [c.name for c in cursor.description]
+
+            def model_factory__(values: Sequence[Any]) -> M:
+                return model(**dict(zip(fields, values)))
+
+            return model_factory__
+
+        return model_factory_
+
+which can be used to declare the types of connections and cursors:
+
+.. code:: python
+
+    conn = psycopg.connect()
+    cur = conn.cursor(row_factory=model_factory(Person))
+    x = cur.fetchone()
+    # the type of x is Optional[Person]
index 2966c13d5ca5f168cf8a9f32188c0e28840f517e..a8bd431fc295e8dd1fb4bb64a8d69a08c3d949ba 100644 (file)
@@ -106,17 +106,28 @@ The `!Connection` class
 
     .. autoattribute:: cursor_factory
 
-        The type, of factory function, returned by `cursor()` and `execute()`.
+        The type, or factory function, returned by `cursor()` and `execute()`.
 
         Default is `psycopg.Cursor`.
 
     .. autoattribute:: server_cursor_factory
 
-        The type, of factory function, returned by `cursor()` when a name is
+        The type, or factory function, returned by `cursor()` when a name is
         specified.
 
         Default is `psycopg.ServerCursor`.
 
+    .. autoattribute:: row_factory
+
+        The row factory defining the type of rows returned by
+        `~Cursor.fetchone()` and the other cursor fetch methods.
+
+        The default is `~psycopg.rows.tuple_row`, which means that the fetch
+        methods will return simple tuples.
+
+        .. seealso:: See :ref:`row-factories` for details about defining the
+            objects returned by cursors.
+
     .. automethod:: execute(query, params=None, prepare=None) -> Cursor
 
         :param query: The query to execute.
@@ -134,10 +145,6 @@ The `!Connection` class
         See :ref:`query-parameters` for all the details about executing
         queries.
 
-    .. autoattribute:: row_factory
-
-        See :ref:`row-factories` for details.
-
     .. rubric:: Transaction management methods
 
     For details see :ref:`transactions`.
@@ -315,6 +322,8 @@ The `!AsyncConnection` class
 
         Default is `psycopg.AsyncServerCursor`.
 
+    .. autoattribute:: row_factory
+
     .. automethod:: execute(query, params=None, prepare=None) -> AsyncCursor
     .. automethod:: commit
     .. automethod:: rollback