\w+)'\)")
""" A pattern to find a class name in a ForwardRef typing annotation.
@@ -344,10 +341,10 @@ def build_kwargs(
# Some special casing for methods that have "exactly one of the optionals" type args
elif name in ["location", "contact", "venue", "inline_message_id"]:
kws[name] = True
- elif name == "until_date":
+ elif name.endswith("_date"):
if manually_passed_value not in [None, DEFAULT_NONE]:
# Europe/Berlin
- kws[name] = pytz.timezone("Europe/Berlin").localize(dtm.datetime(2000, 1, 1, 0))
+ kws[name] = dtm.datetime(2000, 1, 1, 0, tzinfo=zoneinfo.ZoneInfo("Europe/Berlin"))
else:
# naive UTC
kws[name] = dtm.datetime(2000, 1, 1, 0)
@@ -395,6 +392,15 @@ def make_assertion_for_link_preview_options(
)
+_EUROPE_BERLIN_TS = to_timestamp(
+ dtm.datetime(2000, 1, 1, 0, tzinfo=zoneinfo.ZoneInfo("Europe/Berlin"))
+)
+_UTC_TS = to_timestamp(dtm.datetime(2000, 1, 1, 0), tzinfo=zoneinfo.ZoneInfo("UTC"))
+_AMERICA_NEW_YORK_TS = to_timestamp(
+ dtm.datetime(2000, 1, 1, 0, tzinfo=zoneinfo.ZoneInfo("America/New_York"))
+)
+
+
async def make_assertion(
url,
request_data: RequestData,
@@ -530,14 +536,16 @@ def check_input_media(m: dict):
)
# Check datetime conversion
- until_date = data.pop("until_date", None)
- if until_date:
- if manual_value_expected and until_date != 946681200:
- pytest.fail("Non-naive until_date should have been interpreted as Europe/Berlin.")
- if not any((manually_passed_value, expected_defaults_value)) and until_date != 946684800:
- pytest.fail("Naive until_date should have been interpreted as UTC")
- if default_value_expected and until_date != 946702800:
- pytest.fail("Naive until_date should have been interpreted as America/New_York")
+ date_keys = [key for key in data if key.endswith("_date")]
+ for key in date_keys:
+ date_param = data.pop(key)
+ if date_param:
+ if manual_value_expected and date_param != _EUROPE_BERLIN_TS:
+ pytest.fail(f"Non-naive `{key}` should have been interpreted as Europe/Berlin.")
+ if not any((manually_passed_value, expected_defaults_value)) and date_param != _UTC_TS:
+ pytest.fail(f"Naive `{key}` should have been interpreted as UTC")
+ if default_value_expected and date_param != _AMERICA_NEW_YORK_TS:
+ pytest.fail(f"Naive `{key}` should have been interpreted as America/New_York")
if method_name in ["get_file", "get_small_file", "get_big_file"]:
# This is here mainly for PassportFile.get_file, which calls .set_credentials on the
@@ -596,7 +604,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(
diff --git a/tests/auxil/ci_bots.py b/tests/auxil/ci_bots.py
index 903f4fa5979..81e0c4819b8 100644
--- a/tests/auxil/ci_bots.py
+++ b/tests/auxil/ci_bots.py
@@ -24,6 +24,8 @@
from telegram._utils.strings import TextEncoding
+from .envvars import GITHUB_ACTIONS
+
# Provide some public fallbacks so it's easy for contributors to run tests on their local machine
# These bots are only able to talk in our test chats, so they are quite useless for other
# purposes than testing.
@@ -41,10 +43,9 @@
"NjcmlwdGlvbl9jaGFubmVsX2lkIjogLTEwMDIyMjk2NDkzMDN9XQ=="
)
-GITHUB_ACTION = os.getenv("GITHUB_ACTION", None)
BOTS = os.getenv("BOTS", None)
JOB_INDEX = os.getenv("JOB_INDEX", None)
-if GITHUB_ACTION is not None and BOTS is not None and JOB_INDEX is not None:
+if GITHUB_ACTIONS and BOTS is not None and JOB_INDEX is not None:
BOTS = json.loads(base64.b64decode(BOTS).decode(TextEncoding.UTF_8))
JOB_INDEX = int(JOB_INDEX)
@@ -60,7 +61,7 @@ def __init__(self):
@staticmethod
def _get_value(key, fallback):
# If we're running as a github action then fetch bots from the repo secrets
- if GITHUB_ACTION is not None and BOTS is not None and JOB_INDEX is not None:
+ if GITHUB_ACTIONS and BOTS is not None and JOB_INDEX is not None:
try:
return BOTS[JOB_INDEX][key]
except (IndexError, KeyError):
diff --git a/tests/auxil/envvars.py b/tests/auxil/envvars.py
index 9d3665000a0..5fb2d20c8a1 100644
--- a/tests/auxil/envvars.py
+++ b/tests/auxil/envvars.py
@@ -27,6 +27,9 @@ def env_var_2_bool(env_var: object) -> bool:
return env_var.lower().strip() == "true"
-GITHUB_ACTION = os.getenv("GITHUB_ACTION", "")
-TEST_WITH_OPT_DEPS = env_var_2_bool(os.getenv("TEST_WITH_OPT_DEPS", "true"))
-RUN_TEST_OFFICIAL = env_var_2_bool(os.getenv("TEST_OFFICIAL"))
+GITHUB_ACTIONS: bool = env_var_2_bool(os.getenv("GITHUB_ACTIONS", "false"))
+TEST_WITH_OPT_DEPS: bool = env_var_2_bool(os.getenv("TEST_WITH_OPT_DEPS", "false")) or (
+ # on local setups, we usually want to test with optional dependencies
+ not GITHUB_ACTIONS
+)
+RUN_TEST_OFFICIAL: bool = env_var_2_bool(os.getenv("TEST_OFFICIAL"))
diff --git a/tests/conftest.py b/tests/conftest.py
index 48965395e19..e5e74a0271b 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -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 as dtm
import logging
import sys
+import zoneinfo
from pathlib import Path
from uuid import uuid4
@@ -40,11 +40,10 @@
from tests.auxil.build_messages import DATE
from tests.auxil.ci_bots import BOT_INFO_PROVIDER, JOB_INDEX
from tests.auxil.constants import PRIVATE_KEY, TEST_TOPIC_ICON_COLOR, TEST_TOPIC_NAME
-from tests.auxil.envvars import GITHUB_ACTION, RUN_TEST_OFFICIAL, TEST_WITH_OPT_DEPS
+from tests.auxil.envvars import GITHUB_ACTIONS, RUN_TEST_OFFICIAL, TEST_WITH_OPT_DEPS
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
@@ -97,7 +96,7 @@ def pytest_collection_modifyitems(items: list[pytest.Item]):
parent.add_marker(pytest.mark.no_req)
-if GITHUB_ACTION and JOB_INDEX == 0:
+if GITHUB_ACTIONS and JOB_INDEX == 0:
# let's not slow down the tests too much with these additional checks
# that's why we run them only in GitHub actions and only on *one* of the several test
# matrix entries
@@ -308,12 +307,20 @@ def false_update(request):
return Update(update_id=1, **request.param)
+@pytest.fixture(
+ scope="session",
+ params=[pytz.timezone, zoneinfo.ZoneInfo] if TEST_WITH_OPT_DEPS else [zoneinfo.ZoneInfo],
+)
+def _tz_implementation(request): # noqa: PT005
+ # This fixture is used to parametrize the timezone fixture
+ # This is similar to what @pyttest.mark.parametrize does but for fixtures
+ # However, this is needed only internally for the `tzinfo` fixture, so we keep it private
+ return request.param
+
+
@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=dtm.timedelta(hours=hours_offset), name=request.param)
+def tzinfo(request, _tz_implementation):
+ return _tz_implementation(request.param)
@pytest.fixture(scope="session")
diff --git a/tests/ext/test_defaults.py b/tests/ext/test_defaults.py
index 143b084a491..dc852aba19f 100644
--- a/tests/ext/test_defaults.py
+++ b/tests/ext/test_defaults.py
@@ -38,10 +38,17 @@ def test_slot_behaviour(self):
def test_utc(self):
defaults = Defaults()
- if not TEST_WITH_OPT_DEPS:
- assert defaults.tzinfo is dtm.timezone.utc
- else:
- assert defaults.tzinfo is not dtm.timezone.utc
+ assert defaults.tzinfo is dtm.timezone.utc
+
+ @pytest.mark.skipif(not TEST_WITH_OPT_DEPS, reason="pytz not installed")
+ def test_pytz_deprecation(self, recwarn):
+ import pytz
+
+ with pytest.warns(PTBDeprecationWarning, match="pytz") as record:
+ Defaults(tzinfo=pytz.timezone("Europe/Berlin"))
+
+ assert record[0].category == PTBDeprecationWarning
+ assert record[0].filename == __file__, "wrong stacklevel!"
def test_data_assignment(self):
defaults = Defaults()
diff --git a/tests/ext/test_jobqueue.py b/tests/ext/test_jobqueue.py
index 3de60cfbcfc..7af0040d632 100644
--- a/tests/ext/test_jobqueue.py
+++ b/tests/ext/test_jobqueue.py
@@ -18,6 +18,7 @@
# along with this program. If not, see [http://www.gnu.org/licenses/].
import asyncio
import calendar
+import contextlib
import datetime as dtm
import logging
import platform
@@ -26,7 +27,7 @@
import pytest
from telegram.ext import ApplicationBuilder, CallbackContext, ContextTypes, Defaults, Job, JobQueue
-from tests.auxil.envvars import GITHUB_ACTION, TEST_WITH_OPT_DEPS
+from tests.auxil.envvars import GITHUB_ACTIONS, TEST_WITH_OPT_DEPS
from tests.auxil.pytest_classes import make_bot
from tests.auxil.slots import mro_slots
@@ -68,7 +69,7 @@ def test_init_job(self):
not TEST_WITH_OPT_DEPS, reason="Only relevant if the optional dependency is installed"
)
@pytest.mark.skipif(
- bool(GITHUB_ACTION and platform.system() in ["Windows", "Darwin"]),
+ GITHUB_ACTIONS and platform.system() in ["Windows", "Darwin"],
reason="On Windows & MacOS precise timings are not accurate.",
)
@pytest.mark.flaky(10, 1) # Timings aren't quite perfect
@@ -77,6 +78,13 @@ class TestJobQueue:
job_time = 0
received_error = None
+ @staticmethod
+ def normalize(datetime: dtm.datetime, timezone: dtm.tzinfo) -> dtm.datetime:
+ with contextlib.suppress(AttributeError):
+ return timezone.normalize(datetime)
+
+ return datetime
+
async def test_repr(self, app):
jq = JobQueue()
jq.set_application(app)
@@ -102,7 +110,7 @@ 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
+ assert job_queue.scheduler_configuration["timezone"] is dtm.timezone.utc
tz_app = ApplicationBuilder().defaults(Defaults(tzinfo=timezone)).token(bot.token).build()
assert tz_app.job_queue.scheduler_configuration["timezone"] is timezone
@@ -356,6 +364,17 @@ async def test_time_unit_dt_time_tomorrow(self, job_queue):
scheduled_time = job_queue.jobs()[0].next_t.timestamp()
assert scheduled_time == pytest.approx(expected_time)
+ async def test_time_unit_dt_aware_time(self, job_queue, timezone):
+ # Testing running at a specific tz-aware time today
+ delta, now = 0.5, dtm.datetime.now(timezone)
+ expected_time = now + dtm.timedelta(seconds=delta)
+ when = expected_time.timetz()
+ expected_time = expected_time.timestamp()
+
+ job_queue.run_once(self.job_datetime_tests, when)
+ await asyncio.sleep(0.6)
+ assert self.job_time == pytest.approx(expected_time)
+
async def test_run_daily(self, job_queue):
delta, now = 1, dtm.datetime.now(UTC)
time_of_day = (now + dtm.timedelta(seconds=delta)).time()
@@ -397,7 +416,7 @@ async def test_run_monthly(self, job_queue, timezone):
if day > next_months_days:
expected_reschedule_time += dtm.timedelta(next_months_days)
- expected_reschedule_time = timezone.normalize(expected_reschedule_time)
+ expected_reschedule_time = self.normalize(expected_reschedule_time, timezone)
# Adjust the hour for the special case that between now and next month a DST switch happens
expected_reschedule_time += dtm.timedelta(
hours=time_of_day.hour - expected_reschedule_time.hour
@@ -419,7 +438,7 @@ async def test_run_monthly_non_strict_day(self, job_queue, timezone):
calendar.monthrange(now.year, now.month)[1]
) - dtm.timedelta(days=now.day)
# Adjust the hour for the special case that between now & end of month a DST switch happens
- expected_reschedule_time = timezone.normalize(expected_reschedule_time)
+ expected_reschedule_time = self.normalize(expected_reschedule_time, timezone)
expected_reschedule_time += dtm.timedelta(
hours=time_of_day.hour - expected_reschedule_time.hour
)
diff --git a/tests/ext/test_ratelimiter.py b/tests/ext/test_ratelimiter.py
index 0b8a8c6b8ed..dd88f7cb1cf 100644
--- a/tests/ext/test_ratelimiter.py
+++ b/tests/ext/test_ratelimiter.py
@@ -35,7 +35,7 @@
from telegram.error import RetryAfter
from telegram.ext import AIORateLimiter, BaseRateLimiter, Defaults, ExtBot
from telegram.request import BaseRequest, RequestData
-from tests.auxil.envvars import GITHUB_ACTION, TEST_WITH_OPT_DEPS
+from tests.auxil.envvars import GITHUB_ACTIONS, TEST_WITH_OPT_DEPS
@pytest.mark.skipif(
@@ -142,7 +142,7 @@ async def do_request(self, *args, **kwargs):
not TEST_WITH_OPT_DEPS, reason="Only relevant if the optional dependency is installed"
)
@pytest.mark.skipif(
- bool(GITHUB_ACTION and platform.system() == "Darwin"),
+ GITHUB_ACTIONS and platform.system() == "Darwin",
reason="The timings are apparently rather inaccurate on MacOS.",
)
@pytest.mark.flaky(10, 1) # Timings aren't quite perfect
diff --git a/tests/test_bot.py b/tests/test_bot.py
index 985b2b5078d..65603dbda97 100644
--- a/tests/test_bot.py
+++ b/tests/test_bot.py
@@ -79,7 +79,7 @@
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.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_ACTIONS
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
@@ -154,7 +154,7 @@ def inline_results():
BASE_GAME_SCORE = 60 # Base game score for game tests
xfail = pytest.mark.xfail(
- bool(GITHUB_ACTION), # This condition is only relevant for github actions game tests.
+ GITHUB_ACTIONS, # This condition is only relevant for github actions game tests.
reason=(
"Can fail due to race conditions when multiple test suites "
"with the same bot token are run at the same time"
@@ -3467,7 +3467,6 @@ async def test_create_chat_invite_link_basics(
)
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 @@ async def test_advanced_chat_invite_links(self, bot, channel_id, datetime):
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 @@ async def test_advanced_chat_invite_links(self, bot, channel_id, datetime):
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,
diff --git a/tests/test_constants.py b/tests/test_constants.py
index dc352a42f98..3cd9e56e7ab 100644
--- a/tests/test_constants.py
+++ b/tests/test_constants.py
@@ -58,7 +58,7 @@ def test__all__(self):
not key.startswith("_")
# exclude imported stuff
and getattr(member, "__module__", "telegram.constants") == "telegram.constants"
- and key not in ("sys", "dtm")
+ and key not in ("sys", "dtm", "UTC")
)
}
actual = set(constants.__all__)
pFad - Phonifier reborn
Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.
Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.
Alternative Proxies:
Alternative Proxy
pFad Proxy
pFad v3 Proxy
pFad v4 Proxy