From: Daniele Varrazzo Date: Sat, 28 Aug 2021 04:59:48 +0000 (+0200) Subject: Add composite types adaptation docs X-Git-Tag: 3.0.beta1~20 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=b2d253813eed7f8068ee123b53a1081b9fef602e;p=thirdparty%2Fpsycopg.git Add composite types adaptation docs --- diff --git a/docs/basic/adapt.rst b/docs/basic/adapt.rst index dd1cebf8e..b69f1a820 100644 --- a/docs/basic/adapt.rst +++ b/docs/basic/adapt.rst @@ -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 diff --git a/docs/basic/pgtypes.rst b/docs/basic/pgtypes.rst index aaad59a6c..81f3ffa5d 100644 --- a/docs/basic/pgtypes.rst +++ b/docs/basic/pgtypes.rst @@ -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'} diff --git a/psycopg/psycopg/types/composite.py b/psycopg/psycopg/types/composite.py index 50eea4d8a..e25d0a661 100644 --- a/psycopg/psycopg/types/composite.py +++ b/psycopg/psycopg/types/composite.py @@ -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) diff --git a/psycopg/psycopg/types/hstore.py b/psycopg/psycopg/types/hstore.py index 235cc7cb3..44070870b 100644 --- a/psycopg/psycopg/types/hstore.py +++ b/psycopg/psycopg/types/hstore.py @@ -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) diff --git a/psycopg/psycopg/types/range.py b/psycopg/psycopg/types/range.py index a9f32cdc2..fd96d80ed 100644 --- a/psycopg/psycopg/types/range.py +++ b/psycopg/psycopg/types/range.py @@ -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.