32
32
import sys
33
33
import traceback
34
34
from collections .abc import Sequence
35
- from typing import Any , Awaitable , Callable , Generic , TypeVar , cast
35
+ from typing import Any , Awaitable , Callable , Generic , TypeVar
36
36
37
37
import aiohttp
38
38
@@ -120,18 +120,19 @@ def __init__(
120
120
count : int | None ,
121
121
reconnect : bool ,
122
122
loop : asyncio .AbstractEventLoop | None ,
123
+ create_loop : bool ,
123
124
name : str | None ,
124
125
) -> None :
125
126
self .coro : LF = coro
126
127
self .reconnect : bool = reconnect
127
128
128
- if loop is None :
129
+ if create_loop is True and loop is None :
129
130
try :
130
131
loop = asyncio .get_running_loop ()
131
132
except RuntimeError :
132
133
loop = asyncio .new_event_loop ()
133
134
134
- self .loop = loop
135
+ self .loop : asyncio . AbstractEventLoop | None = loop
135
136
136
137
self .name : str = f'pycord-ext-task ({ id (self ):#x} ): { coro .__qualname__ } ' if name in (None , MISSING ) else name
137
138
self .count : int | None = count
@@ -146,6 +147,7 @@ def __init__(
146
147
aiohttp .ClientError ,
147
148
asyncio .TimeoutError ,
148
149
)
150
+ self ._create_loop = create_loop
149
151
150
152
self ._before_loop = None
151
153
self ._after_loop = None
@@ -168,6 +170,9 @@ def __init__(
168
170
f"Expected coroutine function, not { type (self .coro ).__name__ !r} ."
169
171
)
170
172
173
+ if loop is None and not create_loop :
174
+ discord .Client ._pending_loops .add_loop (self )
175
+
171
176
async def _call_loop_function (self , name : str , * args : Any , ** kwargs : Any ) -> None :
172
177
coro = getattr (self , f"_{ name } " )
173
178
if coro is None :
@@ -280,6 +285,7 @@ def __get__(self, obj: T, objtype: type[T]) -> Loop[LF]:
280
285
reconnect = self .reconnect ,
281
286
name = self .name ,
282
287
loop = self .loop ,
288
+ create_loop = self ._create_loop ,
283
289
)
284
290
copy ._injected = obj
285
291
copy ._before_loop = self ._before_loop
@@ -365,7 +371,7 @@ async def __call__(self, *args: Any, **kwargs: Any) -> Any:
365
371
366
372
return await self .coro (* args , ** kwargs )
367
373
368
- def start (self , * args : Any , ** kwargs : Any ) -> asyncio .Task [None ]:
374
+ def start (self , * args : Any , ** kwargs : Any ) -> asyncio .Task [None ] | None :
369
375
r"""Starts the internal task in the event loop.
370
376
371
377
Parameters
@@ -386,13 +392,21 @@ def start(self, *args: Any, **kwargs: Any) -> asyncio.Task[None]:
386
392
The task that has been created.
387
393
"""
388
394
395
+ if self .loop is None :
396
+ _log .warning (
397
+ f"The task { self .name } has been set to be bound to a discord.Client instance, and will start running automatically "
398
+ "when the client starts. If you want this task to be executed without it being bound to a discord.Client, "
399
+ "set the create_loop parameter in the decorator to True, and don't forget to set the client.loop to the loop.loop"
400
+ )
401
+ return None
402
+
389
403
if self ._task is not MISSING and not self ._task .done ():
390
404
raise RuntimeError ("Task is already launched and is not completed." )
391
405
392
406
if self ._injected is not None :
393
407
args = (self ._injected , * args )
394
408
395
- self ._task = self .loop .create_task (self ._loop (* args , ** kwargs ), name = self .name )
409
+ self ._task = asyncio . ensure_future ( self .loop .create_task (self ._loop (* args , ** kwargs ), name = self .name ) )
396
410
return self ._task
397
411
398
412
def stop (self ) -> None :
@@ -760,15 +774,9 @@ def change_interval(
760
774
self ._time = self ._get_time_parameter (time )
761
775
self ._sleep = self ._seconds = self ._minutes = self ._hours = MISSING
762
776
763
- if self .is_running () and not (
764
- self ._before_loop_running or self ._after_loop_running
765
- ):
766
- if self ._time is not MISSING :
767
- # prepare the next time index starting from after the last iteration
768
- self ._prepare_time_index (now = self ._last_iteration )
769
-
777
+ if self .is_running () and self ._last_iteration is not MISSING :
770
778
self ._next_iteration = self ._get_next_sleep_time ()
771
- if not self ._handle .done ():
779
+ if self . _handle and not self ._handle .done ():
772
780
# the loop is sleeping, recalculate based on new interval
773
781
self ._handle .recalculate (self ._next_iteration )
774
782
@@ -783,6 +791,7 @@ def loop(
783
791
reconnect : bool = True ,
784
792
loop : asyncio .AbstractEventLoop | None = None ,
785
793
name : str | None = MISSING ,
794
+ create_loop : bool = False ,
786
795
) -> Callable [[LF ], Loop [LF ]]:
787
796
"""A decorator that schedules a task in the background for you with
788
797
optional reconnect logic. The decorator returns a :class:`Loop`.
@@ -820,6 +829,11 @@ def loop(
820
829
name: Optional[:class:`str`]
821
830
The name to create the task with, defaults to ``None``.
822
831
832
+ .. versionadded:: 2.7
833
+ create_loop: :class:`bool`
834
+ Whether this task should create their own event loop to start running it
835
+ without a client bound to it.
836
+
823
837
.. versionadded:: 2.7
824
838
825
839
Raises
@@ -842,6 +856,7 @@ def decorator(func: LF) -> Loop[LF]:
842
856
reconnect = reconnect ,
843
857
name = name ,
844
858
loop = loop ,
859
+ create_loop = create_loop ,
845
860
)
846
861
847
862
return decorator
0 commit comments