Merge pull request #258 from tsnoam/cr4.0

CR & fixes for 4.0 (dispatcher mostly, API2.0 still TODO)
This commit is contained in:
Noam Meltzer 2016-04-26 18:14:34 +03:00
commit 0e34c0395f
20 changed files with 142 additions and 136 deletions

View file

@ -371,9 +371,8 @@ Our bot is now up and running (go ahead and try it)! It's not doing anything yet
>>> def echo(bot, update):
... bot.sendMessage(chat_id=update.message.chat_id, text=update.message.text)
...
>>> from telegram.ext import MessageHandler
>>> from telegram.ext import filters
>>> echo_handler = MessageHandler([filters.TEXT], echo)
>>> from telegram.ext import MessageHandler, Filters
>>> echo_handler = MessageHandler([Filters.text], echo)
>>> dispatcher.addHandler(echo_handler)
Our bot should now reply to all text messages that are not a command with a message that has the same content.

View file

@ -1,7 +0,0 @@
telegram.ext.filters module
===========================
.. automodule:: telegram.ext.filters
:members:
:undoc-members:
:show-inheritance:

View file

@ -14,7 +14,6 @@ Submodules
telegram.ext.commandhandler
telegram.ext.inlinequeryhandler
telegram.ext.messagehandler
telegram.ext.filters
telegram.ext.regexhandler
telegram.ext.stringcommandhandler
telegram.ext.stringregexhandler

View file

@ -19,7 +19,7 @@ Type 'stop' on the command line to stop the bot.
"""
from telegram.ext import Updater, StringCommandHandler, StringRegexHandler, \
MessageHandler, CommandHandler, RegexHandler, filters
MessageHandler, CommandHandler, RegexHandler, Filters
from telegram.ext.dispatcher import run_async
from time import sleep
import logging
@ -115,7 +115,7 @@ def main():
dp.addHandler(CommandHandler("start", start))
dp.addHandler(CommandHandler("help", help))
# Message handlers only receive updates that don't contain commands
dp.addHandler(MessageHandler([filters.TEXT], message))
dp.addHandler(MessageHandler([Filters.text], message))
# Regex handlers will receive all updates on which their regex matches,
# but we have to add it in a separate group, since in one group,
# only one handler will be executed

View file

@ -17,7 +17,7 @@ Press Ctrl-C on the command line or send a signal to the process to stop the
bot.
"""
from telegram.ext import Updater, CommandHandler, MessageHandler, filters
from telegram.ext import Updater, CommandHandler, MessageHandler, Filters
import logging
# Enable logging
@ -58,7 +58,7 @@ def main():
dp.addHandler(CommandHandler("help", help))
# on noncommand i.e message - echo the message on Telegram
dp.addHandler(MessageHandler([filters.TEXT], echo))
dp.addHandler(MessageHandler([Filters.text], echo))
# log all errors
dp.addErrorHandler(error)

View file

@ -9,7 +9,7 @@ import logging
from telegram import Emoji, ForceReply, InlineKeyboardButton, \
InlineKeyboardMarkup
from telegram.ext import Updater, CommandHandler, MessageHandler, \
CallbackQueryHandler, filters
CallbackQueryHandler, Filters
logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - '
'%(message)s',
@ -105,7 +105,7 @@ updater = Updater("TOKEN")
# The command
updater.dispatcher.addHandler(CommandHandler('set', set_value))
# The answer
updater.dispatcher.addHandler(MessageHandler([filters.TEXT], entered_value))
updater.dispatcher.addHandler(MessageHandler([Filters.text], entered_value))
# The confirmation
updater.dispatcher.addHandler(CallbackQueryHandler(confirm_value))
updater.dispatcher.addHandler(CommandHandler('start', help))

View file

