Use iscoroutinefunction from inspect instead of asyncio on 3.12+

This commit is contained in:
Jakub Kuczys
2026-04-14 14:46:19 -04:00
committed by GitHub
parent 85144ec5e4
commit 3270121c80
12 changed files with 59 additions and 44 deletions
+18 -9
View File
@@ -58,7 +58,16 @@ from ..message import Message
from ..user import User
from ..member import Member
from ..permissions import Permissions
from ..utils import resolve_annotation, MISSING, is_inside_class, maybe_coroutine, async_all, _shorten, _to_kebab_case
from ..utils import (
resolve_annotation,
MISSING,
is_inside_class,
maybe_coroutine,
async_all,
_iscoroutinefunction,
_shorten,
_to_kebab_case,
)
if TYPE_CHECKING:
from typing_extensions import ParamSpec, Concatenate, Unpack
@@ -346,7 +355,7 @@ def _populate_autocomplete(params: Dict[str, CommandParameter], autocomplete: Di
if callback is MISSING:
continue
if not inspect.iscoroutinefunction(callback):
if not _iscoroutinefunction(callback):
raise TypeError('autocomplete callback must be a coroutine function')
if param.type not in (AppCommandOptionType.string, AppCommandOptionType.number, AppCommandOptionType.integer):
@@ -1037,7 +1046,7 @@ class Command(Generic[GroupT, P, T]):
The coroutine passed is not actually a coroutine.
"""
if not inspect.iscoroutinefunction(coro):
if not _iscoroutinefunction(coro):
raise TypeError('The error handler must be a coroutine.')
self.on_error = coro
@@ -1098,7 +1107,7 @@ class Command(Generic[GroupT, P, T]):
"""
def decorator(coro: AutocompleteCallback[GroupT, ChoiceT]) -> AutocompleteCallback[GroupT, ChoiceT]:
if not inspect.iscoroutinefunction(coro):
if not _iscoroutinefunction(coro):
raise TypeError('The autocomplete callback must be a coroutine function.')
try:
@@ -1347,7 +1356,7 @@ class ContextMenu:
The coroutine passed is not actually a coroutine.
"""
if not inspect.iscoroutinefunction(coro):
if not _iscoroutinefunction(coro):
raise TypeError('The error handler must be a coroutine.')
self.on_error = coro
@@ -1840,7 +1849,7 @@ class Group:
The coroutine passed is not actually a coroutine, or is an invalid coroutine.
"""
if not inspect.iscoroutinefunction(coro):
if not _iscoroutinefunction(coro):
raise TypeError('The error handler must be a coroutine.')
params = inspect.signature(coro).parameters
@@ -1990,7 +1999,7 @@ class Group:
"""
def decorator(func: CommandCallback[GroupT, P, T]) -> Command[GroupT, P, T]:
if not inspect.iscoroutinefunction(func):
if not _iscoroutinefunction(func):
raise TypeError('command function must be a coroutine function')
if description is MISSING:
@@ -2051,7 +2060,7 @@ def command(
"""
def decorator(func: CommandCallback[GroupT, P, T]) -> Command[GroupT, P, T]:
if not inspect.iscoroutinefunction(func):
if not _iscoroutinefunction(func):
raise TypeError('command function must be a coroutine function')
if description is MISSING:
@@ -2123,7 +2132,7 @@ def context_menu(
"""
def decorator(func: ContextMenuCallback) -> ContextMenu:
if not inspect.iscoroutinefunction(func):
if not _iscoroutinefunction(func):
raise TypeError('context menu function must be a coroutine function')
actual_name = func.__name__.title() if name is MISSING else name
+2 -2
View File
@@ -53,7 +53,7 @@ from ..channel import StageChannel, VoiceChannel, TextChannel, CategoryChannel,
from ..abc import GuildChannel
from ..threads import Thread
from ..enums import Enum as InternalEnum, AppCommandOptionType, ChannelType, Locale
from ..utils import MISSING, maybe_coroutine, _human_join, TIMESTAMP_PATTERN
from ..utils import MISSING, maybe_coroutine, _human_join, _iscoroutinefunction, TIMESTAMP_PATTERN
from ..user import User
from ..role import Role
from ..member import Member
@@ -814,7 +814,7 @@ def get_supported_annotation(
params = inspect.signature(transform_classmethod.__func__).parameters
if len(params) != 3:
raise TypeError('Inline transformer with transform classmethod requires 3 parameters')
if not inspect.iscoroutinefunction(transform_classmethod.__func__):
if not _iscoroutinefunction(transform_classmethod.__func__):
raise TypeError('Inline transformer with transform classmethod must be a coroutine')
return (InlineTransformer(annotation), MISSING, False)
+4 -4
View File
@@ -62,7 +62,7 @@ from .installs import AppCommandContext, AppInstallationType
from .translator import Translator, locale_str
from ..errors import ClientException, HTTPException
from ..enums import AppCommandType, InteractionType
from ..utils import MISSING, _get_as_snowflake, _is_submodule, _shorten
from ..utils import MISSING, _get_as_snowflake, _iscoroutinefunction, _is_submodule, _shorten
from .._types import ClientT
@@ -839,7 +839,7 @@ class CommandTree(Generic[ClientT]):
not match the signature.
"""
if not inspect.iscoroutinefunction(coro):
if not _iscoroutinefunction(coro):
raise TypeError('The error handler must be a coroutine.')
params = inspect.signature(coro).parameters
@@ -908,7 +908,7 @@ class CommandTree(Generic[ClientT]):
"""
def decorator(func: CommandCallback[Group, P, T]) -> Command[Group, P, T]:
if not inspect.iscoroutinefunction(func):
if not _iscoroutinefunction(func):
raise TypeError('command function must be a coroutine function')
if description is MISSING:
@@ -1005,7 +1005,7 @@ class CommandTree(Generic[ClientT]):
"""
def decorator(func: ContextMenuCallback) -> ContextMenu:
if not inspect.iscoroutinefunction(func):
if not _iscoroutinefunction(func):
raise TypeError('context menu function must be a coroutine function')
actual_name = func.__name__.title() if name is MISSING else name
+2 -2
View File
@@ -68,7 +68,7 @@ from .voice_client import VoiceClient
from .http import HTTPClient
from .state import ConnectionState
from . import utils
from .utils import MISSING, time_snowflake, deprecated
from .utils import MISSING, time_snowflake, deprecated, _iscoroutinefunction
from .object import Object
from .backoff import ExponentialBackoff
from .webhook import Webhook
@@ -2098,7 +2098,7 @@ class Client:
The coroutine passed is not actually a coroutine.
"""
if not asyncio.iscoroutinefunction(coro):
if not _iscoroutinefunction(coro):
raise TypeError('event registered must be a coroutine function')
setattr(self, coro.__name__, coro)
+4 -5
View File
@@ -25,7 +25,6 @@ DEALINGS IN THE SOFTWARE.
from __future__ import annotations
import asyncio
import collections
import collections.abc
import inspect
@@ -53,7 +52,7 @@ from typing import (
import discord
from discord import app_commands
from discord.app_commands.tree import _retrieve_guild_ids
from discord.utils import MISSING, _is_submodule
from discord.utils import MISSING, _iscoroutinefunction, _is_submodule
from .core import GroupMixin
from .view import StringView
@@ -581,7 +580,7 @@ class BotBase(GroupMixin[None]):
TypeError
The coroutine passed is not actually a coroutine.
"""
if not asyncio.iscoroutinefunction(coro):
if not _iscoroutinefunction(coro):
raise TypeError('The pre-invoke hook must be a coroutine.')
self._before_invoke = coro
@@ -618,7 +617,7 @@ class BotBase(GroupMixin[None]):
TypeError
The coroutine passed is not actually a coroutine.
"""
if not asyncio.iscoroutinefunction(coro):
if not _iscoroutinefunction(coro):
raise TypeError('The post-invoke hook must be a coroutine.')
self._after_invoke = coro
@@ -654,7 +653,7 @@ class BotBase(GroupMixin[None]):
"""
name = func.__name__ if name is MISSING else name
if not asyncio.iscoroutinefunction(func):
if not _iscoroutinefunction(func):
raise TypeError('Listeners must be coroutines')
if name in self.extra_events:
+3 -3
View File
@@ -28,7 +28,7 @@ import inspect
import discord
import logging
from discord import app_commands
from discord.utils import maybe_coroutine, _to_kebab_case
from discord.utils import maybe_coroutine, _iscoroutinefunction, _to_kebab_case
from typing import (
Any,
@@ -233,7 +233,7 @@ class CogMeta(type):
if elem.startswith(('cog_', 'bot_')):
raise TypeError(no_bot_cog.format(base, elem))
cog_app_commands[elem] = value
elif inspect.iscoroutinefunction(value):
elif _iscoroutinefunction(value):
try:
getattr(value, '__cog_listener__')
except AttributeError:
@@ -522,7 +522,7 @@ class Cog(metaclass=CogMeta):
actual = func
if isinstance(actual, staticmethod):
actual = actual.__func__
if not inspect.iscoroutinefunction(actual):
if not _iscoroutinefunction(actual):
raise TypeError('Listener function must be a coroutine function.')
actual.__cog_listener__ = True
to_assign = name or actual.__name__
+7 -7
View File
@@ -427,7 +427,7 @@ class Command(_BaseCommand, Generic[CogT, P, T]):
/,
**kwargs: Unpack[_CommandKwargs],
) -> None:
if not asyncio.iscoroutinefunction(func):
if not discord.utils._iscoroutinefunction(func):
raise TypeError('Callback must be a coroutine.')
name = kwargs.get('name') or func.__name__
@@ -1102,7 +1102,7 @@ class Command(_BaseCommand, Generic[CogT, P, T]):
The coroutine passed is not actually a coroutine.
"""
if not asyncio.iscoroutinefunction(coro):
if not discord.utils._iscoroutinefunction(coro):
raise TypeError('The error handler must be a coroutine.')
self.on_error: Error[CogT, Any] = coro
@@ -1140,7 +1140,7 @@ class Command(_BaseCommand, Generic[CogT, P, T]):
TypeError
The coroutine passed is not actually a coroutine.
"""
if not asyncio.iscoroutinefunction(coro):
if not discord.utils._iscoroutinefunction(coro):
raise TypeError('The pre-invoke hook must be a coroutine.')
self._before_invoke = coro
@@ -1171,7 +1171,7 @@ class Command(_BaseCommand, Generic[CogT, P, T]):
TypeError
The coroutine passed is not actually a coroutine.
"""
if not asyncio.iscoroutinefunction(coro):
if not discord.utils._iscoroutinefunction(coro):
raise TypeError('The post-invoke hook must be a coroutine.')
self._after_invoke = coro
@@ -1945,7 +1945,7 @@ def check(predicate: UserCheck[ContextT], /) -> Check[ContextT]:
return func
if inspect.iscoroutinefunction(predicate):
if discord.utils._iscoroutinefunction(predicate):
decorator.predicate = predicate
else:
@@ -2369,7 +2369,7 @@ def guild_only() -> Check[Any]:
return func
if inspect.iscoroutinefunction(predicate):
if discord.utils._iscoroutinefunction(predicate):
decorator.predicate = predicate
else:
@@ -2444,7 +2444,7 @@ def is_nsfw() -> Check[Any]:
return func
if inspect.iscoroutinefunction(predicate):
if discord.utils._iscoroutinefunction(predicate):
decorator.predicate = predicate
else:
+5 -5
View File
@@ -46,7 +46,7 @@ import inspect
from collections.abc import Sequence
from discord.backoff import ExponentialBackoff
from discord.utils import MISSING
from discord.utils import MISSING, _iscoroutinefunction
_log = logging.getLogger(__name__)
@@ -182,7 +182,7 @@ class Loop(Generic[LF]):
self._last_iteration: datetime.datetime = MISSING
self._next_iteration = None
if not inspect.iscoroutinefunction(self.coro):
if not _iscoroutinefunction(self.coro):
raise TypeError(f'Expected coroutine function, not {type(self.coro).__name__!r}.')
async def _call_loop_function(self, name: str, *args: Any, **kwargs: Any) -> None:
@@ -574,7 +574,7 @@ class Loop(Generic[LF]):
The function was not a coroutine.
"""
if not inspect.iscoroutinefunction(coro):
if not _iscoroutinefunction(coro):
raise TypeError(f'Expected coroutine function, received {coro.__class__.__name__}.')
self._before_loop = coro
@@ -602,7 +602,7 @@ class Loop(Generic[LF]):
The function was not a coroutine.
"""
if not inspect.iscoroutinefunction(coro):
if not _iscoroutinefunction(coro):
raise TypeError(f'Expected coroutine function, received {coro.__class__.__name__}.')
self._after_loop = coro
@@ -632,7 +632,7 @@ class Loop(Generic[LF]):
TypeError
The function was not a coroutine.
"""
if not inspect.iscoroutinefunction(coro):
if not _iscoroutinefunction(coro):
raise TypeError(f'Expected coroutine function, received {coro.__class__.__name__}.')
self._error = coro # type: ignore
+1 -2
View File
@@ -25,7 +25,6 @@ DEALINGS IN THE SOFTWARE.
from __future__ import annotations
import datetime
import inspect
import itertools
from operator import attrgetter
from typing import Any, Awaitable, Callable, Collection, Dict, List, Optional, TYPE_CHECKING, Tuple, TypeVar, Union
@@ -190,7 +189,7 @@ def flatten_user(cls: T) -> T:
# probably a member function by now
def generate_function(x):
# We want sphinx to properly show coroutine functions as coroutines
if inspect.iscoroutinefunction(value):
if utils._iscoroutinefunction(value):
async def general(self, *args, **kwargs): # type: ignore
return await getattr(self._user, x)(*args, **kwargs)
+2 -2
View File
@@ -26,7 +26,6 @@ from __future__ import annotations
import copy
from typing import Callable, Literal, Optional, TYPE_CHECKING, Tuple, TypeVar, Union
import inspect
import os
@@ -34,6 +33,7 @@ from .item import Item, ContainedItemCallbackType as ItemCallbackType, _ItemCall
from ..enums import ButtonStyle, ComponentType
from ..partial_emoji import PartialEmoji, _EmojiTag
from ..components import Button as ButtonComponent
from ..utils import _iscoroutinefunction
__all__ = (
'Button',
@@ -370,7 +370,7 @@ def button(
"""
def decorator(func: ItemCallbackType[S, Button[V]]) -> ItemCallbackType[S, Button[V]]:
if not inspect.iscoroutinefunction(func):
if not _iscoroutinefunction(func):
raise TypeError('button function must be a coroutine function')
func.__discord_ui_model_type__ = Button
+2 -3
View File
@@ -40,14 +40,13 @@ from typing import (
)
from contextvars import ContextVar
import copy
import inspect
import os
from .item import Item, ContainedItemCallbackType as ItemCallbackType, _ItemCallback
from ..enums import ChannelType, ComponentType, SelectDefaultValueType
from ..partial_emoji import PartialEmoji
from ..emoji import Emoji
from ..utils import MISSING, _human_join
from ..utils import MISSING, _human_join, _iscoroutinefunction
from ..components import (
SelectOption,
SelectMenu,
@@ -1209,7 +1208,7 @@ def select(
"""
def decorator(func: ItemCallbackType[S, BaseSelectT]) -> ItemCallbackType[S, BaseSelectT]:
if not inspect.iscoroutinefunction(func):
if not _iscoroutinefunction(func):
raise TypeError('select function must be a coroutine function')
callback_cls = getattr(cls, '__origin__', cls)
if not issubclass(callback_cls, BaseSelect):
+9
View File
@@ -26,6 +26,7 @@ from __future__ import annotations
import array
import asyncio
import inspect
from textwrap import TextWrapper
from typing import (
Any,
@@ -1542,3 +1543,11 @@ class _RawReprMixin:
def __repr__(self) -> str:
value = ' '.join(f'{attr}={getattr(self, attr)!r}' for attr in self.__slots__)
return f'<{self.__class__.__name__} {value}>'
# `inspect.iscoroutinefunction()` only became equivalent to (now deprecated) `inspect.iscoroutinefunction()` in Python 3.12
# https://github.com/python/cpython/issues/122858#issuecomment-2466239748
if sys.version_info >= (3, 12):
_iscoroutinefunction = inspect.iscoroutinefunction
else:
_iscoroutinefunction = asyncio.iscoroutinefunction