Update Filters, CommandHandler and MessageHandler (#1221)

* update_filter attribute on filters

Makes it possible to have filters work on an update instead of message, while keeping behavior for current filters

* add update_type filter

* Messagehandler rework

- remove allow_edited (deprecated for a while)
- set deprecated defaults to None
- Raise deprecation warning when they're used
- add sensible defaults for filters.
- rework tests

* Commandhandler rework

* Remove deprecation test from new handler

* Some tweaks per CR

- rename update_types -> updates
- added some clarification to docstrings

* run webhook set test only on 3.6 on appveyor

* update_filter attribute on filters

Makes it possible to have filters work on an update instead of message, while keeping behavior for current filters

* add update_type filter

* Messagehandler rework

- remove allow_edited (deprecated for a while)
- set deprecated defaults to None
- Raise deprecation warning when they're used
- add sensible defaults for filters.
- rework tests

* Commandhandler rework

* Remove deprecation test from new handler

* Some tweaks per CR

- rename update_types -> updates
- added some clarification to docstrings

* run webhook set test only on 3.6 on appveyor

* Changes per CR

* Update travis to build v12

* small doc update

* try to make ci build version branches

* doc for BaseFilter

* Modify regexfilter and mergedfilter

Now returns a list of match objects for every regexfilter

* Change callbackcontext (+ docs)

* integrate in CommandHandler and PrefixHandler

* integrate in MessageHandler

* cbqhandler, iqhandler and srhandler

* make regexhandler a shell over MessageHandler

And raise deprecationWarning on creation

* clean up code and add some comments

* Rework based on internal group feedback

- use data_filter instead of regex_filter on BaseFilter
- have these filters return a dict that is then updated onto CallbackContext instead of using a list is before
- Add a .match property on CallbackContext that returns .matches[0] or None

* Fix and add test for callbackcontext.match

* Lots of documentation fixes and improvements [ci skip]
This commit is contained in:
Eldinnie 2019-02-13 12:07:25 +01:00 committed by Jasmin Bom
parent 950ec35970
commit 2c5eade4f0
24 changed files with 1116 additions and 591 deletions

View file

@ -6,7 +6,7 @@ Changes
*Released 11.0.0*
Context based callbacks:
See https://git.io/vp113 for help.
See https://git.io/fxJuV for help.
- Use of `pass_` in handlers is deprecated.
- Instead use `use_context=True` on `Updater` or `Dispatcher` and change callback from (bot, update, others...) to (update, context).

View file

@ -28,10 +28,10 @@ from .jobqueue import JobQueue, Job
from .updater import Updater
from .callbackqueryhandler import CallbackQueryHandler
from .choseninlineresulthandler import ChosenInlineResultHandler
from .commandhandler import CommandHandler, PrefixHandler
from .inlinequeryhandler import InlineQueryHandler
from .messagehandler import MessageHandler
from .filters import BaseFilter, Filters
from .messagehandler import MessageHandler
from .commandhandler import CommandHandler, PrefixHandler
from .regexhandler import RegexHandler
from .stringcommandhandler import StringCommandHandler
from .stringregexhandler import StringRegexHandler

View file

@ -33,9 +33,9 @@ class CallbackContext(object):
update from the same chat it will be the same ``dict``.
user_data (:obj:`dict`, optional): A dict that can be used to keep any data in. For each
update from the same user it will be the same ``dict``.
match (:obj:`re match object`, optional): If the associated update originated from a
regex-supported handler, this will contain the object returned from
``re.match(pattern, string)``.
matches (List[:obj:`re match object`], optional): If the associated update originated from
a regex-supported handler or had a :class:`Filters.regex`, this will contain a list of
match objects for every pattern where ``re.search(pattern, string)`` returned a match.
args (List[:obj:`str`], optional): Arguments passed to a command if the associated update
is handled by :class:`telegram.ext.CommandHandler`, :class:`telegram.ext.PrefixHandler`
or :class:`telegram.ext.StringCommandHandler`. It contains a list of the words in the
@ -60,7 +60,7 @@ class CallbackContext(object):
self.chat_data = None
self.user_data = None
self.args = None
self.match = None
self.matches = None
self.error = None
self.job = None
@ -89,6 +89,9 @@ class CallbackContext(object):
self.job = job
return self
def update(self, data):
self.__dict__.update(data)
@property
def bot(self):
""":class:`telegram.Bot`: The bot associated with this context."""
@ -113,3 +116,15 @@ class CallbackContext(object):
"""
return self._dispatcher.update_queue
@property
def match(self):
"""
`Regex match type`: The first match from :attr:`matches`.
Useful if you are only filtering using a single regex filter.
Returns `None` if :attr:`matches` is empty.
"""
try:
return self.matches[0] # pylint: disable=unsubscriptable-object
except (IndexError, TypeError):
return None

View file

@ -55,7 +55,7 @@ class CallbackQueryHandler(Handler):
or in the same chat, it will be the same ``dict``.
Note that this is DEPRECATED, and you should use context based callbacks. See
https://git.io/vp113 for more info.
https://git.io/fxJuV for more info.
Args:
callback (:obj:`callable`): The callback function for this handler. Will be called when
@ -151,4 +151,4 @@ class CallbackQueryHandler(Handler):
def collect_additional_context(self, context, update, dispatcher, check_result):
if self.pattern:
context.match = check_result
context.matches = [check_result]

View file

@ -43,7 +43,7 @@ class ChosenInlineResultHandler(Handler):
or in the same chat, it will be the same ``dict``.
Note that this is DEPRECATED, and you should use context based callbacks. See
https://git.io/vp113 for more info.
https://git.io/fxJuV for more info.
Args:
callback (:obj:`callable`): The callback function for this handler. Will be called when

View file

@ -18,9 +18,13 @@
# along with this program. If not, see [http://www.gnu.org/licenses/].
"""This module contains the CommandHandler and PrefixHandler classes."""
import re
import warnings
from future.utils import string_types
from telegram.ext import Filters
from telegram.utils.deprecate import TelegramDeprecationWarning
from telegram import Update, MessageEntity
from .handler import Handler
@ -33,6 +37,9 @@ class CommandHandler(Handler):
:class:`CallbackContext` named :attr:`CallbackContext.args`. It will contain a list of strings,
which is the text following the command split on single or consecutive whitespace characters.
By default the handler listens to messages as well as edited messages. To change this behavior
use ``~Filters.update.edited_message`` in the filter argument.
Attributes:
command (:obj:`str` | List[:obj:`str`]): The command or list of commands this handler
should listen for. Limitations are the same as described here
@ -60,7 +67,7 @@ class CommandHandler(Handler):
or in the same chat, it will be the same ``dict``.
Note that this is DEPRECATED, and you should use context based callbacks. See
https://git.io/vp113 for more info.
https://git.io/fxJuV for more info.
Args:
command (:obj:`str` | List[:obj:`str`]): The command or list of commands this handler
@ -80,6 +87,8 @@ class CommandHandler(Handler):
operators (& for and, | for or, ~ for not).
allow_edited (:obj:`bool`, optional): Determines whether the handler should also accept
edited messages. Default is ``False``.
DEPRECATED: Edited is allowed by default. To change this behavior use
``~Filters.update.edited_message``.
pass_args (:obj:`bool`, optional): Determines whether the handler should be passed the
arguments passed to the command as a keyword argument called ``args``. It will contain
a list of strings, which is the text following the command split on single or
@ -110,7 +119,7 @@ class CommandHandler(Handler):
command,
callback,
filters=None,
allow_edited=False,
allow_edited=None,
pass_args=False,
pass_update_queue=False,
pass_job_queue=False,
@ -131,8 +140,17 @@ class CommandHandler(Handler):
if not re.match(r'^[\da-z_]{1,32}$', comm):
raise ValueError('Command is not a valid bot command')
self.filters = filters
self.allow_edited = allow_edited
if filters:
self.filters = Filters.update.messages & filters
else:
self.filters = Filters.update.messages
if allow_edited is not None:
warnings.warn('allow_edited is deprecated. See https://git.io/fxJuV for more info',
TelegramDeprecationWarning,
stacklevel=2)
if not allow_edited:
self.filters &= ~Filters.update.edited_message
self.pass_args = pass_args
def check_update(self, update):
@ -142,11 +160,10 @@ class CommandHandler(Handler):
update (:class:`telegram.Update`): Incoming telegram update.
Returns:
:obj:`bool`
:obj:`list`: The list of args for the handler
"""
if (isinstance(update, Update)
and (update.message or update.edited_message and self.allow_edited)):
if isinstance(update, Update) and update.effective_message:
message = update.effective_message
if (message.entities and message.entities[0].type == MessageEntity.BOT_COMMAND
@ -160,17 +177,22 @@ class CommandHandler(Handler):
and command[1].lower() == message.bot.username.lower()):
return None
if self.filters is None or self.filters(message):
return args
filter_result = self.filters(update)
if filter_result:
return args, filter_result
else:
return False
def collect_optional_args(self, dispatcher, update=None, check_result=None):
optional_args = super(CommandHandler, self).collect_optional_args(dispatcher, update)
if self.pass_args:
optional_args['args'] = check_result
optional_args['args'] = check_result[0]
return optional_args
def collect_additional_context(self, context, update, dispatcher, check_result):
context.args = check_result
context.args = check_result[0]
if isinstance(check_result[1], dict):
context.update(check_result[1])
class PrefixHandler(CommandHandler):
@ -198,6 +220,10 @@ class PrefixHandler(CommandHandler):
PrefixHandler(['!', '#'], ['test', 'help`], callback) will respond to '!test',
'#test', '!help' and '#help'.
By default the handler listens to messages as well as edited messages. To change this behavior
use ~``Filters.update.edited_message``.
Attributes:
prefix (:obj:`str` | List[:obj:`str`]): The prefix(es) that will precede :attr:`command`.
command (:obj:`str` | List[:obj:`str`]): The command or list of commands this handler
@ -205,8 +231,6 @@ class PrefixHandler(CommandHandler):
callback (:obj:`callable`): The callback function for this handler.
filters (:class:`telegram.ext.BaseFilter`): Optional. Only allow updates with these
Filters.
allow_edited (:obj:`bool`): Determines Whether the handler should also accept
edited messages.
pass_args (:obj:`bool`): Determines whether the handler should be passed
``args``.
pass_update_queue (:obj:`bool`): Determines whether ``update_queue`` will be
@ -225,7 +249,7 @@ class PrefixHandler(CommandHandler):
or in the same chat, it will be the same ``dict``.
Note that this is DEPRECATED, and you should use context based callbacks. See
https://git.io/vp113 for more info.
https://git.io/fxJuV for more info.
Args:
prefix (:obj:`str` | List[:obj:`str`]): The prefix(es) that will precede :attr:`command`.
@ -243,8 +267,6 @@ class PrefixHandler(CommandHandler):
:class:`telegram.ext.filters.BaseFilter`. Standard filters can be found in
:class:`telegram.ext.filters.Filters`. Filters can be combined using bitwise
operators (& for and, | for or, ~ for not).
allow_edited (:obj:`bool`, optional): Determines whether the handler should also accept
edited messages. Default is ``False``.
pass_args (:obj:`bool`, optional): Determines whether the handler should be passed the
arguments passed to the command as a keyword argument called ``args``. It will contain
a list of strings, which is the text following the command split on single or
@ -274,7 +296,6 @@ class PrefixHandler(CommandHandler):
command,
callback,
filters=None,
allow_edited=False,
pass_args=False,
pass_update_queue=False,
pass_job_queue=False,
@ -282,7 +303,7 @@ class PrefixHandler(CommandHandler):
pass_chat_data=False):
super(PrefixHandler, self).__init__(
'nocommand', callback, filters=filters, allow_edited=allow_edited, pass_args=pass_args,
'nocommand', callback, filters=filters, allow_edited=None, pass_args=pass_args,
pass_update_queue=pass_update_queue,
pass_job_queue=pass_job_queue,
pass_user_data=pass_user_data,
@ -305,15 +326,22 @@ class PrefixHandler(CommandHandler):
update (:class:`telegram.Update`): Incoming telegram update.
Returns:
:obj:`bool`
:obj:`list`: The list of args for the handler
"""
if (isinstance(update, Update)
and (update.message or update.edited_message and self.allow_edited)):
if isinstance(update, Update) and update.effective_message:
message = update.effective_message
text_list = message.text.split()
if text_list[0].lower() not in self.command:
return None
if self.filters is None or self.filters(message):
return text_list[1:]
filter_result = self.filters(update)
if filter_result:
return text_list[1:], filter_result
else:
return False
def collect_additional_context(self, context, update, dispatcher, check_result):
context.args = check_result[0]
if isinstance(check_result[1], dict):
context.update(check_result[1])

View file

@ -115,7 +115,7 @@ class Dispatcher(object):
self.use_context = use_context
if not use_context:
warnings.warn('Old Handler API is deprecated - see https://git.io/vp113 for details',
warnings.warn('Old Handler API is deprecated - see https://git.io/fxJuV for details',
TelegramDeprecationWarning, stacklevel=3)
self.user_data = defaultdict(dict)
@ -431,7 +431,7 @@ class Dispatcher(object):
The error that happened will be present in context.error.
Note:
See https://git.io/vp113 for more info about switching to context based API.
See https://git.io/fxJuV for more info about switching to context based API.
"""
self.error_handlers.append(callback)

View file

@ -19,9 +19,11 @@
"""This module contains the Filters for use with the MessageHandler class."""
import re
from telegram import Chat
from future.utils import string_types
from telegram import Chat
class BaseFilter(object):
"""Base class for all Message Filters.
@ -56,13 +58,23 @@ class BaseFilter(object):
Attributes:
name (:obj:`str`): Name for this filter. Defaults to the type of filter.
update_filter (:obj:`bool`): Whether this filter should work on update. If ``False`` it
will run the filter on :attr:`update.effective_message``. Default is ``False``.
data_filter (:obj:`bool`): Whether this filter is a data filter. A data filter should
return a dict with lists. The dict will be merged with
:class:`telegram.ext.CallbackContext`'s internal dict in most cases
(depends on the handler).
"""
name = None
update_filter = False
data_filter = False
def __call__(self, message):
return self.filter(message)
def __call__(self, update):
if self.update_filter:
return self.filter(update)
else:
return self.filter(update.effective_message)
def __and__(self, other):
return MergedFilter(self, and_filter=other)
@ -79,14 +91,18 @@ class BaseFilter(object):
self.name = self.__class__.__name__
return self.name
def filter(self, message):
def filter(self, update):
"""This method must be overwritten.
Note:
If :attr:`update_filter` is false then the first argument is `message` and of
type :class:`telegram.Message`.
Args:
message (:class:`telegram.Message`): The message that is tested.
update (:class:`telegram.Update`): The update that is tested.
Returns:
:obj:`bool`
:obj:`dict` or :obj:`bool`
"""
@ -100,12 +116,13 @@ class InvertedFilter(BaseFilter):
f: The filter to invert.
"""
update_filter = True
def __init__(self, f):
self.f = f
def filter(self, message):
return not self.f(message)
def filter(self, update):
return not bool(self.f(update))
def __repr__(self):
return "<inverted {}>".format(self.f)
@ -120,17 +137,60 @@ class MergedFilter(BaseFilter):
or_filter: Optional filter to "or" with base_filter. Mutually exclusive with and_filter.
"""
update_filter = True
def __init__(self, base_filter, and_filter=None, or_filter=None):
self.base_filter = base_filter
if self.base_filter.data_filter:
self.data_filter = True
self.and_filter = and_filter
if (self.and_filter
and not isinstance(self.and_filter, bool)
and self.and_filter.data_filter):
self.data_filter = True
self.or_filter = or_filter
if (self.or_filter
and not isinstance(self.and_filter, bool)
and self.or_filter.data_filter):
self.data_filter = True
def filter(self, message):
def _merge(self, base_output, comp_output):
base = base_output if isinstance(base_output, dict) else {}
comp = comp_output if isinstance(comp_output, dict) else {}
for k in comp.keys():
# Make sure comp values are lists
comp_value = comp[k] if isinstance(comp[k], list) else []
try:
# If base is a list then merge
if isinstance(base[k], list):
base[k] += comp_value
else:
base[k] = [base[k]] + comp_value
except KeyError:
base[k] = comp_value
return base
def filter(self, update):
base_output = self.base_filter(update)
# We need to check if the filters are data filters and if so return the merged data.
# If it's not a data filter or an or_filter but no matches return bool
if self.and_filter:
return self.base_filter(message) and self.and_filter(message)
comp_output = self.and_filter(update)
if base_output and comp_output:
if self.data_filter:
merged = self._merge(base_output, comp_output)
if merged:
return merged
return True
elif self.or_filter:
return self.base_filter(message) or self.or_filter(message)
comp_output = self.or_filter(update)
if base_output or comp_output:
if self.data_filter:
merged = self._merge(base_output, comp_output)
if merged:
return merged
return True
return False
def __repr__(self):
return "<{} {} {}>".format(self.base_filter, "and" if self.and_filter else "or",
@ -180,11 +240,7 @@ class Filters(object):
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 `Github Issue
<https://github.com/python-telegram-bot/python-telegram-bot/issues/835/>`_).
To get the groups and groupdict matched, see :attr:`telegram.ext.CallbackContext.matches`.
Examples:
Use ``MessageHandler(Filters.regex(r'help'), callback)`` to capture all messages that
@ -193,25 +249,25 @@ class Filters(object):
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.
"""
data_filter = True
def __init__(self, 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``."""
"""""" # remove method from docs
if message.text:
return bool(self.pattern.search(message.text))
return False
match = self.pattern.search(message.text)
if match:
return {'matches': [match]}
return {}
class _Reply(BaseFilter):
name = 'Filters.reply'
@ -257,6 +313,7 @@ class Filters(object):
self.name = "Filters.document.category('{}')".format(self.category)
def filter(self, message):
"""""" # remove method from docs
if message.document:
return message.document.mime_type.startswith(self.category)
@ -288,6 +345,7 @@ class Filters(object):
self.name = "Filters.document.mime_type('{}')".format(self.mimetype)
def filter(self, message):
"""""" # remove method from docs
if message.document:
return message.document.mime_type == self.mimetype
@ -402,6 +460,7 @@ class Filters(object):
``Filters.status_update`` for all status update messages.
"""
update_filter = True
class _NewChatMembers(BaseFilter):
name = 'Filters.status_update.new_chat_members'
@ -563,6 +622,7 @@ class Filters(object):
self.name = 'Filters.entity({})'.format(self.entity_type)
def filter(self, message):
"""""" # remove method from docs
return any(entity.type == self.entity_type for entity in message.entities)
class caption_entity(BaseFilter):
@ -584,6 +644,7 @@ class Filters(object):
self.name = 'Filters.caption_entity({})'.format(self.entity_type)
def filter(self, message):
"""""" # remove method from docs
return any(entity.type == self.entity_type for entity in message.caption_entities)
class _Private(BaseFilter):
@ -635,6 +696,7 @@ class Filters(object):
self.usernames = [user.replace('@', '') for user in username]
def filter(self, message):
"""""" # remove method from docs
if self.user_ids is not None:
return bool(message.from_user and message.from_user.id in self.user_ids)
else:
@ -673,6 +735,7 @@ class Filters(object):
self.usernames = [chat.replace('@', '') for chat in username]
def filter(self, message):
"""""" # remove method from docs
if self.chat_ids is not None:
return bool(message.chat_id in self.chat_ids)
else:
@ -730,5 +793,80 @@ class Filters(object):
self.name = 'Filters.language({})'.format(self.lang)
def filter(self, message):
"""""" # remove method from docs
return message.from_user.language_code and any(
[message.from_user.language_code.startswith(x) for x in self.lang])
class _UpdateType(BaseFilter):
update_filter = True
class _Message(BaseFilter):
update_filter = True
def filter(self, update):
return update.message is not None
message = _Message()
class _EditedMessage(BaseFilter):
update_filter = True
def filter(self, update):
return update.edited_message is not None
edited_message = _EditedMessage()
class _Messages(BaseFilter):
update_filter = True
def filter(self, update):
return update.message is not None or update.edited_message is not None
messages = _Messages()
class _ChannelPost(BaseFilter):
update_filter = True
def filter(self, update):
return update.channel_post is not None
channel_post = _ChannelPost()
class _EditedChannelPost(BaseFilter):
update_filter = True
def filter(self, update):
return update.edited_channel_post is not None
edited_channel_post = _EditedChannelPost()
class _ChannelPosts(BaseFilter):
update_filter = True
def filter(self, update):
return update.channel_post is not None or update.edited_channel_post is not None
channel_posts = _ChannelPosts()
def filter(self, update):
return self.messages(update) or self.channel_posts(update)
update = _UpdateType()
"""Subset for filtering the type of update.
Examples:
Use these filters like: ``Filters.update.message`` or
``Filters.update.channel_posts`` etc. Or use just ``Filters.update`` for all
types.
Attributes:
message (:obj:`Filter`): Updates with :attr:`telegram.Update.message`
edited_message (:obj:`Filter`): Updates with :attr:`telegram.Update.edited_message`
messages (:obj:`Filter`): Updates with either :attr:`telegram.Update.message` or
:attr:`telegram.Update.edited_message`
channel_post (:obj:`Filter`): Updates with :attr:`telegram.Update.channel_post`
edited_channel_post (:obj:`Filter`): Updates with
:attr:`telegram.Update.edited_channel_post`
channel_posts (:obj:`Filter`): Updates with either :attr:`telegram.Update.channel_post` or
:attr:`telegram.Update.edited_channel_post`
"""

View file

@ -41,7 +41,7 @@ class Handler(object):
or in the same chat, it will be the same ``dict``.
Note that this is DEPRECATED, and you should use context based callbacks. See
https://git.io/vp113 for more info.
https://git.io/fxJuV for more info.
Args:
callback (:obj:`callable`): The callback function for this handler. Will be called when
@ -139,7 +139,7 @@ class Handler(object):
it should subclass this method, but remember to call this super method.
DEPRECATED: This method is being replaced by new context based callbacks. Please see
https://git.io/vp113 for more info.
https://git.io/fxJuV for more info.
Args:
dispatcher (:class:`telegram.ext.Dispatcher`): The dispatcher.

View file

@ -54,7 +54,7 @@ class InlineQueryHandler(Handler):
or in the same chat, it will be the same ``dict``.
Note that this is DEPRECATED, and you should use context based callbacks. See
https://git.io/vp113 for more info.
https://git.io/fxJuV for more info.
Args:
callback (:obj:`callable`): The callback function for this handler. Will be called when
@ -151,4 +151,4 @@ class InlineQueryHandler(Handler):
def collect_additional_context(self, context, update, dispatcher, check_result):
if self.pattern:
context.match = check_result
context.matches = [check_result]

View file

@ -20,7 +20,10 @@
"""This module contains the MessageHandler class."""
import warnings
from telegram.utils.deprecate import TelegramDeprecationWarning
from telegram import Update
from telegram.ext import Filters
from .handler import Handler
@ -40,13 +43,11 @@ class MessageHandler(Handler):
pass_chat_data (:obj:`bool`): Determines whether ``chat_data`` will be passed to
the callback function.
message_updates (:obj:`bool`): Should "normal" message updates be handled?
Default is ``True``.
Default is ``None``.
channel_post_updates (:obj:`bool`): Should channel posts updates be handled?
Default is ``True``.
Default is ``None``.
edited_updates (:obj:`bool`): Should "edited" message updates be handled?
Default is ``False``.
allow_edited (:obj:`bool`): If the handler should also accept edited messages.
Default is ``False`` - Deprecated. use edited_updates instead.
Default is ``None``.
Note:
:attr:`pass_user_data` and :attr:`pass_chat_data` determine whether a ``dict`` you
@ -55,13 +56,17 @@ class MessageHandler(Handler):
or in the same chat, it will be the same ``dict``.
Note that this is DEPRECATED, and you should use context based callbacks. See
https://git.io/vp113 for more info.
https://git.io/fxJuV for more info.
Args:
filters (:class:`telegram.ext.BaseFilter`, optional): A filter inheriting from
:class:`telegram.ext.filters.BaseFilter`. Standard filters can be found in
:class:`telegram.ext.filters.Filters`. Filters can be combined using bitwise
operators (& for and, | for or, ~ for not).
operators (& for and, | for or, ~ for not). Default is
:attr:`telegram.ext.filters.Filters.update`. This defaults to all message_type updates
being: ``message``, ``edited_message``, ``channel_post`` and ``edited_channel_post``.
If you don't want or need any of those pass ``~Filters.update.*`` in the filter
argument.
callback (:obj:`callable`): The callback function for this handler. Will be called when
:attr:`check_update` has determined that an update should be processed by this handler.
Callback signature for context based API:
@ -87,13 +92,14 @@ class MessageHandler(Handler):
``chat_data`` will be passed to the callback function. Default is ``False``.
DEPRECATED: Please switch to context based callbacks.
message_updates (:obj:`bool`, optional): Should "normal" message updates be handled?
Default is ``True``.
Default is ``None``.
DEPRECATED: Please switch to filters for update filtering.
channel_post_updates (:obj:`bool`, optional): Should channel posts updates be handled?
Default is ``True``.
Default is ``None``.
DEPRECATED: Please switch to filters for update filtering.
edited_updates (:obj:`bool`, optional): Should "edited" message updates be handled? Default
is ``False``.
allow_edited (:obj:`bool`, optional): If the handler should also accept edited messages.
Default is ``False`` - Deprecated. use edited_updates instead.
is ``None``.
DEPRECATED: Please switch to filters for update filtering.
Raises:
ValueError
@ -103,20 +109,13 @@ class MessageHandler(Handler):
def __init__(self,
filters,
callback,
allow_edited=False,
pass_update_queue=False,
pass_job_queue=False,
pass_user_data=False,
pass_chat_data=False,
message_updates=True,
channel_post_updates=True,
edited_updates=False):
if not message_updates and not channel_post_updates and not edited_updates:
raise ValueError(
'message_updates, channel_post_updates and edited_updates are all False')
if allow_edited:
warnings.warn('allow_edited is getting deprecated, please use edited_updates instead')
edited_updates = allow_edited
message_updates=None,
channel_post_updates=None,
edited_updates=None):
super(MessageHandler, self).__init__(
callback,
@ -124,15 +123,36 @@ class MessageHandler(Handler):
pass_job_queue=pass_job_queue,
pass_user_data=pass_user_data,
pass_chat_data=pass_chat_data)
if message_updates is False and channel_post_updates is False and edited_updates is False:
raise ValueError(
'message_updates, channel_post_updates and edited_updates are all False')
self.filters = filters
self.message_updates = message_updates
self.channel_post_updates = channel_post_updates
self.edited_updates = edited_updates
if self.filters is not None:
self.filters &= Filters.update
else:
self.filters = Filters.update
if message_updates is not None:
warnings.warn('message_updates is deprecated. See https://git.io/fxJuV for more info',
TelegramDeprecationWarning,
stacklevel=2)
if message_updates is False:
self.filters &= ~Filters.update.message
def _is_allowed_update(self, update):
return any([self.message_updates and update.message,
self.edited_updates and (update.edited_message or update.edited_channel_post),
self.channel_post_updates and update.channel_post])
if channel_post_updates is not None:
warnings.warn('channel_post_updates is deprecated. See https://git.io/fxJuV '
'for more info',
TelegramDeprecationWarning,
stacklevel=2)
if channel_post_updates is False:
self.filters &= ~Filters.update.channel_post
if edited_updates is not None:
warnings.warn('edited_updates is deprecated. See https://git.io/fxJuV for more info',
TelegramDeprecationWarning,
stacklevel=2)
if edited_updates is False:
self.filters &= ~(Filters.update.edited_message
| Filters.update.edited_channel_post)
def check_update(self, update):
"""Determines whether an update should be passed to this handlers :attr:`callback`.
@ -144,8 +164,9 @@ class MessageHandler(Handler):
:obj:`bool`
"""
if isinstance(update, Update) and self._is_allowed_update(update):
if not self.filters:
return True
else:
return self.filters(update.effective_message)
if isinstance(update, Update) and update.effective_message:
return self.filters(update)
def collect_additional_context(self, context, update, dispatcher, check_result):
if isinstance(check_result, dict):
context.update(check_result)

View file

@ -43,7 +43,7 @@ class PreCheckoutQueryHandler(Handler):
or in the same chat, it will be the same ``dict``.
Note that this is DEPRECATED, and you should use context based callbacks. See
https://git.io/vp113 for more info.
https://git.io/fxJuV for more info.
Args:
callback (:obj:`callable`): The callback function for this handler. Will be called when

View file

@ -19,16 +19,14 @@
# TODO: Remove allow_edited
"""This module contains the RegexHandler class."""
import re
import warnings
from future.utils import string_types
from telegram.utils.deprecate import TelegramDeprecationWarning
from telegram import Update
from .handler import Handler
from telegram.ext import MessageHandler, Filters
class RegexHandler(Handler):
class RegexHandler(MessageHandler):
"""Handler class to handle Telegram updates based on a regex.
It uses a regular expression to check text messages. Read the documentation of the ``re``
@ -52,13 +50,9 @@ class RegexHandler(Handler):
the callback function.
Note:
:attr:`pass_user_data` and :attr:`pass_chat_data` determine whether a ``dict`` you
can use to keep any data in will be sent to the :attr:`callback` function. Related to
either the user or the chat that the update was sent in. For each update from the same user
or in the same chat, it will be the same ``dict``.
This handler is being deprecated. For the same usecase use:
``MessageHandler(Filters.regex(r'pattern'), callback)``
Note that this is DEPRECATED, and you should use context based callbacks. See
https://git.io/vp113 for more info.
Args:
pattern (:obj:`str` | :obj:`Pattern`): The regex pattern.
@ -73,35 +67,27 @@ class RegexHandler(Handler):
pass_groups (:obj:`bool`, optional): If the callback should be passed the result of
``re.match(pattern, data).groups()`` as a keyword argument called ``groups``.
Default is ``False``
DEPRECATED: Please switch to context based callbacks.
pass_groupdict (:obj:`bool`, optional): If the callback should be passed the result of
``re.match(pattern, data).groupdict()`` as a keyword argument called ``groupdict``.
Default is ``False``
DEPRECATED: Please switch to context based callbacks.
pass_update_queue (:obj:`bool`, optional): If set to ``True``, a keyword argument called
``update_queue`` will be passed to the callback function. It will be the ``Queue``
instance used by the :class:`telegram.ext.Updater` and :class:`telegram.ext.Dispatcher`
that contains new updates which can be used to insert updates. Default is ``False``.
DEPRECATED: Please switch to context based callbacks.
pass_job_queue (:obj:`bool`, optional): If set to ``True``, a keyword argument called
``job_queue`` will be passed to the callback function. It will be a
:class:`telegram.ext.JobQueue` instance created by the :class:`telegram.ext.Updater`
which can be used to schedule new jobs. Default is ``False``.
DEPRECATED: Please switch to context based callbacks.
pass_user_data (:obj:`bool`, optional): If set to ``True``, a keyword argument called
``user_data`` will be passed to the callback function. Default is ``False``.
DEPRECATED: Please switch to context based callbacks.
pass_chat_data (:obj:`bool`, optional): If set to ``True``, a keyword argument called
``chat_data`` will be passed to the callback function. Default is ``False``.
DEPRECATED: Please switch to context based callbacks.
message_updates (:obj:`bool`, optional): Should "normal" message updates be handled?
Default is ``True``.
channel_post_updates (:obj:`bool`, optional): Should channel posts updates be handled?
Default is ``True``.
edited_updates (:obj:`bool`, optional): Should "edited" message updates be handled? Default
is ``False``.
allow_edited (:obj:`bool`, optional): If the handler should also accept edited messages.
Default is ``False`` - Deprecated. use edited_updates instead.
Raises:
ValueError
@ -121,59 +107,26 @@ class RegexHandler(Handler):
message_updates=True,
channel_post_updates=False,
edited_updates=False):
if not message_updates and not channel_post_updates and not edited_updates:
raise ValueError(
'message_updates, channel_post_updates and edited_updates are all False')
if allow_edited:
warnings.warn('allow_edited is getting deprecated, please use edited_updates instead')
edited_updates = allow_edited
super(RegexHandler, self).__init__(
callback,
pass_update_queue=pass_update_queue,
pass_job_queue=pass_job_queue,
pass_user_data=pass_user_data,
pass_chat_data=pass_chat_data)
if isinstance(pattern, string_types):
pattern = re.compile(pattern)
self.pattern = pattern
warnings.warn('RegexHandler is deprecated. See https://git.io/fxJuV for more info',
TelegramDeprecationWarning,
stacklevel=2)
super(RegexHandler, self).__init__(Filters.regex(pattern),
callback,
pass_update_queue=pass_update_queue,
pass_job_queue=pass_job_queue,
pass_user_data=pass_user_data,
pass_chat_data=pass_chat_data,
message_updates=message_updates,
channel_post_updates=channel_post_updates,
edited_updates=edited_updates)
self.pass_groups = pass_groups
self.pass_groupdict = pass_groupdict
self.allow_edited = allow_edited
self.message_updates = message_updates
self.channel_post_updates = channel_post_updates
self.edited_updates = edited_updates
def check_update(self, update):
"""Determines whether an update should be passed to this handlers :attr:`callback`.
Args:
update (:class:`telegram.Update`): Incoming telegram update.
Returns:
:obj:`bool`
"""
if not isinstance(update, Update) and not update.effective_message:
return None
if any([self.message_updates and update.message,
self.edited_updates and (update.edited_message or update.edited_channel_post),
self.channel_post_updates and update.channel_post]) and \
update.effective_message.text:
match = re.match(self.pattern, update.effective_message.text)
if match:
return match
def collect_optional_args(self, dispatcher, update=None, check_result=None):
optional_args = super(RegexHandler, self).collect_optional_args(dispatcher, update,
check_result)
if self.pass_groups:
optional_args['groups'] = check_result.groups()
optional_args['groups'] = check_result['matches'][0].groups()
if self.pass_groupdict:
optional_args['groupdict'] = check_result.groupdict()
optional_args['groupdict'] = check_result['matches'][0].groupdict()
return optional_args
def collect_additional_context(self, context, update, dispatcher, check_result):
context.match = check_result

View file

@ -43,7 +43,7 @@ class ShippingQueryHandler(Handler):
or in the same chat, it will be the same ``dict``.
Note that this is DEPRECATED, and you should use context based callbacks. See
https://git.io/vp113 for more info.
https://git.io/fxJuV for more info.
Args:
callback (:obj:`callable`): The callback function for this handler. Will be called when

View file

@ -124,4 +124,4 @@ class StringRegexHandler(Handler):
def collect_additional_context(self, context, update, dispatcher, check_result):
if self.pattern:
context.match = check_result
context.matches = [check_result]

View file

@ -16,6 +16,8 @@
#
# 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 os
import sys
import time
from datetime import datetime
from platform import python_implementation
@ -313,6 +315,8 @@ class TestBot(object):
@flaky(3, 1)
@pytest.mark.timeout(15)
@pytest.mark.xfail
@pytest.mark.skipif(os.getenv('APPVEYOR') and (sys.version_info < (3, 6)),
reason='only run on 3.6 on appveyor')
def test_set_webhook_get_webhook_info_and_delete_webhook(self, bot):
url = 'https://python-telegram-bot.org/test/webhook'
max_connections = 7

View file

@ -96,3 +96,14 @@ class TestCallbackContext(object):
assert callback_context.bot is cdp.bot
assert callback_context.job_queue is cdp.job_queue
assert callback_context.update_queue is cdp.update_queue
def test_match(self, cdp):
callback_context = CallbackContext(cdp)
assert callback_context.match is None
callback_context.matches = ['test', 'blah']
assert callback_context.match == 'test'

View file

@ -93,10 +93,10 @@ class TestCallbackQueryHandler(object):
and isinstance(update.callback_query, CallbackQuery))
def callback_context_pattern(self, update, context):
if context.match.groups():
self.test_flag = context.match.groups() == ('t', ' data')
if context.match.groupdict():
self.test_flag = context.match.groupdict() == {'begin': 't', 'end': ' data'}
if context.matches[0].groups():
self.test_flag = context.matches[0].groups() == ('t', ' data')
if context.matches[0].groupdict():
self.test_flag = context.matches[0].groupdict() == {'begin': 't', 'end': ' data'}
def test_basic(self, dp, callback_query):
handler = CallbackQueryHandler(self.callback_basic)

View file

@ -17,9 +17,11 @@
#
# 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 re
from queue import Queue
import pytest
from telegram.utils.deprecate import TelegramDeprecationWarning
from telegram import (Message, Update, Chat, Bot, User, CallbackQuery, InlineQuery,
ChosenInlineResult, ShippingQuery, PreCheckoutQuery, MessageEntity)
@ -64,6 +66,7 @@ def message(bot):
class TestCommandHandler(object):
test_flag = False
SRE_TYPE = type(re.match("", ""))
@pytest.fixture(autouse=True)
def reset(self):
@ -107,6 +110,18 @@ class TestCommandHandler(object):
def callback_context_args(self, update, context):
self.test_flag = context.args == ['one', 'two']
def callback_context_regex1(self, update, context):
if context.matches:
types = all([type(res) == self.SRE_TYPE for res in context.matches])
num = len(context.matches) == 1
self.test_flag = types and num
def callback_context_regex2(self, update, context):
if context.matches:
types = all([type(res) == self.SRE_TYPE for res in context.matches])
num = len(context.matches) == 2
self.test_flag = types and num
def test_basic(self, dp, message):
handler = CommandHandler('test', self.callback_basic)
dp.add_handler(handler)
@ -153,10 +168,30 @@ class TestCommandHandler(object):
check = handler.check_update(Update(0, message))
assert check is None or check is False
def test_edited(self, message):
def test_deprecation_warning(self):
with pytest.warns(TelegramDeprecationWarning, match='See https://git.io/fxJuV'):
CommandHandler('test', self.callback_basic, allow_edited=True)
def test_no_edited(self, message):
handler = CommandHandler('test', self.callback_basic)
message.text = '/test'
check = handler.check_update(Update(0, message))
assert check is not None and check is not False
check = handler.check_update(Update(0, edited_message=message))
assert check is not None and check is not False
handler = CommandHandler('test', self.callback_basic,
filters=~Filters.update.edited_message)
check = handler.check_update(Update(0, message))
assert check is not None and check is not False
check = handler.check_update(Update(0, edited_message=message))
assert check is None or check is False
def test_edited_deprecated(self, message):
handler = CommandHandler('test', self.callback_basic,
allow_edited=False)
message.text = '/test'
check = handler.check_update(Update(0, message))
assert check is not None and check is not False
@ -164,7 +199,8 @@ class TestCommandHandler(object):
check = handler.check_update(Update(0, edited_message=message))
assert check is None or check is False
handler.allow_edited = True
handler = CommandHandler('test', self.callback_basic,
allow_edited=True)
check = handler.check_update(Update(0, message))
assert check is not None and check is not False
@ -333,6 +369,31 @@ class TestCommandHandler(object):
cdp.process_update(Update(0, message))
assert self.test_flag
def test_context_regex(self, cdp, message):
handler = CommandHandler('test', self.callback_context_regex1, Filters.regex('one two'))
cdp.add_handler(handler)
message.text = '/test'
cdp.process_update(Update(0, message))
assert not self.test_flag
message.text += ' one two'
cdp.process_update(Update(0, message))
assert self.test_flag
def test_context_multiple_regex(self, cdp, message):
handler = CommandHandler('test', self.callback_context_regex2,
Filters.regex('one') & Filters.regex('two'))
cdp.add_handler(handler)
message.text = '/test'
cdp.process_update(Update(0, message))
assert not self.test_flag
message.text += ' one two'
cdp.process_update(Update(0, message))
assert self.test_flag
par = ['!help', '!test', '#help', '#test', 'mytrig-help', 'mytrig-test']
@ -349,6 +410,7 @@ def prefixmessage(bot, request):
class TestPrefixHandler(object):
test_flag = False
SRE_TYPE = type(re.match("", ""))
@pytest.fixture(autouse=True)
def reset(self):
@ -390,6 +452,18 @@ class TestPrefixHandler(object):
def callback_context_args(self, update, context):
self.test_flag = context.args == ['one', 'two']
def callback_context_regex1(self, update, context):
if context.matches:
types = all([type(res) == self.SRE_TYPE for res in context.matches])
num = len(context.matches) == 1
self.test_flag = types and num
def callback_context_regex2(self, update, context):
if context.matches:
types = all([type(res) == self.SRE_TYPE for res in context.matches])
num = len(context.matches) == 2
self.test_flag = types and num
def test_basic(self, dp, prefixmessage):
handler = PrefixHandler(['!', '#', 'mytrig-'], ['help', 'test'], self.callback_basic)
dp.add_handler(handler)
@ -436,22 +510,22 @@ class TestPrefixHandler(object):
else:
assert check is None or check is False
def test_edited(self, prefixmessage):
def test_no_edited(self, prefixmessage):
handler = PrefixHandler(['!', '#', 'mytrig-'], ['help', 'test'], self.callback_basic)
check = handler.check_update(Update(0, prefixmessage))
assert check is not None and check is not False
check = handler.check_update(Update(0, edited_message=prefixmessage))
assert check is not None and check is not False
handler = PrefixHandler(['!', '#', 'mytrig-'], ['help', 'test'], self.callback_basic,
filters=~Filters.update.edited_message)
check = handler.check_update(Update(0, prefixmessage))
assert check is not None and check is not False
check = handler.check_update(Update(0, edited_message=prefixmessage))
assert check is None or check is False
handler.allow_edited = True
check = handler.check_update(Update(0, prefixmessage))
assert check is not None and check is not False
check = handler.check_update(Update(0, edited_message=prefixmessage))
assert check is not None and check is not False
def test_with_filter(self, prefixmessage):
handler = PrefixHandler(['!', '#', 'mytrig-'], ['help', 'test'], self.callback_basic,
filters=Filters.group)
@ -570,3 +644,28 @@ class TestPrefixHandler(object):
prefixmessage.text += ' one two'
cdp.process_update(Update(0, prefixmessage))
assert self.test_flag
def test_context_regex(self, cdp, prefixmessage):
handler = PrefixHandler(['!', '#', 'mytrig-'], ['help', 'test'],
self.callback_context_regex1, Filters.regex('one two'))
cdp.add_handler(handler)
cdp.process_update(Update(0, prefixmessage))
assert not self.test_flag
prefixmessage.text += ' one two'
cdp.process_update(Update(0, prefixmessage))
assert self.test_flag
def test_context_multiple_regex(self, cdp, prefixmessage):
handler = PrefixHandler(['!', '#', 'mytrig-'], ['help', 'test'],
self.callback_context_regex2,
Filters.regex('one') & Filters.regex('two'))
cdp.add_handler(handler)
cdp.process_update(Update(0, prefixmessage))
assert not self.test_flag
prefixmessage.text += ' one two'
cdp.process_update(Update(0, prefixmessage))
assert self.test_flag

File diff suppressed because it is too large Load diff

View file

@ -97,10 +97,10 @@ class TestCallbackQueryHandler(object):
and isinstance(update.inline_query, InlineQuery))
def callback_context_pattern(self, update, context):
if context.match.groups():
self.test_flag = context.match.groups() == ('t', ' query')
if context.match.groupdict():
self.test_flag = context.match.groupdict() == {'begin': 't', 'end': ' query'}
if context.matches[0].groups():
self.test_flag = context.matches[0].groups() == ('t', ' query')
if context.matches[0].groupdict():
self.test_flag = context.matches[0].groupdict() == {'begin': 't', 'end': ' query'}
def test_basic(self, dp, inline_query):
handler = InlineQueryHandler(self.callback_basic)

