chore: replace calls to asyncio.iscoroutinefunction
What changed, and why it matters
This is a routine cleanup change that swaps one Python built-in check for an equivalent one. It removes future deprecation warnings but does not change what the program actually does or fix any security problem.
No security action needed. Treat as normal maintenance.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The commit replaces all uses of asyncio.iscoroutinefunction() with inspect.iscoroutinefunction(). The two functions are semantically equivalent for detecting coroutine functions. The change only affects type/introspection checks and assertions; no runtime behavior, logic, or trust boundary is altered. It is purely a forward-compatibility chore for Python 3.14/3.16.
Changed components
electrum/commands.pyelectrum/gui/qt/main_window.pyelectrum/lnpeer.pyelectrum/util.pyInspect captured patch +12 / −9
diff --git a/electrum/commands.py b/electrum/commands.py
index a886197..9d2cf9a 100644
--- a/electrum/commands.py
+++ b/electrum/commands.py
@@ -2204,7 +2204,7 @@ def plugin_command(s, plugin_name):
name = plugin_name + '_' + func.__name__
if name in known_commands or hasattr(Commands, name):
raise Exception(f"Command name {name} already exists. Plugin commands should not overwrite other commands.")
- assert asyncio.iscoroutinefunction(func), f"Plugin commands must be a coroutine: {name}"
+ assert inspect.iscoroutinefunction(func), f"Plugin commands must be a coroutine: {name}"
@command(s)
@wraps(func)
diff --git a/electrum/gui/qt/main_window.py b/electrum/gui/qt/main_window.py
index a2aaf6a..45783aa 100644
--- a/electrum/gui/qt/main_window.py
+++ b/electrum/gui/qt/main_window.py
@@ -36,6 +36,7 @@ import queue
import asyncio
from typing import Optional, TYPE_CHECKING, Sequence, Union, Dict, Mapping, Callable, List, Set
import concurrent.futures
+import inspect
from PyQt6.QtGui import QPixmap, QKeySequence, QIcon, QCursor, QFont, QFontMetrics, QAction, QShortcut
from PyQt6.QtCore import Qt, QRect, QStringListModel, QSize, pyqtSignal, QTimer
@@ -2734,7 +2735,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger, QtEventListener):
Registers a callback that will be called when the wallet is closed. If the callback
returns a string it will be shown to the user as a warning to prevent them closing the wallet.
"""
- assert not asyncio.iscoroutinefunction(callback)
+ assert not inspect.iscoroutinefunction(callback)
def warning_callback() -> Optional[str]:
try:
return callback()
diff --git a/electrum/lnpeer.py b/electrum/lnpeer.py
index bad460b..ea0d16e 100644
--- a/electrum/lnpeer.py
+++ b/electrum/lnpeer.py
@@ -13,6 +13,7 @@ from typing import Tuple, Dict, TYPE_CHECKING, Optional, Union, Set, Callable, A
from datetime import datetime
import functools
from functools import partial
+import inspect
import electrum_ecc as ecc
from electrum_ecc import ecdsa_sig64_from_r_and_s, ecdsa_der_sig_from_ecdsa_sig64, ECPubkey
@@ -256,7 +257,7 @@ class Peer(Logger, EventListener):
payload['sender_node_id'] = self.pubkey
# note: the message handler might be async or non-async. In either case, by default,
# we wait for it to complete before we return, i.e. before the next message is processed.
- if asyncio.iscoroutinefunction(f):
+ if inspect.iscoroutinefunction(f):
async with AsyncHangDetector(
message=f"message handler still running for {message_type.upper()}",
logger=self.logger,
@@ -269,7 +270,7 @@ class Peer(Logger, EventListener):
"""Makes a message handler non-blocking: while processing the message,
the message_loop keeps processing subsequent incoming messages asynchronously.
"""
- assert asyncio.iscoroutinefunction(func), 'func needs to be a coroutine'
+ assert inspect.iscoroutinefunction(func), 'func needs to be a coroutine'
@functools.wraps(func)
async def wrapper(self: 'Peer', *args, **kwargs):
return await self.taskgroup.spawn(func(self, *args, **kwargs))
diff --git a/electrum/util.py b/electrum/util.py
index 0bad5f5..748c1c8 100644
--- a/electrum/util.py
+++ b/electrum/util.py
@@ -54,6 +54,7 @@ from abc import abstractmethod, ABC
import enum
from contextlib import nullcontext
import traceback
+import inspect
import aiohttp
from aiohttp_socks import ProxyConnector, ProxyType
@@ -504,7 +505,7 @@ def profiler(func=None, *, min_threshold: Union[int, float, None] = None):
if min_threshold is None or t > min_threshold:
_profiler_logger.debug(f"{func.__qualname__} {t:,.4f} sec")
- if asyncio.iscoroutinefunction(func):
+ if inspect.iscoroutinefunction(func):
async def do_profile(*args, **kw_args):
timer_start()
o = await func(*args, **kw_args)
@@ -1222,7 +1223,7 @@ def is_subpath(long_path: str, short_path: str) -> bool:
def log_exceptions(func):
"""Decorator to log AND re-raise exceptions."""
- assert asyncio.iscoroutinefunction(func), 'func needs to be a coroutine'
+ assert inspect.iscoroutinefunction(func), 'func needs to be a coroutine'
@functools.wraps(func)
async def wrapper(*args, **kwargs):
@@ -1243,7 +1244,7 @@ def log_exceptions(func):
def ignore_exceptions(func):
"""Decorator to silently swallow all exceptions."""
- assert asyncio.iscoroutinefunction(func), 'func needs to be a coroutine'
+ assert inspect.iscoroutinefunction(func), 'func needs to be a coroutine'
@functools.wraps(func)
async def wrapper(*args, **kwargs):
@@ -1761,7 +1762,7 @@ def run_sync_function_on_asyncio_thread(func: Callable[[], Any], *, block: bool)
For any other thread, we only wait for completion if `block` is True.
"""
- assert not asyncio.iscoroutinefunction(func), "func must be a non-async function"
+ assert not inspect.iscoroutinefunction(func), "func must be a non-async function"
asyncio_loop = get_asyncio_loop()
if get_running_loop() == asyncio_loop: # we are running on the asyncio thread
func()
@@ -1964,7 +1965,7 @@ class CallbackManager(Logger):
with self.callback_lock:
callbacks = self.callbacks[event][:]
for callback in callbacks:
- if asyncio.iscoroutinefunction(callback): # async cb
+ if inspect.iscoroutinefunction(callback): # async cb
fut = asyncio.run_coroutine_threadsafe(callback(*args), loop)
def on_done(fut_: concurrent.futures.Future):
Why this scored 15/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.