Try getting independent of pytz

This commit is contained in:
Hinrich Mahler 2024-12-30 19:16:05 +01:00
parent 8c02b05550
commit 84c93bb91a
8 changed files with 36 additions and 39 deletions

View file

@ -158,7 +158,7 @@ PTB can be installed with optional dependencies:
* ``pip install "python-telegram-bot[rate-limiter]"`` installs `aiolimiter~=1.1,<1.3 <https://aiolimiter.readthedocs.io/en/stable/>`_. Use this, if you want to use ``telegram.ext.AIORateLimiter``.
* ``pip install "python-telegram-bot[webhooks]"`` installs the `tornado~=6.4 <https://www.tornadoweb.org/en/stable/>`_ library. Use this, if you want to use ``telegram.ext.Updater.start_webhook``/``telegram.ext.Application.run_webhook``.
* ``pip install "python-telegram-bot[callback-data]"`` installs the `cachetools>=5.3.3,<5.6.0 <https://cachetools.readthedocs.io/en/latest/>`_ library. Use this, if you want to use `arbitrary callback_data <https://github.com/python-telegram-bot/python-telegram-bot/wiki/Arbitrary-callback_data>`_.
* ``pip install "python-telegram-bot[job-queue]"`` installs the `APScheduler~=3.10.4 <https://apscheduler.readthedocs.io/en/3.x/>`_ library and enforces `pytz>=2018.6 <https://pypi.org/project/pytz/>`_, where ``pytz`` is a dependency of ``APScheduler``. Use this, if you want to use the ``telegram.ext.JobQueue``.
* ``pip install "python-telegram-bot[job-queue]"`` installs the `APScheduler~=3.10.4 <https://apscheduler.readthedocs.io/en/3.x/>`_ library. Use this, if you want to use the ``telegram.ext.JobQueue``.
To install multiple optional dependencies, separate them by commas, e.g. ``pip install "python-telegram-bot[socks,webhooks]"``.

View file

