]> git.ipfire.org Git - thirdparty/psycopg.git/commitdiff
docs: add generic object to composite adaptation docs 1202/head
authorDaniele Varrazzo <daniele.varrazzo@gmail.com>
Fri, 7 Nov 2025 02:37:56 +0000 (03:37 +0100)
committerDaniele Varrazzo <daniele.varrazzo@gmail.com>
Fri, 7 Nov 2025 04:10:52 +0000 (05:10 +0100)
docs/basic/pgtypes.rst
psycopg/psycopg/types/composite.py

index cfca67cdc41c891f3e9cf49968918f327bcf6efd..a15e8d1a3e0e41cc18aebe42a72b2c7a989cd8dc 100644 (file)
@@ -21,8 +21,8 @@ Psycopg offers wrappers and conversion functions to allow their use.
 
 .. _adapt-composite:
 
-Composite types casting
------------------------
+Composite types adaptation
+--------------------------
 
 Psycopg can adapt PostgreSQL composite types (either created with the |CREATE
 TYPE|_ command or implicitly defined after a table row type) to and from
@@ -49,24 +49,33 @@ using `~psycopg.types.composite.register_composite()`.
 
 .. autofunction:: psycopg.types.composite.register_composite
 
-   After registering, fetching data of the registered composite will invoke
-   `!factory` to create corresponding Python objects.
+   After registering the `CompositeInfo`, fetching data of the registered
+   composite type will invoke `!factory` to create corresponding Python
+   objects. If no factory is specified, Psycopg will generate a
+   `~collection.namedtuple` with the same fields of the composite type, and
+   use it to return fetched data. The factory will be made available on
+   `!info.`\ `~CompositeInfo.python_type`.
 
-   If no factory is specified, a `~collection.namedtuple` is created and used
-   to return data.
-
-   If the `!factory` is a type (and not a generic callable), then dumpers for
-   that type are created and registered too, so that passing objects of that
-   type to a query will adapt them to the registered type.
+   If the `!factory` is a type (and not a generic callable) then dumpers for
+   such type are created and registered too, so that passing objects of that
+   type to a query will adapt them to the registered type. This assumes that
+   the `!factory` is a sequence; if this is not the case you can specify the
+   `!make_sequence` parameter. See :ref:`composite-generic`.
 
    The `!factory` callable will be called with the sequence of value from the
    composite. If passing the sequence of positional arguments is not suitable
-   you can specify a :samp:`make_instance({values}, {names})` callable.
+   you can specify a `!make_instance` callable.
 
    .. versionadded:: 3.3
-        the `!make_instance` parameter
+        the `!make_instance` and `!make_sequence` parameters.
+
+
+.. _composite-sequence:
+
+Example: Python sequence
+^^^^^^^^^^^^^^^^^^^^^^^^
 
-Example: registering a composite without `!factory` information will create a
+Registering a composite without `!factory` information will create a
 type on the fly, stored in `!CompositeInfo.python_type`. ::
 
     >>> from psycopg.types.composite import CompositeInfo, register_composite
@@ -100,10 +109,18 @@ composite components are registered as well::
     >>> conn.execute("SELECT ((8, 'hearts'), 'blue')::card_back").fetchone()[0]
     card_back(face=card(value=8, suit='hearts'), back='blue')
 
+
+.. _composite-generic:
+
+Example: Generic Python object
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+.. versionadded:: 3.3
+
 If your Python type takes keyword arguments, or if the sequence of value
-coming from the PostgreSQL type is not suitable, it is possible to specify
-a :samp:`make_instance({values}, {names})` function to adapt the values from
-the composite to the right type requirements. For example::
+coming from the PostgreSQL type is not suitable for it, it is possible to
+specify a :samp:`make_instance({values}, {names})` function to adapt the
+values from the composite to the right type requirements. For example::
 
     >>> from dataclasses import dataclass
     >>> from typing import Any, Sequence
@@ -120,6 +137,26 @@ the composite to the right type requirements. For example::
     >>> conn.execute("select '(1,spades)'::card").fetchone()[0]
     Card(suit='spades', value=1)
 
+The previous example only configures loaders to convert data from PostgreSQL
+to Python. If we are also interested in dumping Python `!Card` objects we need
+to specify `!Card` as the factory (to declare which object we want to dump)
+and, because `!Card` is not a sequence, we need to specify a
+:samp:`make_sequence({object}, {names})` to convert objects attributes into
+a sequence matching the composite fields::
+
+    >>> def card_to_db(card: Card, names: Sequence[str]) -> Sequence[Any]:
+    ...     return [getattr(card, name) for name in names]
+
+    >>> register_composite(
+    ...     info, conn, factory=Card,
+    ...     make_instance=card_from_db, make_sequence=card_to_db)
+
+    >>> conn.execute(
+    ...     "select %(card)s.value + 1, %(card)s.suit",
+    ...     {"card": Card(suit="hearts", value=8)},
+    ...     ).fetchone()
+    (9, 'hearts')
+
 
 .. index::
     pair: range; Data types
index a8adbab0bae5d783fe1726044c993912e783f937..d1cdbc6743db83dc7f1cba054178ebb4e8541240 100644 (file)
@@ -289,8 +289,8 @@ def register_composite(
     :param context: The context where to register the adapters. If `!None`,
         register it globally.
     :type context: `~psycopg.abc.AdaptContext` | `!None`
-    :param factory: Callable to convert the sequence of attributes read from
-        the composite into a Python object.
+    :param factory: Callable to create a Python object from the sequence of
+        attributes read from the composite.
     :type factory: `!Callable[..., T]` | `!None`
     :param make_instance: optional function taking values and names as input and
         returning the new type.