]> git.ipfire.org Git - thirdparty/psycopg.git/commitdiff
fix: raise a warning when notifies generator and handlers are used together
authorDaniele Varrazzo <daniele.varrazzo@gmail.com>
Sat, 6 Sep 2025 22:14:48 +0000 (00:14 +0200)
committerDaniele Varrazzo <daniele.varrazzo@gmail.com>
Sat, 6 Sep 2025 22:16:31 +0000 (00:16 +0200)
psycopg/psycopg/connection.py
psycopg/psycopg/connection_async.py
tests/test_notify.py
tests/test_notify_async.py

index 2a55f0485f77c786a2c50fbdea5acbb20d7638da..f6af3edc35011a0d85119b36547f19fc0d4d2dd1 100644 (file)
@@ -354,6 +354,12 @@ class Connection(BaseConnection[Row]):
 
         nreceived = 0
 
+        if self._notify_handlers:
+            warnings.warn(
+                "using 'notifies()' together with notifies handlers on the same connection is not reliable. Please use only one of thees methods",
+                RuntimeWarning,
+            )
+
         with self.lock:
             enc = self.pgconn._encoding
 
index cc9bd7247013ad5bdbc414309d25b343b172c61e..28e36919162bbdc5fe58a6cbef5d5a57aa276a3e 100644 (file)
@@ -383,6 +383,14 @@ class AsyncConnection(BaseConnection[Row]):
 
         nreceived = 0
 
+        if self._notify_handlers:
+            warnings.warn(
+                "using 'notifies()' together with notifies handlers on the"
+                " same connection is not reliable."
+                " Please use only one of thees methods",
+                RuntimeWarning,
+            )
+
         async with self.lock:
             enc = self.pgconn._encoding
 
index 99c99e698dc7ebc2e747131bbc13be9ae57cb742..fde74058dc76bab153b1b05f4b7dcce05487c3f0 100644 (file)
@@ -223,7 +223,13 @@ def test_notifies_blocking(conn):
 
 
 @pytest.mark.slow
-def test_generator_and_handler(conn, conn_cls, dsn):
+def test_generator_and_handler(conn, conn_cls, dsn, recwarn):
+    # NOTE: we don't support generator+handlers anymore. So, if in the future
+    # this behaviour will change, we will not consider it a regression. However
+    # we will want to keep the warning check.
+
+    recwarn.clear()
+
     conn.set_autocommit(True)
     conn.execute("listen foo")
 
@@ -255,6 +261,9 @@ def test_generator_and_handler(conn, conn_cls, dsn):
     assert n1
     assert n2
 
+    msg = str(recwarn.pop(RuntimeWarning).message)
+    assert "notifies()" in msg
+
 
 @pytest.mark.parametrize("query_between", [True, False])
 def test_first_notify_not_lost(conn, conn_cls, dsn, query_between):
@@ -289,25 +298,41 @@ def test_notify_query_notify(conn_cls, dsn, sleep_on, listen_by):
                 conn.execute("select pg_notify('counter', %s)", (str(i),))
                 sleep(0.2)
 
-    def listener():
-        with conn_cls.connect(dsn, autocommit=True) as conn:
-            if listen_by == "callback":
+    def nap(conn):
+        if sleep_on == "server":
+            conn.execute("select pg_sleep(0.2)")
+        else:
+            assert sleep_on == "client"
+            sleep(0.2)
+
+    if listen_by == "callback":
+
+        def listener():
+            with conn_cls.connect(dsn, autocommit=True) as conn:
                 conn.add_notify_handler(lambda n: notifies.append(int(n.payload)))
 
-            conn.execute("listen counter")
-            e.set()
-            for n in conn.notifies(timeout=0.2):
-                if listen_by == "generator":
+                conn.execute("listen counter")
+                e.set()
+
+                nap(conn)
+                conn.execute("")
+                nap(conn)
+                conn.execute("")
+                nap(conn)
+                conn.execute("")
+
+    else:
+
+        def listener():
+            with conn_cls.connect(dsn, autocommit=True) as conn:
+                conn.execute("listen counter")
+                e.set()
+                for n in conn.notifies(timeout=0.2):
                     notifies.append(int(n.payload))
 
-            if sleep_on == "server":
-                conn.execute("select pg_sleep(0.2)")
-            else:
-                assert sleep_on == "client"
-                sleep(0.2)
+                nap(conn)
 
-            for n in conn.notifies(timeout=0.2):
-                if listen_by == "generator":
+                for n in conn.notifies(timeout=0.2):
                     notifies.append(int(n.payload))
 
     workers.append(spawn(listener))
index 404e6a8e3e87466b33df3c717f9de55ebb0197e5..6faf9be4f1c0ab96ddb360f339857e763618a992 100644 (file)
@@ -219,7 +219,13 @@ async def test_notifies_blocking(aconn):
 
 
 @pytest.mark.slow
-async def test_generator_and_handler(aconn, aconn_cls, dsn):
+async def test_generator_and_handler(aconn, aconn_cls, dsn, recwarn):
+    # NOTE: we don't support generator+handlers anymore. So, if in the future
+    # this behaviour will change, we will not consider it a regression. However
+    # we will want to keep the warning check.
+
+    recwarn.clear()
+
     await aconn.set_autocommit(True)
     await aconn.execute("listen foo")
 
@@ -252,6 +258,9 @@ async def test_generator_and_handler(aconn, aconn_cls, dsn):
     assert n1
     assert n2
 
+    msg = str(recwarn.pop(RuntimeWarning).message)
+    assert "notifies()" in msg
+
 
 @pytest.mark.parametrize("query_between", [True, False])
 async def test_first_notify_not_lost(aconn, aconn_cls, dsn, query_between):
@@ -286,25 +295,41 @@ async def test_notify_query_notify(aconn_cls, dsn, sleep_on, listen_by):
                 await aconn.execute("select pg_notify('counter', %s)", (str(i),))
                 await asleep(0.2)
 
-    async def listener():
-        async with await aconn_cls.connect(dsn, autocommit=True) as aconn:
-            if listen_by == "callback":
+    async def nap(aconn):
+        if sleep_on == "server":
+            await aconn.execute("select pg_sleep(0.2)")
+        else:
+            assert sleep_on == "client"
+            await asleep(0.2)
+
+    if listen_by == "callback":
+
+        async def listener():
+            async with await aconn_cls.connect(dsn, autocommit=True) as aconn:
                 aconn.add_notify_handler(lambda n: notifies.append(int(n.payload)))
 
-            await aconn.execute("listen counter")
-            e.set()
-            async for n in aconn.notifies(timeout=0.2):
-                if listen_by == "generator":
+                await aconn.execute("listen counter")
+                e.set()
+
+                await nap(aconn)
+                await aconn.execute("")
+                await nap(aconn)
+                await aconn.execute("")
+                await nap(aconn)
+                await aconn.execute("")
+
+    else:
+
+        async def listener():
+            async with await aconn_cls.connect(dsn, autocommit=True) as aconn:
+                await aconn.execute("listen counter")
+                e.set()
+                async for n in aconn.notifies(timeout=0.2):
                     notifies.append(int(n.payload))
 
-            if sleep_on == "server":
-                await aconn.execute("select pg_sleep(0.2)")
-            else:
-                assert sleep_on == "client"
-                await asleep(0.2)
+                await nap(aconn)
 
-            async for n in aconn.notifies(timeout=0.2):
-                if listen_by == "generator":
+                async for n in aconn.notifies(timeout=0.2):
                     notifies.append(int(n.payload))
 
     workers.append(spawn(listener))