docker setup

This commit is contained in:
AdrienLSH
2023-11-23 16:43:30 +01:00
parent fd19180e1d
commit f29003c66a
5410 changed files with 869440 additions and 0 deletions

View File

@ -0,0 +1,133 @@
"""
psycopg libpq wrapper
This package exposes the libpq functionalities as Python objects and functions.
The real implementation (the binding to the C library) is
implementation-dependant but all the implementations share the same interface.
"""
# Copyright (C) 2020 The Psycopg Team
import os
import logging
from typing import Callable, List, Type
from . import abc
from .misc import ConninfoOption, PGnotify, PGresAttDesc
from .misc import error_message
from ._enums import ConnStatus, DiagnosticField, ExecStatus, Format, Trace
from ._enums import Ping, PipelineStatus, PollingStatus, TransactionStatus
logger = logging.getLogger(__name__)
__impl__: str
"""The currently loaded implementation of the `!psycopg.pq` package.
Possible values include ``python``, ``c``, ``binary``.
"""
__build_version__: int
"""The libpq version the C package was built with.
A number in the same format of `~psycopg.ConnectionInfo.server_version`
representing the libpq used to build the speedup module (``c``, ``binary``) if
available.
Certain features might not be available if the built version is too old.
"""
version: Callable[[], int]
PGconn: Type[abc.PGconn]
PGresult: Type[abc.PGresult]
Conninfo: Type[abc.Conninfo]
Escaping: Type[abc.Escaping]
PGcancel: Type[abc.PGcancel]
def import_from_libpq() -> None:
"""
Import pq objects implementation from the best libpq wrapper available.
If an implementation is requested try to import only it, otherwise
try to import the best implementation available.
"""
# import these names into the module on success as side effect
global __impl__, version, __build_version__
global PGconn, PGresult, Conninfo, Escaping, PGcancel
impl = os.environ.get("PSYCOPG_IMPL", "").lower()
module = None
attempts: List[str] = []
def handle_error(name: str, e: Exception) -> None:
if not impl:
msg = f"couldn't import psycopg '{name}' implementation: {e}"
logger.debug(msg)
attempts.append(msg)
else:
msg = f"couldn't import requested psycopg '{name}' implementation: {e}"
raise ImportError(msg) from e
# The best implementation: fast but requires the system libpq installed
if not impl or impl == "c":
try:
from psycopg_c import pq as module # type: ignore
except Exception as e:
handle_error("c", e)
# Second best implementation: fast and stand-alone
if not module and (not impl or impl == "binary"):
try:
from psycopg_binary import pq as module # type: ignore
except Exception as e:
handle_error("binary", e)
# Pure Python implementation, slow and requires the system libpq installed.
if not module and (not impl or impl == "python"):
try:
from . import pq_ctypes as module # type: ignore[assignment]
except Exception as e:
handle_error("python", e)
if module:
__impl__ = module.__impl__
version = module.version
PGconn = module.PGconn
PGresult = module.PGresult
Conninfo = module.Conninfo
Escaping = module.Escaping
PGcancel = module.PGcancel
__build_version__ = module.__build_version__
elif impl:
raise ImportError(f"requested psycopg implementation '{impl}' unknown")
else:
sattempts = "\n".join(f"- {attempt}" for attempt in attempts)
raise ImportError(
f"""\
no pq wrapper available.
Attempts made:
{sattempts}"""
)
import_from_libpq()
__all__ = (
"ConnStatus",
"PipelineStatus",
"PollingStatus",
"TransactionStatus",
"ExecStatus",
"Ping",
"DiagnosticField",
"Format",
"Trace",
"PGconn",
"PGnotify",
"Conninfo",
"PGresAttDesc",
"error_message",
"ConninfoOption",
"version",
)

View File

@ -0,0 +1,106 @@
"""
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

View File

@ -0,0 +1,249 @@
"""
libpq enum definitions for psycopg
"""
# Copyright (C) 2020 The Psycopg Team
from enum import IntEnum, IntFlag, auto
class ConnStatus(IntEnum):
"""
Current status of the connection.
"""
__module__ = "psycopg.pq"
OK = 0
"""The connection is in a working state."""
BAD = auto()
"""The connection is closed."""
STARTED = auto()
MADE = auto()
AWAITING_RESPONSE = auto()
AUTH_OK = auto()
SETENV = auto()
SSL_STARTUP = auto()
NEEDED = auto()
CHECK_WRITABLE = auto()
CONSUME = auto()
GSS_STARTUP = auto()
CHECK_TARGET = auto()
CHECK_STANDBY = auto()
class PollingStatus(IntEnum):
"""
The status of the socket during a connection.
If ``READING`` or ``WRITING`` you may select before polling again.
"""
__module__ = "psycopg.pq"
FAILED = 0
"""Connection attempt failed."""
READING = auto()
"""Will have to wait before reading new data."""
WRITING = auto()
"""Will have to wait before writing new data."""
OK = auto()
"""Connection completed."""
ACTIVE = auto()
class ExecStatus(IntEnum):
"""
The status of a command.
"""
__module__ = "psycopg.pq"
EMPTY_QUERY = 0
"""The string sent to the server was empty."""
COMMAND_OK = auto()
"""Successful completion of a command returning no data."""
TUPLES_OK = auto()
"""
Successful completion of a command returning data (such as a SELECT or SHOW).
"""
COPY_OUT = auto()
"""Copy Out (from server) data transfer started."""
COPY_IN = auto()
"""Copy In (to server) data transfer started."""
BAD_RESPONSE = auto()
"""The server's response was not understood."""
NONFATAL_ERROR = auto()
"""A nonfatal error (a notice or warning) occurred."""
FATAL_ERROR = auto()
"""A fatal error occurred."""
COPY_BOTH = auto()
"""
Copy In/Out (to and from server) data transfer started.
This feature is currently used only for streaming replication, so this
status should not occur in ordinary applications.
"""
SINGLE_TUPLE = auto()
"""
The PGresult contains a single result tuple from the current command.
This status occurs only when single-row mode has been selected for the
query.
"""
PIPELINE_SYNC = auto()
"""
The PGresult represents a synchronization point in pipeline mode,
requested by PQpipelineSync.
This status occurs only when pipeline mode has been selected.
"""
PIPELINE_ABORTED = auto()
"""
The PGresult represents a pipeline that has received an error from the server.
PQgetResult must be called repeatedly, and each time it will return this
status code until the end of the current pipeline, at which point it will
return PGRES_PIPELINE_SYNC and normal processing can resume.
"""
class TransactionStatus(IntEnum):
"""
The transaction status of a connection.
"""
__module__ = "psycopg.pq"
IDLE = 0
"""Connection ready, no transaction active."""
ACTIVE = auto()
"""A command is in progress."""
INTRANS = auto()
"""Connection idle in an open transaction."""
INERROR = auto()
"""An error happened in the current transaction."""
UNKNOWN = auto()
"""Unknown connection state, broken connection."""
class Ping(IntEnum):
"""Response from a ping attempt."""
__module__ = "psycopg.pq"
OK = 0
"""
The server is running and appears to be accepting connections.
"""
REJECT = auto()
"""
The server is running but is in a state that disallows connections.
"""
NO_RESPONSE = auto()
"""
The server could not be contacted.
"""
NO_ATTEMPT = auto()
"""
No attempt was made to contact the server.
"""
class PipelineStatus(IntEnum):
"""Pipeline mode status of the libpq connection."""
__module__ = "psycopg.pq"
OFF = 0
"""
The libpq connection is *not* in pipeline mode.
"""
ON = auto()
"""
The libpq connection is in pipeline mode.
"""
ABORTED = auto()
"""
The libpq connection is in pipeline mode and an error occurred while
processing the current pipeline. The aborted flag is cleared when
PQgetResult returns a result of type PGRES_PIPELINE_SYNC.
"""
class DiagnosticField(IntEnum):
"""
Fields in an error report.
"""
__module__ = "psycopg.pq"
# from postgres_ext.h
SEVERITY = ord("S")
SEVERITY_NONLOCALIZED = ord("V")
SQLSTATE = ord("C")
MESSAGE_PRIMARY = ord("M")
MESSAGE_DETAIL = ord("D")
MESSAGE_HINT = ord("H")
STATEMENT_POSITION = ord("P")
INTERNAL_POSITION = ord("p")
INTERNAL_QUERY = ord("q")
CONTEXT = ord("W")
SCHEMA_NAME = ord("s")
TABLE_NAME = ord("t")
COLUMN_NAME = ord("c")
DATATYPE_NAME = ord("d")
CONSTRAINT_NAME = ord("n")
SOURCE_FILE = ord("F")
SOURCE_LINE = ord("L")
SOURCE_FUNCTION = ord("R")
class Format(IntEnum):
"""
Enum representing the format of a query argument or return value.
These values are only the ones managed by the libpq. `~psycopg` may also
support automatically-chosen values: see `psycopg.adapt.PyFormat`.
"""
__module__ = "psycopg.pq"
TEXT = 0
"""Text parameter."""
BINARY = 1
"""Binary parameter."""
class Trace(IntFlag):
"""
Enum to control tracing of the client/server communication.
"""
__module__ = "psycopg.pq"
SUPPRESS_TIMESTAMPS = 1
"""Do not include timestamps in messages."""
REGRESS_MODE = 2
"""Redact some fields, e.g. OIDs, from messages."""

View File

@ -0,0 +1,804 @@
"""
libpq access using ctypes
"""
# Copyright (C) 2020 The Psycopg Team
import sys
import ctypes
import ctypes.util
from ctypes import Structure, CFUNCTYPE, POINTER
from ctypes import c_char, c_char_p, c_int, c_size_t, c_ubyte, c_uint, c_void_p
from typing import List, Optional, Tuple
from .misc import find_libpq_full_path
from ..errors import NotSupportedError
libname = find_libpq_full_path()
if not libname:
raise ImportError("libpq library not found")
pq = ctypes.cdll.LoadLibrary(libname)
class FILE(Structure):
pass
FILE_ptr = POINTER(FILE)
if sys.platform == "linux":
libcname = ctypes.util.find_library("c")
assert libcname
libc = ctypes.cdll.LoadLibrary(libcname)
fdopen = libc.fdopen
fdopen.argtypes = (c_int, c_char_p)
fdopen.restype = FILE_ptr
# Get the libpq version to define what functions are available.
PQlibVersion = pq.PQlibVersion
PQlibVersion.argtypes = []
PQlibVersion.restype = c_int
libpq_version = PQlibVersion()
# libpq data types
Oid = c_uint
class PGconn_struct(Structure):
_fields_: List[Tuple[str, type]] = []
class PGresult_struct(Structure):
_fields_: List[Tuple[str, type]] = []
class PQconninfoOption_struct(Structure):
_fields_ = [
("keyword", c_char_p),
("envvar", c_char_p),
("compiled", c_char_p),
("val", c_char_p),
("label", c_char_p),
("dispchar", c_char_p),
("dispsize", c_int),
]
class PGnotify_struct(Structure):
_fields_ = [
("relname", c_char_p),
("be_pid", c_int),
("extra", c_char_p),
]
class PGcancel_struct(Structure):
_fields_: List[Tuple[str, type]] = []
class PGresAttDesc_struct(Structure):
_fields_ = [
("name", c_char_p),
("tableid", Oid),
("columnid", c_int),
("format", c_int),
("typid", Oid),
("typlen", c_int),
("atttypmod", c_int),
]
PGconn_ptr = POINTER(PGconn_struct)
PGresult_ptr = POINTER(PGresult_struct)
PQconninfoOption_ptr = POINTER(PQconninfoOption_struct)
PGnotify_ptr = POINTER(PGnotify_struct)
PGcancel_ptr = POINTER(PGcancel_struct)
PGresAttDesc_ptr = POINTER(PGresAttDesc_struct)
# Function definitions as explained in PostgreSQL 12 documentation
# 33.1. Database Connection Control Functions
# PQconnectdbParams: doesn't seem useful, won't wrap for now
PQconnectdb = pq.PQconnectdb
PQconnectdb.argtypes = [c_char_p]
PQconnectdb.restype = PGconn_ptr
# PQsetdbLogin: not useful
# PQsetdb: not useful
# PQconnectStartParams: not useful
PQconnectStart = pq.PQconnectStart
PQconnectStart.argtypes = [c_char_p]
PQconnectStart.restype = PGconn_ptr
PQconnectPoll = pq.PQconnectPoll
PQconnectPoll.argtypes = [PGconn_ptr]
PQconnectPoll.restype = c_int
PQconndefaults = pq.PQconndefaults
PQconndefaults.argtypes = []
PQconndefaults.restype = PQconninfoOption_ptr
PQconninfoFree = pq.PQconninfoFree
PQconninfoFree.argtypes = [PQconninfoOption_ptr]
PQconninfoFree.restype = None
PQconninfo = pq.PQconninfo
PQconninfo.argtypes = [PGconn_ptr]
PQconninfo.restype = PQconninfoOption_ptr
PQconninfoParse = pq.PQconninfoParse
PQconninfoParse.argtypes = [c_char_p, POINTER(c_char_p)]
PQconninfoParse.restype = PQconninfoOption_ptr
PQfinish = pq.PQfinish
PQfinish.argtypes = [PGconn_ptr]
PQfinish.restype = None
PQreset = pq.PQreset
PQreset.argtypes = [PGconn_ptr]
PQreset.restype = None
PQresetStart = pq.PQresetStart
PQresetStart.argtypes = [PGconn_ptr]
PQresetStart.restype = c_int
PQresetPoll = pq.PQresetPoll
PQresetPoll.argtypes = [PGconn_ptr]
PQresetPoll.restype = c_int
PQping = pq.PQping
PQping.argtypes = [c_char_p]
PQping.restype = c_int
# 33.2. Connection Status Functions
PQdb = pq.PQdb
PQdb.argtypes = [PGconn_ptr]
PQdb.restype = c_char_p
PQuser = pq.PQuser
PQuser.argtypes = [PGconn_ptr]
PQuser.restype = c_char_p
PQpass = pq.PQpass
PQpass.argtypes = [PGconn_ptr]
PQpass.restype = c_char_p
PQhost = pq.PQhost
PQhost.argtypes = [PGconn_ptr]
PQhost.restype = c_char_p
_PQhostaddr = None
if libpq_version >= 120000:
_PQhostaddr = pq.PQhostaddr
_PQhostaddr.argtypes = [PGconn_ptr]
_PQhostaddr.restype = c_char_p
def PQhostaddr(pgconn: PGconn_struct) -> bytes:
if not _PQhostaddr:
raise NotSupportedError(
"PQhostaddr requires libpq from PostgreSQL 12,"
f" {libpq_version} available instead"
)
return _PQhostaddr(pgconn)
PQport = pq.PQport
PQport.argtypes = [PGconn_ptr]
PQport.restype = c_char_p
PQtty = pq.PQtty
PQtty.argtypes = [PGconn_ptr]
PQtty.restype = c_char_p
PQoptions = pq.PQoptions
PQoptions.argtypes = [PGconn_ptr]
PQoptions.restype = c_char_p
PQstatus = pq.PQstatus
PQstatus.argtypes = [PGconn_ptr]
PQstatus.restype = c_int
PQtransactionStatus = pq.PQtransactionStatus
PQtransactionStatus.argtypes = [PGconn_ptr]
PQtransactionStatus.restype = c_int
PQparameterStatus = pq.PQparameterStatus
PQparameterStatus.argtypes = [PGconn_ptr, c_char_p]
PQparameterStatus.restype = c_char_p
PQprotocolVersion = pq.PQprotocolVersion
PQprotocolVersion.argtypes = [PGconn_ptr]
PQprotocolVersion.restype = c_int
PQserverVersion = pq.PQserverVersion
PQserverVersion.argtypes = [PGconn_ptr]
PQserverVersion.restype = c_int
PQerrorMessage = pq.PQerrorMessage
PQerrorMessage.argtypes = [PGconn_ptr]
PQerrorMessage.restype = c_char_p
PQsocket = pq.PQsocket
PQsocket.argtypes = [PGconn_ptr]
PQsocket.restype = c_int
PQbackendPID = pq.PQbackendPID
PQbackendPID.argtypes = [PGconn_ptr]
PQbackendPID.restype = c_int
PQconnectionNeedsPassword = pq.PQconnectionNeedsPassword
PQconnectionNeedsPassword.argtypes = [PGconn_ptr]
PQconnectionNeedsPassword.restype = c_int
PQconnectionUsedPassword = pq.PQconnectionUsedPassword
PQconnectionUsedPassword.argtypes = [PGconn_ptr]
PQconnectionUsedPassword.restype = c_int
PQsslInUse = pq.PQsslInUse
PQsslInUse.argtypes = [PGconn_ptr]
PQsslInUse.restype = c_int
# TODO: PQsslAttribute, PQsslAttributeNames, PQsslStruct, PQgetssl
# 33.3. Command Execution Functions
PQexec = pq.PQexec
PQexec.argtypes = [PGconn_ptr, c_char_p]
PQexec.restype = PGresult_ptr
PQexecParams = pq.PQexecParams
PQexecParams.argtypes = [
PGconn_ptr,
c_char_p,
c_int,
POINTER(Oid),
POINTER(c_char_p),
POINTER(c_int),
POINTER(c_int),
c_int,
]
PQexecParams.restype = PGresult_ptr
PQprepare = pq.PQprepare
PQprepare.argtypes = [PGconn_ptr, c_char_p, c_char_p, c_int, POINTER(Oid)]
PQprepare.restype = PGresult_ptr
PQexecPrepared = pq.PQexecPrepared
PQexecPrepared.argtypes = [
PGconn_ptr,
c_char_p,
c_int,
POINTER(c_char_p),
POINTER(c_int),
POINTER(c_int),
c_int,
]
PQexecPrepared.restype = PGresult_ptr
PQdescribePrepared = pq.PQdescribePrepared
PQdescribePrepared.argtypes = [PGconn_ptr, c_char_p]
PQdescribePrepared.restype = PGresult_ptr
PQdescribePortal = pq.PQdescribePortal
PQdescribePortal.argtypes = [PGconn_ptr, c_char_p]
PQdescribePortal.restype = PGresult_ptr
PQresultStatus = pq.PQresultStatus
PQresultStatus.argtypes = [PGresult_ptr]
PQresultStatus.restype = c_int
# PQresStatus: not needed, we have pretty enums
PQresultErrorMessage = pq.PQresultErrorMessage
PQresultErrorMessage.argtypes = [PGresult_ptr]
PQresultErrorMessage.restype = c_char_p
# TODO: PQresultVerboseErrorMessage
PQresultErrorField = pq.PQresultErrorField
PQresultErrorField.argtypes = [PGresult_ptr, c_int]
PQresultErrorField.restype = c_char_p
PQclear = pq.PQclear
PQclear.argtypes = [PGresult_ptr]
PQclear.restype = None
# 33.3.2. Retrieving Query Result Information
PQntuples = pq.PQntuples
PQntuples.argtypes = [PGresult_ptr]
PQntuples.restype = c_int
PQnfields = pq.PQnfields
PQnfields.argtypes = [PGresult_ptr]
PQnfields.restype = c_int
PQfname = pq.PQfname
PQfname.argtypes = [PGresult_ptr, c_int]
PQfname.restype = c_char_p
# PQfnumber: useless and hard to use
PQftable = pq.PQftable
PQftable.argtypes = [PGresult_ptr, c_int]
PQftable.restype = Oid
PQftablecol = pq.PQftablecol
PQftablecol.argtypes = [PGresult_ptr, c_int]
PQftablecol.restype = c_int
PQfformat = pq.PQfformat
PQfformat.argtypes = [PGresult_ptr, c_int]
PQfformat.restype = c_int
PQftype = pq.PQftype
PQftype.argtypes = [PGresult_ptr, c_int]
PQftype.restype = Oid
PQfmod = pq.PQfmod
PQfmod.argtypes = [PGresult_ptr, c_int]
PQfmod.restype = c_int
PQfsize = pq.PQfsize
PQfsize.argtypes = [PGresult_ptr, c_int]
PQfsize.restype = c_int
PQbinaryTuples = pq.PQbinaryTuples
PQbinaryTuples.argtypes = [PGresult_ptr]
PQbinaryTuples.restype = c_int
PQgetvalue = pq.PQgetvalue
PQgetvalue.argtypes = [PGresult_ptr, c_int, c_int]
PQgetvalue.restype = POINTER(c_char) # not a null-terminated string
PQgetisnull = pq.PQgetisnull
PQgetisnull.argtypes = [PGresult_ptr, c_int, c_int]
PQgetisnull.restype = c_int
PQgetlength = pq.PQgetlength
PQgetlength.argtypes = [PGresult_ptr, c_int, c_int]
PQgetlength.restype = c_int
PQnparams = pq.PQnparams
PQnparams.argtypes = [PGresult_ptr]
PQnparams.restype = c_int
PQparamtype = pq.PQparamtype
PQparamtype.argtypes = [PGresult_ptr, c_int]
PQparamtype.restype = Oid
# PQprint: pretty useless
# 33.3.3. Retrieving Other Result Information
PQcmdStatus = pq.PQcmdStatus
PQcmdStatus.argtypes = [PGresult_ptr]
PQcmdStatus.restype = c_char_p
PQcmdTuples = pq.PQcmdTuples
PQcmdTuples.argtypes = [PGresult_ptr]
PQcmdTuples.restype = c_char_p
PQoidValue = pq.PQoidValue
PQoidValue.argtypes = [PGresult_ptr]
PQoidValue.restype = Oid
# 33.3.4. Escaping Strings for Inclusion in SQL Commands
PQescapeLiteral = pq.PQescapeLiteral
PQescapeLiteral.argtypes = [PGconn_ptr, c_char_p, c_size_t]
PQescapeLiteral.restype = POINTER(c_char)
PQescapeIdentifier = pq.PQescapeIdentifier
PQescapeIdentifier.argtypes = [PGconn_ptr, c_char_p, c_size_t]
PQescapeIdentifier.restype = POINTER(c_char)
PQescapeStringConn = pq.PQescapeStringConn
# TODO: raises "wrong type" error
# PQescapeStringConn.argtypes = [
# PGconn_ptr, c_char_p, c_char_p, c_size_t, POINTER(c_int)
# ]
PQescapeStringConn.restype = c_size_t
PQescapeString = pq.PQescapeString
# TODO: raises "wrong type" error
# PQescapeString.argtypes = [c_char_p, c_char_p, c_size_t]
PQescapeString.restype = c_size_t
PQescapeByteaConn = pq.PQescapeByteaConn
PQescapeByteaConn.argtypes = [
PGconn_ptr,
POINTER(c_char), # actually POINTER(c_ubyte) but this is easier
c_size_t,
POINTER(c_size_t),
]
PQescapeByteaConn.restype = POINTER(c_ubyte)
PQescapeBytea = pq.PQescapeBytea
PQescapeBytea.argtypes = [
POINTER(c_char), # actually POINTER(c_ubyte) but this is easier
c_size_t,
POINTER(c_size_t),
]
PQescapeBytea.restype = POINTER(c_ubyte)
PQunescapeBytea = pq.PQunescapeBytea
PQunescapeBytea.argtypes = [
POINTER(c_char), # actually POINTER(c_ubyte) but this is easier
POINTER(c_size_t),
]
PQunescapeBytea.restype = POINTER(c_ubyte)
# 33.4. Asynchronous Command Processing
PQsendQuery = pq.PQsendQuery
PQsendQuery.argtypes = [PGconn_ptr, c_char_p]
PQsendQuery.restype = c_int
PQsendQueryParams = pq.PQsendQueryParams
PQsendQueryParams.argtypes = [
PGconn_ptr,
c_char_p,
c_int,
POINTER(Oid),
POINTER(c_char_p),
POINTER(c_int),
POINTER(c_int),
c_int,
]
PQsendQueryParams.restype = c_int
PQsendPrepare = pq.PQsendPrepare
PQsendPrepare.argtypes = [PGconn_ptr, c_char_p, c_char_p, c_int, POINTER(Oid)]
PQsendPrepare.restype = c_int
PQsendQueryPrepared = pq.PQsendQueryPrepared
PQsendQueryPrepared.argtypes = [
PGconn_ptr,
c_char_p,
c_int,
POINTER(c_char_p),
POINTER(c_int),
POINTER(c_int),
c_int,
]
PQsendQueryPrepared.restype = c_int
PQsendDescribePrepared = pq.PQsendDescribePrepared
PQsendDescribePrepared.argtypes = [PGconn_ptr, c_char_p]
PQsendDescribePrepared.restype = c_int
PQsendDescribePortal = pq.PQsendDescribePortal
PQsendDescribePortal.argtypes = [PGconn_ptr, c_char_p]
PQsendDescribePortal.restype = c_int
PQgetResult = pq.PQgetResult
PQgetResult.argtypes = [PGconn_ptr]
PQgetResult.restype = PGresult_ptr
PQconsumeInput = pq.PQconsumeInput
PQconsumeInput.argtypes = [PGconn_ptr]
PQconsumeInput.restype = c_int
PQisBusy = pq.PQisBusy
PQisBusy.argtypes = [PGconn_ptr]
PQisBusy.restype = c_int
PQsetnonblocking = pq.PQsetnonblocking
PQsetnonblocking.argtypes = [PGconn_ptr, c_int]
PQsetnonblocking.restype = c_int
PQisnonblocking = pq.PQisnonblocking
PQisnonblocking.argtypes = [PGconn_ptr]
PQisnonblocking.restype = c_int
PQflush = pq.PQflush
PQflush.argtypes = [PGconn_ptr]
PQflush.restype = c_int
# 33.5. Retrieving Query Results Row-by-Row
PQsetSingleRowMode = pq.PQsetSingleRowMode
PQsetSingleRowMode.argtypes = [PGconn_ptr]
PQsetSingleRowMode.restype = c_int
# 33.6. Canceling Queries in Progress
PQgetCancel = pq.PQgetCancel
PQgetCancel.argtypes = [PGconn_ptr]
PQgetCancel.restype = PGcancel_ptr
PQfreeCancel = pq.PQfreeCancel
PQfreeCancel.argtypes = [PGcancel_ptr]
PQfreeCancel.restype = None
PQcancel = pq.PQcancel
# TODO: raises "wrong type" error
# PQcancel.argtypes = [PGcancel_ptr, POINTER(c_char), c_int]
PQcancel.restype = c_int
# 33.8. Asynchronous Notification
PQnotifies = pq.PQnotifies
PQnotifies.argtypes = [PGconn_ptr]
PQnotifies.restype = PGnotify_ptr
# 33.9. Functions Associated with the COPY Command
PQputCopyData = pq.PQputCopyData
PQputCopyData.argtypes = [PGconn_ptr, c_char_p, c_int]
PQputCopyData.restype = c_int
PQputCopyEnd = pq.PQputCopyEnd
PQputCopyEnd.argtypes = [PGconn_ptr, c_char_p]
PQputCopyEnd.restype = c_int
PQgetCopyData = pq.PQgetCopyData
PQgetCopyData.argtypes = [PGconn_ptr, POINTER(c_char_p), c_int]
PQgetCopyData.restype = c_int
# 33.10. Control Functions
PQtrace = pq.PQtrace
PQtrace.argtypes = [PGconn_ptr, FILE_ptr]
PQtrace.restype = None
_PQsetTraceFlags = None
if libpq_version >= 140000:
_PQsetTraceFlags = pq.PQsetTraceFlags
_PQsetTraceFlags.argtypes = [PGconn_ptr, c_int]
_PQsetTraceFlags.restype = None
def PQsetTraceFlags(pgconn: PGconn_struct, flags: int) -> None:
if not _PQsetTraceFlags:
raise NotSupportedError(
"PQsetTraceFlags requires libpq from PostgreSQL 14,"
f" {libpq_version} available instead"
)
_PQsetTraceFlags(pgconn, flags)
PQuntrace = pq.PQuntrace
PQuntrace.argtypes = [PGconn_ptr]
PQuntrace.restype = None
# 33.11. Miscellaneous Functions
PQfreemem = pq.PQfreemem
PQfreemem.argtypes = [c_void_p]
PQfreemem.restype = None
if libpq_version >= 100000:
_PQencryptPasswordConn = pq.PQencryptPasswordConn
_PQencryptPasswordConn.argtypes = [
PGconn_ptr,
c_char_p,
c_char_p,
c_char_p,
]
_PQencryptPasswordConn.restype = POINTER(c_char)
def PQencryptPasswordConn(
pgconn: PGconn_struct, passwd: bytes, user: bytes, algorithm: bytes
) -> Optional[bytes]:
if not _PQencryptPasswordConn:
raise NotSupportedError(
"PQencryptPasswordConn requires libpq from PostgreSQL 10,"
f" {libpq_version} available instead"
)
return _PQencryptPasswordConn(pgconn, passwd, user, algorithm)
PQmakeEmptyPGresult = pq.PQmakeEmptyPGresult
PQmakeEmptyPGresult.argtypes = [PGconn_ptr, c_int]
PQmakeEmptyPGresult.restype = PGresult_ptr
PQsetResultAttrs = pq.PQsetResultAttrs
PQsetResultAttrs.argtypes = [PGresult_ptr, c_int, PGresAttDesc_ptr]
PQsetResultAttrs.restype = c_int
# 33.12. Notice Processing
PQnoticeReceiver = CFUNCTYPE(None, c_void_p, PGresult_ptr)
PQsetNoticeReceiver = pq.PQsetNoticeReceiver
PQsetNoticeReceiver.argtypes = [PGconn_ptr, PQnoticeReceiver, c_void_p]
PQsetNoticeReceiver.restype = PQnoticeReceiver
# 34.5 Pipeline Mode
_PQpipelineStatus = None
_PQenterPipelineMode = None
_PQexitPipelineMode = None
_PQpipelineSync = None
_PQsendFlushRequest = None
if libpq_version >= 140000:
_PQpipelineStatus = pq.PQpipelineStatus
_PQpipelineStatus.argtypes = [PGconn_ptr]
_PQpipelineStatus.restype = c_int
_PQenterPipelineMode = pq.PQenterPipelineMode
_PQenterPipelineMode.argtypes = [PGconn_ptr]
_PQenterPipelineMode.restype = c_int
_PQexitPipelineMode = pq.PQexitPipelineMode
_PQexitPipelineMode.argtypes = [PGconn_ptr]
_PQexitPipelineMode.restype = c_int
_PQpipelineSync = pq.PQpipelineSync
_PQpipelineSync.argtypes = [PGconn_ptr]
_PQpipelineSync.restype = c_int
_PQsendFlushRequest = pq.PQsendFlushRequest
_PQsendFlushRequest.argtypes = [PGconn_ptr]
_PQsendFlushRequest.restype = c_int
def PQpipelineStatus(pgconn: PGconn_struct) -> int:
if not _PQpipelineStatus:
raise NotSupportedError(
"PQpipelineStatus requires libpq from PostgreSQL 14,"
f" {libpq_version} available instead"
)
return _PQpipelineStatus(pgconn)
def PQenterPipelineMode(pgconn: PGconn_struct) -> int:
if not _PQenterPipelineMode:
raise NotSupportedError(
"PQenterPipelineMode requires libpq from PostgreSQL 14,"
f" {libpq_version} available instead"
)
return _PQenterPipelineMode(pgconn)
def PQexitPipelineMode(pgconn: PGconn_struct) -> int:
if not _PQexitPipelineMode:
raise NotSupportedError(
"PQexitPipelineMode requires libpq from PostgreSQL 14,"
f" {libpq_version} available instead"
)
return _PQexitPipelineMode(pgconn)
def PQpipelineSync(pgconn: PGconn_struct) -> int:
if not _PQpipelineSync:
raise NotSupportedError(
"PQpipelineSync requires libpq from PostgreSQL 14,"
f" {libpq_version} available instead"
)
return _PQpipelineSync(pgconn)
def PQsendFlushRequest(pgconn: PGconn_struct) -> int:
if not _PQsendFlushRequest:
raise NotSupportedError(
"PQsendFlushRequest requires libpq from PostgreSQL 14,"
f" {libpq_version} available instead"
)
return _PQsendFlushRequest(pgconn)
# 33.18. SSL Support
PQinitOpenSSL = pq.PQinitOpenSSL
PQinitOpenSSL.argtypes = [c_int, c_int]
PQinitOpenSSL.restype = None
def generate_stub() -> None:
import re
from ctypes import _CFuncPtr # type: ignore
def type2str(fname, narg, t):
if t is None:
return "None"
elif t is c_void_p:
return "Any"
elif t is c_int or t is c_uint or t is c_size_t:
return "int"
elif t is c_char_p or t.__name__ == "LP_c_char":
if narg is not None:
return "bytes"
else:
return "Optional[bytes]"
elif t.__name__ in (
"LP_PGconn_struct",
"LP_PGresult_struct",
"LP_PGcancel_struct",
):
if narg is not None:
return f"Optional[{t.__name__[3:]}]"
else:
return t.__name__[3:]
elif t.__name__ in ("LP_PQconninfoOption_struct",):
return f"Sequence[{t.__name__[3:]}]"
elif t.__name__ in (
"LP_c_ubyte",
"LP_c_char_p",
"LP_c_int",
"LP_c_uint",
"LP_c_ulong",
"LP_FILE",
):
return f"_Pointer[{t.__name__[3:]}]"
else:
assert False, f"can't deal with {t} in {fname}"
fn = __file__ + "i"
with open(fn) as f:
lines = f.read().splitlines()
istart, iend = (
i
for i, line in enumerate(lines)
if re.match(r"\s*#\s*autogenerated:\s+(start|end)", line)
)
known = {
line[4:].split("(", 1)[0] for line in lines[:istart] if line.startswith("def ")
}
signatures = []
for name, obj in globals().items():
if name in known:
continue
if not isinstance(obj, _CFuncPtr):
continue
params = []
for i, t in enumerate(obj.argtypes):
params.append(f"arg{i + 1}: {type2str(name, i, t)}")
resname = type2str(name, None, obj.restype)
signatures.append(f"def {name}({', '.join(params)}) -> {resname}: ...")
lines[istart + 1 : iend] = signatures
with open(fn, "w") as f:
f.write("\n".join(lines))
f.write("\n")
if __name__ == "__main__":
generate_stub()

View File

@ -0,0 +1,384 @@
"""
Protocol objects to represent objects exposed by different pq implementations.
"""
# Copyright (C) 2020 The Psycopg Team
from typing import Any, Callable, List, Optional, Sequence, Tuple
from typing import Union, TYPE_CHECKING
from typing_extensions import TypeAlias
from ._enums import Format, Trace
from .._compat import Protocol
if TYPE_CHECKING:
from .misc import PGnotify, ConninfoOption, PGresAttDesc
# An object implementing the buffer protocol (ish)
Buffer: TypeAlias = Union[bytes, bytearray, memoryview]
class PGconn(Protocol):
notice_handler: Optional[Callable[["PGresult"], None]]
notify_handler: Optional[Callable[["PGnotify"], None]]
@classmethod
def connect(cls, conninfo: bytes) -> "PGconn":
...
@classmethod
def connect_start(cls, conninfo: bytes) -> "PGconn":
...
def connect_poll(self) -> int:
...
def finish(self) -> None:
...
@property
def info(self) -> List["ConninfoOption"]:
...
def reset(self) -> None:
...
def reset_start(self) -> None:
...
def reset_poll(self) -> int:
...
@classmethod
def ping(self, conninfo: bytes) -> int:
...
@property
def db(self) -> bytes:
...
@property
def user(self) -> bytes:
...
@property
def password(self) -> bytes:
...
@property
def host(self) -> bytes:
...
@property
def hostaddr(self) -> bytes:
...
@property
def port(self) -> bytes:
...
@property
def tty(self) -> bytes:
...
@property
def options(self) -> bytes:
...
@property
def status(self) -> int:
...
@property
def transaction_status(self) -> int:
...
def parameter_status(self, name: bytes) -> Optional[bytes]:
...
@property
def error_message(self) -> bytes:
...
@property
def server_version(self) -> int:
...
@property
def socket(self) -> int:
...
@property
def backend_pid(self) -> int:
...
@property
def needs_password(self) -> bool:
...
@property
def used_password(self) -> bool:
...
@property
def ssl_in_use(self) -> bool:
...
def exec_(self, command: bytes) -> "PGresult":
...
def send_query(self, command: bytes) -> None:
...
def exec_params(
self,
command: bytes,
param_values: Optional[Sequence[Optional[Buffer]]],
param_types: Optional[Sequence[int]] = None,
param_formats: Optional[Sequence[int]] = None,
result_format: int = Format.TEXT,
) -> "PGresult":
...
def send_query_params(
self,
command: bytes,
param_values: Optional[Sequence[Optional[Buffer]]],
param_types: Optional[Sequence[int]] = None,
param_formats: Optional[Sequence[int]] = None,
result_format: int = Format.TEXT,
) -> None:
...
def send_prepare(
self,
name: bytes,
command: bytes,
param_types: Optional[Sequence[int]] = None,
) -> None:
...
def send_query_prepared(
self,
name: bytes,
param_values: Optional[Sequence[Optional[Buffer]]],
param_formats: Optional[Sequence[int]] = None,
result_format: int = Format.TEXT,
) -> None:
...
def prepare(
self,
name: bytes,
command: bytes,
param_types: Optional[Sequence[int]] = None,
) -> "PGresult":
...
def exec_prepared(
self,
name: bytes,
param_values: Optional[Sequence[Buffer]],
param_formats: Optional[Sequence[int]] = None,
result_format: int = 0,
) -> "PGresult":
...
def describe_prepared(self, name: bytes) -> "PGresult":
...
def send_describe_prepared(self, name: bytes) -> None:
...
def describe_portal(self, name: bytes) -> "PGresult":
...
def send_describe_portal(self, name: bytes) -> None:
...
def get_result(self) -> Optional["PGresult"]:
...
def consume_input(self) -> None:
...
def is_busy(self) -> int:
...
@property
def nonblocking(self) -> int:
...
@nonblocking.setter
def nonblocking(self, arg: int) -> None:
...
def flush(self) -> int:
...
def set_single_row_mode(self) -> None:
...
def get_cancel(self) -> "PGcancel":
...
def notifies(self) -> Optional["PGnotify"]:
...
def put_copy_data(self, buffer: Buffer) -> int:
...
def put_copy_end(self, error: Optional[bytes] = None) -> int:
...
def get_copy_data(self, async_: int) -> Tuple[int, memoryview]:
...
def trace(self, fileno: int) -> None:
...
def set_trace_flags(self, flags: Trace) -> None:
...
def untrace(self) -> None:
...
def encrypt_password(
self, passwd: bytes, user: bytes, algorithm: Optional[bytes] = None
) -> bytes:
...
def make_empty_result(self, exec_status: int) -> "PGresult":
...
@property
def pipeline_status(self) -> int:
...
def enter_pipeline_mode(self) -> None:
...
def exit_pipeline_mode(self) -> None:
...
def pipeline_sync(self) -> None:
...
def send_flush_request(self) -> None:
...
class PGresult(Protocol):
def clear(self) -> None:
...
@property
def status(self) -> int:
...
@property
def error_message(self) -> bytes:
...
def error_field(self, fieldcode: int) -> Optional[bytes]:
...
@property
def ntuples(self) -> int:
...
@property
def nfields(self) -> int:
...
def fname(self, column_number: int) -> Optional[bytes]:
...
def ftable(self, column_number: int) -> int:
...
def ftablecol(self, column_number: int) -> int:
...
def fformat(self, column_number: int) -> int:
...
def ftype(self, column_number: int) -> int:
...
def fmod(self, column_number: int) -> int:
...
def fsize(self, column_number: int) -> int:
...
@property
def binary_tuples(self) -> int:
...
def get_value(self, row_number: int, column_number: int) -> Optional[bytes]:
...
@property
def nparams(self) -> int:
...
def param_type(self, param_number: int) -> int:
...
@property
def command_status(self) -> Optional[bytes]:
...
@property
def command_tuples(self) -> Optional[int]:
...
@property
def oid_value(self) -> int:
...
def set_attributes(self, descriptions: List["PGresAttDesc"]) -> None:
...
class PGcancel(Protocol):
def free(self) -> None:
...
def cancel(self) -> None:
...
class Conninfo(Protocol):
@classmethod
def get_defaults(cls) -> List["ConninfoOption"]:
...
@classmethod
def parse(cls, conninfo: bytes) -> List["ConninfoOption"]:
...
@classmethod
def _options_from_array(cls, opts: Sequence[Any]) -> List["ConninfoOption"]:
...
class Escaping(Protocol):
def __init__(self, conn: Optional[PGconn] = None):
...
def escape_literal(self, data: Buffer) -> bytes:
...
def escape_identifier(self, data: Buffer) -> bytes:
...
def escape_string(self, data: Buffer) -> bytes:
...
def escape_bytea(self, data: Buffer) -> bytes:
...
def unescape_bytea(self, data: Buffer) -> bytes:
...

View File

@ -0,0 +1,146 @@
"""
Various functionalities to make easier to work with the libpq.
"""
# Copyright (C) 2020 The Psycopg Team
import os
import sys
import logging
import ctypes.util
from typing import cast, NamedTuple, Optional, Union
from .abc import PGconn, PGresult
from ._enums import ConnStatus, TransactionStatus, PipelineStatus
from .._compat import cache
from .._encodings import pgconn_encoding
logger = logging.getLogger("psycopg.pq")
OK = ConnStatus.OK
class PGnotify(NamedTuple):
relname: bytes
be_pid: int
extra: bytes
class ConninfoOption(NamedTuple):
keyword: bytes
envvar: Optional[bytes]
compiled: Optional[bytes]
val: Optional[bytes]
label: bytes
dispchar: bytes
dispsize: int
class PGresAttDesc(NamedTuple):
name: bytes
tableid: int
columnid: int
format: int
typid: int
typlen: int
atttypmod: int
@cache
def find_libpq_full_path() -> Optional[str]:
if sys.platform == "win32":
libname = ctypes.util.find_library("libpq.dll")
elif sys.platform == "darwin":
libname = ctypes.util.find_library("libpq.dylib")
# (hopefully) temporary hack: libpq not in a standard place
# https://github.com/orgs/Homebrew/discussions/3595
# If pg_config is available and agrees, let's use its indications.
if not libname:
try:
import subprocess as sp
libdir = sp.check_output(["pg_config", "--libdir"]).strip().decode()
libname = os.path.join(libdir, "libpq.dylib")
if not os.path.exists(libname):
libname = None
except Exception as ex:
logger.debug("couldn't use pg_config to find libpq: %s", ex)
else:
libname = ctypes.util.find_library("pq")
return libname
def error_message(obj: Union[PGconn, PGresult], encoding: str = "utf8") -> str:
"""
Return an error message from a `PGconn` or `PGresult`.
The return value is a `!str` (unlike pq data which is usually `!bytes`):
use the connection encoding if available, otherwise the `!encoding`
parameter as a fallback for decoding. Don't raise exceptions on decoding
errors.
"""
bmsg: bytes
if hasattr(obj, "error_field"):
# obj is a PGresult
obj = cast(PGresult, obj)
bmsg = obj.error_message
# strip severity and whitespaces
if bmsg:
bmsg = bmsg.split(b":", 1)[-1].strip()
elif hasattr(obj, "error_message"):
# obj is a PGconn
if obj.status == OK:
encoding = pgconn_encoding(obj)
bmsg = obj.error_message
# strip severity and whitespaces
if bmsg:
bmsg = bmsg.split(b":", 1)[-1].strip()
else:
raise TypeError(f"PGconn or PGresult expected, got {type(obj).__name__}")
if bmsg:
msg = bmsg.decode(encoding, "replace")
else:
msg = "no details available"
return msg
def connection_summary(pgconn: PGconn) -> str:
"""
Return summary information on a connection.
Useful for __repr__
"""
parts = []
if pgconn.status == OK:
# Put together the [STATUS]
status = TransactionStatus(pgconn.transaction_status).name
if pgconn.pipeline_status:
status += f", pipeline={PipelineStatus(pgconn.pipeline_status).name}"
# Put together the (CONNECTION)
if not pgconn.host.startswith(b"/"):
parts.append(("host", pgconn.host.decode()))
if pgconn.port != b"5432":
parts.append(("port", pgconn.port.decode()))
if pgconn.user != pgconn.db:
parts.append(("user", pgconn.user.decode()))
parts.append(("database", pgconn.db.decode()))
else:
status = ConnStatus(pgconn.status).name
sparts = " ".join("%s=%s" % part for part in parts)
if sparts:
sparts = f" ({sparts})"
return f"[{status}]{sparts}"

File diff suppressed because it is too large Load Diff