Skip to content

Commit da9960f

Browse files
authored
fix: Update event loop retrieval to support Python 3.14+ (#3187)
1 parent 227a128 commit da9960f

File tree

7 files changed

+42
-17
lines changed

7 files changed

+42
-17
lines changed

discord/client.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,13 @@
7070
from .threads import Thread
7171
from .ui.view import BaseView
7272
from .user import ClientUser, User
73-
from .utils import _D, _FETCHABLE, MISSING, warn_if_voice_dependencies_missing
73+
from .utils import (
74+
_D,
75+
_FETCHABLE,
76+
MISSING,
77+
_get_event_loop,
78+
warn_if_voice_dependencies_missing,
79+
)
7480
from .webhook import Webhook
7581
from .widget import Widget
7682

@@ -147,7 +153,7 @@ class Client:
147153
loop: Optional[:class:`asyncio.AbstractEventLoop`]
148154
The :class:`asyncio.AbstractEventLoop` to use for asynchronous operations.
149155
Defaults to ``None``, in which case the default event loop is used via
150-
:func:`asyncio.get_event_loop()`.
156+
:func:`asyncio.get_event_loop()` if it exists or one is created via :func:`asyncio.new_event_loop()`.
151157
connector: Optional[:class:`aiohttp.BaseConnector`]
152158
The connector to use for connection pooling.
153159
proxy: Optional[:class:`str`]
@@ -245,7 +251,7 @@ def __init__(
245251
# self.ws is set in the connect method
246252
self.ws: DiscordWebSocket = None # type: ignore
247253
self.loop: asyncio.AbstractEventLoop = (
248-
asyncio.get_event_loop() if loop is None else loop
254+
_get_event_loop() if loop is None else loop
249255
)
250256
self._listeners: dict[str, list[tuple[asyncio.Future, Callable[..., bool]]]] = (
251257
{}

discord/ext/commands/cooldowns.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232

3333
import discord.abc
3434
from discord.enums import Enum
35+
from discord.utils import _get_event_loop
3536

3637
from ...abc import PrivateChannel
3738
from .errors import MaxConcurrencyReached
@@ -308,7 +309,7 @@ class _Semaphore:
308309

309310
def __init__(self, number: int) -> None:
310311
self.value: int = number
311-
self.loop: asyncio.AbstractEventLoop = asyncio.get_event_loop()
312+
self.loop: asyncio.AbstractEventLoop = _get_event_loop()
312313
self._waiters: Deque[asyncio.Future] = deque()
313314

314315
def __repr__(self) -> str:

discord/ext/tasks/__init__.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@
3838

3939
import discord
4040
from discord.backoff import ExponentialBackoff
41-
from discord.utils import MISSING
41+
from discord.utils import MISSING, _get_event_loop
4242

4343
__all__ = ("loop",)
4444

@@ -384,7 +384,7 @@ def start(self, *args: Any, **kwargs: Any) -> asyncio.Task[None]:
384384
args = (self._injected, *args)
385385

386386
if self.loop is MISSING:
387-
self.loop = asyncio.get_event_loop()
387+
self.loop = _get_event_loop()
388388

389389
self._task = self.loop.create_task(self._loop(*args, **kwargs))
390390
return self._task
@@ -825,8 +825,8 @@ def loop(
825825
using an exponential back-off algorithm similar to the
826826
one used in :meth:`discord.Client.connect`.
827827
loop: :class:`asyncio.AbstractEventLoop`
828-
The loop to use to register the task, if not given
829-
defaults to :func:`asyncio.get_event_loop`.
828+
The loop to use to register the task, if not given the default event loop is used via
829+
:func:`asyncio.get_event_loop()` if it exists or one is created via :func:`asyncio.new_event_loop()`.
830830
overlap: Union[:class:`bool`, :class:`int`]
831831
Controls whether overlapping executions of the task loop are allowed.
832832
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.

discord/http.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@
5555
from .file import VoiceMessage
5656
from .gateway import DiscordClientWebSocketResponse
5757
from .soundboard import PartialSoundboardSound, SoundboardSound
58-
from .utils import MISSING
58+
from .utils import MISSING, _get_event_loop
5959

6060
_log = logging.getLogger(__name__)
6161

@@ -192,7 +192,7 @@ def __init__(
192192
unsync_clock: bool = True,
193193
) -> None:
194194
self.loop: asyncio.AbstractEventLoop = (
195-
asyncio.get_event_loop() if loop is None else loop
195+
_get_event_loop() if loop is None else loop
196196
)
197197
self.connector = connector
198198
self.__session: aiohttp.ClientSession = MISSING # filled in static_login

discord/ui/modal.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
from typing import TYPE_CHECKING, Any, Iterator, TypeVar
3434

3535
from ..enums import ComponentType
36-
from ..utils import find
36+
from ..utils import _get_event_loop, find
3737
from .core import ItemInterface
3838
from .input_text import InputText
3939
from .item import ModalItem
@@ -91,7 +91,7 @@ def __init__(
9191
for item in children:
9292
self.add_item(item)
9393
self._title = title
94-
self.loop = asyncio.get_event_loop()
94+
self.loop = _get_event_loop()
9595

9696
def __repr__(self) -> str:
9797
attrs = " ".join(

discord/utils.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1665,3 +1665,24 @@ def warn_if_voice_dependencies_missing() -> None:
16651665
deps,
16661666
"is" if len(missing) == 1 else "are",
16671667
)
1668+
1669+
1670+
def _get_event_loop() -> asyncio.AbstractEventLoop:
1671+
"""Get the current event loop, creating one if necessary.
1672+
1673+
If no event loop is running and none is set, a new event loop
1674+
is created and set as the current event loop.
1675+
1676+
Returns
1677+
-------
1678+
asyncio.AbstractEventLoop
1679+
The current event loop.
1680+
"""
1681+
if sys.version_info >= (3, 14):
1682+
try:
1683+
loop = asyncio.get_event_loop()
1684+
except RuntimeError:
1685+
loop = asyncio.new_event_loop()
1686+
asyncio.set_event_loop(loop)
1687+
return loop
1688+
return asyncio.get_event_loop()

examples/basic_voice.py

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -42,11 +42,8 @@ def __init__(self, source: discord.AudioSource, *, data: dict, volume: float = 0
4242
self.url = data.get("url")
4343

4444
@classmethod
45-
async def from_url(cls, url, *, loop=None, stream=False):
46-
loop = loop or asyncio.get_event_loop()
47-
data = await loop.run_in_executor(
48-
None, lambda: ytdl.extract_info(url, download=not stream)
49-
)
45+
async def from_url(cls, url, *, stream=False):
46+
data = await asyncio.to_thread(ytdl.extract_info, url, download=not stream)
5047

5148
if "entries" in data:
5249
# Takes the first item from a playlist

0 commit comments

Comments
 (0)