Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package python-anyio for openSUSE:Factory 
checked in at 2026-03-06 18:16:52
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-anyio (Old)
 and      /work/SRC/openSUSE:Factory/.python-anyio.new.561 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-anyio"

Fri Mar  6 18:16:52 2026 rev:31 rq:1336595 version:4.12.1

Changes:
--------
--- /work/SRC/openSUSE:Factory/python-anyio/python-anyio.changes        
2025-12-09 12:51:15.822235595 +0100
+++ /work/SRC/openSUSE:Factory/.python-anyio.new.561/python-anyio.changes       
2026-03-06 18:16:57.600089857 +0100
@@ -1,0 +2,10 @@
+Wed Mar  4 21:31:28 UTC 2026 - Dirk Müller <[email protected]>
+
+- update to 4.12.1:
+  * Changed all functions currently raising the private
+    NoCurrentAsyncBackend exception (since v4.12.0) to instead
+    raise the public NoEventLoopError exception
+  * Fixed anyio.functools.lru_cache not working with instance
+    methods
+
+-------------------------------------------------------------------

Old:
----
  anyio-4.12.0.tar.gz

New:
----
  anyio-4.12.1.tar.gz

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

Other differences:
------------------
++++++ python-anyio.spec ++++++
--- /var/tmp/diff_new_pack.t3IXxc/_old  2026-03-06 18:16:58.448125056 +0100
+++ /var/tmp/diff_new_pack.t3IXxc/_new  2026-03-06 18:16:58.448125056 +0100
@@ -1,7 +1,7 @@
 #
 # spec file for package python-anyio
 #
-# Copyright (c) 2025 SUSE LLC and contributors
+# 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
@@ -26,7 +26,7 @@
 %endif
 %{?sle15_python_module_pythons}
 Name:           python-anyio%{psuffix}
-Version:        4.12.0
+Version:        4.12.1
 Release:        0
 Summary:        High level compatibility layer for asynchronous event loop 
implementations
 License:        MIT

++++++ anyio-4.12.0.tar.gz -> anyio-4.12.1.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/anyio-4.12.0/.github/workflows/publish.yml 
new/anyio-4.12.1/.github/workflows/publish.yml
--- old/anyio-4.12.0/.github/workflows/publish.yml      2025-11-29 
00:36:29.000000000 +0100
+++ new/anyio-4.12.1/.github/workflows/publish.yml      2026-01-06 
12:44:46.000000000 +0100
@@ -24,7 +24,7 @@
       - name: Create packages
         run: python -m build
       - name: Archive packages
-        uses: actions/upload-artifact@v5
+        uses: actions/upload-artifact@v6
         with:
           name: dist
           path: dist
@@ -38,7 +38,7 @@
       id-token: write
     steps:
       - name: Retrieve packages
-        uses: actions/download-artifact@v6
+        uses: actions/download-artifact@v7
         with:
           name: dist
           path: dist
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/anyio-4.12.0/.github/workflows/test.yml 
new/anyio-4.12.1/.github/workflows/test.yml
--- old/anyio-4.12.0/.github/workflows/test.yml 2025-11-29 00:36:29.000000000 
+0100
+++ new/anyio-4.12.1/.github/workflows/test.yml 2026-01-06 12:44:46.000000000 
+0100
@@ -2,7 +2,7 @@
 
 on:
   push:
-    branches: [master]
+    branches: [master, "4.12.x"]
   pull_request:
 
 jobs:
@@ -47,7 +47,7 @@
       uses: actions/setup-python@v6
       with:
         python-version: 3.x
-    - uses: actions/cache@v4
+    - uses: actions/cache@v5
       with:
         path: ~/.cache/pip
         key: pip-pyright
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/anyio-4.12.0/.pre-commit-config.yaml 
new/anyio-4.12.1/.pre-commit-config.yaml
--- old/anyio-4.12.0/.pre-commit-config.yaml    2025-11-29 00:36:29.000000000 
+0100
+++ new/anyio-4.12.1/.pre-commit-config.yaml    2026-01-06 12:44:46.000000000 
+0100
@@ -29,14 +29,14 @@
           - tomli
 
   - repo: https://github.com/astral-sh/ruff-pre-commit
