Merge branch 'master' into jobqueue

This commit is contained in:
Jannes Höke 2016-01-04 00:01:27 +01:00
commit aa125e59fb
9 changed files with 187 additions and 87 deletions

7
.gitignore vendored
View file

@ -61,3 +61,10 @@ target/
# Sublime Text 2 # Sublime Text 2
*.sublime* *.sublime*
# unitests files
telegram.mp3
telegram.mp4
telegram.ogg
telegram.png
telegram.webp

View file

@ -9,6 +9,7 @@ python:
install: install:
- pip install pylint flake8 coveralls - pip install pylint flake8 coveralls
- pip install -r requirements.txt - pip install -r requirements.txt
- 'if [[ $TRAVIS_PYTHON_VERSION == 2.6 ]]; then pip install unittest2; fi'
script: script:
- nosetests --with-coverage --cover-package telegram/ - nosetests --with-coverage --cover-package telegram/
- flake8 telegram - flake8 telegram

View file

@ -19,6 +19,7 @@ The following wonderful people contributed directly or indirectly to this projec
- `macrojames <https://github.com/macrojames>`_ - `macrojames <https://github.com/macrojames>`_
- `naveenvhegde <https://github.com/naveenvhegde>`_ - `naveenvhegde <https://github.com/naveenvhegde>`_
- `njittam <https://github.com/njittam>`_ - `njittam <https://github.com/njittam>`_
- `Noam Meltzer <https://github.com/tsnoam>`_
- `Rahiel Kasim <https://github.com/rahiel>`_ - `Rahiel Kasim <https://github.com/rahiel>`_
- `sooyhwang <https://github.com/sooyhwang>`_ - `sooyhwang <https://github.com/sooyhwang>`_
- `wjt <https://github.com/wjt>`_ - `wjt <https://github.com/wjt>`_

View file

@ -52,7 +52,7 @@ class Bot(TelegramObject):
def __init__(self, def __init__(self,
token, token,
base_url=None): base_url=None):
self.token = token self.token = self._valid_token(token)
if base_url is None: if base_url is None:
self.base_url = 'https://api.telegram.org/bot%s' % self.token self.base_url = 'https://api.telegram.org/bot%s' % self.token
@ -744,3 +744,11 @@ class Bot(TelegramObject):
def __reduce__(self): def __reduce__(self):
return (self.__class__, (self.token, return (self.__class__, (self.token,
self.base_url.replace(self.token, ''))) self.base_url.replace(self.token, '')))
@staticmethod
def _valid_token(token):
"""a very basic validation on token"""
left, sep, _right = token.partition(':')
if (not sep) or (not left.isdigit()) or (len(left) < 3):
raise TelegramError('Invalid token')
return token

View file

@ -18,7 +18,22 @@
"""This module contains a object that represents a Telegram Error""" """This module contains a object that represents a Telegram Error"""
import re
def _lstrip_str(in_s, lstr):
"""
Args:
in_s (str): in string
lstr (str): substr to strip from left side
Returns:
str:
"""
if in_s.startswith(lstr):
res = in_s[len(lstr):]
else:
res = in_s
return res
class TelegramError(Exception): class TelegramError(Exception):
@ -26,16 +41,20 @@ class TelegramError(Exception):
def __init__(self, message): def __init__(self, message):
""" """
Args:
message (str):
Returns: Returns:
str:
""" """
super(TelegramError, self).__init__() super(TelegramError, self).__init__()
api_error = re.match(r'^Error: (?P<message>.*)', message) msg = _lstrip_str(message, 'Error: ')
if api_error: msg = _lstrip_str(msg, '[Error]: ')
self.message = api_error.group('message').capitalize() if msg != message:
else: # api_error - capitalize the msg...
self.message = message msg = msg.capitalize()
self.message = msg
def __str__(self): def __str__(self):
return '%s' % (self.message) return '%s' % (self.message)

View file

