From: Daniele Varrazzo Date: Fri, 6 Jan 2023 16:53:36 +0000 (+0000) Subject: refactor: move TypeInfo subclasses to the module they are exported from X-Git-Tag: pool-3.2.0~135^2~1 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=bc17d4909e6e5319cb27172f9cd9fe18908030c4;p=thirdparty%2Fpsycopg.git refactor: move TypeInfo subclasses to the module they are exported from --- diff --git a/psycopg/psycopg/_typeinfo.py b/psycopg/psycopg/_typeinfo.py index 7ca7db7ae..c7cb362a0 100644 --- a/psycopg/psycopg/_typeinfo.py +++ b/psycopg/psycopg/_typeinfo.py @@ -7,7 +7,6 @@ information to the adapters if needed. # Copyright (C) 2020 The Psycopg Team -from enum import Enum from typing import Any, Dict, Iterator, Optional, overload from typing import Sequence, Tuple, Type, TypeVar, Union, TYPE_CHECKING from typing_extensions import TypeAlias @@ -190,181 +189,6 @@ ORDER BY t.oid pass -class RangeInfo(TypeInfo): - """Manage information about a range type.""" - - __module__ = "psycopg.types.range" - - def __init__( - self, - name: str, - oid: int, - array_oid: int, - *, - regtype: str = "", - subtype_oid: int, - ): - super().__init__(name, oid, array_oid, regtype=regtype) - self.subtype_oid = subtype_oid - - @classmethod - def _get_info_query(cls, conn: "BaseConnection[Any]") -> Query: - from .sql import SQL - - return SQL( - """\ -SELECT t.typname AS name, t.oid AS oid, t.typarray AS array_oid, - t.oid::regtype::text AS regtype, - r.rngsubtype AS subtype_oid -FROM pg_type t -JOIN pg_range r ON t.oid = r.rngtypid -WHERE t.oid = {regtype} -""" - ).format(regtype=cls._to_regtype(conn)) - - def _added(self, registry: "TypesRegistry") -> None: - # Map ranges subtypes to info - registry._registry[RangeInfo, self.subtype_oid] = self - - -class MultirangeInfo(TypeInfo): - """Manage information about a multirange type.""" - - __module__ = "psycopg.types.multirange" - - def __init__( - self, - name: str, - oid: int, - array_oid: int, - *, - regtype: str = "", - range_oid: int, - subtype_oid: int, - ): - super().__init__(name, oid, array_oid, regtype=regtype) - self.range_oid = range_oid - self.subtype_oid = subtype_oid - - @classmethod - def _get_info_query(cls, conn: "BaseConnection[Any]") -> Query: - from .sql import SQL - - if conn.info.server_version < 140000: - raise e.NotSupportedError( - "multirange types are only available from PostgreSQL 14" - ) - - return SQL( - """\ -SELECT t.typname AS name, t.oid AS oid, t.typarray AS array_oid, - t.oid::regtype::text AS regtype, - r.rngtypid AS range_oid, r.rngsubtype AS subtype_oid -FROM pg_type t -JOIN pg_range r ON t.oid = r.rngmultitypid -WHERE t.oid = {regtype} -""" - ).format(regtype=cls._to_regtype(conn)) - - def _added(self, registry: "TypesRegistry") -> None: - # Map multiranges ranges and subtypes to info - registry._registry[MultirangeInfo, self.range_oid] = self - registry._registry[MultirangeInfo, self.subtype_oid] = self - - -class CompositeInfo(TypeInfo): - """Manage information about a composite type.""" - - __module__ = "psycopg.types.composite" - - def __init__( - self, - name: str, - oid: int, - array_oid: int, - *, - regtype: str = "", - field_names: Sequence[str], - field_types: Sequence[int], - ): - super().__init__(name, oid, array_oid, regtype=regtype) - self.field_names = field_names - self.field_types = field_types - # Will be set by register() if the `factory` is a type - self.python_type: Optional[type] = None - - @classmethod - def _get_info_query(cls, conn: "BaseConnection[Any]") -> Query: - from .sql import SQL - - return SQL( - """\ -SELECT - t.typname AS name, t.oid AS oid, t.typarray AS array_oid, - t.oid::regtype::text AS regtype, - coalesce(a.fnames, '{{}}') AS field_names, - coalesce(a.ftypes, '{{}}') AS field_types -FROM pg_type t -LEFT JOIN ( - SELECT - attrelid, - array_agg(attname) AS fnames, - array_agg(atttypid) AS ftypes - FROM ( - SELECT a.attrelid, a.attname, a.atttypid - FROM pg_attribute a - JOIN pg_type t ON t.typrelid = a.attrelid - WHERE t.oid = {regtype} - AND a.attnum > 0 - AND NOT a.attisdropped - ORDER BY a.attnum - ) x - GROUP BY attrelid -) a ON a.attrelid = t.typrelid -WHERE t.oid = {regtype} -""" - ).format(regtype=cls._to_regtype(conn)) - - -class EnumInfo(TypeInfo): - """Manage information about an enum type.""" - - __module__ = "psycopg.types.enum" - - def __init__( - self, - name: str, - oid: int, - array_oid: int, - labels: Sequence[str], - ): - super().__init__(name, oid, array_oid) - self.labels = labels - # Will be set by register_enum() - self.enum: Optional[Type[Enum]] = None - - @classmethod - def _get_info_query(cls, conn: "BaseConnection[Any]") -> Query: - from .sql import SQL - - return SQL( - """\ -SELECT name, oid, array_oid, array_agg(label) AS labels -FROM ( - SELECT - t.typname AS name, t.oid AS oid, t.typarray AS array_oid, - e.enumlabel AS label - FROM pg_type t - LEFT JOIN pg_enum e - ON e.enumtypid = t.oid - WHERE t.oid = {regtype} - ORDER BY e.enumsortorder -) x -GROUP BY name, oid, array_oid -""" - ).format(regtype=cls._to_regtype(conn)) - - class TypesRegistry: """ Container for the information about types in a database. diff --git a/psycopg/psycopg/postgres.py b/psycopg/psycopg/postgres.py index 10843a2d6..7a5d9dd47 100644 --- a/psycopg/psycopg/postgres.py +++ b/psycopg/psycopg/postgres.py @@ -4,7 +4,7 @@ Types configuration specific to PostgreSQL. # Copyright (C) 2020 The Psycopg Team -from ._typeinfo import TypeInfo, RangeInfo, MultirangeInfo, TypesRegistry +from ._typeinfo import TypeInfo, TypesRegistry from .abc import AdaptContext from ._adapters_map import AdaptersMap @@ -17,6 +17,9 @@ adapters = AdaptersMap(types=types) def register_default_types(types: TypesRegistry) -> None: + from .types.range import RangeInfo + from .types.multirange import MultirangeInfo + # Use tools/update_oids.py to update this data. for t in [ TypeInfo('"char"', 18, 1002), diff --git a/psycopg/psycopg/types/composite.py b/psycopg/psycopg/types/composite.py index 9170aa279..12059852e 100644 --- a/psycopg/psycopg/types/composite.py +++ b/psycopg/psycopg/types/composite.py @@ -8,17 +8,21 @@ import re import struct from collections import namedtuple from typing import Any, Callable, cast, Iterator, List, Optional -from typing import Sequence, Tuple, Type +from typing import Sequence, Tuple, Type, TYPE_CHECKING from .. import pq +from .. import sql from .. import postgres -from ..abc import AdaptContext, Buffer +from ..abc import AdaptContext, Buffer, Query from ..adapt import Transformer, PyFormat, RecursiveDumper, Loader from .._oids import TEXT_OID from .._struct import pack_len, unpack_len -from .._typeinfo import CompositeInfo as CompositeInfo # exported here +from .._typeinfo import TypeInfo from .._encodings import _as_python_identifier +if TYPE_CHECKING: + from ..connection import BaseConnection + _struct_oidlen = struct.Struct("!Ii") _pack_oidlen = cast(Callable[[int, int], bytes], _struct_oidlen.pack) _unpack_oidlen = cast( @@ -26,6 +30,56 @@ _unpack_oidlen = cast( ) +class CompositeInfo(TypeInfo): + """Manage information about a composite type.""" + + def __init__( + self, + name: str, + oid: int, + array_oid: int, + *, + regtype: str = "", + field_names: Sequence[str], + field_types: Sequence[int], + ): + super().__init__(name, oid, array_oid, regtype=regtype) + self.field_names = field_names + self.field_types = field_types + # Will be set by register() if the `factory` is a type + self.python_type: Optional[type] = None + + @classmethod + def _get_info_query(cls, conn: "BaseConnection[Any]") -> Query: + return sql.SQL( + """\ +SELECT + t.typname AS name, t.oid AS oid, t.typarray AS array_oid, + t.oid::regtype::text AS regtype, + coalesce(a.fnames, '{{}}') AS field_names, + coalesce(a.ftypes, '{{}}') AS field_types +FROM pg_type t +LEFT JOIN ( + SELECT + attrelid, + array_agg(attname) AS fnames, + array_agg(atttypid) AS ftypes + FROM ( + SELECT a.attrelid, a.attname, a.atttypid + FROM pg_attribute a + JOIN pg_type t ON t.typrelid = a.attrelid + WHERE t.oid = {regtype} + AND a.attnum > 0 + AND NOT a.attisdropped + ORDER BY a.attnum + ) x + GROUP BY attrelid +) a ON a.attrelid = t.typrelid +WHERE t.oid = {regtype} +""" + ).format(regtype=cls._to_regtype(conn)) + + class SequenceDumper(RecursiveDumper): def _dump_sequence( self, obj: Sequence[Any], start: bytes, end: bytes, sep: bytes diff --git a/psycopg/psycopg/types/enum.py b/psycopg/psycopg/types/enum.py index d3c73874f..3eaaee504 100644 --- a/psycopg/psycopg/types/enum.py +++ b/psycopg/psycopg/types/enum.py @@ -3,16 +3,20 @@ Adapters for the enum type. """ from enum import Enum from typing import Any, Dict, Generic, Optional, Mapping, Sequence -from typing import Tuple, Type, TypeVar, Union, cast +from typing import Tuple, Type, TypeVar, Union, cast, TYPE_CHECKING from typing_extensions import TypeAlias +from .. import sql from .. import postgres from .. import errors as e from ..pq import Format -from ..abc import AdaptContext +from ..abc import AdaptContext, Query from ..adapt import Buffer, Dumper, Loader from .._encodings import conn_encoding -from .._typeinfo import EnumInfo as EnumInfo # exported here +from .._typeinfo import TypeInfo + +if TYPE_CHECKING: + from ..connection import BaseConnection E = TypeVar("E", bound=Enum) @@ -21,6 +25,41 @@ EnumLoadMap: TypeAlias = Dict[bytes, E] EnumMapping: TypeAlias = Union[Mapping[E, str], Sequence[Tuple[E, str]], None] +class EnumInfo(TypeInfo): + """Manage information about an enum type.""" + + def __init__( + self, + name: str, + oid: int, + array_oid: int, + labels: Sequence[str], + ): + super().__init__(name, oid, array_oid) + self.labels = labels + # Will be set by register_enum() + self.enum: Optional[Type[Enum]] = None + + @classmethod + def _get_info_query(cls, conn: "BaseConnection[Any]") -> Query: + return sql.SQL( + """\ +SELECT name, oid, array_oid, array_agg(label) AS labels +FROM ( + SELECT + t.typname AS name, t.oid AS oid, t.typarray AS array_oid, + e.enumlabel AS label + FROM pg_type t + LEFT JOIN pg_enum e + ON e.enumtypid = t.oid + WHERE t.oid = {regtype} + ORDER BY e.enumsortorder +) x +GROUP BY name, oid, array_oid +""" + ).format(regtype=cls._to_regtype(conn)) + + class _BaseEnumLoader(Loader, Generic[E]): """ Loader for a specific Enum class diff --git a/psycopg/psycopg/types/multirange.py b/psycopg/psycopg/types/multirange.py index 682402a20..2fa1c67aa 100644 --- a/psycopg/psycopg/types/multirange.py +++ b/psycopg/psycopg/types/multirange.py @@ -5,23 +5,67 @@ Support for multirange types adaptation. # Copyright (C) 2021 The Psycopg Team from decimal import Decimal -from typing import Any, Generic, List, Iterable -from typing import MutableSequence, Optional, Type, Union, overload +from typing import Any, Generic, List, Iterable, MutableSequence +from typing import Optional, Type, Union, overload, TYPE_CHECKING from datetime import date, datetime +from .. import sql from .. import _oids from .. import errors as e from .. import postgres from ..pq import Format -from ..abc import AdaptContext, Buffer, Dumper, DumperKey +from ..abc import AdaptContext, Buffer, Dumper, DumperKey, Query from ..adapt import RecursiveDumper, RecursiveLoader, PyFormat from .._oids import INVALID_OID, TEXT_OID from .._struct import pack_len, unpack_len -from .._typeinfo import MultirangeInfo as MultirangeInfo # exported here +from .._typeinfo import TypeInfo, TypesRegistry from .range import Range, T, load_range_text, load_range_binary from .range import dump_range_text, dump_range_binary, fail_dump +if TYPE_CHECKING: + from ..connection import BaseConnection + + +class MultirangeInfo(TypeInfo): + """Manage information about a multirange type.""" + + def __init__( + self, + name: str, + oid: int, + array_oid: int, + *, + regtype: str = "", + range_oid: int, + subtype_oid: int, + ): + super().__init__(name, oid, array_oid, regtype=regtype) + self.range_oid = range_oid + self.subtype_oid = subtype_oid + + @classmethod + def _get_info_query(cls, conn: "BaseConnection[Any]") -> Query: + if conn.info.server_version < 140000: + raise e.NotSupportedError( + "multirange types are only available from PostgreSQL 14" + ) + return sql.SQL( + """\ +SELECT t.typname AS name, t.oid AS oid, t.typarray AS array_oid, + t.oid::regtype::text AS regtype, + r.rngtypid AS range_oid, r.rngsubtype AS subtype_oid +FROM pg_type t +JOIN pg_range r ON t.oid = r.rngmultitypid +WHERE t.oid = {regtype} +""" + ).format(regtype=cls._to_regtype(conn)) + + def _added(self, registry: "TypesRegistry") -> None: + # Map multiranges ranges and subtypes to info + registry._registry[MultirangeInfo, self.range_oid] = self + registry._registry[MultirangeInfo, self.subtype_oid] = self + class Multirange(MutableSequence[Range[T]]): """Python representation for a PostgreSQL multirange type. diff --git a/psycopg/psycopg/types/range.py b/psycopg/psycopg/types/range.py index 81ca38e38..98ad7c44d 100644 --- a/psycopg/psycopg/types/range.py +++ b/psycopg/psycopg/types/range.py @@ -6,19 +6,23 @@ Support for range types adaptation. import re from typing import Any, Callable, Dict, Generic, List, Optional, TypeVar, Type, Tuple -from typing import cast +from typing import cast, TYPE_CHECKING from decimal import Decimal from datetime import date, datetime +from .. import sql from .. import _oids from .. import errors as e from .. import postgres from ..pq import Format -from ..abc import AdaptContext, Buffer, Dumper, DumperKey +from ..abc import AdaptContext, Buffer, Dumper, DumperKey, Query from ..adapt import RecursiveDumper, RecursiveLoader, PyFormat from .._oids import INVALID_OID, TEXT_OID from .._struct import pack_len, unpack_len -from .._typeinfo import RangeInfo as RangeInfo # exported here +from .._typeinfo import TypeInfo, TypesRegistry + +if TYPE_CHECKING: + from ..connection import BaseConnection RANGE_EMPTY = 0x01 # range is empty RANGE_LB_INC = 0x02 # lower bound is inclusive @@ -31,6 +35,39 @@ _EMPTY_HEAD = bytes([RANGE_EMPTY]) T = TypeVar("T") +class RangeInfo(TypeInfo): + """Manage information about a range type.""" + + def __init__( + self, + name: str, + oid: int, + array_oid: int, + *, + regtype: str = "", + subtype_oid: int, + ): + super().__init__(name, oid, array_oid, regtype=regtype) + self.subtype_oid = subtype_oid + + @classmethod + def _get_info_query(cls, conn: "BaseConnection[Any]") -> Query: + return sql.SQL( + """\ +SELECT t.typname AS name, t.oid AS oid, t.typarray AS array_oid, + t.oid::regtype::text AS regtype, + r.rngsubtype AS subtype_oid +FROM pg_type t +JOIN pg_range r ON t.oid = r.rngtypid +WHERE t.oid = {regtype} +""" + ).format(regtype=cls._to_regtype(conn)) + + def _added(self, registry: TypesRegistry) -> None: + # Map ranges subtypes to info + registry._registry[RangeInfo, self.subtype_oid] = self + + class Range(Generic[T]): """Python representation for a PostgreSQL range type.