API 4.2 changes

actually works now

Add tests

Fix flake8 issues.

Add poll argument to Update.

Fix pre-commit config
Finalizing 4.2

No notify on pin
This commit is contained in:
Pieter Schutz 2019-06-05 14:59:52 +02:00
parent 984bea16d1
commit 725c21b88d
18 changed files with 455 additions and 43 deletions

View file

@ -1,20 +1,20 @@
repos:
- repo: git://github.com/python-telegram-bot/mirrors-yapf
rev: master
sha: 5769e088ef6e0a0d1eb63bd6d0c1fe9f3606d6c8
hooks:
- id: yapf
files: ^(telegram|tests)/.*\.py$
args:
- --diff
- repo: git://github.com/pre-commit/pre-commit-hooks
rev: v2.0.0
sha: 0b70e285e369bcb24b57b74929490ea7be9c4b19
hooks:
- id: flake8
exclude: ^(setup.py|docs/source/conf.py)$
args:
- --ignore=W605,W503
- repo: git://github.com/pre-commit/mirrors-pylint
rev: v2.3.0
sha: 9d8dcbc2b86c796275680f239c1e90dcd50bd398
hooks:
- id: pylint
files: ^telegram/.*\.py$

View file

@ -0,0 +1,6 @@
telegram.Poll
=============
.. autoclass:: telegram.Poll
:members:
:show-inheritance:

View file

@ -0,0 +1,6 @@
telegram.PollOption
===================
.. autoclass:: telegram.PollOption
:members:
:show-inheritance:

View file

@ -35,6 +35,8 @@ telegram package
telegram.messageentity
telegram.parsemode
telegram.photosize
telegram.poll
telegram.polloption
telegram.replykeyboardremove
telegram.replykeyboardmarkup
telegram.replymarkup

View file

@ -47,6 +47,7 @@ from .files.file import File
from .parsemode import ParseMode
from .messageentity import MessageEntity
from .games.game import Game
from .poll import Poll, PollOption
from .games.callbackgame import CallbackGame
from .payment.shippingaddress import ShippingAddress
from .payment.orderinfo import OrderInfo
@ -152,5 +153,5 @@ __all__ = [
'PersonalDetails', 'ResidentialAddress', 'InputMediaVideo', 'InputMediaAnimation',
'InputMediaAudio', 'InputMediaDocument', 'TelegramDecryptionError',
'PassportElementErrorSelfie', 'PassportElementErrorTranslationFile',
'PassportElementErrorTranslationFiles', 'PassportElementErrorUnspecified'
'PassportElementErrorTranslationFiles', 'PassportElementErrorUnspecified', 'Poll', 'PollOption'
]

View file

@ -37,7 +37,7 @@ from future.utils import string_types
from telegram import (User, Message, Update, Chat, ChatMember, UserProfilePhotos, File,
ReplyMarkup, TelegramObject, WebhookInfo, GameHighScore, StickerSet,
PhotoSize, Audio, Document, Sticker, Video, Animation, Voice, VideoNote,
Location, Venue, Contact, InputFile)
Location, Venue, Contact, InputFile, Poll)
from telegram.error import InvalidToken, TelegramError
from telegram.utils.helpers import to_timestamp
from telegram.utils.request import Request
@ -260,13 +260,16 @@ class Bot(TelegramObject):
@log
def delete_message(self, chat_id, message_id, timeout=None, **kwargs):
"""
Use this method to delete a message. A message can only be deleted if it was sent less
than 48 hours ago. Any such recently sent outgoing message may be deleted. Additionally,
if the bot is an administrator in a group chat, it can delete any message. If the bot is
an administrator in a supergroup, it can delete messages from any other user and service
messages about people joining or leaving the group (other types of service messages may
only be removed by the group creator). In channels, bots can only remove their own
messages.
Use this method to delete a message, including service messages, with the following
limitations:
- A message can only be deleted if it was sent less than 48 hours ago.
- Bots can delete outgoing messages in private chats, groups, and supergroups.
- Bots can delete incoming messages in private chats.
- Bots granted can_post_messages permissions can delete outgoing messages in channels.
- If the bot is an administrator of a group, it can delete any message there.
- If the bot has can_delete_messages permission in a supergroup or a channel, it can
delete any message there.
Args:
chat_id (:obj:`int` | :obj:`str`): Unique identifier for the target chat or username
@ -3319,6 +3322,101 @@ class Bot(TelegramObject):
return result
@log
def send_poll(self,
chat_id,
question,
options,
disable_notification=None,
reply_to_message_id=None,
reply_markup=None,
timeout=None,
**kwargs):
"""
Use this method to send a native poll. A native poll can't be sent to a private chat.
Args:
chat_id (:obj:`int` | :obj:`str`): Unique identifier for the target private chat.
question (:obj:`str`): Poll question, 1-255 characters.
options (List[:obj:`str`]): List of answer options, 2-10 strings 1-100 characters each.
disable_notification (:obj:`bool`, optional): Sends the message silently. Users will
receive a notification with no sound.
reply_to_message_id (:obj:`int`, optional): If the message is a reply, ID of the
original message.
reply_markup (:class:`telegram.ReplyMarkup`, optional): Additional interface options. A
JSON-serialized object for an inline keyboard, custom reply keyboard, instructions
to remove reply keyboard or to force a reply from the 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
the connection pool).
**kwargs (:obj:`dict`): Arbitrary keyword arguments.
Returns:
:class:`telegram.Message`: On success, the sent Message is returned.
Raises:
:class:`telegram.TelegramError`
"""
url = '{0}/sendPoll'.format(self.base_url)
data = {
'chat_id': chat_id,
'question': question,
'options': options
}
return self._message(url, data, timeout=timeout, disable_notification=disable_notification,
reply_to_message_id=reply_to_message_id, reply_markup=reply_markup,
**kwargs)
@log
def stop_poll(self,
chat_id,
message_id,
reply_markup=None,
timeout=None,
**kwargs):
"""
Use this method to stop a poll which was sent by the bot.
Args:
chat_id (:obj:`int` | :obj:`str`): Unique identifier for the target chat or username
of the target channel (in the format @channelusername).
message_id (:obj:`int`): Identifier of the original message with the poll.
reply_markup (:class:`telegram.ReplyMarkup`, optional): Additional interface options. A
JSON-serialized object for an inline keyboard, custom reply keyboard, instructions
to remove reply keyboard or to force a reply from the 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
the connection pool).
**kwargs (:obj:`dict`): Arbitrary keyword arguments.
Returns:
:class:`telegram.Poll`: On success, the stopped Poll with the
final results is returned.
Raises:
:class:`telegram.TelegramError`
"""
url = '{0}/stopPoll'.format(self.base_url)
data = {
'chat_id': chat_id,
'message_id': message_id
}
if reply_markup:
if isinstance(reply_markup, ReplyMarkup):
data['reply_markup'] = reply_markup.to_json()
else:
data['reply_markup'] = reply_markup
result = self._request.post(url, data, timeout=timeout)
return Poll.de_json(result, self)
def to_dict(self):
data = {'id': self.id, 'username': self.username, 'first_name': self.username}
@ -3456,3 +3554,7 @@ class Bot(TelegramObject):
"""Alias for :attr:`delete_sticker_from_set`"""
setPassportDataErrors = set_passport_data_errors
"""Alias for :attr:`set_passport_data_errors`"""
sendPoll = send_poll
"""Alias for :attr:`send_poll`"""
stopPoll = stop_poll
"""Alias for :attr:`stop_poll`"""

View file

@ -337,3 +337,16 @@ class Chat(TelegramObject):
"""
return self.bot.send_voice(self.id, *args, **kwargs)
def send_poll(self, *args, **kwargs):
"""Shortcut for::
bot.send_poll(Chat.id, *args, **kwargs)
Where Chat is the current instance.
Returns:
:class:`telegram.Message`: On success, instance representing the message posted.
"""
return self.bot.send_poll(self.id, *args, **kwargs)

View file

