""" libpq debugging tools These functionalities are exposed here for convenience, but are not part of the public interface and are subject to change at any moment. Suggested usage:: import logging import psycopg from psycopg import pq from psycopg.pq._debug import PGconnDebug logging.basicConfig(level=logging.INFO, format="%(message)s") logger = logging.getLogger("psycopg.debug") logger.setLevel(logging.INFO) assert pq.__impl__ == "python" pq.PGconn = PGconnDebug with psycopg.connect("") as conn: conn.pgconn.trace(2) conn.pgconn.set_trace_flags( pq.Trace.SUPPRESS_TIMESTAMPS | pq.Trace.REGRESS_MODE) ... """ # Copyright (C) 2022 The Psycopg Team import inspect import logging from typing import Any, Callable, Type, TypeVar, TYPE_CHECKING from functools import wraps from . import PGconn from .misc import connection_summary if TYPE_CHECKING: from . import abc Func = TypeVar("Func", bound=Callable[..., Any]) logger = logging.getLogger("psycopg.debug") class PGconnDebug: """Wrapper for a PQconn logging all its access.""" _Self = TypeVar("_Self", bound="PGconnDebug") _pgconn: "abc.PGconn" def __init__(self, pgconn: "abc.PGconn"): super().__setattr__("_pgconn", pgconn) def __repr__(self) -> str: cls = f"{self.__class__.__module__}.{self.__class__.__qualname__}" info = connection_summary(self._pgconn) return f"<{cls} {info} at 0x{id(self):x}>" def __getattr__(self, attr: str) -> Any: value = getattr(self._pgconn, attr) if callable(value): return debugging(value) else: logger.info("PGconn.%s -> %s", attr, value) return value def __setattr__(self, attr: str, value: Any) -> None: setattr(self._pgconn, attr, value) logger.info("PGconn.%s <- %s", attr, value) @classmethod def connect(cls: Type[_Self], conninfo: bytes) -> _Self: return cls(debugging(PGconn.connect)(conninfo)) @classmethod def connect_start(cls: Type[_Self], conninfo: bytes) -> _Self: return cls(debugging(PGconn.connect_start)(conninfo)) @classmethod def ping(self, conninfo: bytes) -> int: return debugging(PGconn.ping)(conninfo) def debugging(f: Func) -> Func: """Wrap a function in order to log its arguments and return value on call.""" @wraps(f) def debugging_(*args: Any, **kwargs: Any) -> Any: reprs = [] for arg in args: reprs.append(f"{arg!r}") for k, v in kwargs.items(): reprs.append(f"{k}={v!r}") logger.info("PGconn.%s(%s)", f.__name__, ", ".join(reprs)) rv = f(*args, **kwargs) # Display the return value only if the function is declared to return # something else than None. ra = inspect.signature(f).return_annotation if ra is not None or rv is not None: logger.info(" <- %r", rv) return rv return debugging_ # type: ignore