]> git.ipfire.org Git - thirdparty/psycopg.git/commitdiff
feat: add `ConnectionInfo.full_protocol_version`, `ConnectionInfo.service` pg18 1098/head
authorDaniele Varrazzo <daniele.varrazzo@gmail.com>
Sat, 5 Jul 2025 12:54:39 +0000 (14:54 +0200)
committerDaniele Varrazzo <daniele.varrazzo@gmail.com>
Sat, 5 Jul 2025 14:10:08 +0000 (16:10 +0200)
Add matching capabilities methods too to allow checking if they are
supported.

docs/api/objects.rst
docs/news.rst
psycopg/psycopg/_capabilities.py
psycopg/psycopg/_connection_info.py
tests/test_capabilities.py
tests/test_connection_info.py

index 5f6e2902e73539536ce9a080b2b3f00baaa831e8..c832f71cd54f34338a16590d590f42bd6a596d54 100644 (file)
@@ -52,6 +52,10 @@ Connection information
         second group of digits is always 00. For example, version 9.3.5 is
         returned as 90305, version 10.2 as 100002.
 
+    .. autoattribute:: full_protocol_version
+
+        .. versionadded:: 3.3
+
     .. autoattribute:: error_message
 
     .. automethod:: get_parameters
@@ -80,6 +84,15 @@ Connection information
 
     .. autoattribute:: port
     .. autoattribute:: dbname
+
+    .. autoattribute:: service
+
+        Only available if the libpq used is from PostgreSQL 18 or newer.
+        Raise `~psycopg.NotSupportedError` otherwise. You can use the
+        `~Capabilities.has_service` capability to check for support.
+
+        .. versionadded:: 3.3
+
     .. autoattribute:: user
     .. autoattribute:: password
     .. autoattribute:: options
@@ -146,7 +159,16 @@ Libpq capabilities information
 
     .. versionadded:: 3.2
 
+    .. automethod:: has_full_protocol_version
+
+        .. versionadded:: 3.3
+
     .. automethod:: has_encrypt_password
+
+    .. automethod:: has_service
+
+        .. versionadded:: 3.3
+
     .. automethod:: has_hostaddr
     .. automethod:: has_pipeline
     .. automethod:: has_set_trace_flags
index 3599cf2988d112bcaaa8f7dc2431467ad627f29b..3024cb23d16b890b93526b987ab539dceabeb074 100644 (file)
@@ -15,6 +15,10 @@ Psycopg 3.3.0 (unreleased)
 
 - Cursors are now iterators, not only iterables. This means you can call
   ``next(cur)`` to fetch the next row (:ticket:`#1064`).
+- Add `ConnectionInfo.full_protocol_version` and `~ConnectionInfo.service`
+  attributes (related to :ticket:`#1079`).
+- Add `~Capabilities.has_service` and `~Capabilities.has_full_protocol_version`
+  capabilities (related to :ticket:`#1079`).
 - Add support for functions :pq:`PQfullProtocolVersion`, :pq:`PQservice`,
   available in libpq v18 (:ticket:`#1079`).
 - Drop support for Python 3.8 (:ticket:`#976`) and 3.9 (:ticket:`#1056`).
index 323141efc62519b36b699ada8aaddbb6a97c48d5..4596f0cf41e8b7acdb1518db539ae18a0be9568a 100644 (file)
@@ -18,6 +18,18 @@ class Capabilities:
     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.
 
@@ -25,6 +37,13 @@ class Capabilities:
         """
         return self._has_feature("pq.PGconn.encrypt_password()", 100000, check=check)
 
+    def has_service(self, check: bool = False) -> bool:
+        """Check if the `ConnectionInfo.service` attribute is implemented.
+
+        The feature requires libpq 18.0 and greater.
+        """
+        return self._has_feature("Connection.info.service", 180000, check=check)
+
     def has_hostaddr(self, check: bool = False) -> bool:
         """Check if the `ConnectionInfo.hostaddr` attribute is implemented.
 