@ -46,6 +46,8 @@ class ChatMember(TelegramObject):
can_pin_messages (:obj:`bool`): Optional. If the administrator can pin messages.
can_promote_members (:obj:`bool`): Optional. If the administrator can add new
administrators.
is_member (:obj:`bool`): Optional. Restricted only. True, if the user is a member of the
chat at the moment of the request.
can_send_messages (:obj:`bool`): Optional. If the user can send text messages, contacts,
locations and venues.
can_send_media_messages (:obj:`bool`): Optional. If the user can send media messages,
@ -81,6 +83,8 @@ class ChatMember(TelegramObject):
administrator can add new administrators with a subset of his own privileges or demote
administrators that he has promoted, directly or indirectly (promoted by administrators
that were appointed by the user).
is_member (:obj:`bool`, optional): Restricted only. True, if the user is a member of the
chat at the moment of the request.
can_send_messages (:obj:`bool`, optional): Restricted only. True, if the user can send text
messages, contacts, locations and venues.
can_send_media_messages (:obj:`bool`, optional): Restricted only. True, if the user can
@ -111,7 +115,7 @@ class ChatMember(TelegramObject):
can_restrict_members=None, can_pin_messages=None,
can_promote_members=None, can_send_messages=None,
can_send_media_messages=None, can_send_other_messages=None,
can_add_web_page_previews=None, **kwargs):
can_add_web_page_previews=None, is_member=None, **kwargs):
# Required
self.user = user
self.status = status
@ -129,6 +133,7 @@ class ChatMember(TelegramObject):
self.can_send_media_messages = can_send_media_messages
self.can_send_other_messages = can_send_other_messages
self.can_add_web_page_previews = can_add_web_page_previews
self.is_member = is_member
self._id_attrs = (self.user, self.status)

View file

@ -23,7 +23,7 @@ from html import escape
from telegram import (Animation, Audio, Contact, Document, Chat, Location, PhotoSize, Sticker,
TelegramObject, User, Video, Voice, Venue, MessageEntity, Game, Invoice,
SuccessfulPayment, VideoNote, PassportData)
SuccessfulPayment, VideoNote, PassportData, Poll)
from telegram import ParseMode
from telegram.utils.helpers import escape_markdown, to_timestamp, from_timestamp
@ -99,9 +99,13 @@ class Message(TelegramObject):
has logged in.
forward_signature (:obj:`str`): Optional. Signature of the post author for messages
forwarded from channels.
forward_sender_name (:obj:`str`): Optional. Sender's name for messages forwarded from users
who disallow adding a link to their account in forwarded messages.
author_signature (:obj:`str`): Optional. Signature of the post author for messages
in channels.
passport_data (:class:`telegram.PassportData`): Optional. Telegram Passport data
passport_data (:class:`telegram.PassportData`): Optional. Telegram Passport data.
poll (:class:`telegram.Poll`): Optional. Message is a native poll,
information about the poll.
bot (:class:`telegram.Bot`): Optional. The Bot to use for instance methods.
Args:
@ -117,6 +121,8 @@ class Message(TelegramObject):
channel, information about the original channel.
forward_from_message_id (:obj:`int`, optional): For forwarded channel posts, identifier of
the original message in the channel.
forward_sender_name (:obj:`str`, optional): Sender's name for messages forwarded from users
who disallow adding a link to their account in forwarded messages.
forward_date (:class:`datetime.datetime`, optional): For forwarded messages, date the
original message was sent in Unix time. Converted to :class:`datetime.datetime`.
reply_to_message (:class:`telegram.Message`, optional): For replies, the original message.
@ -201,7 +207,9 @@ class Message(TelegramObject):
forwarded from channels.
author_signature (:obj:`str`, optional): Signature of the post author for messages
in channels.
passport_data (:class:`telegram.PassportData`, optional): Telegram Passport data
passport_data (:class:`telegram.PassportData`, optional): Telegram Passport data.
poll (:class:`telegram.Poll`, optional): Message is a native poll,
information about the poll.
"""
_effective_attachment = _UNDEFINED
@ -260,6 +268,8 @@ class Message(TelegramObject):
connected_website=None,
animation=None,
passport_data=None,
poll=None,
forward_sender_name=None,
bot=None,
**kwargs):
# Required
@ -304,10 +314,12 @@ class Message(TelegramObject):
self.successful_payment = successful_payment
self.connected_website = connected_website
self.forward_signature = forward_signature
self.forward_sender_name = forward_sender_name
self.author_signature = author_signature
self.media_group_id = media_group_id
self.animation = animation
self.passport_data = passport_data
self.poll = poll
self.bot = bot
@ -362,6 +374,7 @@ class Message(TelegramObject):
data['invoice'] = Invoice.de_json(data.get('invoice'), bot)
data['successful_payment'] = SuccessfulPayment.de_json(data.get('successful_payment'), bot)
data['passport_data'] = PassportData.de_json(data.get('passport_data'), bot)
data['poll'] = Poll.de_json(data.get('poll'), bot)
return cls(bot=bot, **data)
@ -705,6 +718,23 @@ class Message(TelegramObject):
self._quote(kwargs)
return self.bot.send_contact(self.chat_id, *args, **kwargs)
def reply_poll(self, *args, **kwargs):
"""Shortcut for::
bot.send_poll(update.message.chat_id, *args, **kwargs)
Keyword Args:
quote (:obj:`bool`, optional): If set to ``True``, the photo is sent as an actual reply
to this message. If ``reply_to_message_id`` is passed in ``kwargs``, this parameter
will be ignored. Default: ``True`` in group chats and ``False`` in private chats.
Returns:
:class:`telegram.Message`: On success, instance representing the message posted.
"""
self._quote(kwargs)
return self.bot.send_poll(self.chat_id, *args, **kwargs)
def forward(self, chat_id, disable_notification=False):
"""Shortcut for::

