]> git.ipfire.org Git - thirdparty/psycopg.git/commitdiff
refactor: break circular import problem between sql, adapt, _typeinfo
authorDaniele Varrazzo <daniele.varrazzo@gmail.com>
Fri, 6 Jan 2023 17:55:44 +0000 (17:55 +0000)
committerDaniele Varrazzo <daniele.varrazzo@gmail.com>
Mon, 9 Jan 2023 17:44:16 +0000 (17:44 +0000)
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.

psycopg/psycopg/_py_transformer.py [moved from psycopg/psycopg/_transform.py with 94% similarity]
psycopg/psycopg/_transformer.py [new file with mode: 0644]
psycopg/psycopg/_typeinfo.py
psycopg/psycopg/adapt.py
psycopg/psycopg/sql.py

similarity index 94%
rename from psycopg/psycopg/_transform.py
rename to psycopg/psycopg/_py_transformer.py
index 281cd26f2ed1cd013e7073d967b8b5f79ed5d15e..045a6543ff2907e1e0fa69f8ebd709ff75c5613d 100644 (file)
@@ -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 (file)
index 0000000..fe21d2b
--- /dev/null
@@ -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
index c7cb362a0e8e263c5f6d49b1fafed363a93daf94..87260d159cafd0c77550b7c04d11d18d53001410 100644 (file)
@@ -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."""
index 7ec4a5597e79b55b2fefce36b0adba4e0cb29a83..f09bd6a84e3d3c3443b966a32a746a93453bb6ed 100644 (file)
@@ -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."""
 
index 099a01cc429c7c1b9262f0daa864397dcc28043e..86c0540c5c506e887e888de78d15a8c90ea6dc04 100644 (file)
@@ -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: