Merge branch 'master' into V12

This commit is contained in:
Jasmin Bom 2019-02-08 12:49:28 +01:00
commit 1b52e6148e
30 changed files with 396 additions and 82 deletions

View file

@ -13,7 +13,7 @@
args:
- --ignore=W605,W503
- repo: git://github.com/pre-commit/mirrors-pylint
sha: v1.7.1
sha: v2.2.2
hooks:
- id: pylint
files: ^telegram/.*\.py$

View file

@ -16,9 +16,11 @@ Contributors
The following wonderful people contributed directly or indirectly to this project:
- `Alateas <https://github.com/alateas>`_
- `Ambro17 <https://github.com/Ambro17>`_
- `Anton Tagunov <https://github.com/anton-tagunov>`_
- `Avanatiker <https://github.com/Avanatiker>`_
- `Balduro <https://github.com/Balduro>`_
- `Bibo-Joshi <https://github.com/Bibo-Joshi>`_
- `bimmlerd <https://github.com/bimmlerd>`_
- `d-qoi <https://github.com/d-qoi>`_
- `daimajia <https://github.com/daimajia>`_

View file

@ -55,6 +55,6 @@ with codecs.open('README.rst', 'r', 'utf-8') as fd:
'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.4',
'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.6'
'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: 3.7'
],)

View file

