Merge branch 'master' into restructure-internal

This commit is contained in:
Jacob Bom 2017-06-20 22:28:14 +02:00
commit 1e4248136c
8 changed files with 143 additions and 134 deletions

View file

@ -2,6 +2,14 @@
Changes Changes
======= =======
**2017-06-18**
*Released 6.1.0*
- Fully support Bot API 3.0
- Add more fine-grained filters for status updates
- Bug fixes and other improvements
**2017-05-29** **2017-05-29**
*Released 6.0.3* *Released 6.0.3*

View file

@ -84,13 +84,7 @@ make the development of bots easy and straightforward. These classes are contain
Telegram API support Telegram API support
==================== ====================
As of **21. May 2017**, all types and methods of the Telegram Bot API 2.3.1 are supported. Additionally, the ``deleteMessage`` API function and the field ``User.language_code`` are supported. As of **18. June 2017**, all types and methods of the Telegram Bot API 3.0 are supported.
Also, version 6.1 beta 0 is available, offering full but experimental Bot API 3.0 coverage:
.. code:: shell
$ pip install python-telegram-bot==6.1b0
========== ==========
Installing Installing

29
appveyor.yml Normal file
View file

@ -0,0 +1,29 @@
environment:
matrix:
# For Python versions available on Appveyor, see
# http://www.appveyor.com/docs/installed-software#python
# The list here is complete (excluding Python 2.6, which
# isn't covered by this document) at the time of writing.
- PYTHON: "C:\\Python27"
- PYTHON: "C:\\Python33"
- PYTHON: "C:\\Python34"
- PYTHON: "C:\\Python35"
- PYTHON: "C:\\Python36"
install:
# We need wheel installed to build wheels
- "git submodule update --init --recursive"
- "%PYTHON%\\python.exe -m pip install wheel"
- "%PYTHON%\\python.exe -m pip install -r requirements.txt"
- "%PYTHON%\\python.exe -m pip install -r requirements-dev.txt"
build: off
test_script:
- "%python%\\Scripts\\nosetests -v --with-flaky --no-flaky-report tests"
after_test:
# This step builds your wheels.
- "%PYTHON%\\python.exe setup.py bdist_wheel"

View file

@ -58,9 +58,9 @@ author = u'Leandro Toledo'
# built documents. # built documents.
# #
# The short X.Y version. # The short X.Y version.
version = '6.0' # telegram.__version__[:3] version = '6.1' # telegram.__version__[:3]
# The full version, including alpha/beta/rc tags. # The full version, including alpha/beta/rc tags.
release = '6.0.3' # telegram.__version__ release = '6.1.0' # telegram.__version__
# The language for content autogenerated by Sphinx. Refer to documentation # The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages. # for a list of supported languages.

View file

