mirror of
https://github.com/python-telegram-bot/python-telegram-bot.git
synced 2024-12-22 06:25:12 +01:00
Defaults.tzinfo (#2042)
This commit is contained in:
parent
b07e42ef33
commit
103b115486
12 changed files with 194 additions and 55 deletions
|
@ -1766,6 +1766,8 @@ class Bot(TelegramObject):
|
|||
until_date (:obj:`int` | :obj:`datetime.datetime`, optional): Date when the user will
|
||||
be unbanned, unix time. If user is banned for more than 366 days or less than 30
|
||||
seconds from the current time they are considered to be banned forever.
|
||||
For timezone naive :obj:`datetime.datetime` objects, the default timezone of the
|
||||
bot will be used.
|
||||
api_kwargs (:obj:`dict`, optional): Arbitrary keyword arguments to be passed to the
|
||||
Telegram API.
|
||||
|
||||
|
@ -1780,7 +1782,8 @@ class Bot(TelegramObject):
|
|||
|
||||
if until_date is not None:
|
||||
if isinstance(until_date, datetime):
|
||||
until_date = to_timestamp(until_date)
|
||||
until_date = to_timestamp(until_date,
|
||||
tzinfo=self.defaults.tzinfo if self.defaults else None)
|
||||
data['until_date'] = until_date
|
||||
|
||||
result = self._post('kickChatMember', data, timeout=timeout, api_kwargs=api_kwargs)
|
||||
|
@ -2866,6 +2869,8 @@ class Bot(TelegramObject):
|
|||
will be lifted for the user, unix time. If user is restricted for more than 366
|
||||
days or less than 30 seconds from the current time, they are considered to be
|
||||
restricted forever.
|
||||
For timezone naive :obj:`datetime.datetime` objects, the default timezone of the
|
||||
bot will be used.
|
||||
permissions (:class:`telegram.ChatPermissions`): A JSON-serialized object for new user
|
||||
permissions.
|
||||
timeout (:obj:`int` | :obj:`float`, optional): If this value is specified, use it as
|
||||
|
@ -2884,7 +2889,8 @@ class Bot(TelegramObject):
|
|||
|
||||
if until_date is not None:
|
||||
if isinstance(until_date, datetime):
|
||||
until_date = to_timestamp(until_date)
|
||||
until_date = to_timestamp(until_date,
|
||||
tzinfo=self.defaults.tzinfo if self.defaults else None)
|
||||
data['until_date'] = until_date
|
||||
|
||||
result = self._post('restrictChatMember', data, timeout=timeout, api_kwargs=api_kwargs)
|
||||
|
@ -3630,6 +3636,8 @@ class Bot(TelegramObject):
|
|||
timestamp) when the poll will be automatically closed. Must be at least 5 and no
|
||||
more than 600 seconds in the future. Can't be used together with
|
||||
:attr:`open_period`.
|
||||
For timezone naive :obj:`datetime.datetime` objects, the default timezone of the
|
||||
bot will be used.
|
||||
is_closed (:obj:`bool`, optional): Pass :obj:`True`, if the poll needs to be
|
||||
immediately closed. This can be useful for poll preview.
|
||||
disable_notification (:obj:`bool`, optional): Sends the message silently. Users will
|
||||
|
@ -3682,7 +3690,8 @@ class Bot(TelegramObject):
|
|||
data['open_period'] = open_period
|
||||
if close_date:
|
||||
if isinstance(close_date, datetime):
|
||||
close_date = to_timestamp(close_date)
|
||||
close_date = to_timestamp(close_date,
|
||||
tzinfo=self.defaults.tzinfo if self.defaults else None)
|
||||
data['close_date'] = close_date
|
||||
|
||||
return self._message('sendPoll', data, timeout=timeout,
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
# You should have received a copy of the GNU Lesser Public License
|
||||
# along with this program. If not, see [http://www.gnu.org/licenses/].
|
||||
"""This module contains the class Defaults, which allows to pass default values to Updater."""
|
||||
import pytz
|
||||
|
||||
from telegram.utils.helpers import DEFAULT_NONE
|
||||
|
||||
|
@ -37,6 +38,8 @@ class Defaults:
|
|||
quote (:obj:`bool`): Optional. If set to :obj:`True`, the reply is sent as an actual reply
|
||||
to the message. If ``reply_to_message_id`` is passed in ``kwargs``, this parameter will
|
||||
be ignored. Default: :obj:`True` in group chats and :obj:`False` in private chats.
|
||||
tzinfo (:obj:`tzinfo`): A timezone to be used for all date(time) objects appearing
|
||||
throughout PTB.
|
||||
|
||||
Parameters:
|
||||
parse_mode (:obj:`str`, optional): Send Markdown or HTML, if you want Telegram apps to show
|
||||
|
@ -51,6 +54,10 @@ class Defaults:
|
|||
quote (:obj:`bool`, optional): If set to :obj:`True`, the reply is sent as an actual reply
|
||||
to the message. If ``reply_to_message_id`` is passed in ``kwargs``, this parameter will
|
||||
be ignored. Default: :obj:`True` in group chats and :obj:`False` in private chats.
|
||||
tzinfo (:obj:`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 ``tzinfo``. Must be a timezone provided by the
|
||||
``pytz`` module. Defaults to UTC.
|
||||
"""
|
||||
def __init__(self,
|
||||
parse_mode=None,
|
||||
|
@ -59,12 +66,14 @@ class Defaults:
|
|||
# Timeout needs special treatment, since the bot methods have two different
|
||||
# default values for timeout (None and 20s)
|
||||
timeout=DEFAULT_NONE,
|
||||
quote=None):
|
||||
quote=None,
|
||||
tzinfo=pytz.utc):
|
||||
self._parse_mode = parse_mode
|
||||
self._disable_notification = disable_notification
|
||||
self._disable_web_page_preview = disable_web_page_preview
|
||||
self._timeout = timeout
|
||||
self._quote = quote
|
||||
self._tzinfo = tzinfo
|
||||
|
||||
@property
|
||||
def parse_mode(self):
|
||||
|
@ -111,12 +120,22 @@ class Defaults:
|
|||
raise AttributeError("You can not assign a new value to defaults after because it would "
|
||||
"not have any effect.")
|
||||
|
||||
@property
|
||||
def tzinfo(self):
|
||||
return self._tzinfo
|
||||
|
||||
@tzinfo.setter
|
||||
def tzinfo(self, value):
|
||||
raise AttributeError("You can not assign a new value to defaults after because it would "
|
||||
"not have any effect.")
|
||||
|
||||
def __hash__(self):
|
||||
return hash((self._parse_mode,
|
||||
self._disable_notification,
|
||||
self._disable_web_page_preview,
|
||||
self._timeout,
|
||||
self._quote))
|
||||
self._quote,
|
||||
self._tzinfo))
|
||||
|
||||
def __eq__(self, other):
|
||||
if isinstance(other, Defaults):
|
||||
|
|
|
@ -89,8 +89,9 @@ class JobQueue:
|
|||
return self._tz_now() + time
|
||||
if isinstance(time, datetime.time):
|
||||
dt = datetime.datetime.combine(
|
||||
datetime.datetime.now(tz=time.tzinfo or self.scheduler.timezone).date(), time,
|
||||
tzinfo=time.tzinfo or self.scheduler.timezone)
|
||||
datetime.datetime.now(tz=time.tzinfo or self.scheduler.timezone).date(), time)
|
||||
if dt.tzinfo is None:
|
||||
dt = self.scheduler.timezone.localize(dt)
|
||||
if shift_day and dt <= datetime.datetime.now(pytz.utc):
|
||||
dt += datetime.timedelta(days=1)
|
||||
return dt
|
||||
|
@ -106,6 +107,9 @@ class JobQueue:
|
|||
|
||||
"""
|
||||
self._dispatcher = dispatcher
|
||||
if dispatcher.bot.defaults:
|
||||
if dispatcher.bot.defaults:
|
||||
self.scheduler.configure(timezone=dispatcher.bot.defaults.tzinfo or pytz.utc)
|
||||
|
||||
def run_once(self, callback, when, context=None, name=None, job_kwargs=None):
|
||||
"""Creates a new ``Job`` that runs once and adds it to the queue.
|
||||
|
@ -129,13 +133,11 @@ class JobQueue:
|
|||
job should run.
|
||||
* :obj:`datetime.datetime` will be interpreted as a specific date and time at
|
||||
which the job should run. If the timezone (``datetime.tzinfo``) is :obj:`None`,
|
||||
UTC will be assumed.
|
||||
the default timezone of the bot will be used.
|
||||
* :obj:`datetime.time` will be interpreted as a specific time of day at which the
|
||||
job should run. This could be either today or, if the time has already passed,
|
||||
tomorrow. If the timezone (``time.tzinfo``) is :obj:`None`, UTC will be assumed.
|
||||
|
||||
If ``when`` is :obj:`datetime.datetime` or :obj:`datetime.time` type
|
||||
then ``when.tzinfo`` will define ``Job.tzinfo``. Otherwise UTC will be assumed.
|
||||
tomorrow. If the timezone (``time.tzinfo``) is :obj:`None`, the
|
||||
default timezone of the bot will be used.
|
||||
|
||||
context (:obj:`object`, optional): Additional data needed for the callback function.
|
||||
Can be accessed through ``job.context`` in the callback. Defaults to :obj:`None`.
|
||||
|
@ -193,13 +195,11 @@ class JobQueue:
|
|||
job should run.
|
||||
* :obj:`datetime.datetime` will be interpreted as a specific date and time at
|
||||
which the job should run. If the timezone (``datetime.tzinfo``) is :obj:`None`,
|
||||
UTC will be assumed.
|
||||
the default timezone of the bot will be used.
|
||||
* :obj:`datetime.time` will be interpreted as a specific time of day at which the
|
||||
job should run. This could be either today or, if the time has already passed,
|
||||
tomorrow. If the timezone (``time.tzinfo``) is :obj:`None`, UTC will be assumed.
|
||||
|
||||
If ``first`` is :obj:`datetime.datetime` or :obj:`datetime.time` type
|
||||
then ``first.tzinfo`` will define ``Job.tzinfo``. Otherwise UTC will be assumed.
|
||||
tomorrow. If the timezone (``time.tzinfo``) is :obj:`None`, the
|
||||
default timezone of the bot will be used.
|
||||
|
||||
Defaults to ``interval``
|
||||
last (:obj:`int` | :obj:`float` | :obj:`datetime.timedelta` | \
|
||||
|
@ -208,7 +208,8 @@ class JobQueue:
|
|||
depending on its type. See ``first`` for details.
|
||||
|
||||
If ``last`` is :obj:`datetime.datetime` or :obj:`datetime.time` type
|
||||
and ``last.tzinfo`` is :obj:`None`, UTC will be assumed.
|
||||
and ``last.tzinfo`` is :obj:`None`, the default timezone of the bot will be
|
||||
assumed.
|
||||
|
||||
Defaults to :obj:`None`.
|
||||
context (:obj:`object`, optional): Additional data needed for the callback function.
|
||||
|
@ -268,8 +269,7 @@ class JobQueue:
|
|||
``context.job`` is the :class:`telegram.ext.Job` instance. It can be used to access
|
||||
its ``job.context`` or change it to a repeating job.
|
||||
when (:obj:`datetime.time`): Time of day at which the job should run. If the timezone
|
||||
(``when.tzinfo``) is :obj:`None`, UTC will be assumed. This will also implicitly
|
||||
define ``Job.tzinfo``.
|
||||
(``when.tzinfo``) is :obj:`None`, the default timezone of the bot will be used.
|
||||
day (:obj:`int`): Defines the day of the month whereby the job would run. It should
|
||||
be within the range of 1 and 31, inclusive.
|
||||
context (:obj:`object`, optional): Additional data needed for the callback function.
|
||||
|
@ -338,8 +338,7 @@ class JobQueue:
|
|||
``context.job`` is the :class:`telegram.ext.Job` instance. It can be used to access
|
||||
its ``job.context`` or change it to a repeating job.
|
||||
time (:obj:`datetime.time`): Time of day at which the job should run. If the timezone
|
||||
(``time.tzinfo``) is :obj:`None`, UTC will be assumed.
|
||||
``time.tzinfo`` will implicitly define ``Job.tzinfo``.
|
||||
(``time.tzinfo``) is :obj:`None`, the default timezone of the bot will be used.
|
||||
days (Tuple[:obj:`int`], optional): Defines on which days of the week the job should
|
||||
run. Defaults to ``EVERY_DAY``
|
||||
context (:obj:`object`, optional): Additional data needed for the callback function.
|
||||
|
|
|
@ -26,6 +26,8 @@ from collections import defaultdict
|
|||
from html import escape
|
||||
from numbers import Number
|
||||
|
||||
import pytz
|
||||
|
||||
try:
|
||||
import ujson as json
|
||||
except ImportError:
|
||||
|
@ -72,8 +74,6 @@ def escape_markdown(text, version=1, entity_type=None):
|
|||
|
||||
|
||||
# -------- date/time related helpers --------
|
||||
# TODO: add generic specification of UTC for naive datetimes to docs
|
||||
|
||||
def _datetime_to_float_timestamp(dt_obj):
|
||||
"""
|
||||
Converts a datetime object to a float timestamp (with sub-second precision).
|
||||
|
@ -85,13 +85,13 @@ def _datetime_to_float_timestamp(dt_obj):
|
|||
return dt_obj.timestamp()
|
||||
|
||||
|
||||
def to_float_timestamp(t, reference_timestamp=None):
|
||||
def to_float_timestamp(t, reference_timestamp=None, tzinfo=None):
|
||||
"""
|
||||
Converts a given time object to a float POSIX timestamp.
|
||||
Used to convert different time specifications to a common format. The time object
|
||||
can be relative (i.e. indicate a time increment, or a time of day) or absolute.
|
||||
Any objects from the :class:`datetime` module that are timezone-naive will be assumed
|
||||
to be in UTC.
|
||||
to be in UTC, if ``bot`` is not passed or ``bot.defaults`` is :obj:`None`.
|
||||
|
||||
:obj:`None` s are left alone (i.e. ``to_float_timestamp(None)`` is :obj:`None`).
|
||||
|
||||
|
@ -113,6 +113,9 @@ def to_float_timestamp(t, reference_timestamp=None):
|
|||
If ``t`` is given as an absolute representation of date & time (i.e. a
|
||||
``datetime.datetime`` object), ``reference_timestamp`` is not relevant and so its
|
||||
value should be :obj:`None`. If this is not the case, a ``ValueError`` will be raised.
|
||||
tzinfo (:obj:`datetime.tzinfo`, optional): If ``t`` is a naive object from the
|
||||
:class:`datetime` module, it will be interpreted as this timezone. Defaults to
|
||||
``pytz.utc``.
|
||||
|
||||
Returns:
|
||||
(float | None) The return value depends on the type of argument ``t``. If ``t`` is
|
||||
|
@ -138,33 +141,43 @@ def to_float_timestamp(t, reference_timestamp=None):
|
|||
return reference_timestamp + t.total_seconds()
|
||||
elif isinstance(t, Number):
|
||||
return reference_timestamp + t
|
||||
elif isinstance(t, dtm.time):
|
||||
if t.tzinfo is not None:
|
||||
reference_dt = dtm.datetime.fromtimestamp(reference_timestamp, tz=t.tzinfo)
|
||||
else:
|
||||
reference_dt = dtm.datetime.utcfromtimestamp(reference_timestamp) # assume UTC
|
||||
|
||||
if tzinfo is None:
|
||||
tzinfo = pytz.utc
|
||||
|
||||
if isinstance(t, dtm.time):
|
||||
reference_dt = dtm.datetime.fromtimestamp(reference_timestamp, tz=t.tzinfo or tzinfo)
|
||||
reference_date = reference_dt.date()
|
||||
reference_time = reference_dt.timetz()
|
||||
if reference_time > t: # if the time of day has passed today, use tomorrow
|
||||
reference_date += dtm.timedelta(days=1)
|
||||
return _datetime_to_float_timestamp(dtm.datetime.combine(reference_date, t))
|
||||
|
||||
aware_datetime = dtm.datetime.combine(reference_date, t)
|
||||
if aware_datetime.tzinfo is None:
|
||||
aware_datetime = tzinfo.localize(aware_datetime)
|
||||
|
||||
# if the time of day has passed today, use tomorrow
|
||||
if reference_time > aware_datetime.timetz():
|
||||
aware_datetime += dtm.timedelta(days=1)
|
||||
return _datetime_to_float_timestamp(aware_datetime)
|
||||
elif isinstance(t, dtm.datetime):
|
||||
if t.tzinfo is None:
|
||||
t = tzinfo.localize(t)
|
||||
return _datetime_to_float_timestamp(t)
|
||||
|
||||
raise TypeError('Unable to convert {} object to timestamp'.format(type(t).__name__))
|
||||
|
||||
|
||||
def to_timestamp(dt_obj, reference_timestamp=None):
|
||||
def to_timestamp(dt_obj, reference_timestamp=None, tzinfo=pytz.utc):
|
||||
"""
|
||||
Wrapper over :func:`to_float_timestamp` which returns an integer (the float value truncated
|
||||
down to the nearest integer).
|
||||
|
||||
See the documentation for :func:`to_float_timestamp` for more details.
|
||||
"""
|
||||
return int(to_float_timestamp(dt_obj, reference_timestamp)) if dt_obj is not None else None
|
||||
return (int(to_float_timestamp(dt_obj, reference_timestamp, tzinfo))
|
||||
if dt_obj is not None else None)
|
||||
|
||||
|
||||
def from_timestamp(unixtime, tzinfo=dtm.timezone.utc):
|
||||
def from_timestamp(unixtime, tzinfo=pytz.utc):
|
||||
"""
|
||||
Converts an (integer) unix timestamp to a timezone aware datetime object.
|
||||
:obj:`None`s are left alone (i.e. ``from_timestamp(None)`` is :obj:`None`).
|
||||
|
|
|
@ -69,6 +69,18 @@ def default_bot(request, bot_info):
|
|||
return default_bot
|
||||
|
||||
|
||||
@pytest.fixture(scope='function')
|
||||
def tz_bot(timezone, bot_info):
|
||||
defaults = Defaults(tzinfo=timezone)
|
||||
default_bot = DEFAULT_BOTS.get(defaults)
|
||||
if default_bot:
|
||||
return default_bot
|
||||
else:
|
||||
default_bot = make_bot(bot_info, **{'defaults': defaults})
|
||||
DEFAULT_BOTS[defaults] = default_bot
|
||||
return default_bot
|
||||
|
||||
|
||||
@pytest.fixture(scope='session')
|
||||
def chat_id(bot_info):
|
||||
return bot_info['chat_id']
|
||||
|
|
|
@ -29,7 +29,7 @@ from telegram import (Bot, Update, ChatAction, TelegramError, User, InlineKeyboa
|
|||
InlineQueryResultDocument, Dice, MessageEntity, ParseMode)
|
||||
from telegram.constants import MAX_INLINE_QUERY_RESULTS
|
||||
from telegram.error import BadRequest, InvalidToken, NetworkError, RetryAfter
|
||||
from telegram.utils.helpers import from_timestamp, escape_markdown
|
||||
from telegram.utils.helpers import from_timestamp, escape_markdown, to_timestamp
|
||||
from tests.conftest import expect_bad_request
|
||||
|
||||
BASE_TIME = time.time()
|
||||
|
@ -272,6 +272,29 @@ class TestBot:
|
|||
assert new_message.poll.id == message.poll.id
|
||||
assert new_message.poll.is_closed
|
||||
|
||||
@flaky(5, 1)
|
||||
@pytest.mark.timeout(10)
|
||||
def test_send_close_date_default_tz(self, tz_bot, super_group_id):
|
||||
question = 'Is this a test?'
|
||||
answers = ['Yes', 'No', 'Maybe']
|
||||
reply_markup = InlineKeyboardMarkup.from_button(
|
||||
InlineKeyboardButton(text='text', callback_data='data'))
|
||||
|
||||
aware_close_date = dtm.datetime.now(tz=tz_bot.defaults.tzinfo) + dtm.timedelta(seconds=5)
|
||||
close_date = aware_close_date.replace(tzinfo=None)
|
||||
|
||||
message = tz_bot.send_poll(chat_id=super_group_id, question=question, options=answers,
|
||||
close_date=close_date, timeout=60)
|
||||
assert message.poll.close_date == aware_close_date.replace(microsecond=0)
|
||||
|
||||
time.sleep(5.1)
|
||||
|
||||
new_message = tz_bot.edit_message_reply_markup(chat_id=super_group_id,
|
||||
message_id=message.message_id,
|
||||
reply_markup=reply_markup, timeout=60)
|
||||
assert new_message.poll.id == message.poll.id
|
||||
assert new_message.poll.is_closed
|
||||
|
||||
@flaky(3, 1)
|
||||
@pytest.mark.timeout(10)
|
||||
@pytest.mark.parametrize('default_bot', [{'parse_mode': 'Markdown'}], indirect=True)
|
||||
|
@ -518,6 +541,22 @@ class TestBot:
|
|||
assert bot.kick_chat_member(2, 32, until_date=until)
|
||||
assert bot.kick_chat_member(2, 32, until_date=1577887200)
|
||||
|
||||
def test_kick_chat_member_default_tz(self, monkeypatch, tz_bot):
|
||||
until = dtm.datetime(2020, 1, 11, 16, 13)
|
||||
until_timestamp = to_timestamp(until, tzinfo=tz_bot.defaults.tzinfo)
|
||||
|
||||
def test(url, data, *args, **kwargs):
|
||||
chat_id = data['chat_id'] == 2
|
||||
user_id = data['user_id'] == 32
|
||||
until_date = data.get('until_date', until_timestamp) == until_timestamp
|
||||
return chat_id and user_id and until_date
|
||||
|
||||
monkeypatch.setattr(tz_bot.request, 'post', test)
|
||||
|
||||
assert tz_bot.kick_chat_member(2, 32)
|
||||
assert tz_bot.kick_chat_member(2, 32, until_date=until)
|
||||
assert tz_bot.kick_chat_member(2, 32, until_date=until_timestamp)
|
||||
|
||||
# TODO: Needs improvement.
|
||||
def test_unban_chat_member(self, monkeypatch, bot):
|
||||
def test(url, data, *args, **kwargs):
|
||||
|
@ -951,6 +990,28 @@ class TestBot:
|
|||
chat_permissions,
|
||||
until_date=dtm.datetime.utcnow())
|
||||
|
||||
def test_restrict_chat_member_default_tz(self, monkeypatch, tz_bot, channel_id,
|
||||
chat_permissions):
|
||||
until = dtm.datetime(2020, 1, 11, 16, 13)
|
||||
until_timestamp = to_timestamp(until, tzinfo=tz_bot.defaults.tzinfo)
|
||||
|
||||
def test(url, data, *args, **kwargs):
|
||||
return data.get('until_date', until_timestamp) == until_timestamp
|
||||
|
||||
monkeypatch.setattr(tz_bot.request, 'post', test)
|
||||
|
||||
assert tz_bot.restrict_chat_member(channel_id,
|
||||
95205500,
|
||||
chat_permissions)
|
||||
assert tz_bot.restrict_chat_member(channel_id,
|
||||
95205500,
|
||||
chat_permissions,
|
||||
until_date=until)
|
||||
assert tz_bot.restrict_chat_member(channel_id,
|
||||
95205500,
|
||||
chat_permissions,
|
||||
until_date=until_timestamp)
|
||||
|
||||
@flaky(3, 1)
|
||||
@pytest.mark.timeout(10)
|
||||
def test_promote_chat_member(self, bot, channel_id):
|
||||
|
|
|
@ -37,6 +37,8 @@ class TestDefault:
|
|||
defaults.timeout = True
|
||||
with pytest.raises(AttributeError):
|
||||
defaults.quote = True
|
||||
with pytest.raises(AttributeError):
|
||||
defaults.tzinfo = True
|
||||
|
||||
def test_equality(self):
|
||||
a = Defaults(parse_mode='HTML', quote=True)
|
||||
|
|
|
@ -190,7 +190,7 @@ class TestFilters:
|
|||
matches = result['matches']
|
||||
assert isinstance(matches, list)
|
||||
assert all([type(res) == SRE_TYPE for res in matches])
|
||||
update.message.forward_date = False
|
||||
update.message.forward_date = None
|
||||
result = filter(update)
|
||||
assert not result
|
||||
update.message.text = 'test it out'
|
||||
|
@ -926,7 +926,7 @@ class TestFilters:
|
|||
update.message.text = 'test'
|
||||
update.message.forward_date = datetime.datetime.utcnow()
|
||||
assert (Filters.text & (Filters.status_update | Filters.forwarded))(update)
|
||||
update.message.forward_date = False
|
||||
update.message.forward_date = None
|
||||
assert not (Filters.text & (Filters.forwarded | Filters.status_update))(update)
|
||||
update.message.pinned_message = True
|
||||
assert (Filters.text & (Filters.forwarded | Filters.status_update)(update))
|
||||
|
|
|
@ -25,6 +25,7 @@ from telegram import Sticker
|
|||
from telegram import Update
|
||||
from telegram import User
|
||||
from telegram import MessageEntity
|
||||
from telegram.ext import Defaults
|
||||
from telegram.message import Message
|
||||
from telegram.utils import helpers
|
||||
from telegram.utils.helpers import _datetime_to_float_timestamp
|
||||
|
@ -135,6 +136,10 @@ class TestHelpers:
|
|||
assert (helpers.to_float_timestamp(time_spec)
|
||||
== pytest.approx(helpers.to_float_timestamp(time_spec, reference_timestamp=now)))
|
||||
|
||||
def test_to_float_timestamp_error(self):
|
||||
with pytest.raises(TypeError, match='Defaults'):
|
||||
helpers.to_float_timestamp(Defaults())
|
||||
|
||||
@pytest.mark.parametrize('time_spec', TIME_SPECS, ids=str)
|
||||
def test_to_timestamp(self, time_spec):
|
||||
# delegate tests to `to_float_timestamp`
|
||||
|
@ -144,6 +149,9 @@ class TestHelpers:
|
|||
# this 'convenience' behaviour has been left left for backwards compatibility
|
||||
assert helpers.to_timestamp(None) is None
|
||||
|
||||
def test_from_timestamp_none(self):
|
||||
assert helpers.from_timestamp(None) is None
|
||||
|
||||
def test_from_timestamp_naive(self):
|
||||
datetime = dtm.datetime(2019, 11, 11, 0, 26, 16, tzinfo=None)
|
||||
assert helpers.from_timestamp(1573431976, tzinfo=None) == datetime
|
||||
|
|
|
@ -121,7 +121,7 @@ class TestJobQueue:
|
|||
sleep(0.07)
|
||||
assert self.result == 1
|
||||
|
||||
def test_run_repeating_first_timezone(self, job_queue, timezone):
|
||||
def test_run_repeating_last_timezone(self, job_queue, timezone):
|
||||
"""Test correct scheduling of job when passing a timezone-aware datetime as ``first``"""
|
||||
job_queue.run_repeating(self.job_run_once, 0.1,
|
||||
first=dtm.datetime.now(timezone) + dtm.timedelta(seconds=0.05))
|
||||
|
@ -135,15 +135,6 @@ class TestJobQueue:
|
|||
sleep(0.1)
|
||||
assert self.result == 1
|
||||
|
||||
def test_run_repeating_last_timezone(self, job_queue, timezone):
|
||||
"""Test correct scheduling of job when passing a timezone-aware datetime as ``first``"""
|
||||
job_queue.run_repeating(self.job_run_once, 0.05,
|
||||
last=dtm.datetime.now(timezone) + dtm.timedelta(seconds=0.06))
|
||||
sleep(0.1)
|
||||
assert self.result == 1
|
||||
sleep(0.1)
|
||||
assert self.result == 1
|
||||
|
||||
def test_run_repeating_last_before_first(self, job_queue):
|
||||
with pytest.raises(ValueError, match="'last' must not be before 'first'!"):
|
||||
job_queue.run_repeating(self.job_run_once, 0.05, first=1, last=0.5)
|
||||
|
@ -300,7 +291,11 @@ class TestJobQueue:
|
|||
time_of_day = expected_reschedule_time.time().replace(tzinfo=timezone)
|
||||
|
||||
day = now.day
|
||||
expected_reschedule_time += dtm.timedelta(calendar.monthrange(now.year, now.month)[1])
|
||||
expected_reschedule_time = timezone.normalize(
|
||||
expected_reschedule_time + dtm.timedelta(calendar.monthrange(now.year, now.month)[1]))
|
||||
# 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)
|
||||
expected_reschedule_time = expected_reschedule_time.timestamp()
|
||||
|
||||
job_queue.run_monthly(self.job_run_once, time_of_day, day)
|
||||
|
@ -326,6 +321,25 @@ class TestJobQueue:
|
|||
scheduled_time = job_queue.jobs()[0].next_t.timestamp()
|
||||
assert scheduled_time == pytest.approx(expected_reschedule_time)
|
||||
|
||||
def test_default_tzinfo(self, _dp, tz_bot):
|
||||
# we're parametrizing this with two different UTC offsets to exclude the possibility
|
||||
# of an xpass when the test is run in a timezone with the same UTC offset
|
||||
jq = JobQueue()
|
||||
original_bot = _dp.bot
|
||||
_dp.bot = tz_bot
|
||||
jq.set_dispatcher(_dp)
|
||||
try:
|
||||
jq.start()
|
||||
|
||||
when = dtm.datetime.now(tz_bot.defaults.tzinfo) + dtm.timedelta(seconds=0.0005)
|
||||
jq.run_once(self.job_run_once, when.time())
|
||||
sleep(0.001)
|
||||
assert self.result == 1
|
||||
|
||||
jq.stop()
|
||||
finally:
|
||||
_dp.bot = original_bot
|
||||
|
||||
@pytest.mark.parametrize('use_context', [True, False])
|
||||
def test_get_jobs(self, job_queue, use_context):
|
||||
job_queue._dispatcher.use_context = use_context
|
||||
|
|
|
@ -169,13 +169,13 @@ class TestMessage:
|
|||
MessageEntity(**e) for e in test_entities_v2
|
||||
])
|
||||
|
||||
def test_all_posibilities_de_json_and_to_dict(self, bot, message_params):
|
||||
def test_all_possibilities_de_json_and_to_dict(self, bot, message_params):
|
||||
new = Message.de_json(message_params.to_dict(), bot)
|
||||
|
||||
assert new.to_dict() == message_params.to_dict()
|
||||
|
||||
def test_dict_approach(self, message):
|
||||
assert message['date'] == message.date
|
||||
assert message['text'] == message.text
|
||||
assert message['chat_id'] == message.chat_id
|
||||
assert message['no_key'] is None
|
||||
|
||||
|
|
|
@ -20,6 +20,8 @@
|
|||
import pytest
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
from telegram import Poll, PollOption, PollAnswer, User, MessageEntity
|
||||
from telegram.utils.helpers import to_timestamp
|
||||
|
||||
|
@ -154,7 +156,7 @@ class TestPoll:
|
|||
open_period = 42
|
||||
close_date = datetime.utcnow()
|
||||
|
||||
def test_de_json(self):
|
||||
def test_de_json(self, bot):
|
||||
json_dict = {
|
||||
'id': self.id_,
|
||||
'question': self.question,
|
||||
|
@ -169,7 +171,7 @@ class TestPoll:
|
|||
'open_period': self.open_period,
|
||||
'close_date': to_timestamp(self.close_date)
|
||||
}
|
||||
poll = Poll.de_json(json_dict, None)
|
||||
poll = Poll.de_json(json_dict, bot)
|
||||
|
||||
assert poll.id == self.id_
|
||||
assert poll.question == self.question
|
||||
|
|
Loading…
Reference in a new issue