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]