Skip to content

Commit

Permalink
Add JobQueue.scheduler_configuration and Corresponding Warnings (#3913
Browse files Browse the repository at this point in the history
)
  • Loading branch information
Bibo-Joshi committed Oct 16, 2023
1 parent af130ef commit f67e8c0
Show file tree
Hide file tree
Showing 2 changed files with 64 additions and 10 deletions.
58 changes: 50 additions & 8 deletions telegram/ext/_jobqueue.py
Expand Up @@ -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`
Expand All @@ -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=...]``.
Expand All @@ -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() <apscheduler.schedulers.base.BaseScheduler.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)

Expand Down Expand Up @@ -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:
Expand Down
16 changes: 14 additions & 2 deletions tests/ext/test_jobqueue.py
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand All @@ -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):
Expand All @@ -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)
Expand Down

0 comments on commit f67e8c0

Please sign in to comment.