@ -31,10 +31,45 @@ from telegram.utils.request import Request
logging.getLogger(__name__).addHandler(logging.NullHandler()) logging.getLogger(__name__).addHandler(logging.NullHandler())
def info(func):
@functools.wraps(func)
def decorator(self, *args, **kwargs):
if not self.bot:
self.get_me()
result = func(self, *args, **kwargs)
return result
return decorator
def log(func):
logger = logging.getLogger(func.__module__)
@functools.wraps(func)
def decorator(self, *args, **kwargs):
logger.debug('Entering: %s', func.__name__)
result = func(self, *args, **kwargs)
logger.debug(result)
logger.debug('Exiting: %s', func.__name__)
return result
return decorator
def message(func):
@functools.wraps(func)
def decorator(self, *args, **kwargs):
url, data = func(self, *args, **kwargs)
return self._message_wrapper(url, data, *args, **kwargs)
return decorator
class Bot(TelegramObject): class Bot(TelegramObject):
"""This object represents a Telegram Bot. """This object represents a Telegram Bot.
Attributes: Properties:
id (int): Unique identifier for this bot. id (int): Unique identifier for this bot.
first_name (str): Bot's first name. first_name (str): Bot's first name.
last_name (str): Bot's last name. last_name (str): Bot's last name.
@ -80,18 +115,6 @@ class Bot(TelegramObject):
return token return token
def info(func):
@functools.wraps(func)
def decorator(self, *args, **kwargs):
if not self.bot:
self.get_me()
result = func(self, *args, **kwargs)
return result
return decorator
@property @property
@info @info
def id(self): def id(self):
@ -116,19 +139,6 @@ class Bot(TelegramObject):
def name(self): def name(self):
return '@{0}'.format(self.username) return '@{0}'.format(self.username)
def log(func):
logger = logging.getLogger(func.__module__)
@functools.wraps(func)
def decorator(self, *args, **kwargs):
logger.debug('Entering: %s', func.__name__)
result = func(self, *args, **kwargs)
logger.debug(result)
logger.debug('Exiting: %s', func.__name__)
return result
return decorator
def _message_wrapper(self, url, data, *args, **kwargs): def _message_wrapper(self, url, data, *args, **kwargs):
if kwargs.get('reply_to_message_id'): if kwargs.get('reply_to_message_id'):
data['reply_to_message_id'] = kwargs.get('reply_to_message_id') data['reply_to_message_id'] = kwargs.get('reply_to_message_id')
@ -150,15 +160,6 @@ class Bot(TelegramObject):
return Message.de_json(result, self) return Message.de_json(result, self)
def message(func):
@functools.wraps(func)
def decorator(self, *args, **kwargs):
url, data = func(self, *args, **kwargs)
return Bot._message_wrapper(self, url, data, *args, **kwargs)
return decorator
@log @log
def get_me(self, timeout=None, **kwargs): def get_me(self, timeout=None, **kwargs):
"""A simple method for testing your bot's auth token. """A simple method for testing your bot's auth token.
@ -242,8 +243,7 @@ class Bot(TelegramObject):
return url, data return url, data
@log @log
@message def delete_message(self, chat_id, message_id, timeout=None, **kwargs):
def delete_message(self, chat_id, message_id):
"""Use this method to delete a message. A message can only be deleted if it was sent less """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, 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 if the bot is an administrator in a group chat, it can delete any message. If the bot is
@ -257,6 +257,10 @@ class Bot(TelegramObject):
username of the target channel (in the format username of the target channel (in the format
@channelusername). @channelusername).
message_id (int): Unique message identifier. message_id (int): Unique message identifier.
timeout (Optional[int|float]): 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 (dict): Arbitrary keyword arguments.
Returns: Returns:
bool: On success, `True` is returned. bool: On success, `True` is returned.
@ -269,7 +273,9 @@ class Bot(TelegramObject):
data = {'chat_id': chat_id, 'message_id': message_id} data = {'chat_id': chat_id, 'message_id': message_id}
return url, data result = self._request.post(url, data, timeout=timeout)
return result
@log @log
@message @message
@ -315,6 +321,7 @@ class Bot(TelegramObject):
return url, data return url, data
@log @log
@message
def send_photo(self, def send_photo(self,
chat_id, chat_id,
photo, photo,
@ -356,19 +363,10 @@ class Bot(TelegramObject):
if caption: if caption:
data['caption'] = caption data['caption'] = caption
return self._message_wrapper( return url, data
url,
data,
chat_id=chat_id,
photo=photo,
caption=caption,
disable_notification=disable_notification,
reply_to_message_id=reply_to_message_id,
reply_markup=reply_markup,
timeout=timeout,
**kwargs)
@log @log
@message
def send_audio(self, def send_audio(self,
chat_id, chat_id,
audio, audio,
@ -431,22 +429,10 @@ class Bot(TelegramObject):
if caption: if caption:
data['caption'] = caption data['caption'] = caption
return self._message_wrapper( return url, data
url,
data,
chat_id=chat_id,
audio=audio,
duration=duration,
performer=performer,
title=title,
caption=caption,
disable_notification=disable_notification,
reply_to_message_id=reply_to_message_id,
reply_markup=reply_markup,
timeout=timeout,
**kwargs)
@log @log
@message
def send_document(self, def send_document(self,
chat_id, chat_id,
document, document,
@ -493,18 +479,7 @@ class Bot(TelegramObject):
if caption: if caption:
data['caption'] = caption data['caption'] = caption
return self._message_wrapper( return url, data
url,
data,
chat_id=chat_id,
document=document,
filename=filename,
caption=caption,
disable_notification=disable_notification,
reply_to_message_id=reply_to_message_id,
reply_markup=reply_markup,
timeout=timeout,
**kwargs)
@log @log
@message @message
@ -549,6 +524,7 @@ class Bot(TelegramObject):
return url, data return url, data
@log @log
@message
def send_video(self, def send_video(self,
chat_id, chat_id,
video, video,
@ -595,20 +571,10 @@ class Bot(TelegramObject):
if caption: if caption:
data['caption'] = caption data['caption'] = caption
return self._message_wrapper( return url, data
url,
data,
chat_id=chat_id,
video=video,
duration=duration,
caption=caption,
disable_notification=disable_notification,
reply_to_message_id=reply_to_message_id,
reply_markup=reply_markup,
timeout=timeout,
**kwargs)
@log @log
@message
def send_voice(self, def send_voice(self,
chat_id, chat_id,
voice, voice,
@ -658,20 +624,10 @@ class Bot(TelegramObject):
if caption: if caption:
data['caption'] = caption data['caption'] = caption
return self._message_wrapper( return url, data
url,
data,
chat_id=chat_id,
voice=voice,
duration=duration,
caption=caption,
disable_notification=disable_notification,
reply_to_message_id=reply_to_message_id,
reply_markup=reply_markup,
timeout=timeout,
**kwargs)
@log @log
@message
def send_video_note(self, def send_video_note(self,
chat_id, chat_id,
video_note, video_note,
@ -718,18 +674,7 @@ class Bot(TelegramObject):
if length is not None: if length is not None:
data['length'] = length data['length'] = length
return self._message_wrapper( return url, data
url,
data,
chat_id=chat_id,
video_note=video_note,
duration=duration,
length=length,
disable_notification=disable_notification,
reply_to_message_id=reply_to_message_id,
reply_markup=reply_markup,
timeout=timeout,
**kwargs)
@log @log
@message @message
@ -898,8 +843,6 @@ class Bot(TelegramObject):
channel (in the format @channelusername). channel (in the format @channelusername).
game_short_name (str): Short name of the game, serves as the unique identifier for the game_short_name (str): Short name of the game, serves as the unique identifier for the
game. game.
Keyword Args:
disable_notification (Optional[bool]): Sends the message silently. iOS users will not disable_notification (Optional[bool]): Sends the message silently. iOS users will not
receive a notification, Android users will receive a notification with no sound. receive a notification, Android users will receive a notification with no sound.
reply_to_message_id (Optional[int]): If the message is a reply, reply_to_message_id (Optional[int]): If the message is a reply,
@ -910,6 +853,7 @@ class Bot(TelegramObject):
timeout (Optional[int|float]): If this value is specified, use it as the read timeout timeout (Optional[int|float]): If this value is specified, use it as the read timeout
from the server (instead of the one specified during creation of the connection from the server (instead of the one specified during creation of the connection
pool). pool).
**kwargs (dict): Arbitrary keyword arguments.
Returns: Returns:
:class:`telegram.Message`: On success, the sent message is returned. :class:`telegram.Message`: On success, the sent message is returned.
@ -925,7 +869,6 @@ class Bot(TelegramObject):
return url, data return url, data
@log @log
@message
def send_chat_action(self, chat_id, action, timeout=None, **kwargs): def send_chat_action(self, chat_id, action, timeout=None, **kwargs):
"""Use this method when you need to tell the user that something is happening on the bot's """Use this method when you need to tell the user that something is happening on the bot's
side. The status is set for 5 seconds or less (when a message arrives from your bot, side. The status is set for 5 seconds or less (when a message arrives from your bot,
@ -952,7 +895,9 @@ class Bot(TelegramObject):
data = {'chat_id': chat_id, 'action': action} data = {'chat_id': chat_id, 'action': action}
return url, data result = self._request.post(url, data, timeout=timeout)
return result
@log @log
def answer_inline_query(self, def answer_inline_query(self,
@ -1698,6 +1643,7 @@ class Bot(TelegramObject):
timeout (Optional[int|float]): If this value is specified, use it as the read timeout timeout (Optional[int|float]): If this value is specified, use it as the read timeout
from the server (instead of the one specified during creation of the connection from the server (instead of the one specified during creation of the connection
pool). pool).
**kwargs (dict): Arbitrary keyword arguments.
Returns: Returns:
:class: `telegram.WebhookInfo` :class: `telegram.WebhookInfo`
@ -1711,6 +1657,8 @@ class Bot(TelegramObject):
return WebhookInfo.de_json(result, self) return WebhookInfo.de_json(result, self)
@log
@message
def set_game_score(self, def set_game_score(self,
user_id, user_id,
score, score,
@ -1743,6 +1691,7 @@ class Bot(TelegramObject):
timeout (Optional[int|float]): If this value is specified, use it as the read timeout timeout (Optional[int|float]): If this value is specified, use it as the read timeout
from the server (instead of the one specified during creation of the connection from the server (instead of the one specified during creation of the connection
pool). pool).
**kwargs (dict): Arbitrary keyword arguments.
Returns: Returns:
:class:`telegram.Message` or True: The edited message, or if the :class:`telegram.Message` or True: The edited message, or if the
@ -1770,12 +1719,9 @@ class Bot(TelegramObject):
else: else:
warnings.warn('edit_message is ignored when disable_edit_message is used') warnings.warn('edit_message is ignored when disable_edit_message is used')
result = self._request.post(url, data, timeout=timeout) return url, data
if result is True:
return result
else:
return Message.de_json(result, self)
@log
def get_game_high_scores(self, def get_game_high_scores(self,
user_id, user_id,
chat_id=None, chat_id=None,
@ -1797,6 +1743,7 @@ class Bot(TelegramObject):
timeout (Optional[int|float]): If this value is specified, use it as the read timeout timeout (Optional[int|float]): If this value is specified, use it as the read timeout
from the server (instead of the one specified during creation of the connection from the server (instead of the one specified during creation of the connection
pool). pool).
**kwargs (dict): Arbitrary keyword arguments.
Returns: Returns:
list[:class:`telegram.GameHighScore`]: Scores of the specified user and several of his list[:class:`telegram.GameHighScore`]: Scores of the specified user and several of his
@ -1927,6 +1874,7 @@ class Bot(TelegramObject):
return url, data return url, data
@log
def answer_shipping_query(self, def answer_shipping_query(self,
shipping_query_id, shipping_query_id,
ok, ok,
@ -1950,6 +1898,9 @@ class Bot(TelegramObject):
form that explains why it is impossible to complete the order (e.g. "Sorry, form that explains why it is impossible to complete the order (e.g. "Sorry,
delivery to your desired address is unavailable'). Telegram will display this delivery to your desired address is unavailable'). Telegram will display this
message to the user. message to the user.
timeout (Optional[int|float]): 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 (dict): Arbitrary keyword arguments. **kwargs (dict): Arbitrary keyword arguments.
Returns: Returns:
@ -1960,12 +1911,14 @@ class Bot(TelegramObject):
""" """
if ok is True and (shipping_options is None or error_message is not None): ok = bool(ok)
if ok and (shipping_options is None or error_message is not None):
raise TelegramError( raise TelegramError(
'answerShippingQuery: If ok is True, shipping_options ' 'answerShippingQuery: If ok is True, shipping_options '
'should not be empty and there should not be error_message') 'should not be empty and there should not be error_message')
if ok is False and (shipping_options is not None or error_message is None): if not ok and (shipping_options is not None or error_message is None):
raise TelegramError( raise TelegramError(
'answerShippingQuery: If ok is False, error_message ' 'answerShippingQuery: If ok is False, error_message '
'should not be empty and there should not be shipping_options') 'should not be empty and there should not be shipping_options')
@ -1974,7 +1927,7 @@ class Bot(TelegramObject):
data = {'shipping_query_id': shipping_query_id, 'ok': ok} data = {'shipping_query_id': shipping_query_id, 'ok': ok}
if ok is True: if ok:
data['shipping_options'] = [option.to_dict() for option in shipping_options] data['shipping_options'] = [option.to_dict() for option in shipping_options]
if error_message is not None: if error_message is not None:
data['error_message'] = error_message data['error_message'] = error_message
@ -1983,6 +1936,7 @@ class Bot(TelegramObject):
return result return result
@log
def answer_pre_checkout_query(self, pre_checkout_query_id, ok, def answer_pre_checkout_query(self, pre_checkout_query_id, ok,
error_message=None, timeout=None, **kwargs): error_message=None, timeout=None, **kwargs):
""" """
@ -1999,6 +1953,9 @@ class Bot(TelegramObject):
"Sorry, somebody just bought the last of our amazing black T-shirts while you were "Sorry, somebody just bought the last of our amazing black T-shirts while you were
busy filling out your payment details. Please choose a different color or busy filling out your payment details. Please choose a different color or
garment!"). Telegram will display this message to the user. garment!"). Telegram will display this message to the user.
timeout (Optional[int|float]): 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 (dict): Arbitrary keyword arguments. **kwargs (dict): Arbitrary keyword arguments.
Returns: Returns:
@ -2009,6 +1966,8 @@ class Bot(TelegramObject):
""" """
ok = bool(ok)
if not (ok ^ (error_message is not None)): if not (ok ^ (error_message is not None)):
raise TelegramError( raise TelegramError(
'answerPreCheckoutQuery: If ok is True, there should ' 'answerPreCheckoutQuery: If ok is True, there should '

View file

@ -224,7 +224,7 @@ class Request(object):
"""Request an URL. """Request an URL.
Args: Args:
url (str): The web location we want to retrieve. url (str): The web location we want to retrieve.
data (dict[str, str]): A dict of key/value pairs. Note: On py2.7 value is unicode. data (dict[str, str|int]): A dict of key/value pairs. Note: On py2.7 value is unicode.
timeout (Optional[int|float]): If this value is specified, use it as the read timeout timeout (Optional[int|float]): If this value is specified, use it as the read timeout
from the server (instead of the one specified during creation of the connection from the server (instead of the one specified during creation of the connection
pool). pool).

View file

@ -17,4 +17,4 @@
# You should have received a copy of the GNU Lesser Public License # You should have received a copy of the GNU Lesser Public License
# along with this program. If not, see [http://www.gnu.org/licenses/]. # along with this program. If not, see [http://www.gnu.org/licenses/].
__version__ = '6.0.3' __version__ = '6.1.0'

View file

@ -31,6 +31,7 @@ from flaky import flaky
sys.path.append('.') sys.path.append('.')
import telegram import telegram
from telegram.utils.request import Request
from telegram.error import BadRequest from telegram.error import BadRequest
from tests.base import BaseTest, timeout from tests.base import BaseTest, timeout
@ -467,6 +468,24 @@ class BotTest(BaseTest, unittest.TestCase):
self.assertEqual(name, message.contact.first_name) self.assertEqual(name, message.contact.first_name)
self.assertEqual(last, message.contact.last_name) self.assertEqual(last, message.contact.last_name)
def test_timeout_propagation(self):
class OkException(Exception):
pass
class MockRequest(Request):
def post(self, url, data, timeout=None):
raise OkException(timeout)
_request = self._bot._request
self._bot._request = MockRequest()
timeout = 500
with self.assertRaises(OkException) as ok:
self._bot.send_photo(self._chat_id, open('tests/data/telegram.jpg'), timeout=timeout)
self.assertEqual(ok.exception.args[0], timeout)
self._bot._request = _request
if __name__ == '__main__': if __name__ == '__main__':
unittest.main() unittest.main()