@ -1755,7 +1755,7 @@ class Bot(TelegramObject):
Args:
chat_id (:obj:`int` | :obj:`str`): Unique identifier for the target chat or username
of the target`channel (in the format @channelusername).
of the target channel (in the format @channelusername).
message_id (:obj:`int`, optional): Required if inline_message_id is not specified.
Identifier of the sent message.
inline_message_id (:obj:`str`, optional): Required if chat_id and message_id are not
@ -1821,7 +1821,7 @@ class Bot(TelegramObject):
Args:
chat_id (:obj:`int` | :obj:`str`, optional): Unique identifier for the target chat or
username of the target`channel (in the format @channelusername).
username of the target channel (in the format @channelusername).
message_id (:obj:`int`, optional): Required if inline_message_id is not specified.
Identifier of the sent message.
inline_message_id (:obj:`str`, optional): Required if chat_id and message_id are not
@ -1870,7 +1870,7 @@ class Bot(TelegramObject):
Args:
chat_id (:obj:`int` | :obj:`str`): Unique identifier for the target chat or username
of the target`channel (in the format @channelusername).
of the target channel (in the format @channelusername).
message_id (:obj:`int`, optional): Required if inline_message_id is not specified.
Identifier of the sent message.
inline_message_id (:obj:`str`, optional): Required if chat_id and message_id are not
@ -2104,7 +2104,7 @@ class Bot(TelegramObject):
Args:
chat_id (:obj:`int` | :obj:`str`): Unique identifier for the target chat or username
of the target`channel (in the format @channelusername).
of the target channel (in the format @channelusername).
timeout (:obj:`int` | :obj:`float`, optional): If this value is specified, use it as
the read timeout from the server (instead of the one specified during creation of
the connection pool).
@ -2134,7 +2134,7 @@ class Bot(TelegramObject):
Args:
chat_id (:obj:`int` | :obj:`str`): Unique identifier for the target chat or username
of the target`channel (in the format @channelusername).
of the target channel (in the format @channelusername).
timeout (:obj:`int` | :obj:`float`, optional): If this value is specified, use it as
the read timeout from the server (instead of the one specified during creation of
the connection pool).
@ -2166,7 +2166,7 @@ class Bot(TelegramObject):
Args:
chat_id (:obj:`int` | :obj:`str`): Unique identifier for the target chat or username
of the target`channel (in the format @channelusername).
of the target channel (in the format @channelusername).
timeout (:obj:`int` | :obj:`float`, optional): If this value is specified, use it as
the read timeout from the server (instead of the one specified during creation of
the connection pool).
@ -2194,7 +2194,7 @@ class Bot(TelegramObject):
Args:
chat_id (:obj:`int` | :obj:`str`): Unique identifier for the target chat or username
of the target`channel (in the format @channelusername).
of the target channel (in the format @channelusername).
timeout (:obj:`int` | :obj:`float`, optional): If this value is specified, use it as
the read timeout from the server (instead of the one specified during creation of
the connection pool).
@ -2222,7 +2222,7 @@ class Bot(TelegramObject):
Args:
chat_id (:obj:`int` | :obj:`str`): Unique identifier for the target chat or username
of the target`channel (in the format @channelusername).
of the target channel (in the format @channelusername).
user_id (:obj:`int`): Unique identifier of the target user.
timeout (:obj:`int` | :obj:`float`, optional): If this value is specified, use it as
the read timeout from the server (instead of the one specified during creation of
@ -2816,7 +2816,7 @@ class Bot(TelegramObject):
Args:
chat_id (:obj:`int` | :obj:`str`): Unique identifier for the target chat or username
of the target`channel (in the format @channelusername).
of the target channel (in the format @channelusername).
timeout (:obj:`int` | :obj:`float`, optional): If this value is specified, use it as
the read timeout from the server (instead of the one specified during creation of
the connection pool).
@ -2847,7 +2847,7 @@ class Bot(TelegramObject):
Args:
chat_id (:obj:`int` | :obj:`str`): Unique identifier for the target chat or username
of the target`channel (in the format @channelusername).
of the target channel (in the format @channelusername).
photo (`filelike object`): New chat photo.
timeout (:obj:`int` | :obj:`float`, optional): If this value is specified, use it as
the read timeout from the server (instead of the one specified during creation of
@ -2886,7 +2886,7 @@ class Bot(TelegramObject):
Args:
chat_id (:obj:`int` | :obj:`str`): Unique identifier for the target chat or username
of the target`channel (in the format @channelusername).
of the target channel (in the format @channelusername).
timeout (:obj:`int` | :obj:`float`, optional): If this value is specified, use it as
the read timeout from the server (instead of the one specified during creation of
the connection pool).
@ -2921,7 +2921,7 @@ class Bot(TelegramObject):
Args:
chat_id (:obj:`int` | :obj:`str`): Unique identifier for the target chat or username
of the target`channel (in the format @channelusername).
of the target channel (in the format @channelusername).
title (:obj:`str`): New chat title, 1-255 characters.
timeout (:obj:`int` | :obj:`float`, optional): If this value is specified, use it as
the read timeout from the server (instead of the one specified during creation of
@ -2956,7 +2956,7 @@ class Bot(TelegramObject):
Args:
chat_id (:obj:`int` | :obj:`str`): Unique identifier for the target chat or username
of the target`channel (in the format @channelusername).
of the target channel (in the format @channelusername).
description (:obj:`str`): New chat description, 1-255 characters.
timeout (:obj:`int` | :obj:`float`, optional): If this value is specified, use it as
the read timeout from the server (instead of the one specified during creation of
@ -2988,7 +2988,7 @@ class Bot(TelegramObject):
Args:
chat_id (:obj:`int` | :obj:`str`): Unique identifier for the target chat or username
of the target`channel (in the format @channelusername).
of the target channel (in the format @channelusername).
message_id (:obj:`int`): Identifier of a message to pin.
disable_notification (:obj:`bool`, optional): Pass True, if it is not necessary to send
a notification to all group members about the new pinned message.
@ -3024,7 +3024,7 @@ class Bot(TelegramObject):
Args:
chat_id (:obj:`int` | :obj:`str`): Unique identifier for the target chat or username
of the target`channel (in the format @channelusername).
of the target channel (in the format @channelusername).
timeout (:obj:`int` | :obj:`float`, optional): If this value is specified, use it as
the read timeout from the server (instead of the one specified during creation of
the connection pool).

View file

@ -255,7 +255,7 @@ class ConversationHandler(Handler):
state = self.conversations.get(key)
# Resolve promises
if isinstance(state, tuple) and len(state) is 2 and isinstance(state[1], Promise):
if isinstance(state, tuple) and len(state) == 2 and isinstance(state[1], Promise):
self.logger.debug('waiting for promise...')
old_state, new_state = state

View file

@ -31,7 +31,7 @@ from queue import Queue, Empty
from future.builtins import range
from telegram import TelegramError
from telegram import TelegramError, Update
from telegram.ext.handler import Handler
from telegram.ext.callbackcontext import CallbackContext
from telegram.utils.deprecate import TelegramDeprecationWarning
@ -321,7 +321,7 @@ class Dispatcher(object):
check = handler.check_update(update)
if check is not None and check is not False:
handler.handle_update(update, self, check)
if self.persistence:
if self.persistence and isinstance(update, Update):
if self.persistence.store_chat_data and update.effective_chat:
chat_id = update.effective_chat.id
try:

View file

@ -175,29 +175,40 @@ class Filters(object):
class regex(BaseFilter):
"""
Filters updates by searching for an occurence of ``pattern`` in the message text.
Filters updates by searching for an occurrence of ``pattern`` in the message text.
The ``re.search`` function is used to determine whether an update should be filtered.
Refer to the documentation of the ``re`` module for more information.
Note: Does not allow passing groups or a groupdict like the ``RegexHandler`` yet,
but this will probably be implemented in a future update, gradually phasing out the
RegexHandler (see https://github.com/python-telegram-bot/python-telegram-bot/issues/835).
Note:
Does not allow passing groups or a groupdict like the ``RegexHandler`` yet,
but this will probably be implemented in a future update, gradually phasing out the
RegexHandler (See `Github Issue
<https://github.com/python-telegram-bot/python-telegram-bot/issues/835/>`_).
Examples:
Example ``CommandHandler("start", deep_linked_callback, Filters.regex('parameter'))``
Use ``MessageHandler(Filters.regex(r'help'), callback)`` to capture all messages that
contain the word help. You can also use
``MessageHandler(Filters.regex(re.compile(r'help', re.IGNORECASE), callback)`` if
you want your pattern to be case insensitive. This approach is recommended
if you need to specify flags on your pattern.
Args:
pattern (:obj:`str` | :obj:`Pattern`): The regex pattern.
"""
def __init__(self, pattern):
self.pattern = re.compile(pattern)
if isinstance(pattern, string_types):
pattern = re.compile(pattern)
self.pattern = pattern
self.name = 'Filters.regex({})'.format(self.pattern)
# TODO: Once the callback revamp (#1026) is done, the regex filter should be able to pass
# the matched groups and groupdict to the context object.
def filter(self, message):
""":obj:`Filter`: Messages that have an occurrence of ``pattern``."""
if message.text:
return bool(self.pattern.search(message.text))
return False

View file

@ -77,7 +77,7 @@ class JobQueue(object):
raise ValueError('next_t is None')
if isinstance(next_t, datetime.datetime):
next_t = (next_t - datetime.datetime.now()).total_seconds()
next_t = (next_t - datetime.datetime.now(next_t.tzinfo)).total_seconds()
elif isinstance(next_t, datetime.time):
next_datetime = datetime.datetime.combine(datetime.date.today(), next_t)
@ -105,7 +105,7 @@ class JobQueue(object):
Args:
callback (:obj:`callable`): The callback function that should be executed by the new
job. It should take ``bot, job`` as parameters, where ``job`` is the
:class:`telegram.ext.Job` instance. It can be used to access it's
:class:`telegram.ext.Job` instance. It can be used to access its
``job.context`` or change it to a repeating job.
when (:obj:`int` | :obj:`float` | :obj:`datetime.timedelta` | \
:obj:`datetime.datetime` | :obj:`datetime.time`):
@ -137,12 +137,12 @@ class JobQueue(object):
return job
def run_repeating(self, callback, interval, first=None, context=None, name=None):
"""Creates a new ``Job`` that runs once and adds it to the queue.
"""Creates a new ``Job`` that runs at specified intervals and adds it to the queue.
Args:
callback (:obj:`callable`): The callback function that should be executed by the new
job. It should take ``bot, job`` as parameters, where ``job`` is the
:class:`telegram.ext.Job` instance. It can be used to access it's
:class:`telegram.ext.Job` instance. It can be used to access its
``Job.context`` or change it to a repeating job.
interval (:obj:`int` | :obj:`float` | :obj:`datetime.timedelta`): The interval in which
the job will run. If it is an :obj:`int` or a :obj:`float`, it will be interpreted
@ -183,12 +183,12 @@ class JobQueue(object):
return job
def run_daily(self, callback, time, days=Days.EVERY_DAY, context=None, name=None):
"""Creates a new ``Job`` that runs once and adds it to the queue.
"""Creates a new ``Job`` that runs on a daily basis and adds it to the queue.
Args:
callback (:obj:`callable`): The callback function that should be executed by the new
job. It should take ``bot, job`` as parameters, where ``job`` is the
:class:`telegram.ext.Job` instance. It can be used to access it's ``Job.context``
: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.
days (Tuple[:obj:`int`], optional): Defines on which days of the week the job should

View file

@ -48,3 +48,51 @@ class InlineKeyboardMarkup(ReplyMarkup):
data['inline_keyboard'].append([x.to_dict() for x in inline_keyboard])
return data
@classmethod
def from_button(cls, button, **kwargs):
"""Shortcut for::
InlineKeyboardMarkup([[button]], **kwargs)
Return an InlineKeyboardMarkup from a single InlineKeyboardButton
Args:
button (:class:`telegram.InlineKeyboardButton`): The button to use in the markup
**kwargs (:obj:`dict`): Arbitrary keyword arguments.
"""
return cls([[button]], **kwargs)
@classmethod
def from_row(cls, button_row, **kwargs):
"""Shortcut for::
InlineKeyboardMarkup([button_row], **kwargs)
Return an InlineKeyboardMarkup from a single row of InlineKeyboardButtons
Args:
button_row (List[:class:`telegram.InlineKeyboardButton`]): The button to use in the
markup
**kwargs (:obj:`dict`): Arbitrary keyword arguments.
"""
return cls([button_row], **kwargs)
@classmethod
def from_column(cls, button_column, **kwargs):
"""Shortcut for::
InlineKeyboardMarkup([[button] for button in button_column], **kwargs)
Return an InlineKeyboardMarkup from a single column of InlineKeyboardButtons
Args:
button_column (List[:class:`telegram.InlineKeyboardButton`]): The button to use in the
markup
**kwargs (:obj:`dict`): Arbitrary keyword arguments.
"""
button_grid = [[button] for button in button_column]
return cls(button_grid, **kwargs)

View file

@ -209,10 +209,11 @@ class Message(TelegramObject):
ATTACHMENT_TYPES = ['audio', 'game', 'animation', 'document', 'photo', 'sticker', 'video',
'voice', 'video_note', 'contact', 'location', 'venue', 'invoice',
'successful_payment']
MESSAGE_TYPES = ['text', 'new_chat_members', 'new_chat_title', 'new_chat_photo',
'delete_chat_photo', 'group_chat_created', 'supergroup_chat_created',
'channel_chat_created', 'migrate_to_chat_id', 'migrate_from_chat_id',
'pinned_message', 'passport_data'] + ATTACHMENT_TYPES
MESSAGE_TYPES = ['text', 'new_chat_members', 'left_chat_member', 'new_chat_title',
'new_chat_photo', 'delete_chat_photo', 'group_chat_created',
'supergroup_chat_created', 'channel_chat_created', 'migrate_to_chat_id',
'migrate_from_chat_id', 'pinned_message',
'passport_data'] + ATTACHMENT_TYPES
def __init__(self,
message_id,

View file

@ -85,3 +85,131 @@ class ReplyKeyboardMarkup(ReplyMarkup):
r.append(button) # str
data['keyboard'].append(r)
return data
@classmethod
def from_button(cls,
button,
resize_keyboard=False,
one_time_keyboard=False,
selective=False,
**kwargs):
"""Shortcut for::
ReplyKeyboardMarkup([[button]], **kwargs)
Return an ReplyKeyboardMarkup from a single KeyboardButton
Args:
button (:class:`telegram.KeyboardButton` | :obj:`str`): The button to use in the markup
resize_keyboard (:obj:`bool`, optional): Requests clients to resize the keyboard
vertically for optimal fit (e.g., make the keyboard smaller if there are just two
rows of buttons). Defaults to false, in which case the custom keyboard is always of
the same height as the app's standard keyboard.
Defaults to ``False``
one_time_keyboard (:obj:`bool`, optional): Requests clients to hide the keyboard as
soon as it's been used. The keyboard will still be available, but clients will
automatically display the usual letter-keyboard in the chat - the user can press
a special button in the input field to see the custom keyboard again.
Defaults to ``False``.
selective (:obj:`bool`, optional): Use this parameter if you want to show the keyboard
to specific users only. Targets:
1) users that are @mentioned in the text of the Message object
2) if the bot's message is a reply (has reply_to_message_id), sender of the
original message.
Defaults to ``False``.
**kwargs (:obj:`dict`): Arbitrary keyword arguments.
"""
return cls([[button]],
resize_keyboard=resize_keyboard,
one_time_keyboard=one_time_keyboard,
selective=selective,
**kwargs)
@classmethod
def from_row(cls,
button_row,
resize_keyboard=False,
one_time_keyboard=False,
selective=False,
**kwargs):
"""Shortcut for::
ReplyKeyboardMarkup([button_row], **kwargs)
Return an ReplyKeyboardMarkup from a single row of KeyboardButtons
Args:
button_row (List[:class:`telegram.KeyboardButton` | :obj:`str`]): The button to use in
the markup
resize_keyboard (:obj:`bool`, optional): Requests clients to resize the keyboard
vertically for optimal fit (e.g., make the keyboard smaller if there are just two
rows of buttons). Defaults to false, in which case the custom keyboard is always of
the same height as the app's standard keyboard.
Defaults to ``False``
one_time_keyboard (:obj:`bool`, optional): Requests clients to hide the keyboard as
soon as it's been used. The keyboard will still be available, but clients will
automatically display the usual letter-keyboard in the chat - the user can press
a special button in the input field to see the custom keyboard again.
Defaults to ``False``.
selective (:obj:`bool`, optional): Use this parameter if you want to show the keyboard
to specific users only. Targets:
1) users that are @mentioned in the text of the Message object
2) if the bot's message is a reply (has reply_to_message_id), sender of the
original message.
Defaults to ``False``.
**kwargs (:obj:`dict`): Arbitrary keyword arguments.
"""
return cls([button_row],
resize_keyboard=resize_keyboard,
one_time_keyboard=one_time_keyboard,
selective=selective,
**kwargs)
@classmethod
def from_column(cls,
button_column,
resize_keyboard=False,
one_time_keyboard=False,
selective=False,
**kwargs):
"""Shortcut for::
ReplyKeyboardMarkup([[button] for button in button_column], **kwargs)
Return an ReplyKeyboardMarkup from a single column of KeyboardButtons
Args:
button_column (List[:class:`telegram.KeyboardButton` | :obj:`str`]): The button to use
in the markup
resize_keyboard (:obj:`bool`, optional): Requests clients to resize the keyboard
vertically for optimal fit (e.g., make the keyboard smaller if there are just two
rows of buttons). Defaults to false, in which case the custom keyboard is always of
the same height as the app's standard keyboard.
Defaults to ``False``
one_time_keyboard (:obj:`bool`, optional): Requests clients to hide the keyboard as
soon as it's been used. The keyboard will still be available, but clients will
automatically display the usual letter-keyboard in the chat - the user can press
a special button in the input field to see the custom keyboard again.
Defaults to ``False``.
selective (:obj:`bool`, optional): Use this parameter if you want to show the keyboard
to specific users only. Targets:
1) users that are @mentioned in the text of the Message object
2) if the bot's message is a reply (has reply_to_message_id), sender of the
original message.
Defaults to ``False``.
**kwargs (:obj:`dict`): Arbitrary keyword arguments.
"""
button_grid = [[button] for button in button_column]
return cls(button_grid,
resize_keyboard=resize_keyboard,
one_time_keyboard=one_time_keyboard,
selective=selective,
**kwargs)

View file

@ -323,7 +323,8 @@ class Request(object):
else:
result = self._request_wrapper('POST', url,
body=json.dumps(data).encode('utf-8'),
headers={'Content-Type': 'application/json'})
headers={'Content-Type': 'application/json'},
**urlopen_kwargs)
return self._parse(result)

View file

@ -97,6 +97,7 @@ def dp(_dp):
_dp.update_queue.get(False)
_dp.chat_data = defaultdict(dict)
_dp.user_data = defaultdict(dict)
_dp.persistence = None
_dp.handlers = {}
_dp.groups = []
_dp.error_handlers = []

View file

@ -50,7 +50,7 @@ class TestAnimation(object):
def test_creation(self, animation):
assert isinstance(animation, Animation)
assert isinstance(animation.file_id, str)
assert animation.file_id is not ''
assert animation.file_id != ''
def test_expected_values(self, animation):
assert animation.file_size == self.file_size
@ -72,8 +72,8 @@ class TestAnimation(object):
assert message.animation.file_name == animation.file_name
assert message.animation.mime_type == animation.mime_type
assert message.animation.file_size == animation.file_size
assert message.animation.thumb.width == 50
assert message.animation.thumb.height == 50
assert message.animation.thumb.width == 320
assert message.animation.thumb.height == 180
@flaky(3, 1)
def test_resend(self, bot, chat_id, animation):

View file

@ -48,7 +48,7 @@ class TestAudio(object):
audio_file_url = 'https://goo.gl/3En24v'
mime_type = 'audio/mpeg'
file_size = 122920
thumb_file_size = 2744
thumb_file_size = 1427
thumb_width = 50
thumb_height = 50
@ -56,7 +56,7 @@ class TestAudio(object):
# Make sure file has been uploaded.
assert isinstance(audio, Audio)
assert isinstance(audio.file_id, str)
assert audio.file_id is not ''
assert audio.file_id != ''
def test_expected_values(self, audio):
assert audio.duration == self.duration

View file

@ -618,16 +618,27 @@ class TestBot(object):
# test_sticker module.
def test_timeout_propagation(self, monkeypatch, bot, chat_id):
from telegram.vendor.ptb_urllib3.urllib3.util.timeout import Timeout
class OkException(Exception):
pass
timeout = 500
TIMEOUT = 500
def post(*args, **kwargs):
if kwargs.get('timeout') == 500:
def request_wrapper(*args, **kwargs):
obj = kwargs.get('timeout')
if isinstance(obj, Timeout) and obj._read == TIMEOUT:
raise OkException
monkeypatch.setattr('telegram.utils.request.Request.post', post)
return b'{"ok": true, "result": []}'
monkeypatch.setattr('telegram.utils.request.Request._request_wrapper', request_wrapper)
# Test file uploading
with pytest.raises(OkException):
bot.send_photo(chat_id, open('tests/data/telegram.jpg', 'rb'), timeout=timeout)
bot.send_photo(chat_id, open('tests/data/telegram.jpg', 'rb'), timeout=TIMEOUT)
# Test JSON submition
with pytest.raises(OkException):
bot.get_chat_administrators(chat_id, timeout=TIMEOUT)

View file

@ -42,7 +42,10 @@ class TestDispatcher(object):
received = None
count = 0
@pytest.fixture(autouse=True)
@pytest.fixture(autouse=True, name='reset')
def reset_fixture(self):
self.reset()
def reset(self):
self.received = None
self.count = 0

View file

@ -43,14 +43,14 @@ class TestDocument(object):
file_size = 12948
mime_type = 'image/png'
file_name = 'telegram.png'
thumb_file_size = 2364
thumb_width = 90
thumb_height = 90
thumb_file_size = 8090
thumb_width = 300
thumb_height = 300
def test_creation(self, document):
assert isinstance(document, Document)
assert isinstance(document.file_id, str)
assert document.file_id is not ''
assert document.file_id != ''
def test_expected_values(self, document):
assert document.file_size == self.file_size
@ -75,8 +75,8 @@ class TestDocument(object):
assert message.document.mime_type == document.mime_type
assert message.document.file_size == document.file_size
assert message.caption == self.caption.replace('*', '')
assert message.document.thumb.width == 50
assert message.document.thumb.height == 50
assert message.document.thumb.width == self.thumb_width
assert message.document.thumb.height == self.thumb_height
@flaky(3, 1)
@pytest.mark.timeout(10)

View file

@ -64,6 +64,7 @@ class TestFilters(object):
assert not Filters.regex(r'fail')(message)
assert Filters.regex(r'test')(message)
assert Filters.regex(re.compile(r'test'))(message)
assert Filters.regex(re.compile(r'TEST', re.IGNORECASE))(message)
message.text = 'i love python'
assert Filters.regex(r'.\b[lo]{2}ve python')(message)

View file

@ -32,24 +32,33 @@ class TestHelpers(object):
assert expected_str == helpers.escape_markdown(test_str)
def test_effective_message_type(self):
test_message = Message(message_id=1,
from_user=None,
date=None,
chat=None)
test_message.text = 'Test'
def build_test_message(**kwargs):
config = dict(
message_id=1,
from_user=None,
date=None,
chat=None,
)
config.update(**kwargs)
return Message(**config)
test_message = build_test_message(text='Test')
assert helpers.effective_message_type(test_message) == 'text'
test_message.text = None
test_message.sticker = Sticker('sticker_id', 50, 50)
test_message = build_test_message(sticker=Sticker('sticker_id', 50, 50))
assert helpers.effective_message_type(test_message) == 'sticker'
test_message.sticker = None
test_message.new_chat_members = [User(55, 'new_user', False)]
test_message = build_test_message(new_chat_members=[User(55, 'new_user', False)])
assert helpers.effective_message_type(test_message) == 'new_chat_members'
test_message = build_test_message(left_chat_member=[User(55, 'new_user', False)])
assert helpers.effective_message_type(test_message) == 'left_chat_member'
test_update = Update(1)
test_message.text = 'Test'
test_message = build_test_message(text='Test')
test_update.message = test_message
assert helpers.effective_message_type(test_update) == 'text'

View file

@ -44,6 +44,27 @@ class TestInlineKeyboardMarkup(object):
assert message.text == 'Testing InlineKeyboardMarkup'
def test_from_button(self):
inline_keyboard_markup = InlineKeyboardMarkup.from_button(
InlineKeyboardButton(text='button1', callback_data='data1')).inline_keyboard
assert len(inline_keyboard_markup) == 1
assert len(inline_keyboard_markup[0]) == 1
def test_from_row(self):
inline_keyboard_markup = InlineKeyboardMarkup.from_row([
InlineKeyboardButton(text='button1', callback_data='data1'),
InlineKeyboardButton(text='button1', callback_data='data1')]).inline_keyboard
assert len(inline_keyboard_markup) == 1
assert len(inline_keyboard_markup[0]) == 2
def test_from_column(self):
inline_keyboard_markup = InlineKeyboardMarkup.from_column([
InlineKeyboardButton(text='button1', callback_data='data1'),
InlineKeyboardButton(text='button1', callback_data='data1')]).inline_keyboard
assert len(inline_keyboard_markup) == 2
assert len(inline_keyboard_markup[0]) == 1
assert len(inline_keyboard_markup[1]) == 1
def test_expected_values(self, inline_keyboard_markup):
assert inline_keyboard_markup.inline_keyboard == self.inline_keyboard

View file

@ -290,7 +290,7 @@ class TestInputMediaDocument(object):
@pytest.fixture(scope='function') # noqa: F811
def media_group(photo, thumb):
def media_group(photo, thumb): # noqa: F811
return [InputMediaPhoto(photo, caption='photo `1`', parse_mode='Markdown'),
InputMediaPhoto(thumb, caption='<b>photo</b> 2', parse_mode='HTML')]

View file

@ -198,6 +198,24 @@ class TestJobQueue(object):
sleep(0.06)
assert pytest.approx(self.job_time) == expected_time
def test_datetime_with_timezone_job_run_once(self, job_queue):
# Test that run_once jobs work with timezone aware datetimes.
offset = datetime.timedelta(hours=-3)
when = datetime.datetime.now(datetime.timezone(offset))
job_queue.run_once(self.job_run_once, when)
sleep(0.01)
assert self.result == 1
def test_datetime_with_timezone_job_run_repeating(self, job_queue):
# Test that run_repeating jobs work with timezone aware datetimes.
offset = datetime.timedelta(hours=5)
now_with_offset = datetime.datetime.now(datetime.timezone(offset))
job_queue.run_repeating(self.job_run_once, interval=0.01, first=now_with_offset)
sleep(0.015)
assert self.result == 2
def test_time_unit_dt_time_today(self, job_queue):
# Testing running at a specific time today
delta = 0.05

View file

@ -54,7 +54,7 @@ class TestDelayQueue(object):
dsp.stop()
assert dsp.is_alive() is False
assert self.testtimes or self.N == 0 is True
assert self.testtimes or self.N == 0
passes, fails = [], []
delta = (self.time_limit_ms - self.margin_ms) / 1000
for start, stop in enumerate(range(self.burst_limit + 1, len(self.testtimes))):

View file

@ -31,7 +31,7 @@ import pytest
from telegram import Update, Message, User, Chat, MessageEntity
from telegram.ext import BasePersistence, Updater, ConversationHandler, MessageHandler, Filters, \
PicklePersistence, CommandHandler, DictPersistence
PicklePersistence, CommandHandler, DictPersistence, TypeHandler
@pytest.fixture(scope="function")
@ -201,6 +201,21 @@ class TestBasePersistence(object):
assert dp.user_data[54321][1] == 'test7'
assert dp.chat_data[-987654][2] == 'test8'
def test_persistence_dispatcher_arbitrary_update_types(self, dp, base_persistence, caplog):
# Updates used with TypeHandler doesn't necessarily have the proper attributes for
# persistence, makes sure it works anyways
dp.persistence = base_persistence
class MyUpdate(object):
pass
dp.add_handler(TypeHandler(MyUpdate, lambda *_: None))
with caplog.at_level(logging.ERROR):
dp.process_update(MyUpdate())
assert 'An uncaught error was raised while processing the update' not in caplog.text
@pytest.fixture(scope='function')
def pickle_persistence():

View file

@ -59,11 +59,11 @@ class TestPhoto(object):
# Make sure file has been uploaded.
assert isinstance(photo, PhotoSize)
assert isinstance(photo.file_id, str)
assert photo.file_id is not ''
assert photo.file_id != ''
assert isinstance(thumb, PhotoSize)
assert isinstance(thumb.file_id, str)
assert thumb.file_id is not ''
assert thumb.file_id != ''
def test_expected_values(self, photo, thumb):
assert photo.width == self.width

View file

@ -51,6 +51,40 @@ class TestReplyKeyboardMarkup(object):
assert message.text == 'text 2'
def test_from_button(self):
reply_keyboard_markup = ReplyKeyboardMarkup.from_button(
KeyboardButton(text='button1')).keyboard
assert len(reply_keyboard_markup) == 1
assert len(reply_keyboard_markup[0]) == 1
reply_keyboard_markup = ReplyKeyboardMarkup.from_button('button1').keyboard
assert len(reply_keyboard_markup) == 1
assert len(reply_keyboard_markup[0]) == 1
def test_from_row(self):
reply_keyboard_markup = ReplyKeyboardMarkup.from_row([
KeyboardButton(text='button1'),
KeyboardButton(text='button2')]).keyboard
assert len(reply_keyboard_markup) == 1
assert len(reply_keyboard_markup[0]) == 2
reply_keyboard_markup = ReplyKeyboardMarkup.from_row(['button1', 'button2']).keyboard
assert len(reply_keyboard_markup) == 1
assert len(reply_keyboard_markup[0]) == 2
def test_from_column(self):
reply_keyboard_markup = ReplyKeyboardMarkup.from_column([
KeyboardButton(text='button1'),
KeyboardButton(text='button2')]).keyboard
assert len(reply_keyboard_markup) == 2
assert len(reply_keyboard_markup[0]) == 1
assert len(reply_keyboard_markup[1]) == 1
reply_keyboard_markup = ReplyKeyboardMarkup.from_column(['button1', 'button2']).keyboard
assert len(reply_keyboard_markup) == 2
assert len(reply_keyboard_markup[0]) == 1
assert len(reply_keyboard_markup[1]) == 1
def test_expected_values(self, reply_keyboard_markup):
assert isinstance(reply_keyboard_markup.keyboard, list)
assert isinstance(reply_keyboard_markup.keyboard[0][0], KeyboardButton)

View file

@ -50,9 +50,9 @@ class TestSticker(object):
width = 510
height = 512
file_size = 39518
thumb_width = 90
thumb_heigth = 90
thumb_file_size = 3672
thumb_width = 319
thumb_height = 320
thumb_file_size = 21472
def test_creation(self, sticker):
# Make sure file has been uploaded.
@ -68,7 +68,7 @@ class TestSticker(object):
assert sticker.height == self.height
assert sticker.file_size == self.file_size
assert sticker.thumb.width == self.thumb_width
assert sticker.thumb.height == self.thumb_heigth
assert sticker.thumb.height == self.thumb_height
assert sticker.thumb.file_size == self.thumb_file_size
@flaky(3, 1)

View file

@ -45,6 +45,10 @@ class TestVideo(object):
mime_type = 'video/mp4'
supports_streaming = True
thumb_width = 180
thumb_height = 320
thumb_file_size = 1767
caption = u'<b>VideoTest</b> - *Caption*'
video_file_url = 'https://python-telegram-bot.org/static/testfiles/telegram.mp4'
@ -52,11 +56,11 @@ class TestVideo(object):
# Make sure file has been uploaded.
assert isinstance(video, Video)
assert isinstance(video.file_id, str)
assert video.file_id is not ''
assert video.file_id != ''
assert isinstance(video.thumb, PhotoSize)
assert isinstance(video.thumb.file_id, str)
assert video.thumb.file_id is not ''
assert video.thumb.file_id != ''
def test_expected_values(self, video):
assert video.width == self.width
@ -83,8 +87,9 @@ class TestVideo(object):
assert message.caption == self.caption.replace('*', '')
assert message.video.thumb.width == 50
assert message.video.thumb.height == 50
assert message.video.thumb.file_size == self.thumb_file_size
assert message.video.thumb.width == self.thumb_width
assert message.video.thumb.height == self.thumb_height
@flaky(3, 1)
@pytest.mark.timeout(10)
@ -115,9 +120,9 @@ class TestVideo(object):
assert isinstance(message.video.thumb, PhotoSize)
assert isinstance(message.video.thumb.file_id, str)
assert message.video.thumb.file_id != ''
assert message.video.thumb.width == video.thumb.width
assert message.video.thumb.height == video.thumb.height
assert message.video.thumb.file_size == video.thumb.file_size
assert message.video.thumb.width == 51 # This seems odd that it's not self.thumb_width
assert message.video.thumb.height == 90 # Ditto
assert message.video.thumb.file_size == 645 # same
assert message.caption == self.caption

View file

@ -42,17 +42,21 @@ class TestVideoNote(object):
duration = 3
file_size = 132084
thumb_width = 240
thumb_height = 240
thumb_file_size = 11547
caption = u'VideoNoteTest - Caption'
def test_creation(self, video_note):
# Make sure file has been uploaded.
assert isinstance(video_note, VideoNote)
assert isinstance(video_note.file_id, str)
assert video_note.file_id is not ''
assert video_note.file_id != ''
assert isinstance(video_note.thumb, PhotoSize)
assert isinstance(video_note.thumb.file_id, str)
assert video_note.thumb.file_id is not ''
assert video_note.thumb.file_id != ''
def test_expected_values(self, video_note):
assert video_note.length == self.length
@ -73,8 +77,9 @@ class TestVideoNote(object):
assert message.video_note.duration == video_note.duration
assert message.video_note.file_size == video_note.file_size
assert message.video_note.thumb.width == 50
assert message.video_note.thumb.height == 50
assert message.video_note.thumb.file_size == self.thumb_file_size
assert message.video_note.thumb.width == self.thumb_width
assert message.video_note.thumb.height == self.thumb_height
@flaky(3, 1)
@pytest.mark.timeout(10)