From: Daniele Varrazzo Date: Thu, 26 Aug 2021 17:46:53 +0000 (+0200) Subject: Make register_composite(), register_range() the public interface X-Git-Tag: 3.0.beta1~30^2~3 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=e979ea8a49ccd7ab4354fe6821765d1c4a94185b;p=thirdparty%2Fpsycopg.git Make register_composite(), register_range() the public interface Drop subclassing of `TypeInfo.register()`, leaving it only for the base task of registering type info and array. This makes them consistent with other types which don't have a TypeInfo subclass of their own and only expose a `register_*()` function (e.g. hstore). --- diff --git a/docs/api/abc.rst b/docs/api/abc.rst index ad20f26e8..0740e9c76 100644 --- a/docs/api/abc.rst +++ b/docs/api/abc.rst @@ -77,3 +77,6 @@ checking. .. autoclass:: AdaptContext :members: + + .. seealso:: :ref:`adaptation` for an explanation about how contexts are + connected. diff --git a/docs/api/adapt.rst b/docs/api/adapt.rst index 66f8ffbca..3d7b34843 100644 --- a/docs/api/adapt.rst +++ b/docs/api/adapt.rst @@ -53,6 +53,9 @@ Other objects used in adaptations .. autoclass:: AdaptersMap + .. seealso:: :ref:`adaptation` for an explanation about how contexts are + connected. + .. automethod:: register_dumper .. automethod:: register_loader diff --git a/docs/api/types.rst b/docs/api/types.rst index bf792bb82..6269cb7b1 100644 --- a/docs/api/types.rst +++ b/docs/api/types.rst @@ -65,36 +65,19 @@ can extend the behaviour of the adapters: if you create a loader for database as a list of the base type. -.. autoclass:: TypesRegistry - - The following `!TypeInfo` subclasses allow to fetch more specialised -information from certain class of PostgreSQL types and to create more -specialised adapters configurations. - +information from certain class of PostgreSQL types. .. autoclass:: psycopg.types.composite.CompositeInfo - .. automethod:: register - - Using `!CompositeInfo.register()` will also register a specialised - loader to fetch the composite type as a Python named tuple, or a - custom object if *factory* is specified. - - .. autoclass:: psycopg.types.range.RangeInfo - .. automethod:: register - Using `!RangeInfo.register()` will also register a specialised loaders - and dumpers. For instance, if you create a PostgreSQL range on the - type :sql:`inet`, loading these object with the database will use the - loader for the :sql:`inet` type to parse the range bounds - either the - builtin ones or any one you might have configured. +`!TypeInfo` objects are collected in `TypesRegistry` instances, which help type +information lookup. Every `~psycopg.adapt.AdaptersMap` expose its type map on +its `~psycopg.adapt.AdaptersMap.types` attribute. - The type information will also be used by the `Range` dumper so that - if you dump a `!Range(address1, address2)` object it will use the - correct oid for your :sql:`inetrange` type. +.. autoclass:: TypesRegistry .. _numeric-wrappers: diff --git a/psycopg/psycopg/_typeinfo.py b/psycopg/psycopg/_typeinfo.py index 0f7ff2026..991f1c74a 100644 --- a/psycopg/psycopg/_typeinfo.py +++ b/psycopg/psycopg/_typeinfo.py @@ -7,7 +7,7 @@ information to the adapters if needed. # Copyright (C) 2020-2021 The Psycopg Team -from typing import Any, Callable, Dict, Iterator, Optional +from typing import Any, Dict, Iterator, Optional from typing import Sequence, Type, TypeVar, Union, TYPE_CHECKING from . import errors as e @@ -159,13 +159,6 @@ class RangeInfo(TypeInfo): super().__init__(name, oid, array_oid) self.subtype_oid = subtype_oid - def register(self, context: Optional[AdaptContext] = None) -> None: - super().register(context) - - from .types.range import register_range - - register_range(self, context) - _info_query = """\ SELECT t.typname AS name, t.oid AS oid, t.typarray AS array_oid, r.rngsubtype AS subtype_oid @@ -194,17 +187,6 @@ class CompositeInfo(TypeInfo): # Will be set by register() if the `factory` is a type self.python_type: Optional[type] = None - def register( - self, - context: Optional[AdaptContext] = None, - factory: Optional[Callable[..., Any]] = None, - ) -> None: - super().register(context) - - from .types.composite import register_composite - - register_composite(self, context, factory) - _info_query = """\ SELECT t.typname AS name, t.oid AS oid, t.typarray AS array_oid, diff --git a/psycopg/psycopg/types/composite.py b/psycopg/psycopg/types/composite.py index e1a843946..ac223c757 100644 --- a/psycopg/psycopg/types/composite.py +++ b/psycopg/psycopg/types/composite.py @@ -188,6 +188,10 @@ def register_composite( context: Optional[AdaptContext] = None, factory: Optional[Callable[..., Any]] = None, ) -> None: + + # Register arrays and type info + info.register(context) + if not factory: factory = namedtuple(info.name, info.field_names) # type: ignore diff --git a/psycopg/psycopg/types/hstore.py b/psycopg/psycopg/types/hstore.py index 59a5013b7..675e1f4b9 100644 --- a/psycopg/psycopg/types/hstore.py +++ b/psycopg/psycopg/types/hstore.py @@ -109,6 +109,7 @@ def register_hstore( info: TypeInfo, context: Optional[AdaptContext] = None ) -> None: + # Register arrays and type info info.register(context) adapters = context.adapters if context else postgres.adapters diff --git a/psycopg/psycopg/types/range.py b/psycopg/psycopg/types/range.py index 44e1af425..f68cdac36 100644 --- a/psycopg/psycopg/types/range.py +++ b/psycopg/psycopg/types/range.py @@ -445,6 +445,10 @@ def register_range( right subtype. Dumping the range just works, navigating from tye Python type to the type oid, to the range oid. """ + + # Register arrays and type info + info.register(context) + adapters = context.adapters if context else postgres.adapters # generate and register a customized text loader diff --git a/tests/types/test_composite.py b/tests/types/test_composite.py index 952959d93..8b18b76c2 100644 --- a/tests/types/test_composite.py +++ b/tests/types/test_composite.py @@ -4,8 +4,8 @@ from psycopg import pq, postgres from psycopg.sql import Identifier from psycopg.adapt import PyFormat as Format from psycopg.postgres import types as builtins -from psycopg.types.composite import CompositeInfo, TupleDumper - +from psycopg.types.composite import CompositeInfo, register_composite +from psycopg.types.composite import TupleDumper tests_str = [ ("", ()), @@ -39,7 +39,7 @@ def test_dump_tuple(conn, rec, obj): """ ) info = CompositeInfo.fetch(conn, "tmptype") - info.register(context=conn) + register_composite(info, conn) res = conn.execute("select %s::tmptype", [obj]).fetchone()[0] assert res == obj @@ -172,7 +172,7 @@ def test_dump_composite_all_chars(conn, fmt_in, testcomp): @pytest.mark.parametrize("fmt_out", [pq.Format.TEXT, pq.Format.BINARY]) def test_load_composite(conn, testcomp, fmt_out): info = CompositeInfo.fetch(conn, "testcomp") - info.register(conn) + register_composite(info, conn) cur = conn.cursor(binary=fmt_out) res = cur.execute("select row('hello', 10, 20)::testcomp").fetchone()[0] @@ -197,7 +197,7 @@ def test_load_composite_factory(conn, testcomp, fmt_out): def __init__(self, *args): self.foo, self.bar, self.baz = args - info.register(conn, factory=MyThing) + register_composite(info, conn, factory=MyThing) assert info.python_type is MyThing cur = conn.cursor(binary=fmt_out) @@ -216,7 +216,7 @@ def test_load_composite_factory(conn, testcomp, fmt_out): def test_register_scope(conn, testcomp): info = CompositeInfo.fetch(conn, "testcomp") - info.register() + register_composite(info) for fmt in (pq.Format.TEXT, pq.Format.BINARY): for oid in (info.oid, info.array_oid): assert postgres.adapters._loaders[fmt].pop(oid) @@ -227,14 +227,14 @@ def test_register_scope(conn, testcomp): assert info.python_type not in postgres.adapters._dumpers[Format.BINARY] cur = conn.cursor() - info.register(cur) + register_composite(info, cur) for fmt in (pq.Format.TEXT, pq.Format.BINARY): for oid in (info.oid, info.array_oid): assert oid not in postgres.adapters._loaders[fmt] assert oid not in conn.adapters._loaders[fmt] assert oid in cur.adapters._loaders[fmt] - info.register(conn) + register_composite(info, conn) for fmt in (pq.Format.TEXT, pq.Format.BINARY): for oid in (info.oid, info.array_oid): assert oid not in postgres.adapters._loaders[fmt] @@ -243,7 +243,7 @@ def test_register_scope(conn, testcomp): def test_type_dumper_registered(conn, testcomp): info = CompositeInfo.fetch(conn, "testcomp") - info.register(conn) + register_composite(info, conn) assert issubclass(info.python_type, tuple) assert info.python_type.__name__ == "testcomp" d = conn.adapters.get_dumper(info.python_type, "s") @@ -261,7 +261,7 @@ def test_callable_dumper_not_registered(conn, testcomp): def fac(*args): return args + (args[-1],) - info.register(conn, factory=fac) + register_composite(info, conn, factory=fac) assert info.python_type is None # but the loader is registered diff --git a/tests/types/test_range.py b/tests/types/test_range.py index 3ac0c11fe..97a67f97b 100644 --- a/tests/types/test_range.py +++ b/tests/types/test_range.py @@ -9,7 +9,7 @@ from psycopg import pq from psycopg.sql import Identifier from psycopg.adapt import PyFormat as Format from psycopg.types import range as range_module -from psycopg.types.range import Range, RangeInfo +from psycopg.types.range import Range, RangeInfo, register_range type2sub = { @@ -326,7 +326,7 @@ async def test_fetch_info_not_found_async(aconn): def test_dump_custom_empty(conn, testrange): info = RangeInfo.fetch(conn, "testrange") - info.register(conn) + register_range(info, conn) r = Range(empty=True) cur = conn.execute("select 'empty'::testrange = %s", (r,)) @@ -335,7 +335,7 @@ def test_dump_custom_empty(conn, testrange): def test_dump_quoting(conn, testrange): info = RangeInfo.fetch(conn, "testrange") - info.register(conn) + register_range(info, conn) cur = conn.cursor() for i in range(1, 254): cur.execute( @@ -351,7 +351,7 @@ def test_dump_quoting(conn, testrange): @pytest.mark.parametrize("fmt_out", [pq.Format.TEXT, pq.Format.BINARY]) def test_load_custom_empty(conn, testrange, fmt_out): info = RangeInfo.fetch(conn, "testrange") - info.register(conn) + register_range(info, conn) cur = conn.cursor(binary=fmt_out) (got,) = cur.execute("select 'empty'::testrange").fetchone() @@ -362,7 +362,7 @@ def test_load_custom_empty(conn, testrange, fmt_out): @pytest.mark.parametrize("fmt_out", [pq.Format.TEXT, pq.Format.BINARY]) def test_load_quoting(conn, testrange, fmt_out): info = RangeInfo.fetch(conn, "testrange") - info.register(conn) + register_range(info, conn) cur = conn.cursor(binary=fmt_out) for i in range(1, 254): cur.execute(