From f67e8c080479a9bdd764e2c26593453e110073ca Mon Sep 17 00:00:00 2001 From: Bibo-Joshi <22366557+Bibo-Joshi@users.noreply.github.com> Date: Mon, 16 Oct 2023 20:25:25 +0200 Subject: [PATCH] Add `JobQueue.scheduler_configuration` and Corresponding Warnings (#3913) --- telegram/ext/_jobqueue.py | 58 ++++++++++++++++++++++++++++++++------ tests/ext/test_jobqueue.py | 16 +++++++++-- 2 files changed, 64 insertions(+), 10 deletions(-) diff --git a/telegram/ext/_jobqueue.py b/telegram/ext/_jobqueue.py index 339d9466102..765b059b179 100644 --- a/telegram/ext/_jobqueue.py +++ b/telegram/ext/_jobqueue.py @@ -76,6 +76,17 @@ class JobQueue(Generic[CCT]): Attributes: scheduler (:class:`apscheduler.schedulers.asyncio.AsyncIOScheduler`): The scheduler. + Warning: + This scheduler is configured by :meth:`set_application`. Additional configuration + settings can be made by users. However, calling + :meth:`~apscheduler.schedulers.base.BaseScheduler.configure` will delete any + previous configuration settings. Therefore, please make sure to pass the values + returned by :attr:`scheduler_configuration` to the method call in addition to your + custom values. + Alternatively, you can also use methods like + :meth:`~apscheduler.schedulers.base.BaseScheduler.add_jobstore` to avoid using + :meth:`~apscheduler.schedulers.base.BaseScheduler.configure` altogether. + .. versionchanged:: 20.0 Uses :class:`~apscheduler.schedulers.asyncio.AsyncIOScheduler` instead of :class:`~apscheduler.schedulers.background.BackgroundScheduler` @@ -94,9 +105,7 @@ def __init__(self) -> None: self._application: Optional[weakref.ReferenceType[Application]] = None self._executor = AsyncIOExecutor() - self.scheduler: AsyncIOScheduler = AsyncIOScheduler( - timezone=pytz.utc, executors={"default": self._executor} - ) + self.scheduler: AsyncIOScheduler = AsyncIOScheduler(**self.scheduler_configuration) def __repr__(self) -> str: """Give a string representation of the JobQueue in the form ``JobQueue[application=...]``. @@ -119,6 +128,43 @@ def application(self) -> "Application[Any, CCT, Any, Any, Any, JobQueue[CCT]]": return application raise RuntimeError("The application instance is no longer alive.") + @property + def scheduler_configuration(self) -> JSONDict: + """Provides configuration values that are used by :class:`JobQueue` for :attr:`scheduler`. + + Tip: + Since calling + :meth:`scheduler.configure() ` + deletes any previous setting, please make sure to pass these values to the method call + in addition to your custom values: + + .. code-block:: python + + scheduler.configure(..., **job_queue.scheduler_configuration) + + Alternatively, you can also use methods like + :meth:`~apscheduler.schedulers.base.BaseScheduler.add_jobstore` to avoid using + :meth:`~apscheduler.schedulers.base.BaseScheduler.configure` altogether. + + .. versionadded:: NEXT.VERSION + + Returns: + Dict[:obj:`str`, :obj:`object`]: The configuration values as dictionary. + + """ + timezone: object = pytz.utc + if ( + self._application + and isinstance(self.application.bot, ExtBot) + and self.application.bot.defaults + ): + timezone = self.application.bot.defaults.tzinfo or pytz.utc + + return { + "timezone": timezone, + "executors": {"default": self._executor}, + } + def _tz_now(self) -> datetime.datetime: return datetime.datetime.now(self.scheduler.timezone) @@ -166,11 +212,7 @@ def set_application( """ self._application = weakref.ref(application) - if isinstance(application.bot, ExtBot) and application.bot.defaults: - self.scheduler.configure( - timezone=application.bot.defaults.tzinfo or pytz.utc, - executors={"default": self._executor}, - ) + self.scheduler.configure(**self.scheduler_configuration) @staticmethod async def job_callback(job_queue: "JobQueue[CCT]", job: "Job[CCT]") -> None: diff --git a/tests/ext/test_jobqueue.py b/tests/ext/test_jobqueue.py index 441015c480d..7da673bc3f9 100644 --- a/tests/ext/test_jobqueue.py +++ b/tests/ext/test_jobqueue.py @@ -25,7 +25,7 @@ import pytest -from telegram.ext import ApplicationBuilder, CallbackContext, ContextTypes, Job, JobQueue +from telegram.ext import ApplicationBuilder, CallbackContext, ContextTypes, Defaults, Job, JobQueue from telegram.warnings import PTBUserWarning from tests.auxil.envvars import GITHUB_ACTION, TEST_WITH_OPT_DEPS from tests.auxil.pytest_classes import make_bot @@ -106,6 +106,15 @@ def _reset(self): self.job_time = 0 self.received_error = None + def test_scheduler_configuration(self, job_queue, timezone, bot): + # Unfortunately, we can't really test the executor setting explicitly without relying + # on protected attributes. However, this should be tested enough implicitly via all the + # other tests in here + assert job_queue.scheduler_configuration["timezone"] is UTC + + tz_app = ApplicationBuilder().defaults(Defaults(tzinfo=timezone)).token(bot.token).build() + assert tz_app.job_queue.scheduler_configuration["timezone"] is timezone + async def job_run_once(self, context): if ( isinstance(context, CallbackContext) @@ -578,7 +587,7 @@ async def test_process_error_that_raises_errors(self, job_queue, app, caplog): assert rec.name == "telegram.ext.Application" assert "No error handlers are registered" in rec.getMessage() - async def test_custom_context(self, bot, job_queue): + async def test_custom_context(self, bot): application = ( ApplicationBuilder() .token(bot.token) @@ -589,6 +598,7 @@ async def test_custom_context(self, bot, job_queue): ) .build() ) + job_queue = JobQueue() job_queue.set_application(application) async def callback(context): @@ -599,9 +609,11 @@ async def callback(context): type(context.bot_data), ) + await job_queue.start() job_queue.run_once(callback, 0.1) await asyncio.sleep(0.15) assert self.result == (CustomContext, None, None, int) + await job_queue.stop() async def test_attribute_error(self): job = Job(self.job_run_once)