From: Daniele Varrazzo Date: Sun, 1 Aug 2021 19:55:56 +0000 (+0200) Subject: Improve rows docs X-Git-Tag: 3.0.dev2~21^2~1 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=c39137448c74ce84763db5361a6a8fd250d111f0;p=thirdparty%2Fpsycopg.git Improve rows docs --- diff --git a/docs/advanced/rows.rst b/docs/advanced/rows.rst index 3b4c50018..5781429c4 100644 --- a/docs/advanced/rows.rst +++ b/docs/advanced/rows.rst @@ -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] diff --git a/docs/api/connections.rst b/docs/api/connections.rst index 2966c13d5..a8bd431fc 100644 --- a/docs/api/connections.rst +++ b/docs/api/connections.rst @@ -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