-    rev: v0.14.6
+    rev: v0.14.10
     hooks:
       - id: ruff-check
         args: [--fix, --show-fixes]
       - id: ruff-format
 
   - repo: https://github.com/pre-commit/mirrors-mypy
-    rev: v1.18.2
+    rev: v1.19.1
     hooks:
       - id: mypy
         additional_dependencies:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/anyio-4.12.0/PKG-INFO new/anyio-4.12.1/PKG-INFO
--- old/anyio-4.12.0/PKG-INFO   2025-11-29 00:36:34.603323500 +0100
+++ new/anyio-4.12.1/PKG-INFO   2026-01-06 12:44:54.663241600 +0100
@@ -1,6 +1,6 @@
 Metadata-Version: 2.4
 Name: anyio
-Version: 4.12.0
+Version: 4.12.1
 Summary: High-level concurrency and networking framework on top of asyncio or 
Trio
 Author-email: Alex Grönholm <[email protected]>
 License-Expression: MIT
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/anyio-4.12.0/docs/versionhistory.rst 
new/anyio-4.12.1/docs/versionhistory.rst
--- old/anyio-4.12.0/docs/versionhistory.rst    2025-11-29 00:36:29.000000000 
+0100
+++ new/anyio-4.12.1/docs/versionhistory.rst    2026-01-06 12:44:46.000000000 
+0100
@@ -3,6 +3,14 @@
 
 This library adheres to `Semantic Versioning 2.0 <http://semver.org/>`_.
 
+**4.12.1**
+
+- Changed all functions currently raising the private ``NoCurrentAsyncBackend``
+  exception (since v4.12.0) to instead raise the public ``NoEventLoopError`` 
exception
+  (`#1048 <https://github.com/agronholm/anyio/issues/1048>`_)
+- Fixed ``anyio.functools.lru_cache`` not working with instance methods
+  (`#1042 <https://github.com/agronholm/anyio/issues/1042>`_)
+
 **4.12.0**
 
 - Added support for asyncio's `task call graphs`_ on Python 3.14 and later 
when using
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/anyio-4.12.0/src/anyio/_backends/_asyncio.py 
new/anyio-4.12.1/src/anyio/_backends/_asyncio.py
--- old/anyio-4.12.0/src/anyio/_backends/_asyncio.py    2025-11-29 
00:36:29.000000000 +0100
+++ new/anyio-4.12.1/src/anyio/_backends/_asyncio.py    2026-01-06 
12:44:46.000000000 +0100
@@ -1015,29 +1015,6 @@
 _threadpool_workers: RunVar[set[WorkerThread]] = RunVar("_threadpool_workers")
 
 
-class BlockingPortal(abc.BlockingPortal):
-    def __new__(cls) -> BlockingPortal:
-        return object.__new__(cls)
-
-    def __init__(self) -> None:
-        super().__init__()
-        self._loop = get_running_loop()
-
-    def _spawn_task_from_thread(
-        self,
-        func: Callable[[Unpack[PosArgsT]], Awaitable[T_Retval] | T_Retval],
-        args: tuple[Unpack[PosArgsT]],
-        kwargs: dict[str, Any],
-        name: object,
-        future: Future[T_Retval],
-    ) -> None:
-        AsyncIOBackend.run_sync_from_thread(
-            partial(self._task_group.start_soon, name=name),
-            (self._call_func, func, args, kwargs, future),
-            self._loop,
-        )
-
-
 #
 # Subprocesses
 #
@@ -2599,10 +2576,6 @@
         return f.result()
 
     @classmethod
-    def create_blocking_portal(cls) -> abc.BlockingPortal:
-        return BlockingPortal()
-
-    @classmethod
     async def open_process(
         cls,
         command: StrOrBytesPath | Sequence[StrOrBytesPath],
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/anyio-4.12.0/src/anyio/_backends/_trio.py 
new/anyio-4.12.1/src/anyio/_backends/_trio.py
--- old/anyio-4.12.0/src/anyio/_backends/_trio.py       2025-11-29 
00:36:29.000000000 +0100
+++ new/anyio-4.12.1/src/anyio/_backends/_trio.py       2026-01-06 
12:44:46.000000000 +0100
@@ -17,10 +17,8 @@
     Iterable,
     Sequence,
 )
-from concurrent.futures import Future
 from contextlib import AbstractContextManager
 from dataclasses import dataclass
-from functools import partial
 from io import IOBase
 from os import PathLike
 from signal import Signals
@@ -225,38 +223,6 @@
 
 
 #
-# Threads
-#
-
-
-class BlockingPortal(abc.BlockingPortal):
-    def __new__(cls) -> BlockingPortal:
-        return object.__new__(cls)
-
-    def __init__(self) -> None:
-        super().__init__()
-        self._token = trio.lowlevel.current_trio_token()
-
-    def _spawn_task_from_thread(
-        self,
-        func: Callable[[Unpack[PosArgsT]], Awaitable[T_Retval] | T_Retval],
-        args: tuple[Unpack[PosArgsT]],
-        kwargs: dict[str, Any],
-        name: object,
-        future: Future[T_Retval],
-    ) -> None:
-        trio.from_thread.run_sync(
-            partial(self._task_group.start_soon, name=name),
-            self._call_func,
-            func,
-            args,
-            kwargs,
-            future,
-            trio_token=self._token,
-        )
-
-
-#
 # Subprocesses
 #
 
@@ -1115,10 +1081,6 @@
             raise RunFinishedError from None
 
     @classmethod
-    def create_blocking_portal(cls) -> abc.BlockingPortal:
-        return BlockingPortal()
-
-    @classmethod
     async def open_process(
         cls,
         command: StrOrBytesPath | Sequence[StrOrBytesPath],
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/anyio-4.12.0/src/anyio/_core/_eventloop.py 
new/anyio-4.12.1/src/anyio/_core/_eventloop.py
--- old/anyio-4.12.0/src/anyio/_core/_eventloop.py      2025-11-29 
00:36:29.000000000 +0100
+++ new/anyio-4.12.1/src/anyio/_core/_eventloop.py      2026-01-06 
12:44:46.000000000 +0100
@@ -9,6 +9,8 @@
 from importlib import import_module
 from typing import TYPE_CHECKING, Any, TypeVar
 
+from ._exceptions import NoEventLoopError
+
 if sys.version_info >= (3, 11):
     from typing import TypeVarTuple, Unpack
 else:
@@ -118,6 +120,8 @@
     Return the current value of the event loop's internal clock.
 
     :return: the clock value (seconds)
+    :raises NoEventLoopError: if no supported asynchronous event loop is 
running in the
+        current thread
 
     """
     return get_async_backend().current_time()
@@ -150,7 +154,13 @@
 
 
 def get_cancelled_exc_class() -> type[BaseException]:
-    """Return the current async library's cancellation exception class."""
+    """
+    Return the current async library's cancellation exception class.
+
+    :raises NoEventLoopError: if no supported asynchronous event loop is 
running in the
+        current thread
+
+    """
     return get_async_backend().cancelled_exception_class()
 
 
@@ -172,19 +182,14 @@
         del threadlocals.current_token
 
 
-class NoCurrentAsyncBackend(Exception):
-    def __init__(self) -> None:
-        super().__init__(
-            f"Not currently running on any asynchronous event loop"
-            f"Available async backends: {', '.join(get_all_backends())}"
-        )
-
-
 def get_async_backend(asynclib_name: str | None = None) -> type[AsyncBackend]:
     if asynclib_name is None:
         asynclib_name = current_async_library()
         if not asynclib_name:
-            raise NoCurrentAsyncBackend
+            raise NoEventLoopError(
+                f"Not currently running on any asynchronous event loop. "
+                f"Available async backends: {', '.join(get_all_backends())}"
+            )
 
     # We use our own dict instead of sys.modules to get the already imported 
back-end
     # class because the appropriate modules in sys.modules could potentially 
be only
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/anyio-4.12.0/src/anyio/_core/_exceptions.py 
new/anyio-4.12.1/src/anyio/_core/_exceptions.py
--- old/anyio-4.12.0/src/anyio/_core/_exceptions.py     2025-11-29 
00:36:29.000000000 +0100
+++ new/anyio-4.12.1/src/anyio/_core/_exceptions.py     2026-01-06 
12:44:46.000000000 +0100
@@ -136,8 +136,11 @@
 
 class NoEventLoopError(RuntimeError):
     """
-    Raised by :func:`.from_thread.run` and :func:`.from_thread.run_sync` if
-    not calling from an AnyIO worker thread, and no ``token`` was passed.
+    Raised by several functions that require an event loop to be running in 
the current
+    thread when there is no running event loop.
+
+    This is also raised by :func:`.from_thread.run` and 
:func:`.from_thread.run_sync`
+    if not calling from an AnyIO worker thread, and no ``token`` was passed.
     """
 
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/anyio-4.12.0/src/anyio/_core/_signals.py 
new/anyio-4.12.1/src/anyio/_core/_signals.py
--- old/anyio-4.12.0/src/anyio/_core/_signals.py        2025-11-29 
00:36:29.000000000 +0100
+++ new/anyio-4.12.1/src/anyio/_core/_signals.py        2026-01-06 
12:44:46.000000000 +0100
@@ -16,6 +16,8 @@
     :param signals: signals to receive (e.g. ``signal.SIGINT``)
     :return: an asynchronous context manager for an asynchronous iterator 
which yields
         signal numbers
+    :raises NoEventLoopError: if no supported asynchronous event loop is 
running in the
+        current thread
 
     .. warning:: Windows does not support signals natively so it is best to 
avoid
         relying on this in cross-platform applications.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/anyio-4.12.0/src/anyio/_core/_sockets.py 
new/anyio-4.12.1/src/anyio/_core/_sockets.py
--- old/anyio-4.12.0/src/anyio/_core/_sockets.py        2025-11-29 
00:36:29.000000000 +0100
+++ new/anyio-4.12.1/src/anyio/_core/_sockets.py        2026-01-06 
12:44:46.000000000 +0100
@@ -664,6 +664,8 @@
     :param sockaddr: socket address (e.g. (ipaddress, port) for IPv4)
     :param flags: flags to pass to upstream ``getnameinfo()``
     :return: a tuple of (host name, service name)
+    :raises NoEventLoopError: if no supported asynchronous event loop is 
running in the
+        current thread
 
     .. seealso:: :func:`socket.getnameinfo`
 
@@ -687,6 +689,8 @@
         socket to become readable
     :raises ~anyio.BusyResourceError: if another task is already waiting for 
the socket
         to become readable
+    :raises NoEventLoopError: if no supported asynchronous event loop is 
running in the
+        current thread
 
     """
     return get_async_backend().wait_readable(sock.fileno())
@@ -711,6 +715,8 @@
         socket to become writable
     :raises ~anyio.BusyResourceError: if another task is already waiting for 
the socket
         to become writable
+    :raises NoEventLoopError: if no supported asynchronous event loop is 
running in the
+        current thread
 
     """
     return get_async_backend().wait_writable(sock.fileno())
@@ -742,6 +748,8 @@
         object to become readable
     :raises ~anyio.BusyResourceError: if another task is already waiting for 
the object
         to become readable
+    :raises NoEventLoopError: if no supported asynchronous event loop is 
running in the
+        current thread
 
     """
     return get_async_backend().wait_readable(obj)
@@ -756,6 +764,8 @@
         object to become writable
     :raises ~anyio.BusyResourceError: if another task is already waiting for 
the object
         to become writable
+    :raises NoEventLoopError: if no supported asynchronous event loop is 
running in the
+        current thread
 
     .. seealso:: See the documentation of :func:`wait_readable` for the 
definition of
        ``obj`` and notes on backend compatibility.
@@ -792,6 +802,8 @@
     in anyway.
 
     :param obj: an object with a ``.fileno()`` method or an integer handle
+    :raises NoEventLoopError: if no supported asynchronous event loop is 
running in the
+        current thread
 
     """
     get_async_backend().notify_closing(obj)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/anyio-4.12.0/src/anyio/_core/_synchronization.py 
new/anyio-4.12.1/src/anyio/_core/_synchronization.py
--- old/anyio-4.12.0/src/anyio/_core/_synchronization.py        2025-11-29 
00:36:29.000000000 +0100
+++ new/anyio-4.12.1/src/anyio/_core/_synchronization.py        2026-01-06 
12:44:46.000000000 +0100
@@ -8,8 +8,8 @@
 from typing import TypeVar
 
 from ..lowlevel import checkpoint_if_cancelled
-from ._eventloop import NoCurrentAsyncBackend, get_async_backend
-from ._exceptions import BusyResourceError
+from ._eventloop import get_async_backend
+from ._exceptions import BusyResourceError, NoEventLoopError
 from ._tasks import CancelScope
 from ._testing import TaskInfo, get_current_task
 
@@ -83,7 +83,7 @@
     def __new__(cls) -> Event:
         try:
             return get_async_backend().create_event()
-        except NoCurrentAsyncBackend:
+        except NoEventLoopError:
             return EventAdapter()
 
     def set(self) -> None:
@@ -151,7 +151,7 @@
     def __new__(cls, *, fast_acquire: bool = False) -> Lock:
         try:
             return get_async_backend().create_lock(fast_acquire=fast_acquire)
-        except NoCurrentAsyncBackend:
+        except NoEventLoopError:
             return LockAdapter(fast_acquire=fast_acquire)
 
     async def __aenter__(self) -> None:
@@ -378,7 +378,7 @@
             return get_async_backend().create_semaphore(
                 initial_value, max_value=max_value, fast_acquire=fast_acquire
             )
-        except NoCurrentAsyncBackend:
+        except NoEventLoopError:
             return SemaphoreAdapter(initial_value, max_value=max_value)
 
     def __init__(
@@ -513,7 +513,7 @@
     def __new__(cls, total_tokens: float) -> CapacityLimiter:
         try:
             return get_async_backend().create_capacity_limiter(total_tokens)
-        except NoCurrentAsyncBackend:
+        except NoEventLoopError:
             return CapacityLimiterAdapter(total_tokens)
 
     async def __aenter__(self) -> None:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/anyio-4.12.0/src/anyio/_core/_tasks.py 
new/anyio-4.12.1/src/anyio/_core/_tasks.py
--- old/anyio-4.12.0/src/anyio/_core/_tasks.py  2025-11-29 00:36:29.000000000 
+0100
+++ new/anyio-4.12.1/src/anyio/_core/_tasks.py  2026-01-06 12:44:46.000000000 
+0100
@@ -23,6 +23,8 @@
 
     :param deadline: The time (clock value) when this scope is cancelled 
automatically
     :param shield: ``True`` to shield the cancel scope from external 
cancellation
+    :raises NoEventLoopError: if no supported asynchronous event loop is 
running in the
+        current thread
     """
 
     def __new__(
@@ -110,6 +112,8 @@
     :param shield: ``True`` to shield the cancel scope from external 
cancellation
     :return: a context manager that yields a cancel scope
     :rtype: :class:`~typing.ContextManager`\\[:class:`~anyio.CancelScope`\\]
+    :raises NoEventLoopError: if no supported asynchronous event loop is 
running in the
+        current thread
 
     """
     current_time = get_async_backend().current_time
@@ -131,6 +135,8 @@
         ``None`` to disable the timeout
     :param shield: ``True`` to shield the cancel scope from external 
cancellation
     :return: a cancel scope
+    :raises NoEventLoopError: if no supported asynchronous event loop is 
running in the
+        current thread
 
     """
     deadline = (
@@ -148,6 +154,8 @@
         there is no deadline in effect, or ``float('-inf')`` if the current 
scope has
         been cancelled)
     :rtype: float
+    :raises NoEventLoopError: if no supported asynchronous event loop is 
running in the
+        current thread
 
     """
     return get_async_backend().current_effective_deadline()
@@ -158,6 +166,8 @@
     Create a task group.
 
     :return: a task group
+    :raises NoEventLoopError: if no supported asynchronous event loop is 
running in the
+        current thread
 
     """
     return get_async_backend().create_task_group()
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/anyio-4.12.0/src/anyio/_core/_testing.py 
new/anyio-4.12.1/src/anyio/_core/_testing.py
--- old/anyio-4.12.0/src/anyio/_core/_testing.py        2025-11-29 
00:36:29.000000000 +0100
+++ new/anyio-4.12.1/src/anyio/_core/_testing.py        2026-01-06 
12:44:46.000000000 +0100
@@ -58,6 +58,8 @@
     Return the current task.
 
     :return: a representation of the current task
+    :raises NoEventLoopError: if no supported asynchronous event loop is 
running in the
+        current thread
 
     """
     return get_async_backend().get_current_task()
@@ -68,6 +70,8 @@
     Return a list of running tasks in the current event loop.
 
     :return: a list of task info objects
+    :raises NoEventLoopError: if no supported asynchronous event loop is 
running in the
+        current thread
 
     """
     return cast("list[TaskInfo]", get_async_backend().get_running_tasks())
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/anyio-4.12.0/src/anyio/abc/_eventloop.py 
new/anyio-4.12.1/src/anyio/abc/_eventloop.py
--- old/anyio-4.12.0/src/anyio/abc/_eventloop.py        2025-11-29 
00:36:29.000000000 +0100
+++ new/anyio-4.12.1/src/anyio/abc/_eventloop.py        2026-01-06 
12:44:46.000000000 +0100
@@ -33,7 +33,6 @@
     from .._core._synchronization import CapacityLimiter, Event, Lock, 
Semaphore
     from .._core._tasks import CancelScope
     from .._core._testing import TaskInfo
-    from ..from_thread import BlockingPortal
     from ._sockets import (
         ConnectedUDPSocket,
         ConnectedUNIXDatagramSocket,
@@ -232,11 +231,6 @@
         pass
 
     @classmethod
-    @abstractmethod
-    def create_blocking_portal(cls) -> BlockingPortal:
-        pass
-
-    @classmethod
     @abstractmethod
     async def open_process(
         cls,
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/anyio-4.12.0/src/anyio/from_thread.py 
new/anyio-4.12.1/src/anyio/from_thread.py
--- old/anyio-4.12.0/src/anyio/from_thread.py   2025-11-29 00:36:29.000000000 
+0100
+++ new/anyio-4.12.1/src/anyio/from_thread.py   2026-01-06 12:44:46.000000000 
+0100
@@ -18,6 +18,7 @@
     contextmanager,
 )
 from dataclasses import dataclass, field
+from functools import partial
 from inspect import isawaitable
 from threading import Lock, Thread, current_thread, get_ident
 from types import TracebackType
@@ -30,7 +31,6 @@
 )
 
 from ._core._eventloop import (
-    get_async_backend,
     get_cancelled_exc_class,
     threadlocals,
 )
@@ -39,7 +39,7 @@
 from ._core._synchronization import Event
 from ._core._tasks import CancelScope, create_task_group
 from .abc._tasks import TaskStatus
-from .lowlevel import EventLoopToken
+from .lowlevel import EventLoopToken, current_token
 
 if sys.version_info >= (3, 11):
     from typing import TypeVarTuple, Unpack
@@ -183,16 +183,18 @@
 
 
 class BlockingPortal:
-    """An object that lets external threads run code in an asynchronous event 
loop."""
+    """
+    An object that lets external threads run code in an asynchronous event 
loop.
 
-    def __new__(cls) -> BlockingPortal:
-        return get_async_backend().create_blocking_portal()
+    :raises NoEventLoopError: if no supported asynchronous event loop is 
running in the
+        current thread
+    """
 
     def __init__(self) -> None:
+        self._token = current_token()
         self._event_loop_thread_id: int | None = get_ident()
         self._stop_event = Event()
         self._task_group = create_task_group()
-        self._cancelled_exc_class = get_cancelled_exc_class()
 
     async def __aenter__(self) -> BlockingPortal:
         await self._task_group.__aenter__()
@@ -257,7 +259,7 @@
                     retval = await retval_or_awaitable
             else:
                 retval = retval_or_awaitable
-        except self._cancelled_exc_class:
+        except get_cancelled_exc_class():
             future.cancel()
             future.set_running_or_notify_cancel()
         except BaseException as exc:
@@ -284,8 +286,6 @@
         """
         Spawn a new task using the given callable.
 
-        Implementers must ensure that the future is resolved when the task 
finishes.
-
         :param func: a callable
         :param args: positional arguments to be passed to the callable
         :param kwargs: keyword arguments to be passed to the callable
@@ -294,7 +294,15 @@
             or the exception raised during its execution
 
         """
-        raise NotImplementedError
+        run_sync(
+            partial(self._task_group.start_soon, name=name),
+            self._call_func,
+            func,
+            args,
+            kwargs,
+            future,
+            token=self._token,
+        )
 
     @overload
     def call(
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/anyio-4.12.0/src/anyio/functools.py 
new/anyio-4.12.1/src/anyio/functools.py
--- old/anyio-4.12.0/src/anyio/functools.py     2025-11-29 00:36:29.000000000 
+0100
+++ new/anyio-4.12.1/src/anyio/functools.py     2026-01-06 12:44:46.000000000 
+0100
@@ -73,11 +73,32 @@
     always_checkpoint: bool
 
 
+class _LRUMethodWrapper(Generic[T]):
+    def __init__(self, wrapper: AsyncLRUCacheWrapper[..., T], instance: 
object):
+        self.__wrapper = wrapper
+        self.__instance = instance
+
+    def cache_info(self) -> AsyncCacheInfo:
+        return self.__wrapper.cache_info()
+
+    def cache_parameters(self) -> AsyncCacheParameters:
+        return self.__wrapper.cache_parameters()
+
+    def cache_clear(self) -> None:
+        self.__wrapper.cache_clear()
+
+    async def __call__(self, *args: Any, **kwargs: Any) -> T:
+        if self.__instance is None:
+            return await self.__wrapper(*args, **kwargs)
+
+        return await self.__wrapper(self.__instance, *args, **kwargs)
+
+
 @final
 class AsyncLRUCacheWrapper(Generic[P, T]):
     def __init__(
         self,
-        func: Callable[..., Awaitable[T]],
+        func: Callable[P, Awaitable[T]],
         maxsize: int | None,
         typed: bool,
         always_checkpoint: bool,
@@ -174,6 +195,13 @@
 
         return value
 
+    def __get__(
+        self, instance: object, owner: type | None = None
+    ) -> _LRUMethodWrapper[T]:
+        wrapper = _LRUMethodWrapper(self, instance)
+        update_wrapper(wrapper, self.__wrapped__)
+        return wrapper
+
 
 class _LRUCacheWrapper(Generic[T]):
     def __init__(self, maxsize: int | None, typed: bool, always_checkpoint: 
bool):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/anyio-4.12.0/src/anyio/lowlevel.py 
new/anyio-4.12.1/src/anyio/lowlevel.py
--- old/anyio-4.12.0/src/anyio/lowlevel.py      2025-11-29 00:36:29.000000000 
+0100
+++ new/anyio-4.12.1/src/anyio/lowlevel.py      2026-01-06 12:44:46.000000000 
+0100
@@ -32,7 +32,6 @@
         await checkpoint_if_cancelled()
         await cancel_shielded_checkpoint()
 
-
     .. versionadded:: 3.0
 
     """
@@ -60,7 +59,6 @@
         with CancelScope(shield=True):
             await checkpoint()
 
-
     .. versionadded:: 3.0
 
     """
@@ -85,6 +83,9 @@
     Return a token object that can be used to call code in the current event 
loop from
     another thread.
 
+    :raises NoEventLoopError: if no supported asynchronous event loop is 
running in the
+        current thread
+
     .. versionadded:: 4.11.0
 
     """
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/anyio-4.12.0/src/anyio/to_process.py 
new/anyio-4.12.1/src/anyio/to_process.py
--- old/anyio-4.12.0/src/anyio/to_process.py    2025-11-29 00:36:29.000000000 
+0100
+++ new/anyio-4.12.1/src/anyio/to_process.py    2026-01-06 12:44:46.000000000 
+0100
@@ -60,6 +60,8 @@
         running
     :param limiter: capacity limiter to use to limit the total amount of 
processes
         running (if omitted, the default limiter is used)
+    :raises NoEventLoopError: if no supported asynchronous event loop is 
running in the
+        current thread
     :return: an awaitable that yields the return value of the function.
 
     """
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/anyio-4.12.0/src/anyio/to_thread.py 
new/anyio-4.12.1/src/anyio/to_thread.py
--- old/anyio-4.12.0/src/anyio/to_thread.py     2025-11-29 00:36:29.000000000 
+0100
+++ new/anyio-4.12.1/src/anyio/to_thread.py     2026-01-06 12:44:46.000000000 
+0100
@@ -46,6 +46,8 @@
         ``abandon_on_cancel`` if both parameters are passed
     :param limiter: capacity limiter to use to limit the total amount of 
threads running
         (if omitted, the default limiter is used)
+    :raises NoEventLoopError: if no supported asynchronous event loop is 
running in the
+        current thread
     :return: an awaitable that yields the return value of the function.
 
     """
@@ -69,6 +71,8 @@
     concurrent threads.
 
     :return: a capacity limiter object
+    :raises NoEventLoopError: if no supported asynchronous event loop is 
running in the
+        current thread
 
     """
     return get_async_backend().current_default_thread_limiter()
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/anyio-4.12.0/src/anyio.egg-info/PKG-INFO 
new/anyio-4.12.1/src/anyio.egg-info/PKG-INFO
--- old/anyio-4.12.0/src/anyio.egg-info/PKG-INFO        2025-11-29 
00:36:34.000000000 +0100
+++ new/anyio-4.12.1/src/anyio.egg-info/PKG-INFO        2026-01-06 
12:44:54.000000000 +0100
@@ -1,6 +1,6 @@
 Metadata-Version: 2.4
 Name: anyio
-Version: 4.12.0
+Version: 4.12.1
 Summary: High-level concurrency and networking framework on top of asyncio or 
Trio
 Author-email: Alex Grönholm <[email protected]>
 License-Expression: MIT
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/anyio-4.12.0/tests/test_functools.py 
new/anyio-4.12.1/tests/test_functools.py
--- old/anyio-4.12.0/tests/test_functools.py    2025-11-29 00:36:29.000000000 
+0100
+++ new/anyio-4.12.1/tests/test_functools.py    2026-01-06 12:44:46.000000000 
+0100
@@ -14,7 +14,13 @@
     get_cancelled_exc_class,
     wait_all_tasks_blocked,
 )
-from anyio.functools import cache, lru_cache, reduce
+from anyio.functools import (
+    AsyncLRUCacheWrapper,
+    _LRUMethodWrapper,
+    cache,
+    lru_cache,
+    reduce,
+)
 from anyio.lowlevel import checkpoint
 
 
@@ -266,6 +272,63 @@
             scope.cancel()
             await func(1)
 
+    @staticmethod
+    async def _do_cache_asserts(
+        wrapper: _LRUMethodWrapper[int] | AsyncLRUCacheWrapper[..., int],
+    ) -> None:
+        assert wrapper.cache_parameters() == {
+            "always_checkpoint": False,
+            "maxsize": 128,
+            "typed": False,
+        }
+        statistics = wrapper.cache_info()
+        assert statistics.hits == 2
+        assert statistics.misses == 2
+
+        wrapper.cache_clear()
+        statistics = wrapper.cache_info()
+        assert statistics.hits == 0
+        assert statistics.misses == 0
+
+    async def test_cached_static_method(self) -> None:
+        class Foo:
+            @staticmethod
+            @lru_cache
+            async def static_method(x: int) -> int:
+                return x
+
+        for _ in range(2):
+            assert await Foo.static_method(1) == 1
+            assert await Foo.static_method(2) == 2
+
+        await self._do_cache_asserts(Foo.static_method)
+
+    async def test_cached_class_method(self) -> None:
+        class Foo:
+            @classmethod
+            @lru_cache
+            async def cls_method(cls, x: int) -> int:
+                return x
+
+        for _ in range(2):
+            assert await Foo.cls_method(1) == 1
+            assert await Foo.cls_method(2) == 2
+
+        await self._do_cache_asserts(Foo.cls_method)
+
+    async def test_cached_instance_method(self) -> None:
+        class Foo:
+            @lru_cache
+            async def instance_method(self, x: int) -> int:
+                return x
+
+        foo = Foo()
+        for _ in range(2):
+            assert await foo.instance_method(1) == 1
+            assert await foo.instance_method(2) == 2
+
+        await self._do_cache_asserts(Foo().instance_method)
+
 
 class TestReduce:
     async def test_not_iterable(self) -> None:

Reply via email to