@ -6,7 +6,7 @@
import logging
from telegram import Emoji, ForceReply, ReplyKeyboardMarkup, KeyboardButton
from telegram.ext import Updater, CommandHandler, MessageHandler, filters
from telegram.ext import Updater, CommandHandler, MessageHandler, Filters
logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - '
'%(message)s',
@ -93,7 +93,7 @@ updater = Updater("TOKEN")
# The command
updater.dispatcher.addHandler(CommandHandler('set', set_value))
# The answer and confirmation
updater.dispatcher.addHandler(MessageHandler([filters.TEXT], set_value))
updater.dispatcher.addHandler(MessageHandler([Filters.text], set_value))
updater.dispatcher.addHandler(CommandHandler('cancel', cancel))
updater.dispatcher.addHandler(CommandHandler('start', help))
updater.dispatcher.addHandler(CommandHandler('help', help))

View file

@ -64,8 +64,9 @@ class TelegramObject(object):
"""
data = dict()
for key, value in self.__dict__.items():
if value or value == '':
for key in iter(self.__dict__):
value = self.__dict__[key]
if value is not None:
if hasattr(value, 'to_dict'):
data[key] = value.to_dict()
else:

View file

@ -919,7 +919,7 @@ class Bot(TelegramObject):
data['cache_time'] = cache_time
if is_personal:
data['is_personal'] = is_personal
if next_offset or next_offset == '':
if next_offset is not None:
data['next_offset'] = next_offset
if switch_pm_text:
data['switch_pm_text'] = switch_pm_text
@ -1443,7 +1443,7 @@ class Bot(TelegramObject):
data = {}
if webhook_url or webhook_url == '':
if webhook_url is not None:
data['url'] = webhook_url
if certificate:
data['certificate'] = certificate

View file

@ -27,7 +27,7 @@ from .choseninlineresulthandler import ChosenInlineResultHandler
from .commandhandler import CommandHandler
from .handler import Handler
from .inlinequeryhandler import InlineQueryHandler
from .messagehandler import MessageHandler
from .messagehandler import MessageHandler, Filters
from .regexhandler import RegexHandler
from .stringcommandhandler import StringCommandHandler
from .stringregexhandler import StringRegexHandler
@ -35,5 +35,5 @@ from .typehandler import TypeHandler
__all__ = ('Dispatcher', 'JobQueue', 'Updater', 'CallbackQueryHandler',
'ChosenInlineResultHandler', 'CommandHandler', 'Handler',
'InlineQueryHandler', 'MessageHandler', 'RegexHandler',
'InlineQueryHandler', 'MessageHandler', 'Filters', 'RegexHandler',
'StringCommandHandler', 'StringRegexHandler', 'TypeHandler')

View file

@ -24,11 +24,7 @@ from functools import wraps
from threading import Thread, BoundedSemaphore, Lock, Event, current_thread
from time import sleep
# Adjust for differences in Python versions
try:
from queue import Empty # flake8: noqa
except ImportError:
from Queue import Empty # flake8: noqa
from queue import Empty
from telegram import (TelegramError, NullHandler)
from telegram.ext.handler import Handler
@ -100,6 +96,9 @@ class Dispatcher(object):
self.update_queue = update_queue
self.handlers = {}
""":type: dict[int, list[Handler]"""
self.groups = []
""":type: list[int]"""
self.error_handlers = []
self.logger = logging.getLogger(__name__)
@ -174,8 +173,8 @@ class Dispatcher(object):
self.dispatchError(None, update)
else:
for group in self.handlers.values():
for handler in group:
for group in self.groups:
for handler in self.handlers[group]:
try:
if handler.checkUpdate(update):
handler.handleUpdate(update, self)
@ -188,7 +187,7 @@ class Dispatcher(object):
try:
self.dispatchError(update, te)
except:
except Exception:
self.logger.exception(
'An uncaught error was raised while '
'handling the error')
@ -196,7 +195,7 @@ class Dispatcher(object):
break
# Errors should not stop the thread
except:
except Exception:
self.logger.exception(
'An uncaught error was raised while '
'processing the update')
@ -204,25 +203,39 @@ class Dispatcher(object):
def addHandler(self, handler, group=DEFAULT_GROUP):
"""
Register a handler. A handler must be an instance of a subclass of
telegram.ext.Handler. All handlers are organized in groups, the default
group is int(0), but any object can identify a group. Every update will
be tested against each handler in each group from first-added to last-
added. If the update has been handled in one group, it will not be
tested against other handlers in that group. That means an update can
only be handled 0 or 1 times per group, but multiple times across all
groups.
Register a handler.
TL;DR: Order and priority counts. 0 or 1 handlers per group will be
used.
A handler must be an instance of a subclass of
telegram.ext.Handler. All handlers are organized in groups with a
numeric value. The default group is 0. All groups will be evaluated for
handling an update, but only 0 or 1 handler per group will be used.
The priority/order of handlers is determined as follows:
* Priority of the group (lower group number == higher priority)
* The first handler in a group which should handle an update will be
used. Other handlers from the group will not be used. The order in
which handlers were added to the group defines the priority.
Args:
handler (Handler): A Handler instance
group (optional[object]): The group identifier. Default is 0
group (Optional[int]): The group identifier. Default is 0
"""
if not isinstance(handler, Handler):
raise TypeError('Handler is no instance of telegram.ext.Handler')
raise TypeError(
'handler is not an instance of {0}'.format(Handler.__name__))
if not isinstance(group, int):
raise TypeError('group is not int')
if group not in self.handlers:
self.handlers[group] = list()
self.groups.append(group)
self.groups = sorted(self.groups)
self.handlers[group].append(handler)
@ -236,6 +249,9 @@ class Dispatcher(object):
"""
if handler in self.handlers[group]:
self.handlers[group].remove(handler)
if not self.handlers[group]:
del self.handlers[group]
self.groups.remove(group)
def addErrorHandler(self, callback):
"""

View file

@ -1,23 +0,0 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2016
# 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 the filters used by the MessageHandler class """
TEXT, AUDIO, DOCUMENT, PHOTO, STICKER, VIDEO, VOICE, CONTACT, LOCATION, \
VENUE, STATUS_UPDATE = range(11)

View file

@ -46,6 +46,9 @@ class Handler(object):
Args:
update (object): The update to be tested
Returns:
bool
"""
raise NotImplementedError

View file

@ -22,11 +22,7 @@
import logging
import time
from threading import Thread, Lock
try:
from queue import PriorityQueue
except ImportError:
from Queue import PriorityQueue
from queue import PriorityQueue
class JobQueue(object):

View file

@ -22,7 +22,71 @@
from .handler import Handler
from telegram import Update
from .filters import * # flake8: noqa
class Filters(object):
"""
Convenient namespace (class) & methods for the filter funcs of the
MessageHandler class.
"""
@staticmethod
def text(update):
return update.message.text and not update.message.text.startswith('/')
@staticmethod
def command(update):
return update.message.text and update.message.text.startswith('/')
@staticmethod
def audio(update):
return bool(update.message.audio)
@staticmethod
def document(update):
return bool(update.message.document)
@staticmethod
def photo(update):
return bool(update.message.photo)
@staticmethod
def sticker(update):
return bool(update.message.sticker)
@staticmethod
def video(update):
return bool(update.message.video)
@staticmethod
def voice(update):
return bool(update.message.voice)
@staticmethod
def contact(update):
return bool(update.message.contact)
@staticmethod
def location(update):
return bool(update.message.location)
@staticmethod
def venue(update):
return bool(update.message.venue)
@staticmethod
def status_update(update):
return bool(
update.message.new_chat_member or
update.message.left_chat_member or
update.message.new_chat_title or
update.message.new_chat_photo or
update.message.delete_chat_photo or
update.message.group_chat_created or
update.message.supergroup_chat_created or
update.message.channel_chat_created or
update.message.migrate_to_chat_id or
update.message.migrate_from_chat_id or
update.message.pinned_message)
class MessageHandler(Handler):
@ -32,8 +96,10 @@ class MessageHandler(Handler):
updates.
Args:
filters (list): A list of filters defined in ``telegram.ext.filters``.
All messages that match at least one of those filters will be
filters (list[function]): A list of filter functions. Standard filters
can be found in the Filters class above.
| Each `function` takes ``Update`` as arg and returns ``bool``.
| All messages that match at least one of those filters will be
accepted. If ``bool(filters)`` evaluates to ``False``, messages are
not filtered.
callback (function): A function that takes ``bot, update`` as
@ -49,36 +115,14 @@ class MessageHandler(Handler):
self.filters = filters
def checkUpdate(self, update):
filters = self.filters
if isinstance(update, Update) and update.message:
message = update.message
return (not filters or # If filters is empty, accept all messages
TEXT in filters and message.text and
not message.text.startswith('/') or
AUDIO in filters and message.audio or
DOCUMENT in filters and message.document or
PHOTO in filters and message.photo or
STICKER in filters and message.sticker or
VIDEO in filters and message.video or
VOICE in filters and message.voice or
CONTACT in filters and message.contact or
LOCATION in filters and message.location or
VENUE in filters and message.venue or
STATUS_UPDATE in filters and (
message.new_chat_member or
message.left_chat_member or
message.new_chat_title or
message.new_chat_photo or
message.delete_chat_photo or
message.group_chat_created or
message.supergroup_chat_created or
message.channel_chat_created or
message.migrate_to_chat_id or
message.migrate_from_chat_id or
message.pinned_message)
)
if not self.filters:
res = True
else:
res = any(func(update) for func in self.filters)
else:
return False
res = False
return res
def handleUpdate(self, update, dispatcher):
optional_args = self.collectOptionalArgs(dispatcher)

View file

@ -21,6 +21,8 @@
import re
from future.utils import string_types
from .handler import Handler
from telegram import Update
@ -52,7 +54,7 @@ class RegexHandler(Handler):
pass_groupdict=False, pass_update_queue=False):
super(RegexHandler, self).__init__(callback, pass_update_queue)
if isinstance(pattern, str):
if isinstance(pattern, string_types):
pattern = re.compile(pattern)
self.pattern = pattern

View file

@ -21,6 +21,8 @@
import re
from future.utils import string_types
from .handler import Handler
@ -51,7 +53,7 @@ class StringRegexHandler(Handler):
pass_groupdict=False, pass_update_queue=False):
super(StringRegexHandler, self).__init__(callback, pass_update_queue)
if isinstance(pattern, str):
if isinstance(pattern, string_types):
pattern = re.compile(pattern)
self.pattern = pattern
@ -59,11 +61,8 @@ class StringRegexHandler(Handler):
self.pass_groupdict = pass_groupdict
def checkUpdate(self, update):
if isinstance(update, str):
match = re.match(self.pattern, update)
return bool(match)
else:
return False
return isinstance(update, string_types) and bool(
re.match(self.pattern, update))
def handleUpdate(self, update, dispatcher):
optional_args = self.collectOptionalArgs(dispatcher)

View file

@ -28,12 +28,7 @@ from threading import Thread, Lock, current_thread, Event
from time import sleep
import subprocess
from signal import signal, SIGINT, SIGTERM, SIGABRT
# Adjust for differences in Python versions
try:
from queue import Queue # flake8: noqa
except ImportError:
from Queue import Queue # flake8: noqa
from queue import Queue
from telegram import Bot, TelegramError, NullHandler
from telegram.ext import dispatcher, Dispatcher, JobQueue

View file

@ -21,24 +21,6 @@
from telegram.error import InvalidToken
try:
type(basestring)
except NameError:
basestring = str
def validate_string(arg, name):
"""
Validate a string argument. Raises a ValueError if `arg` is neither an
instance of basestring (Python 2) or str (Python 3) nor None.
Args:
arg (basestring): The value to be tested
name (str): The name of the argument, for the error message
"""
if not isinstance(arg, basestring) and arg is not None:
raise ValueError(name + ' is not a string')
def validate_token(token):
"""a very basic validation on token"""

View file

@ -144,8 +144,8 @@ class UpdaterTest(BaseTest, unittest.TestCase):
def test_addRemoveTelegramMessageHandler(self):
self._setup_updater('Test')
d = self.updater.dispatcher
from telegram.ext import filters
handler = MessageHandler([filters.TEXT], self.telegramHandlerTest)
from telegram.ext import Filters
handler = MessageHandler([Filters.text], self.telegramHandlerTest)
d.addHandler(handler)
self.updater.start_polling(0.01)
sleep(.1)