93
telegram/poll.py Normal file
View file

@ -0,0 +1,93 @@
#!/usr/bin/env python
# pylint: disable=R0903
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2018
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser Public License for more details.
#
# 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 an object that represents a Telegram Poll."""
from telegram import (TelegramObject)
class PollOption(TelegramObject):
"""
This object contains information about one answer option in a poll.
Attributes:
text (:obj:`str`): Option text, 1-100 characters.
voter_count (:obj:`int`): Number of users that voted for this option.
Args:
text (:obj:`str`): Option text, 1-100 characters.
voter_count (:obj:`int`): Number of users that voted for this option.
"""
def __init__(self, text, voter_count, **kwargs):
self.text = text
self.voter_count = voter_count
@classmethod
def de_json(cls, data, bot):
if not data:
return None
return cls(**data)
class Poll(TelegramObject):
"""
This object contains information about a poll.
Attributes:
id (:obj:`str`): Unique poll identifier.
question (:obj:`str`): Poll question, 1-255 characters.
options (List[:class:`PollOption`]): List of poll options.
is_closed (:obj:`bool`): True, if the poll is closed.
Args:
id (:obj:`str`): Unique poll identifier.
question (:obj:`str`): Poll question, 1-255 characters.
options (List[:class:`PollOption`]): List of poll options.
is_closed (:obj:`bool`): True, if the poll is closed.
"""
def __init__(self, id, question, options, is_closed, **kwargs):
self.id = id
self.question = question
self.options = options
self.is_closed = is_closed
self._id_attrs = (self.id,)
@classmethod
def de_json(cls, data, bot):
if not data:
return None
data = super(Poll, cls).de_json(data, bot)
data['options'] = [PollOption.de_json(option, bot) for option in data['options']]
return cls(**data)
def to_dict(self):
data = super(Poll, self).to_dict()
data['options'] = [x.to_dict() for x in self.options]
return data

View file

