if TYPE_CHECKING:
from .pq.proto import PGresult
- from .adapt import Loader, AdaptersMap
- from .proto import Dumper
+ from .adapt import AdaptersMap
+ from .proto import Dumper, Loader
from .connection import BaseConnection
DumperCache = Dict[DumperKey, "Dumper"]
"""
_dumpers: Dict[PyFormat, Dict[Union[type, str], Type["proto.Dumper"]]]
- _loaders: List[Dict[int, Type["Loader"]]]
+ _loaders: List[Dict[int, Type["proto.Loader"]]]
types: TypesRegistry
# Record if a dumper or loader has an optimised version.
self._dumpers[fmt][cls] = dumper
def register_loader(
- self, oid: Union[int, str], loader: Type[Loader]
+ self, oid: Union[int, str], loader: Type["proto.Loader"]
) -> None:
"""
Configure the context to use *loader* to convert data of oid *oid*.
def get_loader(
self, oid: int, format: pq.Format
- ) -> Optional[Type[Loader]]:
+ ) -> Optional[Type["proto.Loader"]]:
"""
Return the loader class for the given oid and format.
if TYPE_CHECKING:
from .sql import Composable
from .rows import Row, RowMaker
- from . import adapt
from .adapt import AdaptersMap
from .pq.proto import PGresult
...
+class Loader(Protocol):
+ format: pq.Format
+
+ def __init__(self, oid: int, context: Optional[AdaptContext] = None):
+ ...
+
+ def load(self, data: Buffer) -> Any:
+ ...
+
+
class Transformer(Protocol):
def __init__(self, context: Optional[AdaptContext] = None):
...
) -> Tuple[Any, ...]:
...
- def get_loader(self, oid: int, format: pq.Format) -> "adapt.Loader":
+ def get_loader(self, oid: int, format: pq.Format) -> Loader:
...
from psycopg import pq
from psycopg import proto
from psycopg.rows import Row, RowMaker
-from psycopg.adapt import Loader, AdaptersMap, PyFormat
-from psycopg.proto import Dumper
+from psycopg.adapt import AdaptersMap, PyFormat
+from psycopg.proto import Dumper, Loader
from psycopg.pq.proto import PGconn, PGresult
from psycopg.connection import BaseConnection
assert sql.Literal("hello").as_string(conn) == "'qelloqello'"
+def test_loader_protocol(conn):
+
+ # This class doesn't inherit from adapt.Loader but passes a mypy check
+ from .typing_example import MyTextLoader
+
+ conn.adapters.register_loader("text", MyTextLoader)
+ cur = conn.execute("select 'hello'::text")
+ assert cur.fetchone()[0] == "hellohello"
+ cur = conn.execute("select '{hi,ha}'::text[]")
+ assert cur.fetchone()[0] == ["hihi", "haha"]
+
+
def test_subclass_loader(conn):
# This might be a C fast object: make sure that the Python code is called
from psycopg.types.string import TextLoader
from psycopg import AnyCursor, Connection, Cursor, ServerCursor, connect
from psycopg import pq
-from psycopg.proto import Dumper, AdaptContext, PyFormat
+from psycopg.proto import Dumper, Loader, AdaptContext, PyFormat, Buffer
def int_row_factory(cursor: AnyCursor[int]) -> Callable[[Sequence[int]], int]:
assert d.dump("abc") == b"abcabc"
assert d.quote("abc") == b"'abcabc'"
+ lo: Loader = MyTextLoader(0, None)
+ assert lo.load(b"abc") == "abcabc"
+
class MyStrDumper:
format = pq.Format.TEXT
return self
+class MyTextLoader:
+ format = pq.Format.TEXT
+
+ def __init__(self, oid: int, context: Optional[AdaptContext] = None):
+ pass
+
+ def load(self, data: Buffer) -> str:
+ return (bytes(data) * 2).decode("utf-8")
+
+
# This should be the definition of psycopg.adapt.DumperKey, but mypy doesn't
# support recursive types. When it will, this statement will give an error
# (unused type: ignore) so we can fix our definition.