Initial commit (Clean history)
This commit is contained in:
@@ -0,0 +1,150 @@
|
||||
"""
|
||||
psycopg capabilities objects
|
||||
"""
|
||||
|
||||
# Copyright (C) 2024 The Psycopg Team
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from . import _cmodule, pq
|
||||
from .errors import NotSupportedError
|
||||
|
||||
|
||||
class Capabilities:
|
||||
"""
|
||||
An object to check if a feature is supported by the libpq available on the client.
|
||||
"""
|
||||
|
||||
def __init__(self) -> None:
|
||||
self._cache: dict[str, str] = {}
|
||||
|
||||
def has_full_protocol_version(self, check: bool = False) -> bool:
|
||||
"""Check if the `PGconn.full_protocol_version()` method is implemented.
|
||||
|
||||
If the method is implemented, then `ConnectionInfo.full_protocol_version`
|
||||
will return a meaningful value.
|
||||
|
||||
The feature requires libpq 18.0 and greater.
|
||||
"""
|
||||
return self._has_feature(
|
||||
"pq.PGconn.full_protocol_version()", 180000, check=check
|
||||
)
|
||||
|
||||
def has_encrypt_password(self, check: bool = False) -> bool:
|
||||
"""Check if the `PGconn.encrypt_password()` method is implemented.
|
||||
|
||||
The feature requires libpq 10.0 or greater.
|
||||
"""
|
||||
return self._has_feature("pq.PGconn.encrypt_password()", 100000, check=check)
|
||||
|
||||
def has_hostaddr(self, check: bool = False) -> bool:
|
||||
"""Check if the `ConnectionInfo.hostaddr` attribute is implemented.
|
||||
|
||||
The feature requires libpq 12.0 or greater.
|
||||
"""
|
||||
return self._has_feature("Connection.info.hostaddr", 120000, check=check)
|
||||
|
||||
def has_pipeline(self, check: bool = False) -> bool:
|
||||
"""Check if the :ref:`pipeline mode <pipeline-mode>` is supported.
|
||||
|
||||
The feature requires libpq 14.0 or greater.
|
||||
"""
|
||||
return self._has_feature("Connection.pipeline()", 140000, check=check)
|
||||
|
||||
def has_set_trace_flags(self, check: bool = False) -> bool:
|
||||
"""Check if the `pq.PGconn.set_trace_flags()` method is implemented.
|
||||
|
||||
The feature requires libpq 14.0 or greater.
|
||||
"""
|
||||
return self._has_feature("PGconn.set_trace_flags()", 140000, check=check)
|
||||
|
||||
def has_used_gssapi(self, check: bool = False) -> bool:
|
||||
"""Check if the `pq.PGconn.used_gssapi` attribute is implemented.
|
||||
|
||||
The feature requires libpq 16.0 or greater.
|
||||
"""
|
||||
return self._has_feature("PGconn.used_gssapi", 160000, check=check)
|
||||
|
||||
def has_cancel_safe(self, check: bool = False) -> bool:
|
||||
"""Check if the `Connection.cancel_safe()` method is implemented.
|
||||
|
||||
The feature requires libpq 17.0 or greater.
|
||||
"""
|
||||
return self._has_feature("Connection.cancel_safe()", 170000, check=check)
|
||||
|
||||
def has_stream_chunked(self, check: bool = False) -> bool:
|
||||
"""Check if `Cursor.stream()` can handle a `size` parameter value
|
||||
greater than 1 to retrieve results by chunks.
|
||||
|
||||
The feature requires libpq 17.0 or greater.
|
||||
"""
|
||||
return self._has_feature(
|
||||
"Cursor.stream() with 'size' parameter greater than 1", 170000, check=check
|
||||
)
|
||||
|
||||
def has_send_close_prepared(self, check: bool = False) -> bool:
|
||||
"""Check if the `pq.PGconn.send_closed_prepared()` method is implemented.
|
||||
|
||||
The feature requires libpq 17.0 or greater.
|
||||
"""
|
||||
return self._has_feature("PGconn.send_close_prepared()", 170000, check=check)
|
||||
|
||||
def _has_feature(self, feature: str, want_version: int, check: bool) -> bool:
|
||||
"""
|
||||
Check is a version is supported.
|
||||
|
||||
If `check` is true, raise an exception with an explicative message
|
||||
explaining why the feature is not supported.
|
||||
|
||||
The expletive messages, are left to the user.
|
||||
"""
|
||||
if feature in self._cache:
|
||||
msg = self._cache[feature]
|
||||
else:
|
||||
msg = self._get_unsupported_message(feature, want_version)
|
||||
self._cache[feature] = msg
|
||||
|
||||
if not msg:
|
||||
return True
|
||||
elif check:
|
||||
raise NotSupportedError(msg)
|
||||
else:
|
||||
return False
|
||||
|
||||
def _get_unsupported_message(self, feature: str, want_version: int) -> str:
|
||||
"""
|
||||
Return a descriptinve message to describe why a feature is unsupported.
|
||||
|
||||
Return an empty string if the feature is supported.
|
||||
"""
|
||||
if pq.version() < want_version:
|
||||
return (
|
||||
f"the feature '{feature}' is not available:"
|
||||
f" the client libpq version (imported from {self._libpq_source()})"
|
||||
f" is {pq.version_pretty(pq.version())}; the feature"
|
||||
f" requires libpq version {pq.version_pretty(want_version)}"
|
||||
" or newer"
|
||||
)
|
||||
|
||||
elif pq.__build_version__ < want_version:
|
||||
return (
|
||||
f"the feature '{feature}' is not available:"
|
||||
f" you are using a psycopg[{pq.__impl__}] libpq wrapper built"
|
||||
f" with libpq version {pq.version_pretty(pq.__build_version__)};"
|
||||
" the feature requires libpq version"
|
||||
f" {pq.version_pretty(want_version)} or newer"
|
||||
)
|
||||
else:
|
||||
return ""
|
||||
|
||||
def _libpq_source(self) -> str:
|
||||
"""Return a string reporting where the libpq comes from."""
|
||||
if pq.__impl__ == "binary":
|
||||
version: str = _cmodule.__version__ or "unknown"
|
||||
return f"the psycopg[binary] package version {version}"
|
||||
else:
|
||||
return "system libraries"
|
||||
|
||||
|
||||
# The object that will be exposed by the module.
|
||||
capabilities = Capabilities()
|
||||
Reference in New Issue
Block a user