From: Daniele Varrazzo Date: Fri, 6 Jan 2023 17:55:44 +0000 (+0000) Subject: refactor: break circular import problem between sql, adapt, _typeinfo X-Git-Tag: pool-3.2.0~135^2 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=81e62d39252622c4f36855cf40d7d65a0137aa8b;p=thirdparty%2Fpsycopg.git refactor: break circular import problem between sql, adapt, _typeinfo Move the implementation of the Python Transformer to a further internal module, which is only imported if the Python implementation is used. The new module breaks the dependency of sql on adapt without introducing a lazy import. Drop all the lazy imports required to work around the above. --- diff --git a/psycopg/psycopg/_transform.py b/psycopg/psycopg/_py_transformer.py similarity index 94% rename from psycopg/psycopg/_transform.py rename to psycopg/psycopg/_py_transformer.py index 281cd26f2..045a6543f 100644 --- a/psycopg/psycopg/_transform.py +++ b/psycopg/psycopg/_py_transformer.py @@ -1,5 +1,10 @@ """ Helper object to transform values between Python and PostgreSQL + +Python implementation of the object. Use the `_transformer module to import +the right implementation (Python or C). The public place where the object +is exported is `psycopg.adapt` (which we may not use to avoid circular +dependencies problems). """ # Copyright (C) 2020 The Psycopg Team @@ -10,7 +15,7 @@ from collections import defaultdict from typing_extensions import TypeAlias from . import pq -from . import postgres +from . import abc from . import errors as e from .abc import Buffer, LoadFunc, AdaptContext, PyFormat, DumperKey, NoneType from .rows import Row, RowMaker @@ -18,14 +23,13 @@ from ._oids import INVALID_OID, TEXT_OID from ._encodings import pgconn_encoding if TYPE_CHECKING: - from .abc import Dumper, Loader from .adapt import AdaptersMap from .pq.abc import PGresult from .connection import BaseConnection -DumperCache: TypeAlias = Dict[DumperKey, "Dumper"] -OidDumperCache: TypeAlias = Dict[int, "Dumper"] -LoaderCache: TypeAlias = Dict[int, "Loader"] +DumperCache: TypeAlias = Dict[DumperKey, abc.Dumper] +OidDumperCache: TypeAlias = Dict[int, abc.Dumper] +LoaderCache: TypeAlias = Dict[int, abc.Loader] TEXT = pq.Format.TEXT PY_TEXT = PyFormat.TEXT @@ -65,6 +69,8 @@ class Transformer(AdaptContext): self._adapters = context.adapters self._conn = context.connection else: + from . import postgres + self._adapters = postgres.adapters self._conn = None @@ -80,7 +86,7 @@ class Transformer(AdaptContext): # mapping fmt, oid -> Loader instance self._loaders: Tuple[LoaderCache, LoaderCache] = ({}, {}) - self._row_dumpers: Optional[List["Dumper"]] = None + self._row_dumpers: Optional[List[abc.Dumper]] = None # sequence of load functions from value to python # the length of the result columns @@ -225,7 +231,7 @@ class Transformer(AdaptContext): rv = bytes(rv) return rv - def get_dumper(self, obj: Any, format: PyFormat) -> "Dumper": + def get_dumper(self, obj: Any, format: PyFormat) -> abc.Dumper: """ Return a Dumper instance to dump `!obj`. """ @@ -267,7 +273,7 @@ class Transformer(AdaptContext): return rv - def get_dumper_by_oid(self, oid: int, format: pq.Format) -> "Dumper": + def get_dumper_by_oid(self, oid: int, format: pq.Format) -> abc.Dumper: """ Return a Dumper to dump an object to the type with given oid. """ @@ -335,7 +341,7 @@ class Transformer(AdaptContext): for i, val in enumerate(record) ) - def get_loader(self, oid: int, format: pq.Format) -> "Loader": + def get_loader(self, oid: int, format: pq.Format) -> abc.Loader: try: return self._loaders[format][oid] except KeyError: diff --git a/psycopg/psycopg/_transformer.py b/psycopg/psycopg/_transformer.py new file mode 100644 index 000000000..fe21d2b23 --- /dev/null +++ b/psycopg/psycopg/_transformer.py @@ -0,0 +1,21 @@ +""" +Helper object to transform values between Python and PostgreSQL + +This module exports the requested implementation to the rest of the package. +""" + +# Copyright (C) 2023 The Psycopg Team + +from typing import Type + +from . import abc +from ._cmodule import _psycopg + +Transformer: Type[abc.Transformer] + +if _psycopg: + Transformer = _psycopg.Transformer +else: + from . import _py_transformer + + Transformer = _py_transformer.Transformer diff --git a/psycopg/psycopg/_typeinfo.py b/psycopg/psycopg/_typeinfo.py index c7cb362a0..87260d159 100644 --- a/psycopg/psycopg/_typeinfo.py +++ b/psycopg/psycopg/_typeinfo.py @@ -11,6 +11,7 @@ from typing import Any, Dict, Iterator, Optional, overload from typing import Sequence, Tuple, Type, TypeVar, Union, TYPE_CHECKING from typing_extensions import TypeAlias +from . import sql from . import errors as e from .abc import AdaptContext, Query from .rows import dict_row @@ -18,7 +19,6 @@ from .rows import dict_row if TYPE_CHECKING: from .connection import BaseConnection, Connection from .connection_async import AsyncConnection - from .sql import Identifier, SQL T = TypeVar("T", bound="TypeInfo") RegistryKey: TypeAlias = Union[str, int, Tuple[type, int]] @@ -55,27 +55,26 @@ class TypeInfo: @overload @classmethod def fetch( - cls: Type[T], conn: "Connection[Any]", name: Union[str, "Identifier"] + cls: Type[T], conn: "Connection[Any]", name: Union[str, sql.Identifier] ) -> Optional[T]: ... @overload @classmethod async def fetch( - cls: Type[T], conn: "AsyncConnection[Any]", name: Union[str, "Identifier"] + cls: Type[T], conn: "AsyncConnection[Any]", name: Union[str, sql.Identifier] ) -> Optional[T]: ... @classmethod def fetch( - cls: Type[T], conn: "BaseConnection[Any]", name: Union[str, "Identifier"] + cls: Type[T], conn: "BaseConnection[Any]", name: Union[str, sql.Identifier] ) -> Any: """Query a system catalog to read information about a type.""" - from .sql import Composable from .connection import Connection from .connection_async import AsyncConnection - if isinstance(name, Composable): + if isinstance(name, sql.Composable): name = name.as_string(conn) if isinstance(conn, Connection): @@ -147,9 +146,7 @@ class TypeInfo: @classmethod def _get_info_query(cls, conn: "BaseConnection[Any]") -> Query: - from .sql import SQL - - return SQL( + return sql.SQL( """\ SELECT typname AS name, oid, typarray AS array_oid, @@ -172,17 +169,15 @@ ORDER BY t.oid return False @classmethod - def _to_regtype(cls, conn: "BaseConnection[Any]") -> "SQL": + def _to_regtype(cls, conn: "BaseConnection[Any]") -> sql.SQL: # `to_regtype()` returns the type oid or NULL, unlike the :: operator, # which returns the type or raises an exception, which requires # a transaction rollback and leaves traces in the server logs. - from .sql import SQL - if cls._has_to_regtype_function(conn): - return SQL("to_regtype(%(name)s)") + return sql.SQL("to_regtype(%(name)s)") else: - return SQL("%(name)s::regtype") + return sql.SQL("%(name)s::regtype") def _added(self, registry: "TypesRegistry") -> None: """Method called by the `!registry` when the object is added there.""" diff --git a/psycopg/psycopg/adapt.py b/psycopg/psycopg/adapt.py index 7ec4a5597..f09bd6a84 100644 --- a/psycopg/psycopg/adapt.py +++ b/psycopg/psycopg/adapt.py @@ -5,17 +5,18 @@ Entry point into the adaptation system. # Copyright (C) 2020 The Psycopg Team from abc import ABC, abstractmethod -from typing import Any, Optional, Type, TYPE_CHECKING +from typing import Any, Optional, TYPE_CHECKING from . import pq, abc -from . import _adapters_map + +# Objects exported here from ._enums import PyFormat as PyFormat -from ._cmodule import _psycopg +from ._transformer import Transformer as Transformer +from ._adapters_map import AdaptersMap as AdaptersMap # noqa: F401 if TYPE_CHECKING: from .connection import BaseConnection -AdaptersMap = _adapters_map.AdaptersMap Buffer = abc.Buffer ORD_BS = ord("\\") @@ -135,17 +136,6 @@ class Loader(abc.Loader, ABC): ... -Transformer: Type["abc.Transformer"] - -# Override it with fast object if available -if _psycopg: - Transformer = _psycopg.Transformer -else: - from . import _transform - - Transformer = _transform.Transformer - - class RecursiveDumper(Dumper): """Dumper with a transformer to help dumping recursive types.""" diff --git a/psycopg/psycopg/sql.py b/psycopg/psycopg/sql.py index 099a01cc4..86c0540c5 100644 --- a/psycopg/psycopg/sql.py +++ b/psycopg/psycopg/sql.py @@ -11,9 +11,10 @@ from typing import Any, Iterator, Iterable, List, Optional, Sequence, Union from .pq import Escaping from .abc import AdaptContext -from .adapt import Transformer, PyFormat +from ._enums import PyFormat from ._compat import LiteralString from ._encodings import conn_encoding +from ._transformer import Transformer def quote(obj: Any, context: Optional[AdaptContext] = None) -> str: