From: Daniele Varrazzo Date: Tue, 9 Apr 2024 21:41:28 +0000 (+0200) Subject: docs: add capabilities documentation X-Git-Tag: 3.2.0~45^2~2 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=fd0fee8db02c843730cfa5827274d71efabc3904;p=thirdparty%2Fpsycopg.git docs: add capabilities documentation --- diff --git a/docs/advanced/pipeline.rst b/docs/advanced/pipeline.rst index a56049ccb..cf71e2509 100644 --- a/docs/advanced/pipeline.rst +++ b/docs/advanced/pipeline.rst @@ -302,5 +302,5 @@ The fine prints The pipeline mode is available on any currently supported PostgreSQL version, but, in order to make use of it, the client must use a libpq from PostgreSQL -14 or higher. You can use `Pipeline.is_supported()` to make sure your client -has the right library. +14 or higher. You can use the `~Capabilities.has_pipeline` capability to make +sure your client has the right library. diff --git a/docs/advanced/prepare.rst b/docs/advanced/prepare.rst index 25f98a51c..e376e8e05 100644 --- a/docs/advanced/prepare.rst +++ b/docs/advanced/prepare.rst @@ -65,9 +65,11 @@ Using prepared statements with PgBouncer Starting from 3.2, Psycopg supports prepared statements when using the PgBouncer__ middleware, using the following caveats: -- PgBouncer version must be at least version `1.22`__. +- PgBouncer version must be version `1.22`__ or newer. - PgBouncer `max_prepared_statements`__ must be greater than 0. -- The libpq version on the client must be from PostgreSQL 17 or higher. +- The libpq version on the client must be from PostgreSQL 17 or newer + (you can check the `~Capabilities.has_pgbouncer_prepared` capability to + verify it). .. __: https://www.pgbouncer.org/ .. __: https://www.pgbouncer.org/2024/01/pgbouncer-1-22-0 diff --git a/docs/api/connections.rst b/docs/api/connections.rst index 1aaeac082..a6d95d20b 100644 --- a/docs/api/connections.rst +++ b/docs/api/connections.rst @@ -287,6 +287,11 @@ The `!Connection` class .. automethod:: cancel_safe + .. note:: + + You can use the `~Capabilities.has_cancel_safe` capability to check + if `!cancel_safe()` will not fall back on the legacy implementation. + .. warning:: This method shouldn't be used as a `~signal.signal` handler. diff --git a/docs/api/module.rst b/docs/api/module.rst index 3c3d3c43b..a8c1625af 100644 --- a/docs/api/module.rst +++ b/docs/api/module.rst @@ -17,6 +17,25 @@ it also exposes the `module-level objects`__ required by the specifications. If you need an asynchronous connection use `AsyncConnection.connect` instead. +.. data:: capabilities + + An object that can be used to verify that the client library used by + psycopg implements a certain feature. For instance:: + + # Fail at import time if encrypted passwords is not available + import psycopg + psycopg.capabilities.has_encrypt_password(check=True) + + # Verify at runtime if a feature can be used + if psycopg.capabilities.has_hostaddr(): + print(conn.info.hostaddr) + else: + print("unknown connection hostadd") + + :type: `Capabilities` + + .. versionadded:: 3.2 + .. rubric:: Exceptions diff --git a/docs/api/objects.rst b/docs/api/objects.rst index f085ed9a9..3b285daf7 100644 --- a/docs/api/objects.rst +++ b/docs/api/objects.rst @@ -74,8 +74,9 @@ Connection information .. autoattribute:: hostaddr - Only available if the libpq used is at least from PostgreSQL 12. - Raise `~psycopg.NotSupportedError` otherwise. + Only available if the libpq used is from PostgreSQL 12 or newer. + Raise `~psycopg.NotSupportedError` otherwise. You can use the + `~Capabilities.has_hostaddr` capability to check for support. .. autoattribute:: port .. autoattribute:: dbname @@ -109,6 +110,58 @@ Connection information .. __: https://www.postgresql.org/docs/current/multibyte.html +.. _capabilities: + +Libpq capabilities information +------------------------------ + +.. autoclass:: Capabilities + + An instance of this object is normally exposed by the module as the object + `psycopg.capabilities`. + + Every feature check is implemented by an `!has_SOMETHING()` method. All + the methods return a boolean value stating if the capability is supported, + which can be used by a program to degrade gracefully:: + + if psycopg.capabilities.has_pipeline() + with conn.pipeline(): + operations(conn) + else: + logger.warning("slower") + operations(conn) + + If `check` is `!True`, and the capability is not supported, raise a + `NotSupportedError` instead of returning `!False`, explaining why the + feature is not supported. This allows to make a check at import time, + crashing early and with a clear description of the problem. + + >>> import psycopg + >>> psycopg.capabilities.has_pipeline(check=True) + Traceback (most recent call last): + ... + psycopg.NotSupportedError: the feature 'Connection.pipeline()' is not available: + the client libpq version (imported from system libraries) is 13.4; the + feature requires libpq version 14.0 or newer + + .. versionadded:: 3.2 + + .. automethod:: has_encrypt_password + .. automethod:: has_hostaddr + .. automethod:: has_pipeline + .. automethod:: has_set_trace_flags + .. automethod:: has_cancel_safe + + .. note:: + + The `!cancel_safe()` method is implemented anyway, but it will use + the legacy :pq:`PQcancel` implementation. + + .. automethod:: has_pgbouncer_prepared + + .. seealso:: :ref:`pgbouncer` + + The description `Column` object ------------------------------- diff --git a/docs/news.rst b/docs/news.rst index b4e3586ac..af3f2e32c 100644 --- a/docs/news.rst +++ b/docs/news.rst @@ -19,6 +19,8 @@ Psycopg 3.2 (unreleased) (:ticket:`340`). - Add :ref:`raw-query-cursors` to execute queries using placeholders in PostgreSQL format (`$1`, `$2`...) (:ticket:`#560`). +- Add `psycopg.capabilities` object to :ref:`inspect the libpq capabilities + ` (:ticket:`#772`). - Add `~rows.scalar_row` to return scalar values from a query (:ticket:`#723`). - Prepared statements are now :ref:`compatible with PgBouncer `. (:ticket:`#589`). diff --git a/psycopg/psycopg/_capabilities.py b/psycopg/psycopg/_capabilities.py index 6b67f49f1..f222f61e6 100644 --- a/psycopg/psycopg/_capabilities.py +++ b/psycopg/psycopg/_capabilities.py @@ -11,36 +11,49 @@ from .errors import NotSupportedError class Capabilities: """ - Check if a feature is supported. - - Every feature check is implemented by a check method `has_SOMETHING`. - All the methods return a boolean stating if the capability is supported. - If not supported and `check` is True, raise a `NotSupportedError` instead - explaining why the feature is not supported. + An object to check if a feature is supported by the libpq available on the client. """ def has_encrypt_password(self, check: bool = False) -> bool: - """Check if the `~PGconn.encrypt_password()` method is implemented.""" - return self._has_feature("PGconn.encrypt_password()", 100000, check=check) + """Check if the `PGconn.encrypt_password()` method is implemented. + + The feature requires libpq 10.0 and 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.""" - return self._has_feature("Connection.pipeline()", 120000, check=check) + """Check if the `ConnectionInfo.hostaddr` attribute is implemented. + + The feature requires libpq 12.0 and greater. + """ + return self._has_feature("Connection.info.hostaddr", 120000, check=check) def has_pipeline(self, check: bool = False) -> bool: - """Check if the `~Connection.pipeline()` method is implemented.""" + """Check if the :ref:`pipeline mode ` is supported. + + The feature requires libpq 14.0 and greater. + """ return self._has_feature("Connection.pipeline()", 140000, check=check) - def has_set_trace_flag(self, check: bool = False) -> bool: - """Check if the `~PGconn.set_trace_flag()` method is implemented.""" - return self._has_feature("PGconn.set_trace_flag()", 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 and greater. + """ + return self._has_feature("PGconn.set_trace_flags()", 140000, check=check) def has_cancel_safe(self, check: bool = False) -> bool: - """Check if the `Connection.cancel_safe()` method is implemented.""" + """Check if the `Connection.cancel_safe()` method is implemented. + + The feature requires libpq 17.0 and greater. + """ return self._has_feature("Connection.cancel_safe()", 170000, check=check) def has_pgbouncer_prepared(self, check: bool = False) -> bool: - """Check if prepared statements in PgBouncer are supported.""" + """Check if prepared statements in PgBouncer are supported. + + The feature requires libpq 17.0 and greater. + """ return self._has_feature( "PgBouncer prepared statements compatibility", 170000, check=check ) diff --git a/tests/test_capabilities.py b/tests/test_capabilities.py index b0e3c2be5..6a3a2da93 100644 --- a/tests/test_capabilities.py +++ b/tests/test_capabilities.py @@ -6,10 +6,10 @@ from psycopg import pq, _cmodule from psycopg import capabilities, NotSupportedError caps = [ - ("has_encrypt_password", "encrypt_password", 10), - ("has_hostaddr", "PGconn.hostaddr", 12), + ("has_encrypt_password", "pq.PGconn.encrypt_password()", 10), + ("has_hostaddr", "Connection.info.hostaddr", 12), ("has_pipeline", "Connection.pipeline()", 14), - ("has_set_trace_flag", "PGconn.set_trace_flag()", 14), + ("has_set_trace_flags", "PGconn.set_trace_flags()", 14), ("has_cancel_safe", "Connection.cancel_safe()", 17), ("has_pgbouncer_prepared", "PgBouncer prepared statements compatibility", 17), ]