]> git.ipfire.org Git - thirdparty/psycopg.git/commitdiff
Raise a resource warning if a connection is deleted while open
authorDaniele Varrazzo <daniele.varrazzo@gmail.com>
Sun, 27 Dec 2020 04:18:27 +0000 (05:18 +0100)
committerDaniele Varrazzo <daniele.varrazzo@gmail.com>
Sun, 27 Dec 2020 04:18:27 +0000 (05:18 +0100)
psycopg3/psycopg3/connection.py
tests/test_connection.py
tests/test_connection_async.py

index 319c3179a0b73c9c3e782fd1696da7302d425edb..a5c9aa52977954ab41c8e97c7bd07b204d456c05 100644 (file)
@@ -7,6 +7,7 @@ psycopg3 connection objects
 import sys
 import asyncio
 import logging
+import warnings
 import threading
 from types import TracebackType
 from typing import Any, AsyncIterator, Callable, Iterator, List, NamedTuple
@@ -122,6 +123,22 @@ class BaseConnection(AdaptContext):
         pgconn.notice_handler = partial(BaseConnection._notice_handler, wself)
         pgconn.notify_handler = partial(BaseConnection._notify_handler, wself)
 
+    def __del__(self) -> None:
+        status = self.pgconn.transaction_status
+        if status == TransactionStatus.UNKNOWN:
+            return
+
+        elif status == TransactionStatus.INTRANS:
+            msg = (
+                f"connection {self} was deleted with an open transaction,"
+                " changes discarded by the server"
+            )
+        else:
+            status = TransactionStatus(status)  # in case we got an int
+            msg = f"connection {self} was deleted open in status {status.name}"
+
+        warnings.warn(msg, ResourceWarning)
+
     @property
     def closed(self) -> bool:
         """`True` if the connection is closed."""
index 0be39e0e9c57b25235ffaacdb1573cd0dd53ca37..62366fbe833aad3a2311a559d98977bc8f820210 100644 (file)
@@ -72,6 +72,35 @@ def test_close(conn):
         cur.execute("select 1")
 
 
+def test_connection_warn_close(dsn, recwarn):
+    conn = Connection.connect(dsn)
+    conn.close()
+    del conn
+    assert not recwarn
+
+    conn = Connection.connect(dsn)
+    del conn
+    assert "IDLE" in str(recwarn.pop(ResourceWarning).message)
+
+    conn = Connection.connect(dsn)
+    conn.execute("select 1")
+    del conn
+    assert "discarded" in str(recwarn.pop(ResourceWarning).message)
+
+    conn = Connection.connect(dsn)
+    try:
+        conn.execute("select wat")
+    except Exception:
+        pass
+    del conn
+    assert "INERROR" in str(recwarn.pop(ResourceWarning).message)
+
+    with Connection.connect(dsn) as conn:
+        pass
+    del conn
+    assert not recwarn
+
+
 def test_context_commit(conn, dsn):
     with conn:
         with conn.cursor() as cur:
index 2d57d9c0f29ea20e686a34aafa8d662d2a639f7d..206fda2f6fd637f9494e7321700391a42fe62bd3 100644 (file)
@@ -77,6 +77,35 @@ async def test_close(aconn):
         await cur.execute("select 1")
 
 
+async def test_connection_warn_close(dsn, recwarn):
+    conn = await AsyncConnection.connect(dsn)
+    await conn.close()
+    del conn
+    assert not recwarn
+
+    conn = await AsyncConnection.connect(dsn)
+    del conn
+    assert "IDLE" in str(recwarn.pop(ResourceWarning).message)
+
+    conn = await AsyncConnection.connect(dsn)
+    await conn.execute("select 1")
+    del conn
+    assert "discarded" in str(recwarn.pop(ResourceWarning).message)
+
+    conn = await AsyncConnection.connect(dsn)
+    try:
+        await conn.execute("select wat")
+    except Exception:
+        pass
+    del conn
+    assert "INERROR" in str(recwarn.pop(ResourceWarning).message)
+
+    async with await AsyncConnection.connect(dsn) as conn:
+        pass
+    del conn
+    assert not recwarn
+
+
 async def test_context_commit(aconn, dsn):
     async with aconn:
         async with await aconn.cursor() as cur: