Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add JobQueue.scheduler_configuration and add corresponding warnings #3913

Merged
merged 7 commits into from Oct 16, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
58 changes: 50 additions & 8 deletions telegram/ext/_jobqueue.py
Expand Up @@ -76,6 +76,17 @@
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 @@

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)

Check warning on line 108 in telegram/ext/_jobqueue.py

View check run for this annotation

Codecov / codecov/patch

telegram/ext/_jobqueue.py#L108

Added line #L108 was not covered by tests

def __repr__(self) -> str:
"""Give a string representation of the JobQueue in the form ``JobQueue[application=...]``.
Expand All @@ -119,6 +128,43 @@
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

Check warning on line 155 in telegram/ext/_jobqueue.py

View check run for this annotation

Codecov / codecov/patch

telegram/ext/_jobqueue.py#L155

Added line #L155 was not covered by tests
if (
self._application
and isinstance(self.application.bot, ExtBot)
and self.application.bot.defaults
):
timezone = self.application.bot.defaults.tzinfo or pytz.utc

Check warning on line 161 in telegram/ext/_jobqueue.py

View check run for this annotation

Codecov / codecov/patch

telegram/ext/_jobqueue.py#L161

Added line #L161 was not covered by tests

return {

Check warning on line 163 in telegram/ext/_jobqueue.py

View check run for this annotation

Codecov / codecov/patch

telegram/ext/_jobqueue.py#L163

Added line #L163 was not covered by tests
"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 @@

"""
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)

Check warning on line 215 in telegram/ext/_jobqueue.py

View check run for this annotation

Codecov / codecov/patch

telegram/ext/_jobqueue.py#L215

Added line #L215 was not covered by tests

@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