diff --git a/discord/client.py b/discord/client.py index 211532d68d..f227ccf1df 100644 --- a/discord/client.py +++ b/discord/client.py @@ -70,7 +70,13 @@ from .threads import Thread from .ui.view import BaseView from .user import ClientUser, User -from .utils import _D, _FETCHABLE, MISSING, warn_if_voice_dependencies_missing +from .utils import ( + _D, + _FETCHABLE, + MISSING, + _get_event_loop, + warn_if_voice_dependencies_missing, +) from .webhook import Webhook from .widget import Widget @@ -147,7 +153,7 @@ class Client: loop: Optional[:class:`asyncio.AbstractEventLoop`] The :class:`asyncio.AbstractEventLoop` to use for asynchronous operations. Defaults to ``None``, in which case the default event loop is used via - :func:`asyncio.get_event_loop()`. + :func:`asyncio.get_event_loop()` if it exists or one is created via :func:`asyncio.new_event_loop()`. connector: Optional[:class:`aiohttp.BaseConnector`] The connector to use for connection pooling. proxy: Optional[:class:`str`] @@ -245,7 +251,7 @@ def __init__( # self.ws is set in the connect method self.ws: DiscordWebSocket = None # type: ignore self.loop: asyncio.AbstractEventLoop = ( - asyncio.get_event_loop() if loop is None else loop + _get_event_loop() if loop is None else loop ) self._listeners: dict[str, list[tuple[asyncio.Future, Callable[..., bool]]]] = ( {} diff --git a/discord/ext/commands/cooldowns.py b/discord/ext/commands/cooldowns.py index 6e58d37f7a..7504eed7c8 100644 --- a/discord/ext/commands/cooldowns.py +++ b/discord/ext/commands/cooldowns.py @@ -32,6 +32,7 @@ import discord.abc from discord.enums import Enum +from discord.utils import _get_event_loop from ...abc import PrivateChannel from .errors import MaxConcurrencyReached @@ -308,7 +309,7 @@ class _Semaphore: def __init__(self, number: int) -> None: self.value: int = number - self.loop: asyncio.AbstractEventLoop = asyncio.get_event_loop() + self.loop: asyncio.AbstractEventLoop = _get_event_loop() self._waiters: Deque[asyncio.Future] = deque() def __repr__(self) -> str: diff --git a/discord/ext/tasks/__init__.py b/discord/ext/tasks/__init__.py index 9bdde87f23..6bb719c67f 100644 --- a/discord/ext/tasks/__init__.py +++ b/discord/ext/tasks/__init__.py @@ -38,7 +38,7 @@ import discord from discord.backoff import ExponentialBackoff -from discord.utils import MISSING +from discord.utils import MISSING, _get_event_loop __all__ = ("loop",) @@ -384,7 +384,7 @@ def start(self, *args: Any, **kwargs: Any) -> asyncio.Task[None]: args = (self._injected, *args) if self.loop is MISSING: - self.loop = asyncio.get_event_loop() + self.loop = _get_event_loop() self._task = self.loop.create_task(self._loop(*args, **kwargs)) return self._task @@ -825,8 +825,8 @@ def loop( using an exponential back-off algorithm similar to the one used in :meth:`discord.Client.connect`. loop: :class:`asyncio.AbstractEventLoop` - The loop to use to register the task, if not given - defaults to :func:`asyncio.get_event_loop`. + The loop to use to register the task, if not given the default event loop is used via + :func:`asyncio.get_event_loop()` if it exists or one is created via :func:`asyncio.new_event_loop()`. overlap: Union[:class:`bool`, :class:`int`] Controls whether overlapping executions of the task loop are allowed. Set to False (default) to run iterations one at a time, True for unlimited overlap, or an int to cap the number of concurrent runs. diff --git a/discord/http.py b/discord/http.py index 0717feadf5..fc6ffb22c3 100644 --- a/discord/http.py +++ b/discord/http.py @@ -55,7 +55,7 @@ from .file import VoiceMessage from .gateway import DiscordClientWebSocketResponse from .soundboard import PartialSoundboardSound, SoundboardSound -from .utils import MISSING +from .utils import MISSING, _get_event_loop _log = logging.getLogger(__name__) @@ -192,7 +192,7 @@ def __init__( unsync_clock: bool = True, ) -> None: self.loop: asyncio.AbstractEventLoop = ( - asyncio.get_event_loop() if loop is None else loop + _get_event_loop() if loop is None else loop ) self.connector = connector self.__session: aiohttp.ClientSession = MISSING # filled in static_login diff --git a/discord/ui/modal.py b/discord/ui/modal.py index 6bb3a21d6a..67c68913f9 100644 --- a/discord/ui/modal.py +++ b/discord/ui/modal.py @@ -33,7 +33,7 @@ from typing import TYPE_CHECKING, Any, Iterator, TypeVar from ..enums import ComponentType -from ..utils import find +from ..utils import _get_event_loop, find from .core import ItemInterface from .input_text import InputText from .item import ModalItem @@ -91,7 +91,7 @@ def __init__( for item in children: self.add_item(item) self._title = title - self.loop = asyncio.get_event_loop() + self.loop = _get_event_loop() def __repr__(self) -> str: attrs = " ".join( diff --git a/discord/utils.py b/discord/utils.py index cc6d9d3b19..d09551f508 100644 --- a/discord/utils.py +++ b/discord/utils.py @@ -1665,3 +1665,24 @@ def warn_if_voice_dependencies_missing() -> None: deps, "is" if len(missing) == 1 else "are", ) + + +def _get_event_loop() -> asyncio.AbstractEventLoop: + """Get the current event loop, creating one if necessary. + + If no event loop is running and none is set, a new event loop + is created and set as the current event loop. + + Returns + ------- + asyncio.AbstractEventLoop + The current event loop. + """ + if sys.version_info >= (3, 14): + try: + loop = asyncio.get_event_loop() + except RuntimeError: + loop = asyncio.new_event_loop() + asyncio.set_event_loop(loop) + return loop + return asyncio.get_event_loop() diff --git a/examples/basic_voice.py b/examples/basic_voice.py index 2525415762..f81c7ad373 100644 --- a/examples/basic_voice.py +++ b/examples/basic_voice.py @@ -42,11 +42,8 @@ def __init__(self, source: discord.AudioSource, *, data: dict, volume: float = 0 self.url = data.get("url") @classmethod - async def from_url(cls, url, *, loop=None, stream=False): - loop = loop or asyncio.get_event_loop() - data = await loop.run_in_executor( - None, lambda: ytdl.extract_info(url, download=not stream) - ) + async def from_url(cls, url, *, stream=False): + data = await asyncio.to_thread(ytdl.extract_info, url, download=not stream) if "entries" in data: # Takes the first item from a playlist