index 84554c6f97029aecdd9a29d5e17a8abd8ebce7dd..572062fafa0213814ab6e925725144e90fb50b9c 100644 (file)
@@ -57,6 +57,11 @@ class ConnectionInfo:
         """The database name of the connection. See :pq:`PQdb()`."""
         return self._get_pgconn_attr("db")
 
+    @property
+    def service(self) -> str:
+        """The service name of the connection. See :pq:`PQservice()`."""
+        return self._get_pgconn_attr("service")
+
     @property
     def user(self) -> str:
         """The user name of the connection. See :pq:`PQuser()`."""
@@ -152,6 +157,24 @@ class ConnectionInfo:
         """
         return self.pgconn.server_version
 
+    @property
+    def full_protocol_version(self) -> int:
+        """
+        An integer representing the server full protocol version.
+
+        Return a value in the format described in :pq:`PQfullProtocolVersion()`.
+
+        Only meaningful if the libpq used is version 18 or greater. If the
+        version is lesser than that, return the value reported by
+        :pq:`PQprotocolVersion()` (but in the same format as above, e.g. 30000
+        for version 3). You can use `Capabilities.has_full_protocol_version()`
+        to verify if the value can be considered reliable.
+        """
+        try:
+            return self.pgconn.full_protocol_version
+        except e.NotSupportedError:
+            return self.pgconn.protocol_version * 10000
+
     @property
     def backend_pid(self) -> int:
         """
index 674a083c208e1c3a079adc02ac24209cd527fc63..392a0c4a1c55c8cd1aa51d658867d60c242c9121 100644 (file)
@@ -11,7 +11,9 @@ except ImportError:
     pass
 
 caps = [
+    ("has_full_protocol_version", "pq.PGconn.full_protocol_version()", 18),
     ("has_encrypt_password", "pq.PGconn.encrypt_password()", 10),
+    ("has_service", "Connection.info.service", 18),
     ("has_hostaddr", "Connection.info.hostaddr", 12),
     ("has_pipeline", "Connection.pipeline()", 14),
     ("has_set_trace_flags", "PGconn.set_trace_flags()", 14),
index 2ff96e06cea4ed36aae95685d6c15828ad3c170f..d339255a5c346a38293118ddbfebbc951dc62f6e 100644 (file)
@@ -11,7 +11,7 @@ from .fix_crdb import crdb_encoding
 
 @pytest.mark.parametrize(
     "attr",
-    [("dbname", "db"), "host", "hostaddr", "user", "password", "options"],
+    [("dbname", "db"), "service", "host", "hostaddr", "user", "password", "options"],
 )
 def test_attrs(conn, attr):
     if isinstance(attr, tuple):
@@ -22,6 +22,9 @@ def test_attrs(conn, attr):
     if info_attr == "hostaddr" and psycopg.pq.version() < 120000:
         pytest.skip("hostaddr not supported on libpq < 12")
 
+    if info_attr == "service" and psycopg.pq.version() < 180000:
+        pytest.skip("service not supported on libpq < 18")
+
     info_val = getattr(conn.info, info_attr)
     pgconn_val = getattr(conn.pgconn, pgconn_attr).decode()
     assert info_val == pgconn_val
@@ -31,6 +34,12 @@ def test_attrs(conn, attr):
         getattr(conn.info, info_attr)
 
 
+@pytest.mark.libpq("< 18")
+def test_service_not_supported(conn):
+    with pytest.raises(psycopg.NotSupportedError):
+        conn.info.service
+
+
 @pytest.mark.libpq("< 12")
 def test_hostaddr_not_supported(conn):
     with pytest.raises(psycopg.NotSupportedError):
@@ -44,6 +53,13 @@ def test_port(conn):
         conn.info.port
 
 
+def test_full_protocol_version(conn):
+    assert conn.info.full_protocol_version >= 30000
+    conn.close()
+    with pytest.raises(psycopg.OperationalError):
+        conn.info.full_protocol_version
+
+
 @pytest.mark.skipif(psycopg.pq.__impl__ != "python", reason="can't monkeypatch C")
 def test_blank_port(conn, monkeypatch):