Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package python-aiosqlite for 
openSUSE:Factory checked in at 2026-04-12 17:52:36
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-aiosqlite (Old)
 and      /work/SRC/openSUSE:Factory/.python-aiosqlite.new.21863 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-aiosqlite"

Sun Apr 12 17:52:36 2026 rev:6 rq:1346168 version:0.22.1

Changes:
--------
--- /work/SRC/openSUSE:Factory/python-aiosqlite/python-aiosqlite.changes        
2025-02-24 15:49:35.845337423 +0100
+++ 
/work/SRC/openSUSE:Factory/.python-aiosqlite.new.21863/python-aiosqlite.changes 
    2026-04-12 17:52:45.760266770 +0200
@@ -1,0 +2,10 @@
+Sun Apr 12 11:32:47 UTC 2026 - Dirk Müller <[email protected]>
+
+- update to 0.22.1:
+  * Support `set_authorizer` query access controls (#349)
+  * Wait for transaction queue to complete when closing connection (#305)
+  * Emit warning when connection goes out of scope without being closed (#355)
+  * Remove dependency on `typing_extensions` (#365)
+  * Added synchronous `stop()` method to `aiosqlite.Connection`
+
+-------------------------------------------------------------------

Old:
----
  aiosqlite-0.21.0.tar.gz

New:
----
  aiosqlite-0.22.1.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ python-aiosqlite.spec ++++++
--- /var/tmp/diff_new_pack.7oeuMK/_old  2026-04-12 17:52:46.300288761 +0200
+++ /var/tmp/diff_new_pack.7oeuMK/_new  2026-04-12 17:52:46.300288761 +0200
@@ -1,7 +1,7 @@
 #
 # spec file for package python-aiosqlite
 #
-# Copyright (c) 2025 SUSE LLC
+# Copyright (c) 2026 SUSE LLC and contributors
 # Copyright (c) 2019 Matthias Fehring <[email protected]>
 #
 # All modifications and additions to the file contributed by third parties
@@ -19,7 +19,7 @@
 
 %{?sle15_python_module_pythons}
 Name:           python-aiosqlite
-Version:        0.21.0
+Version:        0.22.1
 Release:        0
 Summary:        AsyncIO Bridge to the Standard Python sqlite3 Module
 License:        MIT

++++++ aiosqlite-0.21.0.tar.gz -> aiosqlite-0.22.1.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/aiosqlite-0.21.0/PKG-INFO 
new/aiosqlite-0.22.1/PKG-INFO
--- old/aiosqlite-0.21.0/PKG-INFO       1970-01-01 01:00:00.000000000 +0100
+++ new/aiosqlite-0.22.1/PKG-INFO       1970-01-01 01:00:00.000000000 +0100
@@ -1,6 +1,6 @@
-Metadata-Version: 2.3
+Metadata-Version: 2.4
 Name: aiosqlite
-Version: 0.21.0
+Version: 0.22.1
 Summary: asyncio bridge to the standard sqlite3 module
 Author-email: Amethyst Reese <[email protected]>
 Requires-Python: >=3.9
@@ -10,19 +10,19 @@
 Classifier: Intended Audience :: Developers
 Classifier: License :: OSI Approved :: MIT License
 Classifier: Topic :: Software Development :: Libraries
-Requires-Dist: typing_extensions >= 4.0
-Requires-Dist: attribution==1.7.1 ; extra == "dev"
-Requires-Dist: black==24.3.0 ; extra == "dev"
+License-File: LICENSE
+Requires-Dist: attribution==1.8.0 ; extra == "dev"
+Requires-Dist: black==25.11.0 ; extra == "dev"
 Requires-Dist: build>=1.2 ; extra == "dev"
-Requires-Dist: coverage[toml]==7.6.10 ; extra == "dev"
-Requires-Dist: flake8==7.0.0 ; extra == "dev"
+Requires-Dist: coverage[toml]==7.10.7 ; extra == "dev"
+Requires-Dist: flake8==7.3.0 ; extra == "dev"
 Requires-Dist: flake8-bugbear==24.12.12 ; extra == "dev"
-Requires-Dist: flit==3.10.1 ; extra == "dev"
-Requires-Dist: mypy==1.14.1 ; extra == "dev"
-Requires-Dist: ufmt==2.5.1 ; extra == "dev"
+Requires-Dist: flit==3.12.0 ; extra == "dev"
+Requires-Dist: mypy==1.19.0 ; extra == "dev"
+Requires-Dist: ufmt==2.8.0 ; extra == "dev"
 Requires-Dist: usort==1.0.8.post1 ; extra == "dev"
 Requires-Dist: sphinx==8.1.3 ; extra == "docs"
-Requires-Dist: sphinx-mdinclude==0.6.1 ; extra == "docs"
+Requires-Dist: sphinx-mdinclude==0.6.2 ; extra == "docs"
 Project-URL: Documentation, https://aiosqlite.omnilib.dev
 Project-URL: Github, https://github.com/omnilib/aiosqlite
 Provides-Extra: dev
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/aiosqlite-0.21.0/aiosqlite/__version__.py 
new/aiosqlite-0.22.1/aiosqlite/__version__.py
--- old/aiosqlite-0.21.0/aiosqlite/__version__.py       2025-02-03 
08:29:49.388680000 +0100
+++ new/aiosqlite-0.22.1/aiosqlite/__version__.py       2025-12-23 
20:25:16.253323800 +0100
@@ -4,4 +4,4 @@
 Do not edit manually. Get more info at https://attribution.omnilib.dev
 """
 
-__version__ = "0.21.0"
+__version__ = "0.22.1"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/aiosqlite-0.21.0/aiosqlite/context.py 
new/aiosqlite-0.22.1/aiosqlite/context.py
--- old/aiosqlite-0.21.0/aiosqlite/context.py   2025-02-03 08:29:49.388680000 
+0100
+++ new/aiosqlite-0.22.1/aiosqlite/context.py   2025-12-23 20:25:16.253323800 
+0100
@@ -47,7 +47,7 @@
 
 
 def contextmanager(
-    method: Callable[..., Coroutine[Any, Any, _T]]
+    method: Callable[..., Coroutine[Any, Any, _T]],
 ) -> Callable[..., Result[_T]]:
     @wraps(method)
     def wrapper(self, *args, **kwargs) -> Result[_T]:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/aiosqlite-0.21.0/aiosqlite/core.py 
new/aiosqlite-0.22.1/aiosqlite/core.py
--- old/aiosqlite-0.21.0/aiosqlite/core.py      2025-02-03 08:29:49.389680000 
+0100
+++ new/aiosqlite-0.22.1/aiosqlite/core.py      2025-12-23 20:25:16.253323800 
+0100
@@ -21,6 +21,8 @@
 
 __all__ = ["connect", "Connection", "Cursor"]
 
+AuthorizerCallback = Callable[[int, str, str, str, str], int]
+
 LOG = logging.getLogger("aiosqlite")
 
 
@@ -40,21 +42,52 @@
 
 
 _STOP_RUNNING_SENTINEL = object()
+_TxQueue = SimpleQueue[tuple[Optional[asyncio.Future], Callable[[], Any]]]
+
+
+def _connection_worker_thread(tx: _TxQueue):
+    """
+    Execute function calls on a separate thread.
+
+    :meta private:
+    """
+    while True:
+        # Continues running until all queue items are processed,
+        # even after connection is closed (so we can finalize all
+        # futures)
+
+        future, function = tx.get()
+
+        try:
+            LOG.debug("executing %s", function)
+            result = function()
+
+            if future:
+                future.get_loop().call_soon_threadsafe(set_result, future, 
result)
+            LOG.debug("operation %s completed", function)
+
+            if result is _STOP_RUNNING_SENTINEL:
+                break
+
+        except BaseException as e:  # noqa B036
+            LOG.debug("returning exception %s", e)
+            if future:
+                future.get_loop().call_soon_threadsafe(set_exception, future, 
e)
 
 
-class Connection(Thread):
+class Connection:
     def __init__(
         self,
         connector: Callable[[], sqlite3.Connection],
         iter_chunk_size: int,
         loop: Optional[asyncio.AbstractEventLoop] = None,
     ) -> None:
-        super().__init__()
         self._running = True
         self._connection: Optional[sqlite3.Connection] = None
         self._connector = connector
-        self._tx: SimpleQueue[tuple[asyncio.Future, Callable[[], Any]]] = 
SimpleQueue()
+        self._tx: _TxQueue = SimpleQueue()
         self._iter_chunk_size = iter_chunk_size
+        self._thread = Thread(target=_connection_worker_thread, 
args=(self._tx,))
 
         if loop is not None:
             warn(
@@ -62,10 +95,41 @@
                 DeprecationWarning,
             )
 
-    def _stop_running(self):
+    def __del__(self):
+        if self._connection is None:
+            return
+
+        warn(
+            (
+                f"{self!r} was deleted before being closed. "
+                "Please use 'async with' or '.close()' to close the connection 
properly."
+            ),
+            ResourceWarning,
+            stacklevel=1,
+        )
+
+        # Don't try to be creative here, the event loop may have already been 
closed.
+        # Simply stop the worker thread, and let the underlying sqlite3 
connection
+        # be finalized by its own __del__.
+        self.stop()
+
+    def stop(self) -> Optional[asyncio.Future]:
+        """Stop the background thread. Prefer `async with` or `await 
close()`"""
         self._running = False
-        # PEP 661 is not accepted yet, so we cannot type a sentinel
-        self._tx.put_nowait(_STOP_RUNNING_SENTINEL)  # type: ignore[arg-type]
+
+        def close_and_stop():
+            if self._connection is not None:
+                self._connection.close()
+                self._connection = None
+            return _STOP_RUNNING_SENTINEL
+
+        try:
+            future = asyncio.get_event_loop().create_future()
+        except Exception:
+            future = None
+
+        self._tx.put_nowait((future, close_and_stop))
+        return future
 
     @property
     def _conn(self) -> sqlite3.Connection:
@@ -83,32 +147,6 @@
         cursor = self._conn.execute(sql, parameters)
         return cursor.fetchall()
 
-    def run(self) -> None:
-        """
-        Execute function calls on a separate thread.
-
-        :meta private:
-        """
-        while True:
-            # Continues running until all queue items are processed,
-            # even after connection is closed (so we can finalize all
-            # futures)
-
-            tx_item = self._tx.get()
-            if tx_item is _STOP_RUNNING_SENTINEL:
-                break
-
-            future, function = tx_item
-
-            try:
-                LOG.debug("executing %s", function)
-                result = function()
-                LOG.debug("operation %s completed", function)
-                future.get_loop().call_soon_threadsafe(set_result, future, 
result)
-            except BaseException as e:  # noqa B036
-                LOG.debug("returning exception %s", e)
-                future.get_loop().call_soon_threadsafe(set_exception, future, 
e)
-
     async def _execute(self, fn, *args, **kwargs):
         """Queue a function with the given arguments for execution."""
         if not self._running or not self._connection:
@@ -129,14 +167,14 @@
                 self._tx.put_nowait((future, self._connector))
                 self._connection = await future
             except BaseException:
-                self._stop_running()
+                self.stop()
                 self._connection = None
                 raise
 
         return self
 
     def __await__(self) -> Generator[Any, None, "Connection"]:
-        self.start()
+        self._thread.start()
         return self._connect().__await__()
 
     async def __aenter__(self) -> "Connection":
@@ -170,8 +208,10 @@
             LOG.info("exception occurred while closing connection")
             raise
         finally:
-            self._stop_running()
             self._connection = None
+            future = self.stop()
+            if future:
+                await future
 
     @contextmanager
     async def execute(
@@ -287,6 +327,50 @@
     async def set_trace_callback(self, handler: Callable) -> None:
         await self._execute(self._conn.set_trace_callback, handler)
 
+    async def set_authorizer(
+        self, authorizer_callback: Optional[AuthorizerCallback]
+    ) -> None:
+        """
+        Set an authorizer callback to control database access.
+
+        The authorizer callback is invoked for each SQL statement that is 
prepared,
+        and controls whether specific operations are permitted.
+
+        Example::
+
+            import sqlite3
+
+            def restrict_drops(action_code, arg1, arg2, db_name, trigger_name):
+                # Deny all DROP operations
+                if action_code == sqlite3.SQLITE_DROP_TABLE:
+                    return sqlite3.SQLITE_DENY
+                # Allow everything else
+                return sqlite3.SQLITE_OK
+
+            await conn.set_authorizer(restrict_drops)
+
+        See ``sqlite3`` documentation for details:
+        
https://docs.python.org/3/library/sqlite3.html#sqlite3.Connection.set_authorizer
+
+        :param authorizer_callback: An optional callable that receives five 
arguments:
+
+            - ``action_code`` (int): The action to be authorized (e.g., 
``SQLITE_READ``)
+            - ``arg1`` (str): First argument, meaning depends on 
``action_code``
+            - ``arg2`` (str): Second argument, meaning depends on 
``action_code``
+            - ``db_name`` (str): Database name (e.g., ``"main"``, ``"temp"``)
+            - ``trigger_name`` (str): Name of trigger or view that is doing 
the access,
+              or ``None``
+
+            The callback should return:
+
+            - ``SQLITE_OK`` (0): Allow the operation
+            - ``SQLITE_DENY`` (1): Deny the operation, raise 
``sqlite3.DatabaseError``
+            - ``SQLITE_IGNORE`` (2): Treat operation as no-op
+
+            Pass ``None`` to remove the authorizer.
+        """
+        await self._execute(self._conn.set_authorizer, authorizer_callback)
+
     async def iterdump(self) -> AsyncIterator[str]:
         """
         Return an async iterator to dump the database in SQL text format.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/aiosqlite-0.21.0/aiosqlite/tests/perf.py 
new/aiosqlite-0.22.1/aiosqlite/tests/perf.py
--- old/aiosqlite-0.21.0/aiosqlite/tests/perf.py        2025-02-03 
08:29:49.389680000 +0100
+++ new/aiosqlite-0.22.1/aiosqlite/tests/perf.py        2025-12-23 
20:25:16.254323700 +0100
@@ -4,6 +4,7 @@
 """
 Simple perf tests for aiosqlite and the asyncio run loop.
 """
+import sqlite3
 import string
 import tempfile
 import time
@@ -120,6 +121,23 @@
             await db.commit()
 
             while True:
+                yield
+                await db.execute("insert into perf (k) values (1), (2), (3)")
+                await db.commit()
+
+    @timed
+    async def test_inserts_authorized(self):
+        def deny_drops(action_code, arg1, arg2, db_name, trigger_name):
+            if action_code == sqlite3.SQLITE_DROP_TABLE:
+                return sqlite3.SQLITE_DENY
+            return sqlite3.SQLITE_OK
+
+        async with aiosqlite.connect(TEST_DB) as db:
+            await db.execute("create table perf (i integer primary key asc, k 
integer)")
+            await db.set_authorizer(deny_drops)
+            await db.commit()
+
+            while True:
                 yield
                 await db.execute("insert into perf (k) values (1), (2), (3)")
                 await db.commit()
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/aiosqlite-0.21.0/aiosqlite/tests/smoke.py 
new/aiosqlite-0.22.1/aiosqlite/tests/smoke.py
--- old/aiosqlite-0.21.0/aiosqlite/tests/smoke.py       2025-02-03 
08:29:49.389680000 +0100
+++ new/aiosqlite-0.22.1/aiosqlite/tests/smoke.py       2025-12-23 
20:25:16.254323700 +0100
@@ -3,6 +3,7 @@
 
 import asyncio
 import sqlite3
+import sys
 from pathlib import Path
 from sqlite3 import OperationalError
 from tempfile import TemporaryDirectory
@@ -344,6 +345,42 @@
             await db.execute("select 10")
             self.assertIn("select 10", statements)
 
+    async def test_set_authorizer_deny_drops(self):
+        """Test authorizer that denies DROP operations"""
+
+        def deny_drops(action_code, arg1, arg2, db_name, trigger_name):
+            if action_code == sqlite3.SQLITE_DROP_TABLE:
+                return sqlite3.SQLITE_DENY
+            return sqlite3.SQLITE_OK
+
+        async with aiosqlite.connect(self.db) as db:
+            await db.set_authorizer(deny_drops)
+
+            # Other operations should succeed
+            await db.execute("CREATE TABLE test_drop (id INTEGER)")
+            await db.execute("INSERT INTO test_drop VALUES (1)")
+            await db.execute("SELECT * FROM test_drop")
+
+            # DROP should fail
+            with self.assertRaises(sqlite3.DatabaseError):
+                await db.execute("DROP TABLE test_drop")
+
+            if sys.version_info >= (3, 11):
+                # Disabling the authorizer re-enables DROP
+                await db.set_authorizer(None)
+                await db.execute("DROP TABLE test_drop")
+
+    async def test_set_authorizer_exception_propagation(self):
+        """Test that exceptions raised in authorizer callback are caught by 
SQLite"""
+
+        def raise_exception(action_code, arg1, arg2, db_name, trigger_name):
+            raise ValueError("Test exception from authorizer")
+
+        async with aiosqlite.connect(self.db) as db:
+            await db.set_authorizer(raise_exception)
+            with self.assertRaises(sqlite3.DatabaseError):
+                await db.execute("CREATE TABLE test_exception (id INTEGER)")
+
     async def test_connect_error(self):
         bad_db = Path("/something/that/shouldnt/exist.db")
         with self.assertRaisesRegex(OperationalError, "unable to open 
database"):
@@ -367,7 +404,7 @@
                 ...
         # Terminate the thread here if the test fails to have a clear error.
         if connection._running:
-            connection._stop_running()
+            connection.stop()
             raise AssertionError("connection thread was not stopped")
 
     async def test_iterdump(self):
@@ -414,6 +451,17 @@
             except sqlite3.ProgrammingError:
                 pass
 
+    async def test_close_blocking_until_transaction_queue_empty(self):
+        db = await aiosqlite.connect(self.db)
+        # Insert transactions into the
+        # transaction queue '_tx'
+        for i in range(1000):
+            await db.execute(f"select 1, {i}")
+        # Wait for all transactions to complete
+        await db.close()
+        # Check no more transaction pending
+        self.assertEqual(db._tx.empty(), True)
+
     async def test_close_twice(self):
         db = await aiosqlite.connect(self.db)
 
@@ -462,3 +510,28 @@
                 cursor = db2.execute("select * from foo")
                 rows = cursor.fetchall()
                 self.assertEqual(rows, [(1, "hello"), (2, "world")])
+
+    async def test_emits_warning_when_left_open(self):
+        db = await aiosqlite.connect(":memory:")
+
+        with self.assertWarnsRegex(
+            ResourceWarning, r".*was deleted before being closed.*"
+        ):
+            del db
+
+    async def test_stop_without_close(self):
+        db = await aiosqlite.connect(":memory:")
+        await db.stop()
+
+    def test_stop_after_event_loop_closed(self):
+        db = None
+
+        async def inner():
+            nonlocal db
+            db = await aiosqlite.connect(":memory:")
+
+        loop = asyncio.new_event_loop()
+        loop.run_until_complete(inner())
+        loop.close()
+
+        db.stop()
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/aiosqlite-0.21.0/pyproject.toml 
new/aiosqlite-0.22.1/pyproject.toml
--- old/aiosqlite-0.21.0/pyproject.toml 2025-02-03 08:29:49.390679800 +0100
+++ new/aiosqlite-0.22.1/pyproject.toml 2025-12-23 20:25:16.254323700 +0100
@@ -18,26 +18,23 @@
     "Topic :: Software Development :: Libraries",
 ]
 requires-python = ">=3.9"
-dependencies = [
-    "typing_extensions >= 4.0",
-]
 
 [project.optional-dependencies]
 dev = [
-    "attribution==1.7.1",
-    "black==24.3.0",
+    "attribution==1.8.0",
+    "black==25.11.0",
     "build>=1.2",
-    "coverage[toml]==7.6.10",
-    "flake8==7.0.0",
+    "coverage[toml]==7.10.7",
+    "flake8==7.3.0",
     "flake8-bugbear==24.12.12",
-    "flit==3.10.1",
-    "mypy==1.14.1",
-    "ufmt==2.5.1",
+    "flit==3.12.0",
+    "mypy==1.19.0",
+    "ufmt==2.8.0",
     "usort==1.0.8.post1",
 ]
 docs = [
     "sphinx==8.1.3",
-    "sphinx-mdinclude==0.6.1",
+    "sphinx-mdinclude==0.6.2",
 ]
 
 [project.urls]

Reply via email to