From 146a37a27117332caced972e9e3367632bbf449b Mon Sep 17 00:00:00 2001 From: Daniele Varrazzo Date: Sun, 6 Feb 2022 01:23:09 +0100 Subject: [PATCH] Add doc section about server messages handling --- docs/advanced/async.rst | 66 +++++++++++++++++++++++++++++++++++ docs/api/connections.rst | 7 ++-- psycopg/psycopg/connection.py | 12 +++++++ 3 files changed, 80 insertions(+), 5 deletions(-) diff --git a/docs/advanced/async.rst b/docs/advanced/async.rst index a9a49d524..a89273d6e 100644 --- a/docs/advanced/async.rst +++ b/docs/advanced/async.rst @@ -106,6 +106,72 @@ Note that the `AsyncConnection.cursor()` function is not an `!async` function you can use the normal `async with` context manager. +.. index:: + pair: Asynchronous; Notifications + pair: LISTEN; SQL command + pair: NOTIFY; SQL command + +.. _async-messages: + +Server messages +--------------- + +PostgreSQL can send, together with the query results, `informative messages`__ +about the operation just performed, such as warnings or debug information. +Notices may be raised even if the operations are successful and don't indicate +an error. You are probably familiar with some of them, because they are +reported by :program:`psql`:: + + $ psql + =# ROLLBACK; + WARNING: there is no transaction in progress + ROLLBACK + +.. __: https://www.postgresql.org/docs/current/runtime-config-logging.html + #RUNTIME-CONFIG-SEVERITY-LEVELS + +Messages can be also sent by the `PL/pgSQL 'RAISE' statement`__ (at a level +lower than EXCEPTION, otherwise the appropriate `DatabaseError` will be +raised). The level of the messages received can be controlled using the +client_min_messages__ setting. + +.. __: https://www.postgresql.org/docs/current/plpgsql-errors-and-messages.html +.. __: https://www.postgresql.org/docs/current/runtime-config-client.html + #GUC-CLIENT-MIN-MESSAGES + + +By default, the messages received are ignored. If you want to process them on +the client you can use the `Connection.add_notice_handler()` function to +register a function that will be invoked whenever a message is received. The +message is passed to the callback as a `~errors.Diagnostic` instance, +containing all the information passed by the server, such as the message text +and the severity. The object is the same found on the `~psycopg.Error.diag` +attribute of the errors raised by the server: + +.. code:: python + + >>> import psycopg + + >>> def log_notice(diag): + ... print(f"The server says: {diag.severity} - {diag.message_primary}") + + >>> conn = psycopg.connect(autocommit=True) + >>> conn.add_notice_handler(log_notice) + + >>> cur = conn.execute("ROLLBACK") + The server says: WARNING - there is no transaction in progress + >>> print(cur.statusmessage) + ROLLBACK + +.. warning:: + + The `!Diagnostic` object received by the callback should not be used after + the callback function terminates, because its data is deallocated after + the callbacks have been processed. If you need to use the information + later please extract the attributes requested and forward them instead of + forwarding the whole `!Diagnostic` object. + + .. index:: pair: Asynchronous; Notifications pair: LISTEN; SQL command diff --git a/docs/api/connections.rst b/docs/api/connections.rst index 8e05dc13b..440679025 100644 --- a/docs/api/connections.rst +++ b/docs/api/connections.rst @@ -254,16 +254,13 @@ The `!Connection` class .. automethod:: add_notify_handler - :param callback: a callable taking a `Notify` parameter. + See :ref:`async-notify` for details. .. automethod:: remove_notify_handler - See :ref:`async-notify` for details. - .. automethod:: add_notice_handler - :param callback: a callable taking a `~psycopg.errors.Diagnostic` - object containing all the details about the notice. + See :ref:`async-messages` for details. .. automethod:: remove_notice_handler diff --git a/psycopg/psycopg/connection.py b/psycopg/psycopg/connection.py index f4e17b9ed..0ca56d104 100644 --- a/psycopg/psycopg/connection.py +++ b/psycopg/psycopg/connection.py @@ -293,12 +293,18 @@ class BaseConnection(Generic[Row]): def add_notice_handler(self, callback: NoticeHandler) -> None: """ Register a callable to be invoked when a notice message is received. + + :param callback: the callback to call upon message received. + :type callback: Callable[[~psycopg.errors.Diagnostic], None] """ self._notice_handlers.append(callback) def remove_notice_handler(self, callback: NoticeHandler) -> None: """ Unregister a notice message callable previously registered. + + :param callback: the callback to remove. + :type callback: Callable[[~psycopg.errors.Diagnostic], None] """ self._notice_handlers.remove(callback) @@ -320,12 +326,18 @@ class BaseConnection(Generic[Row]): def add_notify_handler(self, callback: NotifyHandler) -> None: """ Register a callable to be invoked whenever a notification is received. + + :param callback: the callback to call upon notification received. + :type callback: Callable[[~psycopg.Notify], None] """ self._notify_handlers.append(callback) def remove_notify_handler(self, callback: NotifyHandler) -> None: """ Unregister a notification callable previously registered. + + :param callback: the callback to remove. + :type callback: Callable[[~psycopg.Notify], None] """ self._notify_handlers.remove(callback) -- 2.47.2