@ -64,15 +64,26 @@ class Updater:
Attributes: Attributes:
Args: Args:
token (str): The bots token given by the @BotFather token (Optional[str]): The bot's token given by the @BotFather
base_url (Optional[str]): base_url (Optional[str]):
workers (Optional[int]): Amount of threads in the thread pool for workers (Optional[int]): Amount of threads in the thread pool for
functions decorated with @run_async functions decorated with @run_async
bot (Optional[Bot]):
Raises:
ValueError: If both `token` and `bot` are passed or none of them.
""" """
def __init__(self, token, base_url=None, workers=4): def __init__(self, token=None, base_url=None, workers=4, bot=None):
if (token is None) and (bot is None):
raise ValueError('`token` or `bot` must be passed')
if (token is not None) and (bot is not None):
raise ValueError('`token` and `bot` are mutually exclusive')
self.bot = Bot(token, base_url) if bot is not None:
self.bot = bot
else:
self.bot = Bot(token, base_url)
self.update_queue = Queue() self.update_queue = Queue()
self.dispatcher = Dispatcher(self.bot, self.update_queue, self.dispatcher = Dispatcher(self.bot, self.update_queue,
workers=workers) workers=workers)

View file

@ -19,6 +19,7 @@
"""This module contains methods to make POST and GET requests""" """This module contains methods to make POST and GET requests"""
import functools
import json import json
import socket import socket
from ssl import SSLError from ssl import SSLError
@ -45,7 +46,11 @@ def _parse(json_data):
Returns: Returns:
A JSON parsed as Python dict with results. A JSON parsed as Python dict with results.
""" """
data = json.loads(json_data.decode()) decoded_s = json_data.decode('utf-8')
try:
data = json.loads(decoded_s)
except ValueError:
raise TelegramError('Invalid server response')
if not data.get('ok') and data.get('description'): if not data.get('ok') and data.get('description'):
return data['description'] return data['description']
@ -53,6 +58,34 @@ def _parse(json_data):
return data['result'] return data['result']
def _try_except_req(func):
"""Decorator for requests to handle known exceptions"""
@functools.wraps(func)
def decorator(*args, **kwargs):
try:
return func(*args, **kwargs)
except HTTPError as error:
if error.getcode() == 403:
raise TelegramError('Unauthorized')
if error.getcode() == 502:
raise TelegramError('Bad Gateway')
try:
message = _parse(error.read())
except ValueError:
message = 'Unknown HTTPError {0}'.format(error.getcode())
raise TelegramError(message)
except (SSLError, socket.timeout) as error:
if "operation timed out" in str(error):
raise TelegramError("Timed out")
raise TelegramError(str(error))
return decorator
@_try_except_req
def get(url): def get(url):
"""Request an URL. """Request an URL.
Args: Args:
@ -67,6 +100,7 @@ def get(url):
return _parse(result) return _parse(result)
@_try_except_req
def post(url, def post(url,
data, data,
network_delay=2.): network_delay=2.):
@ -91,39 +125,22 @@ def post(url,
else: else:
timeout = None timeout = None
try: if InputFile.is_inputfile(data):
if InputFile.is_inputfile(data): data = InputFile(data)
data = InputFile(data) request = Request(url,
request = Request(url, data=data.to_form(),
data=data.to_form(), headers=data.headers)
headers=data.headers) else:
else: data = json.dumps(data)
data = json.dumps(data) request = Request(url,
request = Request(url, data=data.encode(),
data=data.encode(), headers={'Content-Type': 'application/json'})
headers={'Content-Type': 'application/json'})
result = urlopen(request, timeout=timeout).read() result = urlopen(request, timeout=timeout).read()
except HTTPError as error:
if error.getcode() == 403:
raise TelegramError('Unauthorized')
if error.getcode() == 502:
raise TelegramError('Bad Gateway')
try:
message = _parse(error.read())
except ValueError:
message = 'Unknown HTTPError'
raise TelegramError(message)
except (SSLError, socket.timeout) as error:
if "operation timed out" in str(error):
raise TelegramError("Timed out")
raise TelegramError(str(error))
return _parse(result) return _parse(result)
@_try_except_req
def download(url, def download(url,
filename): filename):
"""Download a file by its URL. """Download a file by its URL.

View file

@ -20,9 +20,14 @@
"""This module contains a object that represents Tests for Telegram Bot""" """This module contains a object that represents Tests for Telegram Bot"""
import os import os
import unittest
from datetime import datetime from datetime import datetime
import sys import sys
if sys.version_info[0:2] == (2, 6):
import unittest2 as unittest
else:
import unittest
sys.path.append('.') sys.path.append('.')
import telegram import telegram
@ -137,5 +142,33 @@ class BotTest(BaseTest, unittest.TestCase):
self.assertTrue(self.is_json(upf.to_json())) self.assertTrue(self.is_json(upf.to_json()))
self.assertEqual(upf.photos[0][0].file_size, 12421) self.assertEqual(upf.photos[0][0].file_size, 12421)
def _test_invalid_token(self, token):
print('Testing invalid token: {0}'.format(token))
self.assertRaisesRegexp(telegram.TelegramError, 'Invalid token', telegram.Bot, token)
def testInvalidToken1(self):
self._test_invalid_token('123')
def testInvalidToken2(self):
self._test_invalid_token('12a:')
def testInvalidToken3(self):
self._test_invalid_token('12:')
def testUnauthToken(self):
print('Testing unauthorized token')
with self.assertRaisesRegexp(telegram.TelegramError, 'Unauthorized'):
bot = telegram.Bot('1234:abcd1234')
bot.getMe()
def testInvalidSrvResp(self):
print('Testing invalid server response')
with self.assertRaisesRegexp(telegram.TelegramError, 'Invalid server response'):
# bypass the valid token check
bot_cls = type('bot_cls', (telegram.Bot, ), {'_valid_token': lambda self, token: token})
bot = bot_cls('12')
bot.getMe()
if __name__ == '__main__': if __name__ == '__main__':
unittest.main() unittest.main()

View file

@ -22,7 +22,6 @@ This module contains a object that represents Tests for Updater, Dispatcher,
WebhookServer and WebhookHandler WebhookServer and WebhookHandler
""" """
import logging import logging
import unittest
import sys import sys
import re import re
import os import os
@ -31,6 +30,11 @@ from random import randrange
from time import sleep from time import sleep
from datetime import datetime from datetime import datetime
if sys.version_info[0:2] == (2, 6):
import unittest2 as unittest
else:
import unittest
try: try:
from urllib2 import urlopen, Request from urllib2 import urlopen, Request
except ImportError: except ImportError:
@ -38,7 +42,7 @@ except ImportError:
sys.path.append('.') sys.path.append('.')
from telegram import Update, Message, TelegramError, User, Chat, Updater from telegram import Update, Message, TelegramError, User, Chat, Updater, Bot
from telegram.dispatcher import run_async from telegram.dispatcher import run_async
from tests.base import BaseTest from tests.base import BaseTest
from threading import Lock, Thread from threading import Lock, Thread
@ -61,14 +65,18 @@ class UpdaterTest(BaseTest, unittest.TestCase):
""" """
def setUp(self): def setUp(self):
self.updater = Updater('', workers=2) self.updater = None
self.received_message = None self.received_message = None
self.message_count = 0 self.message_count = 0
self.lock = Lock() self.lock = Lock()
def _setup_updater(self, *args, **kwargs):
bot = MockBot(*args, **kwargs)
self.updater = Updater(workers=2, bot=bot)
def tearDown(self): def tearDown(self):
self.updater.stop() if self.updater is not None:
self.updater.stop()
def reset(self): def reset(self):
self.message_count = 0 self.message_count = 0
@ -119,8 +127,7 @@ class UpdaterTest(BaseTest, unittest.TestCase):
def test_addRemoveTelegramMessageHandler(self): def test_addRemoveTelegramMessageHandler(self):
print('Testing add/removeTelegramMessageHandler') print('Testing add/removeTelegramMessageHandler')
bot = MockBot('Test') self._setup_updater('Test')
self.updater.bot = bot
d = self.updater.dispatcher d = self.updater.dispatcher
d.addTelegramMessageHandler( d.addTelegramMessageHandler(
self.telegramHandlerTest) self.telegramHandlerTest)
@ -132,13 +139,13 @@ class UpdaterTest(BaseTest, unittest.TestCase):
d.removeTelegramMessageHandler(self.telegramHandlerTest) d.removeTelegramMessageHandler(self.telegramHandlerTest)
self.reset() self.reset()
bot.send_messages = 1 self.updater.bot.send_messages = 1
sleep(.1) sleep(.1)
self.assertTrue(None is self.received_message) self.assertTrue(None is self.received_message)
def test_addTelegramMessageHandlerMultipleMessages(self): def test_addTelegramMessageHandlerMultipleMessages(self):
print('Testing addTelegramMessageHandler and send 100 messages...') print('Testing addTelegramMessageHandler and send 100 messages...')
self.updater.bot = MockBot('Multiple', 100) self._setup_updater('Multiple', 100)
self.updater.dispatcher.addTelegramMessageHandler( self.updater.dispatcher.addTelegramMessageHandler(
self.telegramHandlerTest) self.telegramHandlerTest)
self.updater.start_polling(0.0) self.updater.start_polling(0.0)
@ -148,8 +155,7 @@ class UpdaterTest(BaseTest, unittest.TestCase):
def test_addRemoveTelegramRegexHandler(self): def test_addRemoveTelegramRegexHandler(self):
print('Testing add/removeStringRegexHandler') print('Testing add/removeStringRegexHandler')
bot = MockBot('Test2') self._setup_updater('Test2')
self.updater.bot = bot
d = self.updater.dispatcher d = self.updater.dispatcher
regobj = re.compile('Te.*') regobj = re.compile('Te.*')
self.updater.dispatcher.addTelegramRegexHandler(regobj, self.updater.dispatcher.addTelegramRegexHandler(regobj,
@ -162,14 +168,13 @@ class UpdaterTest(BaseTest, unittest.TestCase):
d.removeTelegramRegexHandler(regobj, self.telegramHandlerTest) d.removeTelegramRegexHandler(regobj, self.telegramHandlerTest)
self.reset() self.reset()
bot.send_messages = 1 self.updater.bot.send_messages = 1
sleep(.1) sleep(.1)
self.assertTrue(None is self.received_message) self.assertTrue(None is self.received_message)
def test_addRemoveTelegramCommandHandler(self): def test_addRemoveTelegramCommandHandler(self):
print('Testing add/removeTelegramCommandHandler') print('Testing add/removeTelegramCommandHandler')
bot = MockBot('/test') self._setup_updater('/test')
self.updater.bot = bot
d = self.updater.dispatcher d = self.updater.dispatcher
self.updater.dispatcher.addTelegramCommandHandler( self.updater.dispatcher.addTelegramCommandHandler(
'test', self.telegramHandlerTest) 'test', self.telegramHandlerTest)
@ -181,14 +186,13 @@ class UpdaterTest(BaseTest, unittest.TestCase):
d.removeTelegramCommandHandler('test', self.telegramHandlerTest) d.removeTelegramCommandHandler('test', self.telegramHandlerTest)
self.reset() self.reset()
bot.send_messages = 1 self.updater.bot.send_messages = 1
sleep(.1) sleep(.1)
self.assertTrue(None is self.received_message) self.assertTrue(None is self.received_message)
def test_addRemoveUnknownTelegramCommandHandler(self): def test_addRemoveUnknownTelegramCommandHandler(self):
print('Testing add/removeUnknownTelegramCommandHandler') print('Testing add/removeUnknownTelegramCommandHandler')
bot = MockBot('/test2') self._setup_updater('/test2')
self.updater.bot = bot
d = self.updater.dispatcher d = self.updater.dispatcher
self.updater.dispatcher.addUnknownTelegramCommandHandler( self.updater.dispatcher.addUnknownTelegramCommandHandler(
self.telegramHandlerTest) self.telegramHandlerTest)
@ -200,14 +204,13 @@ class UpdaterTest(BaseTest, unittest.TestCase):
d.removeUnknownTelegramCommandHandler(self.telegramHandlerTest) d.removeUnknownTelegramCommandHandler(self.telegramHandlerTest)
self.reset() self.reset()
bot.send_messages = 1 self.updater.bot.send_messages = 1
sleep(.1) sleep(.1)
self.assertTrue(None is self.received_message) self.assertTrue(None is self.received_message)
def test_addRemoveStringRegexHandler(self): def test_addRemoveStringRegexHandler(self):
print('Testing add/removeStringRegexHandler') print('Testing add/removeStringRegexHandler')
bot = MockBot('', messages=0) self._setup_updater('', messages=0)
self.updater.bot = bot
d = self.updater.dispatcher d = self.updater.dispatcher
d.addStringRegexHandler('Te.*', self.stringHandlerTest) d.addStringRegexHandler('Te.*', self.stringHandlerTest)
queue = self.updater.start_polling(0.01) queue = self.updater.start_polling(0.01)
@ -225,8 +228,7 @@ class UpdaterTest(BaseTest, unittest.TestCase):
def test_addRemoveStringCommandHandler(self): def test_addRemoveStringCommandHandler(self):
print('Testing add/removeStringCommandHandler') print('Testing add/removeStringCommandHandler')
bot = MockBot('', messages=0) self._setup_updater('', messages=0)
self.updater.bot = bot
d = self.updater.dispatcher d = self.updater.dispatcher
d.addStringCommandHandler( d.addStringCommandHandler(
'test3', self.stringHandlerTest) 'test3', self.stringHandlerTest)
@ -246,8 +248,7 @@ class UpdaterTest(BaseTest, unittest.TestCase):
def test_addRemoveUnknownStringCommandHandler(self): def test_addRemoveUnknownStringCommandHandler(self):
print('Testing add/removeUnknownStringCommandHandler') print('Testing add/removeUnknownStringCommandHandler')
bot = MockBot('/test') self._setup_updater('/test')
self.updater.bot = bot
d = self.updater.dispatcher d = self.updater.dispatcher
d.addUnknownStringCommandHandler( d.addUnknownStringCommandHandler(
self.stringHandlerTest) self.stringHandlerTest)
@ -260,14 +261,13 @@ class UpdaterTest(BaseTest, unittest.TestCase):
d.removeUnknownStringCommandHandler(self.stringHandlerTest) d.removeUnknownStringCommandHandler(self.stringHandlerTest)
self.reset() self.reset()
bot.send_messages = 1 self.updater.bot.send_messages = 1
sleep(.1) sleep(.1)
self.assertTrue(None is self.received_message) self.assertTrue(None is self.received_message)
def test_addRemoveErrorHandler(self): def test_addRemoveErrorHandler(self):
print('Testing add/removeErrorHandler') print('Testing add/removeErrorHandler')
bot = MockBot('', messages=0) self._setup_updater('', messages=0)
self.updater.bot = bot
d = self.updater.dispatcher d = self.updater.dispatcher
d.addErrorHandler(self.errorHandlerTest) d.addErrorHandler(self.errorHandlerTest)
queue = self.updater.start_polling(0.01) queue = self.updater.start_polling(0.01)
@ -286,8 +286,7 @@ class UpdaterTest(BaseTest, unittest.TestCase):
def test_errorInHandler(self): def test_errorInHandler(self):
print('Testing error in Handler') print('Testing error in Handler')
bot = MockBot('', messages=0) self._setup_updater('', messages=0)
self.updater.bot = bot
d = self.updater.dispatcher d = self.updater.dispatcher
d.addStringRegexHandler('.*', d.addStringRegexHandler('.*',
self.errorRaisingHandlerTest) self.errorRaisingHandlerTest)
@ -300,8 +299,7 @@ class UpdaterTest(BaseTest, unittest.TestCase):
def test_errorOnGetUpdates(self): def test_errorOnGetUpdates(self):
print('Testing error on getUpdates') print('Testing error on getUpdates')
bot = MockBot('', raise_error=True) self._setup_updater('', raise_error=True)
self.updater.bot = bot
d = self.updater.dispatcher d = self.updater.dispatcher
d.addErrorHandler(self.errorHandlerTest) d.addErrorHandler(self.errorHandlerTest)
self.updater.start_polling(0.01) self.updater.start_polling(0.01)
@ -310,8 +308,7 @@ class UpdaterTest(BaseTest, unittest.TestCase):
def test_addRemoveTypeHandler(self): def test_addRemoveTypeHandler(self):
print('Testing add/removeTypeHandler') print('Testing add/removeTypeHandler')
bot = MockBot('', messages=0) self._setup_updater('', messages=0)
self.updater.bot = bot
d = self.updater.dispatcher d = self.updater.dispatcher
d.addTypeHandler(dict, self.stringHandlerTest) d.addTypeHandler(dict, self.stringHandlerTest)
queue = self.updater.start_polling(0.01) queue = self.updater.start_polling(0.01)
@ -330,8 +327,7 @@ class UpdaterTest(BaseTest, unittest.TestCase):
def test_runAsync(self): def test_runAsync(self):
print('Testing @run_async') print('Testing @run_async')
bot = MockBot('Test5', messages=2) self._setup_updater('Test5', messages=2)
self.updater.bot = bot
d = self.updater.dispatcher d = self.updater.dispatcher
d.addTelegramMessageHandler( d.addTelegramMessageHandler(
self.asyncHandlerTest) self.asyncHandlerTest)
@ -342,7 +338,7 @@ class UpdaterTest(BaseTest, unittest.TestCase):
def test_additionalArgs(self): def test_additionalArgs(self):
print('Testing additional arguments for handlers') print('Testing additional arguments for handlers')
self.updater.bot = MockBot('', messages=0) self._setup_updater('', messages=0)
self.updater.dispatcher.addStringCommandHandler( self.updater.dispatcher.addStringCommandHandler(
'test5', self.additionalArgsTest) 'test5', self.additionalArgsTest)
@ -354,8 +350,7 @@ class UpdaterTest(BaseTest, unittest.TestCase):
def test_regexGroupHandler(self): def test_regexGroupHandler(self):
print('Testing optional groups and groupdict parameters') print('Testing optional groups and groupdict parameters')
bot = MockBot('', messages=0) self._setup_updater('', messages=0)
self.updater.bot = bot
d = self.updater.dispatcher d = self.updater.dispatcher
d.addStringRegexHandler('^(This).*?(?P<testgroup>regex group).*', d.addStringRegexHandler('^(This).*?(?P<testgroup>regex group).*',
self.regexGroupHandlerTest) self.regexGroupHandlerTest)
@ -368,8 +363,7 @@ class UpdaterTest(BaseTest, unittest.TestCase):
def test_runAsyncWithAdditionalArgs(self): def test_runAsyncWithAdditionalArgs(self):
print('Testing @run_async with additional parameters') print('Testing @run_async with additional parameters')
bot = MockBot('Test6', messages=2) self._setup_updater('Test6', messages=2)
self.updater.bot = bot
d = self.updater.dispatcher d = self.updater.dispatcher
d.addTelegramMessageHandler( d.addTelegramMessageHandler(
self.asyncAdditionalHandlerTest) self.asyncAdditionalHandlerTest)
@ -380,8 +374,7 @@ class UpdaterTest(BaseTest, unittest.TestCase):
def test_webhook(self): def test_webhook(self):
print('Testing Webhook') print('Testing Webhook')
bot = MockBot('', messages=0) self._setup_updater('', messages=0)
self.updater.bot = bot
d = self.updater.dispatcher d = self.updater.dispatcher
d.addTelegramMessageHandler( d.addTelegramMessageHandler(
self.telegramHandlerTest) self.telegramHandlerTest)
@ -442,8 +435,7 @@ class UpdaterTest(BaseTest, unittest.TestCase):
def test_webhook_no_ssl(self): def test_webhook_no_ssl(self):
print('Testing Webhook without SSL') print('Testing Webhook without SSL')
bot = MockBot('', messages=0) self._setup_updater('', messages=0)
self.updater.bot = bot
d = self.updater.dispatcher d = self.updater.dispatcher
d.addTelegramMessageHandler( d.addTelegramMessageHandler(
self.telegramHandlerTest) self.telegramHandlerTest)
@ -485,7 +477,7 @@ class UpdaterTest(BaseTest, unittest.TestCase):
def test_idle(self): def test_idle(self):
print('Testing idle') print('Testing idle')
self.updater.bot = MockBot('Test6', messages=0) self._setup_updater('Test6', messages=0)
self.updater.start_polling(poll_interval=0.01) self.updater.start_polling(poll_interval=0.01)
Thread(target=self.signalsender).start() Thread(target=self.signalsender).start()
self.updater.idle() self.updater.idle()
@ -493,6 +485,17 @@ class UpdaterTest(BaseTest, unittest.TestCase):
sleep(1) sleep(1)
self.updater.running = False self.updater.running = False
def test_createBot(self):
updater = Updater('123:abcd')
self.assertIsNotNone(updater.bot)
def test_mutualExclusiveTokenBot(self):
bot = Bot('123:zyxw')
self.assertRaises(ValueError, Updater, token='123:abcd', bot=bot)
def test_noTokenOrBot(self):
self.assertRaises(ValueError, Updater)
class MockBot: class MockBot: