]> git.ipfire.org Git - thirdparty/psycopg.git/commitdiff
Add composite types adaptation docs
authorDaniele Varrazzo <daniele.varrazzo@gmail.com>
Sat, 28 Aug 2021 04:59:48 +0000 (06:59 +0200)
committerDaniele Varrazzo <daniele.varrazzo@gmail.com>
Sat, 28 Aug 2021 04:59:48 +0000 (06:59 +0200)
docs/basic/adapt.rst
docs/basic/pgtypes.rst
psycopg/psycopg/types/composite.py
psycopg/psycopg/types/hstore.py
psycopg/psycopg/types/range.py

index dd1cebf8e191e4162eb8ddee4de8f3f32f1e7971..b69f1a82003eaa15b0aad3bde5ca5a98fcf5acd8 100644 (file)
@@ -17,20 +17,6 @@ Converting the following data types between Python and PostgreSQL works
 out-of-the-box and doesn't require any configuration. In case you need to
 customise the conversion you should take a look at :ref:`adaptation`.
 
-TODO: complete table
-
-.. only:: html
-
-  .. table::
-    :class: data-types
-
-    +--------------------+-------------------------+--------------------------+
-    | Python             | PostgreSQL              | See also                 |
-    +====================+=========================+==========================+
-    | | `!tuple`         | Composite types         |:ref:`adapt-composite`    |
-    | | `!namedtuple`    |                         |                          |
-    +--------------------+-------------------------+--------------------------+
-
 
 .. index::
     pair: Boolean; Adaptation
@@ -370,13 +356,3 @@ address types`__:
   :sql:`inet` and :sql:`cidr` values.
 
 .. __: https://www.postgresql.org/docs/current/datatype-net-types.html#DATATYPE-CIDR
-
-.. _adapt-composite:
-
-
-TODO adaptation
-----------------
-
-.. admonition:: TODO
-
-    Document the other types
index aaad59a6c6f334875640ada57f2e84e0b77e8761..81f3ffa5d8ffdc8264da21b8c6b5fbe9da7a024e 100644 (file)
@@ -14,6 +14,85 @@ PostgreSQL offers other data types which don't map to native Python types.
 Psycopg offers wrappers and conversion functions to allow their use.
 
 
+.. index::
+    pair: Composite types; Data types
+    pair: tuple; Adaptation
+    pair: namedtuple; Adaptation
+
+.. _adapt-composite:
+
+Composite types casting
+-----------------------
+
+Psycopg can adapt PostgreSQL composite types (either created with the |CREATE
+TYPE|_ command or implicitly defined after a table row type) to and from
+Python tuples or `~collections.namedtuple`.
+
+.. |CREATE TYPE| replace:: :sql:`CREATE TYPE`
+.. _CREATE TYPE: https://www.postgresql.org/docs/current/static/sql-createtype.html
+
+Before using a composite type it is necessary to get information about it
+using the `~psycopg.types.composite.CompositeInfo` class and to register it
+using `~psycopg.types.composite.register_composite()`.
+
+.. autoclass:: psycopg.types.composite.CompositeInfo
+
+   `!CompositeInfo` is a `~psycopg.types.TypeInfo` subclass: check its
+   documentation for generic details.
+
+   .. attribute:: python_type
+
+       After `register_composite()` is called, it will contain the python type
+       mapping to the registered composite.
+
+.. autofunction:: psycopg.types.composite.register_composite
+
+   After registering, fetching data of the registered composite will invoke
+   *factory* to create corresponding Python objects.
+
+   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.
+
+Example::
+
+    >>> from psycopg.types.composite import CompositeInfo, register_composite
+
+    >>> conn.execute("CREATE TYPE card AS (value int, suit text)")
+
+    >>> info = TypeInfo.fetch(conn, "card")
+    >>> register_composite(info, conn)
+
+    >>> my_card = info.python_type(8, "hearts")
+    >>> my_card
+    card(value=8, suit='hearts')
+
+    >>> conn.execute(
+    ...     "SELECT pg_typeof(%(card)s), (%(card)s).suit", {"card": my_card}
+    ...     ).fetchone()
+    ('card', 'hearts')
+
+    >>> conn.execute("SELECT (%s, %s)::card", [1, "spades"]).fetchone()[0]
+    card(value=1, suit='spades')
+
+
+Nested composite types are handled as expected, provided that the type of the
+composite components are registered as well::
+
+    >>> conn.execute("CREATE TYPE card_back AS (face card, back text)")
+
+    >>> info2 = CompositeInfo.fetch(conn, "card_back")
+    >>> register_composite(info2, conn)
+
+    >>> conn.execute("SELECT ((8, 'hearts'), 'blue')::card_back").fetchone()[0]
+
+
+.. index::
+    pair: range; Data types
+
 .. _adapt-range:
 
 Range adaptation
@@ -64,7 +143,7 @@ its subtype and make it work like the builtin ones.
 
 .. autoclass:: psycopg.types.range.RangeInfo
 
-   `~RangeInfo` is a `~psycopg.types.TypeInfo` subclass: check its
+   `!RangeInfo` is a `~psycopg.types.TypeInfo` subclass: check its
    documentation for generic details.
 
 .. autofunction:: psycopg.types.range.register_range
@@ -77,10 +156,10 @@ Example::
     >>> info = RangeInfo.fetch(conn, "strrange")
     >>> register_range(info, conn)
 
-    >>> conn.execute("select pg_typeof(%s)", [Range("a", "z")]).fetchone()[0]
+    >>> conn.execute("SELECT pg_typeof(%s)", [Range("a", "z")]).fetchone()[0]
     'strrange'
 
-    >>> conn.execute("select '[a,z]'::strrange").fetchone()[0]
+    >>> conn.execute("SELECT '[a,z]'::strrange").fetchone()[0]
     Range('a', 'z', '[]')
 
 
@@ -127,8 +206,8 @@ Example::
     >>> info = TypeInfo.fetch(conn, "hstore")
     >>> register_hstore(info, conn)
 
-    >>> conn.execute("select pg_typeof(%s)", [{"a": "b"}]).fetchone()[0]
+    >>> conn.execute("SELECT pg_typeof(%s)", [{"a": "b"}]).fetchone()[0]
     'hstore'
 
-    >>> conn.execute("select 'foo => bar'::hstore").fetchone()[0]
+    >>> conn.execute("SELECT 'foo => bar'::hstore").fetchone()[0]
     {'foo': 'bar'}
index 50eea4d8a51077162cdd58bada45c65ecb91c110..e25d0a6610411a45d93552663a35b768d6cd4948 100644 (file)
@@ -215,6 +215,14 @@ def register_composite(
     context: Optional[AdaptContext] = None,
     factory: Optional[Callable[..., Any]] = None,
 ) -> None:
+    """Register the adapters to load and dump a composite type.
+
+    :param info: The object with the information about the composite to register.
+    :param context: The context where to register the adapters. If `!None`,
+        register it globally.
+    :param factory: Callable to convert the sequence of attributes read from
+        the composite into a Python object.
+    """
 
     # Register arrays and type info
     info.register(context)
index 235cc7cb3d4ec3be774134f5f5a59a46cc893e9e..44070870ba8c5a0eafaad6054322a00894ca3aab 100644 (file)
@@ -108,6 +108,12 @@ class HstoreLoader(RecursiveLoader):
 def register_hstore(
     info: TypeInfo, context: Optional[AdaptContext] = None
 ) -> None:
+    """Register the adapters to load and dump hstore.
+
+    :param info: The object with the information about the hstore type.
+    :param context: The context where to register the adapters. If `!None`,
+        register it globally.
+    """
 
     # Register arrays and type info
     info.register(context)
index a9f32cdc23b1171557ddf68499c65a8ff0ae00a4..fd96d80edd2d5392c60cccfc627b307eb99dcfea 100644 (file)
@@ -438,8 +438,11 @@ _int2parens = {ord(c): c for c in "[]()"}
 def register_range(
     info: RangeInfo, context: Optional[AdaptContext] = None
 ) -> None:
-    """
-    Register custom range adapters on a context.
+    """Register the adapters to load and dump a range type.
+
+    :param info: The object with the information about the range to register.
+    :param context: The context where to register the adapters. If `!None`,
+        register it globally.
 
     Register loaders so that loading data of this type will result in a `Range`
     with bounds parsed as the right subtype.