diff --git a/docs/changelog.rst b/docs/changelog.rst index 7a773eeb..f6949aa8 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -1,11 +1,12 @@ -:orphan: +orphan: Master ====== - TwitchIO - Bug fixes - Fix IndexError when getting prefix when empty message is sent in a reply. - + - Additioons + - Added :attr:`~twitchio.ext.routines.Routine.time_until_next_execution` and :attr:`~twitchio.ext.routines.Routine.next_event_time` for monitoring 2.7.0 ====== @@ -24,6 +25,7 @@ Master - Added :func:`~twitchio.Client.fetch_content_classification_labels` along with :class:`~twitchio.ContentClassificationLabel` - Added :attr:`~twitchio.ChannelInfo.content_classification_labels` and :attr:`~twitchio.ChannelInfo.is_branded_content` to :class:`~twitchio.ChannelInfo` - Added new parameters to :func:`~twitchio.PartialUser.modify_stream` for ``is_branded_content`` and ``content_classification_labels`` + - Bug fixes - Fix :func:`~twitchio.Client.search_categories` due to :attr:`~twitchio.Game.igdb_id` being added to :class:`~twitchio.Game` diff --git a/twitchio/ext/routines/__init__.py b/twitchio/ext/routines/__init__.py index 89d4867c..5099d870 100644 --- a/twitchio/ext/routines/__init__.py +++ b/twitchio/ext/routines/__init__.py @@ -97,8 +97,10 @@ def __init__( self._instance = None - self._args: tuple | None = None - self._kwargs: dict | None = None + self._args: Optional[tuple] = None + self._kwargs: Optional[dict] = None + + self.next_event_time: Optional[datetime.datetime] = None def __get__(self, instance, owner): if instance is None: @@ -174,6 +176,8 @@ def cancel(self) -> None: Consider using :meth:`stop` if a graceful stop, which will complete the current iteration, is desired. """ if self._can_be_cancelled(): + self.next_event_time = None + self._task.cancel() if not self._restarting: @@ -211,7 +215,6 @@ def restart_when_over(fut, *, args=args, kwargs=kwargs): if self._can_be_cancelled(): self._task.add_done_callback(restart_when_over) - if force: self._task.cancel() else: @@ -320,6 +323,20 @@ def remaining_iterations(self) -> Optional[int]: """A count of remaining iterations.""" return self._remaining_iterations + @property + def time_until_next_execution(self) -> Optional[datetime.timedelta]: + """Return the time left as a datetime object before the next execution. + + None will be returned if the routine is not scheduled + """ + + if self.next_event_time is None: + return None + + return max( + self.next_event_time - datetime.datetime.now(self.next_event_time.tzinfo), datetime.timedelta(seconds=0) + ) + @property def start_time(self) -> Optional[datetime.datetime]: """The time the routine was started. @@ -351,10 +368,14 @@ async def _routine(self, *args, **kwargs) -> None: return self.cancel() if self._time: + self.next_event_time = self._time wait = compute_timedelta(self._time) await asyncio.sleep(wait) if self._wait_first and not self._time: + self.next_event_time = datetime.timedelta(seconds=self._delta) + datetime.datetime.now( + datetime.timezone.utc + ) await asyncio.sleep(self._delta) if self._remaining_iterations == 0: @@ -382,10 +403,12 @@ async def _routine(self, *args, **kwargs) -> None: pass else: if self._remaining_iterations == 0: + self.next_event_time = None break if self._stop_set: self._stop_set = False + self.next_event_time = None break if self._time: @@ -394,6 +417,9 @@ async def _routine(self, *args, **kwargs) -> None: sleep = max((start - datetime.datetime.now(datetime.timezone.utc)).total_seconds() + self._delta, 0) self._completed_loops += 1 + + self.next_event_time = datetime.datetime.now(datetime.timezone.utc) + datetime.timedelta(seconds=sleep) + await asyncio.sleep(sleep) try: