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]

Reply via email to