mysteriendrama/lib/python3.11/site-packages/psycopg/pq/_debug.py

107 lines
3.0 KiB
Python
Raw Permalink Normal View History

2023-07-26 21:33:29 +02:00
"""
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