Script 'mail_helper' called by obssrc
Hello community,
here is the log from the commit of package python-aiodataloader for
openSUSE:Factory checked in at 2026-02-17 16:47:50
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-aiodataloader (Old)
and /work/SRC/openSUSE:Factory/.python-aiodataloader.new.1977 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-aiodataloader"
Tue Feb 17 16:47:50 2026 rev:6 rq:1333406 version:0.4.3
Changes:
--------
---
/work/SRC/openSUSE:Factory/python-aiodataloader/python-aiodataloader.changes
2025-03-20 19:27:16.003282594 +0100
+++
/work/SRC/openSUSE:Factory/.python-aiodataloader.new.1977/python-aiodataloader.changes
2026-02-17 16:48:49.611142580 +0100
@@ -1,0 +2,9 @@
+Mon Feb 16 17:06:29 UTC 2026 - Dirk Müller <[email protected]>
+
+- update to 0.4.3:
+ * Change future checks from .cancelled() to .done() before
+ setting future results.
+ * Import `iscoroutinefunction()` from the `inspect` module on
+ Python >= 3.14
+
+-------------------------------------------------------------------
Old:
----
python-aiodataloader-0.4.2.tar.gz
New:
----
python-aiodataloader-0.4.3.tar.gz
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Other differences:
------------------
++++++ python-aiodataloader.spec ++++++
--- /var/tmp/diff_new_pack.sfTPyo/_old 2026-02-17 16:48:51.927239378 +0100
+++ /var/tmp/diff_new_pack.sfTPyo/_new 2026-02-17 16:48:51.931239545 +0100
@@ -1,7 +1,7 @@
#
# spec file for package python-aiodataloader
#
-# Copyright (c) 2025 SUSE LLC
+# Copyright (c) 2026 SUSE LLC and contributors
#
# All modifications and additions to the file contributed by third parties
# remain the property of their copyright owners, unless otherwise agreed
@@ -18,7 +18,7 @@
%{?sle15_python_module_pythons}
Name: python-aiodataloader
-Version: 0.4.2
+Version: 0.4.3
Release: 0
Summary: Asyncio DataLoader implementation for Python
License: MIT
++++++ python-aiodataloader-0.4.2.tar.gz -> python-aiodataloader-0.4.3.tar.gz
++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/aiodataloader-0.4.2/.github/workflows/release.yml
new/aiodataloader-0.4.3/.github/workflows/release.yml
--- old/aiodataloader-0.4.2/.github/workflows/release.yml 2025-02-17
15:47:47.000000000 +0100
+++ new/aiodataloader-0.4.3/.github/workflows/release.yml 2025-11-29
11:09:39.000000000 +0100
@@ -15,10 +15,10 @@
id-token: write
steps:
- uses: actions/checkout@v4
- - name: Set up Python 3.13
+ - name: Set up Python 3.14
uses: actions/setup-python@v5
with:
- python-version: '3.13'
+ python-version: '3.14'
cache: pip
- name: Install dependencies
run: |
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/aiodataloader-0.4.2/.github/workflows/test.yml
new/aiodataloader-0.4.3/.github/workflows/test.yml
--- old/aiodataloader-0.4.2/.github/workflows/test.yml 2025-02-17
15:47:47.000000000 +0100
+++ new/aiodataloader-0.4.3/.github/workflows/test.yml 2025-11-29
11:09:39.000000000 +0100
@@ -15,10 +15,10 @@
steps:
- uses: actions/checkout@v4
- - name: Set up Python 3.13
+ - name: Set up Python 3.14
uses: actions/setup-python@v5
with:
- python-version: '3.13'
+ python-version: '3.14'
cache: pip
- name: Install dependencies
run: |
@@ -32,7 +32,7 @@
strategy:
matrix:
- python-version: ['3.8', '3.9', '3.10', '3.11', '3.12', '3.13']
+ python-version: ['3.8', '3.9', '3.10', '3.11', '3.12', '3.13', '3.14']
steps:
- uses: actions/checkout@v4
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/aiodataloader-0.4.2/aiodataloader/__init__.py
new/aiodataloader-0.4.3/aiodataloader/__init__.py
--- old/aiodataloader-0.4.2/aiodataloader/__init__.py 2025-02-17
15:47:47.000000000 +0100
+++ new/aiodataloader-0.4.3/aiodataloader/__init__.py 2025-11-29
11:09:39.000000000 +0100
@@ -6,7 +6,6 @@
gather,
get_event_loop,
iscoroutine,
- iscoroutinefunction,
)
from collections import namedtuple
from functools import partial
@@ -24,12 +23,17 @@
Union,
)
+if sys.version_info >= (3, 14):
+ from inspect import iscoroutinefunction
+else:
+ from asyncio import iscoroutinefunction
+
if sys.version_info >= (3, 10):
from typing import TypeGuard
else:
from typing_extensions import TypeGuard
-__version__ = "0.4.2"
+__version__ = "0.4.3"
KeyT = TypeVar("KeyT")
ReturnT = TypeVar("ReturnT")
@@ -196,7 +200,7 @@
# Cache a rejected future if the value is an Error, in order to
match
# the behavior of load(key).
future = self.loop.create_future()
- if not future.cancelled():
+ if not future.done():
if isinstance(value, Exception):
future.set_exception(value)
else:
@@ -295,7 +299,7 @@
# Step through the values, resolving or rejecting each Future in the
# loaded queue.
for ql, value in zip(queue, values):
- if not ql.future.cancelled():
+ if not ql.future.done():
if isinstance(value, Exception):
ql.future.set_exception(value)
else:
@@ -314,5 +318,5 @@
"""
for ql in queue:
loader.clear(ql.key)
- if not ql.future.cancelled():
+ if not ql.future.done():
ql.future.set_exception(error)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/aiodataloader-0.4.2/pyproject.toml
new/aiodataloader-0.4.3/pyproject.toml
--- old/aiodataloader-0.4.2/pyproject.toml 2025-02-17 15:47:47.000000000
+0100
+++ new/aiodataloader-0.4.3/pyproject.toml 2025-11-29 11:09:39.000000000
+0100
@@ -19,6 +19,9 @@
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
+ "Programming Language :: Python :: 3.12",
+ "Programming Language :: Python :: 3.13",
+ "Programming Language :: Python :: 3.14",
"License :: OSI Approved :: MIT License",
]
keywords = ["concurrent", "future", "deferred", "aiodataloader"]
@@ -32,9 +35,6 @@
[project.urls]
"Homepage" = "https://github.com/syrusakbary/aiodataloader"
-[tool.black]
-target-version = ["py37", "py38", "py39", "py310", "py311"]
-
[tool.hatch.envs.default.scripts]
lint = "flake8 && black --check . && mypy"
test = "pytest --cov=aiodataloader"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/aiodataloader-0.4.2/test_aiodataloader.py
new/aiodataloader-0.4.3/test_aiodataloader.py
--- old/aiodataloader-0.4.2/test_aiodataloader.py 2025-02-17
15:47:47.000000000 +0100
+++ new/aiodataloader-0.4.3/test_aiodataloader.py 2025-11-29
11:09:39.000000000 +0100
@@ -323,6 +323,113 @@
exception_handler.assert_not_called()
+async def test_does_not_attempt_to_set_future_with_result() -> None:
+ """
+ Test that demonstrates why done() is better than cancelled().
+ If a future already has a result set (but is not cancelled), checking only
+ cancelled() would allow us to try setting it again, causing
InvalidStateError.
+ Using done() prevents this.
+ """
+ exception_handler = Mock()
+ loop = get_running_loop()
+ loop.set_exception_handler(exception_handler)
+ fut: Future[None] = Future()
+
+ async def call_fn(keys: List[int]) -> List[int]:
+ await fut
+ return keys
+
+ trigger_loader = DataLoader(call_fn)
+
+ promise = trigger_loader.load(1)
+
+ # Set the future to done with a result BEFORE the batch loader tries to
set it
+ # This simulates a race condition or external completion
+ promise.set_result(999)
+ fut.set_result(None)
+
+ # The promise should return the value we set, not the loader's value
+ result = await promise
+ assert result == 999
+
+ # Give time to the event loop to call the exception handler if needed
+ await sleep(0.001)
+
+ # No exception should be raised because done() check prevents
InvalidStateError
+ exception_handler.assert_not_called()
+
+
+async def test_does_not_attempt_to_set_future_with_exception() -> None:
+ """
+ Test that demonstrates why done() is better than cancelled().
+ If a future already has an exception set (but is not cancelled), checking
only
+ cancelled() would allow us to try setting it again, causing
InvalidStateError.
+ Using done() prevents this.
+ """
+ exception_handler = Mock()
+ loop = get_running_loop()
+ loop.set_exception_handler(exception_handler)
+ fut: Future[None] = Future()
+
+ async def call_fn(keys: List[int]) -> List[int]:
+ await fut
+ return keys
+
+ trigger_loader = DataLoader(call_fn)
+
+ promise = trigger_loader.load(1)
+
+ # Set the future to done with an exception BEFORE the batch loader tries
to set it
+ # This simulates a race condition or external completion
+ custom_exception = ValueError("External error")
+ promise.set_exception(custom_exception)
+ fut.set_result(None)
+
+ # The promise should raise the exception we set, not the loader's exception
+ with pytest.raises(ValueError, match="External error"):
+ await promise
+
+ # Give time to the event loop to call the exception handler if needed
+ await sleep(0.001)
+
+ # No exception should be raised because done() check prevents
InvalidStateError
+ exception_handler.assert_not_called()
+
+
+async def test_does_not_attempt_to_set_done_future_in_failed_dispatch() ->
None:
+ """
+ Test that demonstrates done() check in failed_dispatch prevents errors
+ when a future is already done (with result or exception) before the
+ batch fails.
+ """
+ exception_handler = Mock()
+ loop = get_running_loop()
+ loop.set_exception_handler(exception_handler)
+
+ async def call_fn(keys: List[int]) -> List[int]:
+ raise RuntimeError("Batch load failed")
+
+ trigger_loader = DataLoader(call_fn)
+
+ promise = trigger_loader.load(1)
+
+ # Set the future to done with a result BEFORE the batch fails
+ promise.set_result(999)
+
+ # Wait for the batch to fail
+ await sleep(0.01)
+
+ # The promise should still have our result, not the batch error
+ result = await promise
+ assert result == 999
+
+ # Give time to the event loop to call the exception handler if needed
+ await sleep(0.001)
+
+ # No exception should be raised because done() check prevents
InvalidStateError
+ exception_handler.assert_not_called()
+
+
async def test_caches_failed_fetches() -> None:
async def resolve(keys: List[int]) -> List[int]:
mapped_keys = [Exception("Error: {}".format(key)) for key in keys]