View file

@ -16,9 +16,11 @@
#
# 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 re
from queue import Queue
import pytest
from telegram.utils.deprecate import TelegramDeprecationWarning
from telegram import (Message, Update, Chat, Bot, User, CallbackQuery, InlineQuery,
ChosenInlineResult, ShippingQuery, PreCheckoutQuery)
@ -51,6 +53,7 @@ def message(bot):
class TestMessageHandler(object):
test_flag = False
SRE_TYPE = type(re.match("", ""))
@pytest.fixture(autouse=True)
def reset(self):
@ -82,13 +85,23 @@ class TestMessageHandler(object):
and isinstance(context.chat_data, dict)
and ((isinstance(context.user_data, dict)
and (isinstance(update.message, Message)
or isinstance(update.edited_message, Message))
)
or isinstance(update.edited_message, Message)))
or (context.user_data is None
and (isinstance(update.channel_post, Message)
or isinstance(update.edited_channel_post, Message))
))
)
or isinstance(update.edited_channel_post, Message)))
))
def callback_context_regex1(self, update, context):
if context.matches:
types = all([type(res) == self.SRE_TYPE for res in context.matches])
num = len(context.matches) == 1
self.test_flag = types and num
def callback_context_regex2(self, update, context):
if context.matches:
types = all([type(res) == self.SRE_TYPE for res in context.matches])
num = len(context.matches) == 2
self.test_flag = types and num
def test_basic(self, dp, message):
handler = MessageHandler(None, self.callback_basic)
@ -98,7 +111,15 @@ class TestMessageHandler(object):
dp.process_update(Update(0, message))
assert self.test_flag
def test_edited(self, message):
def test_deprecation_warning(self):
with pytest.warns(TelegramDeprecationWarning, match='See https://git.io/fxJuV'):
MessageHandler(None, self.callback_basic, edited_updates=True)
with pytest.warns(TelegramDeprecationWarning, match='See https://git.io/fxJuV'):
MessageHandler(None, self.callback_basic, message_updates=False)
with pytest.warns(TelegramDeprecationWarning, match='See https://git.io/fxJuV'):
MessageHandler(None, self.callback_basic, channel_post_updates=True)
def test_edited_deprecated(self, message):
handler = MessageHandler(None, self.callback_basic, edited_updates=True,
message_updates=False, channel_post_updates=False)
@ -107,17 +128,16 @@ class TestMessageHandler(object):
assert not handler.check_update(Update(0, channel_post=message))
assert handler.check_update(Update(0, edited_channel_post=message))
def test_channel_post(self, message):
def test_channel_post_deprecated(self, message):
handler = MessageHandler(None, self.callback_basic,
edited_updates=False, message_updates=False,
channel_post_updates=True)
assert not handler.check_update(Update(0, edited_message=message))
assert not handler.check_update(Update(0, message=message))
assert handler.check_update(Update(0, channel_post=message))
assert not handler.check_update(Update(0, edited_channel_post=message))
def test_multiple_flags(self, message):
def test_multiple_flags_deprecated(self, message):
handler = MessageHandler(None, self.callback_basic, edited_updates=True,
message_updates=True, channel_post_updates=True)
@ -126,31 +146,31 @@ class TestMessageHandler(object):
assert handler.check_update(Update(0, channel_post=message))
assert handler.check_update(Update(0, edited_channel_post=message))
def test_allow_edited(self, message):
with pytest.warns(UserWarning):
handler = MessageHandler(None, self.callback_basic,
message_updates=True, allow_edited=True,
channel_post_updates=False)
assert handler.check_update(Update(0, edited_message=message))
assert handler.check_update(Update(0, message=message))
assert not handler.check_update(Update(0, channel_post=message))
assert handler.check_update(Update(0, edited_channel_post=message))
def test_none_allowed(self):
def test_none_allowed_deprecated(self):
with pytest.raises(ValueError, match='are all False'):
MessageHandler(None, self.callback_basic, message_updates=False,
channel_post_updates=False, edited_updates=False)
def test_with_filter(self, message):
handler = MessageHandler(Filters.command, self.callback_basic)
handler = MessageHandler(Filters.group, self.callback_basic)
message.text = '/test'
message.chat.type = 'group'
assert handler.check_update(Update(0, message))
message.text = 'test'
message.chat.type = 'private'
assert not handler.check_update(Update(0, message))
def test_specific_filters(self, message):
f = (~Filters.update.messages
& ~Filters.update.channel_post
& Filters.update.edited_channel_post)
handler = MessageHandler(f, self.callback_basic)
assert not handler.check_update(Update(0, edited_message=message))
assert not handler.check_update(Update(0, message=message))
assert not handler.check_update(Update(0, channel_post=message))
assert handler.check_update(Update(0, edited_channel_post=message))
def test_pass_user_or_chat_data(self, dp, message):
handler = MessageHandler(None, self.callback_data_1,
pass_user_data=True)
@ -226,3 +246,28 @@ class TestMessageHandler(object):
self.test_flag = False
cdp.process_update(Update(0, edited_channel_post=message))
assert self.test_flag
def test_context_regex(self, cdp, message):
handler = MessageHandler(Filters.regex('one two'), self.callback_context_regex1)
cdp.add_handler(handler)
message.text = 'not it'
cdp.process_update(Update(0, message))
assert not self.test_flag
message.text += ' one two now it is'
cdp.process_update(Update(0, message))
assert self.test_flag
def test_context_multiple_regex(self, cdp, message):
handler = MessageHandler(Filters.regex('one') & Filters.regex('two'),
self.callback_context_regex2)
cdp.add_handler(handler)
message.text = 'not it'
cdp.process_update(Update(0, message))
assert not self.test_flag
message.text += ' one two now it is'
cdp.process_update(Update(0, message))
assert self.test_flag

View file

@ -19,6 +19,7 @@
from queue import Queue
import pytest
from telegram.utils.deprecate import TelegramDeprecationWarning
from telegram import (Message, Update, Chat, Bot, User, CallbackQuery, InlineQuery,
ChosenInlineResult, ShippingQuery, PreCheckoutQuery)
@ -90,10 +91,14 @@ class TestRegexHandler(object):
and isinstance(update.message, Message))
def callback_context_pattern(self, update, context):
if context.match.groups():
self.test_flag = context.match.groups() == ('t', ' message')
if context.match.groupdict():
self.test_flag = context.match.groupdict() == {'begin': 't', 'end': ' message'}
if context.matches[0].groups():
self.test_flag = context.matches[0].groups() == ('t', ' message')
if context.matches[0].groupdict():
self.test_flag = context.matches[0].groupdict() == {'begin': 't', 'end': ' message'}
def test_deprecation_Warning(self):
with pytest.warns(TelegramDeprecationWarning, match='RegexHandler is deprecated.'):
RegexHandler('.*', self.callback_basic)
def test_basic(self, dp, message):
handler = RegexHandler('.*', self.callback_basic)
@ -115,7 +120,6 @@ class TestRegexHandler(object):
handler = RegexHandler('(?P<begin>.*)est(?P<end>.*)', self.callback_group,
pass_groups=True)
dp.add_handler(handler)
dp.process_update(Update(0, message))
assert self.test_flag
@ -155,17 +159,6 @@ class TestRegexHandler(object):
assert handler.check_update(Update(0, channel_post=message))
assert handler.check_update(Update(0, edited_channel_post=message))
def test_allow_edited(self, message):
with pytest.warns(UserWarning):
handler = RegexHandler('.*', self.callback_basic,
message_updates=True,
allow_edited=True)
assert handler.check_update(Update(0, edited_message=message))
assert handler.check_update(Update(0, message=message))
assert not handler.check_update(Update(0, channel_post=message))
assert handler.check_update(Update(0, edited_channel_post=message))
def test_none_allowed(self):
with pytest.raises(ValueError, match='are all False'):
RegexHandler('.*', self.callback_basic, message_updates=False,

View file

@ -81,10 +81,10 @@ class TestStringRegexHandler(object):
and isinstance(context.job_queue, JobQueue))
def callback_context_pattern(self, update, context):
if context.match.groups():
self.test_flag = context.match.groups() == ('t', ' message')
if context.match.groupdict():
self.test_flag = context.match.groupdict() == {'begin': 't', 'end': ' message'}
if context.matches[0].groups():
self.test_flag = context.matches[0].groups() == ('t', ' message')
if context.matches[0].groupdict():
self.test_flag = context.matches[0].groupdict() == {'begin': 't', 'end': ' message'}
def test_basic(self, dp):
handler = StringRegexHandler('(?P<begin>.*)est(?P<end>.*)', self.callback_basic)