@ -19,7 +19,7 @@
"""This module contains an object that represents a Telegram Update."""
from telegram import (Message, TelegramObject, InlineQuery, ChosenInlineResult,
CallbackQuery, ShippingQuery, PreCheckoutQuery)
CallbackQuery, ShippingQuery, PreCheckoutQuery, Poll)
class Update(TelegramObject):
@ -41,6 +41,8 @@ class Update(TelegramObject):
shipping_query (:class:`telegram.ShippingQuery`): Optional. New incoming shipping query.
pre_checkout_query (:class:`telegram.PreCheckoutQuery`): Optional. New incoming
pre-checkout query.
poll (:class:`telegram.Poll`): Optional. New poll state. Bots receive only updates
about polls, which are sent or stopped by the bot
Args:
update_id (:obj:`int`): The update's unique identifier. Update identifiers start from a
@ -63,6 +65,8 @@ class Update(TelegramObject):
Only for invoices with flexible price.
pre_checkout_query (:class:`telegram.PreCheckoutQuery`, optional): New incoming
pre-checkout query. Contains full information about checkout
poll (:class:`telegram.Poll`, optional): New poll state. Bots receive only updates
about polls, which are sent or stopped by the bot
**kwargs (:obj:`dict`): Arbitrary keyword arguments.
"""
@ -78,6 +82,7 @@ class Update(TelegramObject):
callback_query=None,
shipping_query=None,
pre_checkout_query=None,
poll=None,
**kwargs):
# Required
self.update_id = int(update_id)
@ -91,6 +96,7 @@ class Update(TelegramObject):
self.pre_checkout_query = pre_checkout_query
self.channel_post = channel_post
self.edited_channel_post = edited_channel_post
self.poll = poll
self._effective_user = None
self._effective_chat = None
@ -102,7 +108,7 @@ class Update(TelegramObject):
def effective_user(self):
"""
:class:`telegram.User`: The user that sent this update, no matter what kind of update this
is. Will be ``None`` for :attr:`channel_post`.
is. Will be ``None`` for :attr:`channel_post` and :attr:`poll`.
"""
if self._effective_user:
@ -140,7 +146,7 @@ class Update(TelegramObject):
:class:`telegram.Chat`: The chat that this update was sent in, no matter what kind of
update this is. Will be ``None`` for :attr:`inline_query`,
:attr:`chosen_inline_result`, :attr:`callback_query` from inline messages,
:attr:`shipping_query` and :attr:`pre_checkout_query`.
:attr:`shipping_query`, :attr:`pre_checkout_query` and :attr:`poll`.
"""
if self._effective_chat:
@ -172,7 +178,7 @@ class Update(TelegramObject):
:class:`telegram.Message`: The message included in this update, no matter what kind of
update this is. Will be ``None`` for :attr:`inline_query`,
:attr:`chosen_inline_result`, :attr:`callback_query` from inline messages,
:attr:`shipping_query` and :attr:`pre_checkout_query`.
:attr:`shipping_query`, :attr:`pre_checkout_query` and :attr:`poll`.
"""
if self._effective_message:
@ -215,5 +221,6 @@ class Update(TelegramObject):
data['pre_checkout_query'] = PreCheckoutQuery.de_json(data.get('pre_checkout_query'), bot)
data['channel_post'] = Message.de_json(data.get('channel_post'), bot)
data['edited_channel_post'] = Message.de_json(data.get('edited_channel_post'), bot)
data['poll'] = Poll.de_json(data.get('poll'), bot)
return cls(**data)

View file

@ -31,13 +31,13 @@ FALLBACKS = [
'token': '579694714:AAHRLL5zBVy4Blx2jRFKe1HlfnXCg08WuLY',
'payment_provider_token': '284685063:TEST:NjQ0NjZlNzI5YjJi',
'chat_id': '675666224',
'group_id': '-269513406',
'super_group_id': '-1001493296829',
'channel_id': '@pythontelegrambottests'
}, {
'token': '558194066:AAEEylntuKSLXj9odiv3TnX7Z5KY2J3zY3M',
'payment_provider_token': '284685063:TEST:YjEwODQwMTFmNDcy',
'chat_id': '675666224',
'group_id': '-269513406',
'super_group_id': '-1001493296829',
'channel_id': '@pythontelegrambottests'
}
]

View file

@ -55,8 +55,8 @@ def chat_id(bot_info):
@pytest.fixture(scope='session')
def group_id(bot_info):
return bot_info['group_id']
def super_group_id(bot_info):
return bot_info['super_group_id']
@pytest.fixture(scope='session')

View file

@ -28,7 +28,7 @@ from future.utils import string_types
from telegram import (Bot, Update, ChatAction, TelegramError, User, InlineKeyboardMarkup,
InlineKeyboardButton, InlineQueryResultArticle, InputTextMessageContent,
ShippingOption, LabeledPrice)
ShippingOption, LabeledPrice, Poll)
from telegram.error import BadRequest, InvalidToken, NetworkError, RetryAfter
from telegram.utils.helpers import from_timestamp
@ -149,6 +149,34 @@ class TestBot(object):
assert message.contact.first_name == first_name
assert message.contact.last_name == last_name
# TODO: Add bot to group to test polls too
@flaky(3, 1)
@pytest.mark.timeout(10)
def test_send_and_stop_poll(self, bot, super_group_id):
question = 'Is this a test?'
answers = ['Yes', 'No', 'Maybe']
message = bot.send_poll(chat_id=super_group_id, question=question, options=answers,
timeout=60)
assert message.poll
assert message.poll.question == question
assert message.poll.options[0].text == answers[0]
assert message.poll.options[1].text == answers[1]
assert message.poll.options[2].text == answers[2]
assert not message.poll.is_closed
poll = bot.stop_poll(chat_id=super_group_id, message_id=message.message_id, timeout=60)
assert isinstance(poll, Poll)
assert poll.is_closed
assert poll.options[0].text == answers[0]
assert poll.options[0].voter_count == 0
assert poll.options[1].text == answers[1]
assert poll.options[1].voter_count == 0
assert poll.options[2].text == answers[2]
assert poll.options[2].voter_count == 0
assert poll.question == question
@flaky(3, 1)
@pytest.mark.timeout(10)
def test_send_game(self, bot, chat_id):
@ -346,12 +374,12 @@ class TestBot(object):
@flaky(3, 1)
@pytest.mark.timeout(10)
def test_get_chat(self, bot, group_id):
chat = bot.get_chat(group_id)
def test_get_chat(self, bot, super_group_id):
chat = bot.get_chat(super_group_id)
assert chat.type == 'group'
assert chat.type == 'supergroup'
assert chat.title == '>>> telegram.Bot(test)'
assert chat.id == int(group_id)
assert chat.id == int(super_group_id)
@flaky(3, 1)
@pytest.mark.timeout(10)
@ -609,15 +637,16 @@ class TestBot(object):
def test_set_chat_description(self, bot, channel_id):
assert bot.set_chat_description(channel_id, 'Time: ' + str(time.time()))
@flaky(3, 1)
@pytest.mark.timeout(10)
def test_error_pin_unpin_message(self, bot, message):
# TODO: Add bot to supergroup so this can be tested properly
with pytest.raises(BadRequest, match='Method is available only for supergroups'):
bot.pin_chat_message(message.chat_id, message.message_id, disable_notification=True)
# TODO: Add bot to group to test there too
def test_pin_and_unpin_message(self, bot, super_group_id):
message = bot.send_message(super_group_id, text="test_pin_message")
assert bot.pin_chat_message(chat_id=super_group_id, message_id=message.message_id,
disable_notification=True)
with pytest.raises(BadRequest, match='Method is available only for supergroups'):
bot.unpin_chat_message(message.chat_id)
chat = bot.get_chat(super_group_id)
assert chat.pinned_message == message
assert bot.unpinChatMessage(super_group_id)
# get_sticker_set, upload_sticker_file, create_new_sticker_set, add_sticker_to_set,
# set_sticker_position_in_set and delete_sticker_from_set are tested in the

View file

@ -196,6 +196,13 @@ class TestChat(object):
monkeypatch.setattr('telegram.Bot.send_animation', test)
assert chat.send_animation('test_animation')
def test_instance_method_send_poll(self, monkeypatch, chat):
def test(*args, **kwargs):
return args[1] == chat.id and args[2] == 'test_poll'
monkeypatch.setattr('telegram.Bot.send_poll', test)
assert chat.send_poll('test_poll')
def test_equality(self):
a = Chat(self.id, self.title, self.type)
b = Chat(self.id, self.title, self.type)

View file

@ -554,6 +554,20 @@ class TestMessage(object):
assert message.reply_contact(contact='test_contact')
assert message.reply_contact(contact='test_contact', quote=True)
def test_reply_poll(self, monkeypatch, message):
def test(*args, **kwargs):
id = args[1] == message.chat_id
contact = kwargs['contact'] == 'test_poll'
if kwargs.get('reply_to_message_id'):
reply = kwargs['reply_to_message_id'] == message.message_id
else:
reply = True
return id and contact and reply
monkeypatch.setattr('telegram.Bot.send_poll', test)
assert message.reply_poll(contact='test_poll')
assert message.reply_poll(contact='test_poll', quote=True)
def test_forward(self, monkeypatch, message):
def test(*args, **kwargs):
chat_id = kwargs['chat_id'] == 123456

92
tests/test_poll.py Normal file
View file

@ -0,0 +1,92 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2018
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser Public License for more details.
#
# 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 pytest
from telegram import Poll, PollOption
@pytest.fixture(scope="class")
def poll_option():
return PollOption(text=TestPollOption.text,
voter_count=TestPollOption.voter_count)
class TestPollOption(object):
text = "test option"
voter_count = 3
def test_de_json(self):
json_dict = {
'text': self.text,
'voter_count': self.voter_count
}
poll_option = PollOption.de_json(json_dict, None)
assert poll_option.text == self.text
assert poll_option.voter_count == self.voter_count
def test_to_dict(self, poll_option):
poll_option_dict = poll_option.to_dict()
assert isinstance(poll_option_dict, dict)
assert poll_option_dict['text'] == poll_option.text
assert poll_option_dict['voter_count'] == poll_option.voter_count
@pytest.fixture(scope='class')
def poll():
return Poll(TestPoll.id,
TestPoll.question,
TestPoll.options,
TestPoll.is_closed)
class TestPoll(object):
id = 'id'
question = 'Test?'
options = [PollOption('test', 10), PollOption('test2', 11)]
is_closed = True
def test_de_json(self):
json_dict = {
'id': self.id,
'question': self.question,
'options': [o.to_dict() for o in self.options],
'is_closed': self.is_closed
}
poll = Poll.de_json(json_dict, None)
assert poll.id == self.id
assert poll.question == self.question
assert poll.options == self.options
assert poll.options[0].text == self.options[0].text
assert poll.options[0].voter_count == self.options[0].voter_count
assert poll.options[1].text == self.options[1].text
assert poll.options[1].voter_count == self.options[1].voter_count
assert poll.is_closed == self.is_closed
def test_to_dict(self, poll):
poll_dict = poll.to_dict()
assert isinstance(poll_dict, dict)
assert poll_dict['id'] == poll.id
assert poll_dict['question'] == poll.question
assert poll_dict['options'] == [o.to_dict() for o in poll.options]
assert poll_dict['is_closed'] == poll.is_closed

View file

@ -20,7 +20,7 @@
import pytest
from telegram import (Message, User, Update, Chat, CallbackQuery, InlineQuery,
ChosenInlineResult, ShippingQuery, PreCheckoutQuery)
ChosenInlineResult, ShippingQuery, PreCheckoutQuery, Poll, PollOption)
message = Message(1, User(1, '', False), None, Chat(1, ''), text='Text')
@ -34,12 +34,13 @@ params = [
{'chosen_inline_result': ChosenInlineResult('id', User(1, '', False), '')},
{'shipping_query': ShippingQuery('id', User(1, '', False), '', None)},
{'pre_checkout_query': PreCheckoutQuery('id', User(1, '', False), '', 0, '')},
{'callback_query': CallbackQuery(1, User(1, '', False), 'chat')}
{'callback_query': CallbackQuery(1, User(1, '', False), 'chat')},
{'poll': Poll('id', '?', [PollOption('.', 1)], False)}
]
all_types = ('message', 'edited_message', 'callback_query', 'channel_post',
'edited_channel_post', 'inline_query', 'chosen_inline_result',
'shipping_query', 'pre_checkout_query')
'shipping_query', 'pre_checkout_query', 'poll')
ids = all_types + ('callback_query_without_message',)
@ -91,7 +92,8 @@ class TestUpdate(object):
or (update.callback_query is not None
and update.callback_query.message is None)
or update.shipping_query is not None
or update.pre_checkout_query is not None):
or update.pre_checkout_query is not None
or update.poll is not None):
assert chat.id == 1
else:
assert chat is None
@ -99,7 +101,9 @@ class TestUpdate(object):
def test_effective_user(self, update):
# Test that it's sometimes None per docstring
user = update.effective_user
if not (update.channel_post is not None or update.edited_channel_post is not None):
if not (update.channel_post is not None
or update.edited_channel_post is not None
or update.poll is not None):
assert user.id == 1
else:
assert user is None
@ -112,7 +116,8 @@ class TestUpdate(object):
or (update.callback_query is not None
and update.callback_query.message is None)
or update.shipping_query is not None
or update.pre_checkout_query is not None):
or update.pre_checkout_query is not None
or update.poll is not None):
assert eff_message.message_id == message.message_id
else:
assert eff_message is None