@ -77,8 +77,6 @@ http2 = [
job-queue = [
# APS doesn't have a strict stability policy. Let's be cautious for now.
"APScheduler>=3.10.4,<3.12.0",
# pytz is required by APS and just needs the lower bound due to #2120
"pytz>=2018.6",
]
passport = [
"cryptography!=3.4,!=3.4.1,!=3.4.2,!=3.4.3,>=39.0.1",

View file

@ -27,6 +27,7 @@ Warning:
user. Changes to this module are not considered breaking changes and may not be documented in
the changelog.
"""
import contextlib
import datetime as dtm
import time
from typing import TYPE_CHECKING, Optional, Union
@ -34,22 +35,24 @@ from typing import TYPE_CHECKING, Optional, Union
if TYPE_CHECKING:
from telegram import Bot
# pytz is only available if it was installed as dependency of APScheduler, so we make a little
# workaround here
DTM_UTC = dtm.timezone.utc
UTC = dtm.timezone.utc
try:
import pytz
UTC = pytz.utc
except ImportError:
UTC = DTM_UTC # type: ignore[assignment]
pytz = None # type: ignore[assignment]
def _localize(datetime: dtm.datetime, tzinfo: dtm.tzinfo) -> dtm.datetime:
"""Localize the datetime, where UTC is handled depending on whether pytz is available or not"""
if tzinfo is DTM_UTC:
return datetime.replace(tzinfo=DTM_UTC)
return tzinfo.localize(datetime) # type: ignore[attr-defined]
def localize(datetime: dtm.datetime, tzinfo: dtm.tzinfo) -> dtm.datetime:
"""Localize the datetime, both for pytz and zoneinfo timezones."""
if tzinfo is UTC:
return datetime.replace(tzinfo=UTC)
with contextlib.suppress(AttributeError):
# Since pytz might not be available, we need the suppress context manager
if isinstance(tzinfo, pytz.BaseTzInfo):
return tzinfo.localize(datetime)
return datetime.astimezone(tzinfo)
def to_float_timestamp(
@ -87,7 +90,7 @@ def to_float_timestamp(
will be raised.
tzinfo (:class:`datetime.tzinfo`, optional): If :paramref:`time_object` is a naive object
from the :mod:`datetime` module, it will be interpreted as this timezone. Defaults to
``pytz.utc``, if available, and :attr:`datetime.timezone.utc` otherwise.
:attr:`datetime.timezone.utc` otherwise.
Note:
Only to be used by ``telegram.ext``.
@ -132,7 +135,7 @@ def to_float_timestamp(
aware_datetime = dtm.datetime.combine(reference_date, time_object)
if aware_datetime.tzinfo is None:
aware_datetime = _localize(aware_datetime, tzinfo)
aware_datetime = localize(aware_datetime, tzinfo)
# if the time of day has passed today, use tomorrow
if reference_time > aware_datetime.timetz():
@ -140,7 +143,7 @@ def to_float_timestamp(
return _datetime_to_float_timestamp(aware_datetime)
if isinstance(time_object, dtm.datetime):
if time_object.tzinfo is None:
time_object = _localize(time_object, tzinfo)
time_object = localize(time_object, tzinfo)
return _datetime_to_float_timestamp(time_object)
raise TypeError(f"Unable to convert {type(time_object).__name__} object to timestamp")

View file

@ -57,9 +57,7 @@ class Defaults:
versions.
tzinfo (:class:`datetime.tzinfo`, optional): A timezone to be used for all date(time)
inputs appearing throughout PTB, i.e. if a timezone naive date(time) object is passed
somewhere, it will be assumed to be in :paramref:`tzinfo`. If the
:class:`telegram.ext.JobQueue` is used, this must be a timezone provided
by the ``pytz`` module. Defaults to ``pytz.utc``, if available, and
somewhere, it will be assumed to be in :paramref:`tzinfo`. Defaults to
:attr:`datetime.timezone.utc` otherwise.
block (:obj:`bool`, optional): Default setting for the :paramref:`BaseHandler.block`
parameter

View file

@ -23,7 +23,6 @@ import weakref
from typing import TYPE_CHECKING, Any, Generic, Optional, Union, cast, overload
try:
import pytz
from apscheduler.executors.asyncio import AsyncIOExecutor
from apscheduler.schedulers.asyncio import AsyncIOScheduler
@ -31,6 +30,7 @@ try:
except ImportError:
APS_AVAILABLE = False
from telegram._utils.datetime import UTC, localize
from telegram._utils.logging import get_logger
from telegram._utils.repr import build_repr_with_selected_attrs
from telegram._utils.types import JSONDict
@ -155,13 +155,13 @@ class JobQueue(Generic[CCT]):
dict[:obj:`str`, :obj:`object`]: The configuration values as dictionary.
"""
timezone: object = pytz.utc
timezone: datetime.tzinfo = UTC
if (
self._application
and isinstance(self.application.bot, ExtBot)
and self.application.bot.defaults
):
timezone = self.application.bot.defaults.tzinfo or pytz.utc
timezone = self.application.bot.defaults.tzinfo or UTC
return {
"timezone": timezone,
@ -197,8 +197,8 @@ class JobQueue(Generic[CCT]):
datetime.datetime.now(tz=time.tzinfo or self.scheduler.timezone).date(), time
)
if date_time.tzinfo is None:
date_time = self.scheduler.timezone.localize(date_time)
if shift_day and date_time <= datetime.datetime.now(pytz.utc):
date_time = localize(date_time, self.scheduler.timezone)
if shift_day and date_time <= datetime.datetime.now(UTC):
date_time += datetime.timedelta(days=1)
return date_time
return time

View file

@ -21,6 +21,7 @@ import datetime
import functools
import inspect
import re
import zoneinfo
from collections.abc import Collection, Iterable
from typing import Any, Callable, Optional
@ -46,7 +47,7 @@ from telegram.request import RequestData
from tests.auxil.envvars import TEST_WITH_OPT_DEPS
if TEST_WITH_OPT_DEPS:
import pytz
pass
FORWARD_REF_PATTERN = re.compile(r"ForwardRef\('(?P<class_name>\w+)'\)")
@ -336,8 +337,8 @@ def build_kwargs(
elif name == "until_date":
if manually_passed_value not in [None, DEFAULT_NONE]:
# Europe/Berlin
kws[name] = pytz.timezone("Europe/Berlin").localize(
datetime.datetime(2000, 1, 1, 0)
kws[name] = datetime.datetime(
2000, 1, 1, 0, tzinfo=zoneinfo.ZoneInfo("Europe/Berlin")
)
else:
# naive UTC
@ -587,7 +588,7 @@ async def check_defaults_handling(
defaults_no_custom_defaults = Defaults()
kwargs = {kwarg: "custom_default" for kwarg in inspect.signature(Defaults).parameters}
kwargs["tzinfo"] = pytz.timezone("America/New_York")
kwargs["tzinfo"] = zoneinfo.ZoneInfo("America/New_York")
kwargs.pop("disable_web_page_preview") # mutually exclusive with link_preview_options
kwargs.pop("quote") # mutually exclusive with do_quote
kwargs["link_preview_options"] = LinkPreviewOptions(

View file

@ -17,9 +17,9 @@
# You should have received a copy of the GNU Lesser Public License
# along with this program. If not, see [http://www.gnu.org/licenses/].
import asyncio
import datetime
import logging
import sys
import zoneinfo
from pathlib import Path
from uuid import uuid4
@ -44,7 +44,6 @@ from tests.auxil.envvars import GITHUB_ACTION, RUN_TEST_OFFICIAL, TEST_WITH_OPT_
from tests.auxil.files import data_file
from tests.auxil.networking import NonchalantHttpxRequest
from tests.auxil.pytest_classes import PytestBot, make_bot
from tests.auxil.timezones import BasicTimezone
if TEST_WITH_OPT_DEPS:
import pytz
@ -311,9 +310,8 @@ def false_update(request):
@pytest.fixture(scope="session", params=["Europe/Berlin", "Asia/Singapore", "UTC"])
def tzinfo(request):
if TEST_WITH_OPT_DEPS:
return pytz.timezone(request.param)
hours_offset = {"Europe/Berlin": 2, "Asia/Singapore": 8, "UTC": 0}[request.param]
return BasicTimezone(offset=datetime.timedelta(hours=hours_offset), name=request.param)
yield pytz.timezone(request.param)
yield zoneinfo.ZoneInfo(request.param)
@pytest.fixture(scope="session")

View file

@ -79,7 +79,7 @@ from telegram import (
User,
WebAppInfo,
)
from telegram._utils.datetime import UTC, from_timestamp, to_timestamp
from telegram._utils.datetime import UTC, from_timestamp, localize, to_timestamp
from telegram._utils.defaultvalue import DEFAULT_NONE
from telegram._utils.strings import to_camel_case
from telegram.constants import (
@ -97,7 +97,7 @@ from telegram.request import BaseRequest, HTTPXRequest, RequestData
from telegram.warnings import PTBDeprecationWarning, PTBUserWarning
from tests.auxil.bot_method_checks import check_defaults_handling
from tests.auxil.ci_bots import FALLBACKS
from tests.auxil.envvars import GITHUB_ACTION, TEST_WITH_OPT_DEPS
from tests.auxil.envvars import GITHUB_ACTION
from tests.auxil.files import data_file
from tests.auxil.networking import OfflineRequest, expect_bad_request
from tests.auxil.pytest_classes import PytestBot, PytestExtBot, make_bot
@ -3467,7 +3467,6 @@ class TestBotWithRequest:
)
assert revoked_link.is_revoked
@pytest.mark.skipif(not TEST_WITH_OPT_DEPS, reason="This test's implementation requires pytz")
@pytest.mark.parametrize("datetime", argvalues=[True, False], ids=["datetime", "integer"])
async def test_advanced_chat_invite_links(self, bot, channel_id, datetime):
# we are testing this all in one function in order to save api calls
@ -3475,7 +3474,7 @@ class TestBotWithRequest:
add_seconds = dtm.timedelta(0, 70)
time_in_future = timestamp + add_seconds
expire_time = time_in_future if datetime else to_timestamp(time_in_future)
aware_time_in_future = UTC.localize(time_in_future)
aware_time_in_future = localize(time_in_future, UTC)
invite_link = await bot.create_chat_invite_link(
channel_id, expire_date=expire_time, member_limit=10
@ -3488,7 +3487,7 @@ class TestBotWithRequest:
add_seconds = dtm.timedelta(0, 80)
time_in_future = timestamp + add_seconds
expire_time = time_in_future if datetime else to_timestamp(time_in_future)
aware_time_in_future = UTC.localize(time_in_future)
aware_time_in_future = localize(time_in_future, UTC)
edited_invite_link = await bot.edit_chat_invite_link(
channel_id,