Docs for filters (Not fully)

This commit is contained in:
Alex RootJunior 2019-04-21 01:16:42 +03:00
parent 0f53e5fbd7
commit 2af930149c
6 changed files with 263 additions and 25 deletions

View file

@ -38,5 +38,5 @@ __all__ = [
'utils' 'utils'
] ]
__version__ = '2.1' __version__ = '2.1.1.dev1'
__api_version__ = '4.2' __api_version__ = '4.2'

View file

@ -10,12 +10,15 @@ from babel.support import LazyProxy
from aiogram import types from aiogram import types
from aiogram.dispatcher.filters.filters import BoundFilter, Filter from aiogram.dispatcher.filters.filters import BoundFilter, Filter
from aiogram.types import CallbackQuery, Message, InlineQuery, Poll from aiogram.types import CallbackQuery, Message, InlineQuery, Poll
from aiogram.utils.deprecated import warn_deprecated
class Command(Filter): class Command(Filter):
""" """
You can handle commands by using this filter You can handle commands by using this filter.
If filter is successful processed the :obj:`Command.CommandObj` will be passed to the handler arguments.
By default this filter is registered for messages and edited messages handlers.
""" """
def __init__(self, commands: Union[Iterable, str], def __init__(self, commands: Union[Iterable, str],
@ -23,12 +26,22 @@ class Command(Filter):
ignore_case: bool = True, ignore_case: bool = True,
ignore_mention: bool = False): ignore_mention: bool = False):
""" """
Filter can be initialized from filters factory or by simply creating instance of this class Filter can be initialized from filters factory or by simply creating instance of this class.
:param commands: command or list of commands Examples:
:param prefixes:
:param ignore_case: .. code-block:: python
:param ignore_mention:
@dp.message_handler(commands=['myCommand'])
@dp.message_handler(Command(['myCommand']))
@dp.message_handler(commands=['myCommand'], commands_prefix='!/')
:param commands: Command or list of commands always without leading slashes (prefix)
:param prefixes: Allowed commands prefix. By default is slash.
If you change the default behavior pass the list of prefixes to this argument.
:param ignore_case: Ignore case of the command
:param ignore_mention: Ignore mention in command
(By default this filter pass only the commands addressed to current bot)
""" """
if isinstance(commands, str): if isinstance(commands, str):
commands = (commands,) commands = (commands,)
@ -43,15 +56,21 @@ class Command(Filter):
""" """
Validator for filters factory Validator for filters factory
From filters factory this filter can be registered with arguments:
- ``command``
- ``commands_prefix`` (will be passed as ``prefixes``)
- ``commands_ignore_mention`` (will be passed as ``ignore_mention``
:param full_config: :param full_config:
:return: config or empty dict :return: config or empty dict
""" """
config = {} config = {}
if 'commands' in full_config: if 'commands' in full_config:
config['commands'] = full_config.pop('commands') config['commands'] = full_config.pop('commands')
if 'commands_prefix' in full_config: if config and 'commands_prefix' in full_config:
config['prefixes'] = full_config.pop('commands_prefix') config['prefixes'] = full_config.pop('commands_prefix')
if 'commands_ignore_mention' in full_config: if config and 'commands_ignore_mention' in full_config:
config['ignore_mention'] = full_config.pop('commands_ignore_mention') config['ignore_mention'] = full_config.pop('commands_ignore_mention')
return config return config
@ -74,17 +93,37 @@ class Command(Filter):
@dataclass @dataclass
class CommandObj: class CommandObj:
"""
Instance of this object is always has command and it prefix.
Can be passed as keyword argument ``command`` to the handler
"""
"""Command prefix"""
prefix: str = '/' prefix: str = '/'
"""Command without prefix and mention"""
command: str = '' command: str = ''
"""Mention (if available)"""
mention: str = None mention: str = None
"""Command argument"""
args: str = field(repr=False, default=None) args: str = field(repr=False, default=None)
@property @property
def mentioned(self) -> bool: def mentioned(self) -> bool:
"""
This command has mention?
:return:
"""
return bool(self.mention) return bool(self.mention)
@property @property
def text(self) -> str: def text(self) -> str:
"""
Generate original text from object
:return:
"""
line = self.prefix + self.command line = self.prefix + self.command
if self.mentioned: if self.mentioned:
line += '@' + self.mention line += '@' + self.mention
@ -94,11 +133,32 @@ class Command(Filter):
class CommandStart(Command): class CommandStart(Command):
"""
This filter based on :obj:`Command` filter but can handle only ``/start`` command.
"""
def __init__(self, deep_link: typing.Optional[typing.Union[str, re.Pattern]] = None): def __init__(self, deep_link: typing.Optional[typing.Union[str, re.Pattern]] = None):
"""
Also this filter can handle `deep-linking <https://core.telegram.org/bots#deep-linking>`_ arguments.
Example:
.. code-block:: python
@dp.message_handler(CommandStart(re.compile(r'ref-([\\d]+)')))
:param deep_link: string or compiled regular expression (by ``re.compile(...)``).
"""
super(CommandStart, self).__init__(['start']) super(CommandStart, self).__init__(['start'])
self.deep_link = deep_link self.deep_link = deep_link
async def check(self, message: types.Message): async def check(self, message: types.Message):
"""
If deep-linking is passed to the filter result of the matching will be passed as ``deep_link`` to the handler
:param message:
:return:
"""
check = await super(CommandStart, self).check(message) check = await super(CommandStart, self).check(message)
if check and self.deep_link is not None: if check and self.deep_link is not None:
@ -114,16 +174,28 @@ class CommandStart(Command):
class CommandHelp(Command): class CommandHelp(Command):
"""
This filter based on :obj:`Command` filter but can handle only ``/help`` command.
"""
def __init__(self): def __init__(self):
super(CommandHelp, self).__init__(['help']) super(CommandHelp, self).__init__(['help'])
class CommandSettings(Command): class CommandSettings(Command):
"""
This filter based on :obj:`Command` filter but can handle only ``/settings`` command.
"""
def __init__(self): def __init__(self):
super(CommandSettings, self).__init__(['settings']) super(CommandSettings, self).__init__(['settings'])
class CommandPrivacy(Command): class CommandPrivacy(Command):
"""
This filter based on :obj:`Command` filter but can handle only ``/privacy`` command.
"""
def __init__(self): def __init__(self):
super(CommandPrivacy, self).__init__(['privacy']) super(CommandPrivacy, self).__init__(['privacy'])

View file

@ -6,7 +6,7 @@ from ..handler import Handler
class FiltersFactory: class FiltersFactory:
""" """
Default filters factory Filters factory
""" """
def __init__(self, dispatcher): def __init__(self, dispatcher):

View file

@ -128,24 +128,29 @@ class FilterRecord:
class AbstractFilter(abc.ABC): class AbstractFilter(abc.ABC):
""" """
Abstract class for custom filters Abstract class for custom filters.
""" """
@classmethod @classmethod
@abc.abstractmethod @abc.abstractmethod
def validate(cls, full_config: typing.Dict[str, typing.Any]) -> typing.Optional[typing.Dict[str, typing.Any]]: def validate(cls, full_config: typing.Dict[str, typing.Any]) -> typing.Optional[typing.Dict[str, typing.Any]]:
""" """
Validate and parse config Validate and parse config.
:param full_config: This method will be called by the filters factory when you bind this filter.
:return: config Must be overridden.
:param full_config: dict with arguments passed to handler registrar
:return: Current filter config
""" """
pass pass
@abc.abstractmethod @abc.abstractmethod
async def check(self, *args) -> bool: async def check(self, *args) -> bool:
""" """
Check object Will be called when filters checks.
This method must be overridden.
:param args: :param args:
:return: :return:
@ -173,24 +178,46 @@ class AbstractFilter(abc.ABC):
class Filter(AbstractFilter): class Filter(AbstractFilter):
""" """
You can make subclasses of that class for custom filters You can make subclasses of that class for custom filters.
Method ``check`` must be overridden
""" """
@classmethod @classmethod
def validate(cls, full_config: typing.Dict[str, typing.Any]) -> typing.Optional[typing.Dict[str, typing.Any]]: def validate(cls, full_config: typing.Dict[str, typing.Any]) -> typing.Optional[typing.Dict[str, typing.Any]]:
"""
Here method ``validate`` is optional.
If you need to use filter from filters factory you need to override this method.
:param full_config: dict with arguments passed to handler registrar
:return: Current filter config
"""
pass pass
class BoundFilter(Filter): class BoundFilter(Filter):
""" """
Base class for filters with default validator To easily create your own filters with one parameter, you can inherit from this filter.
You need to implement ``__init__`` method with single argument related with key attribute
and ``check`` method where you need to implement filter logic.
""" """
"""Unique name of the filter argument. You need to override this attribute."""
key = None key = None
"""If :obj:`True` this filter will be added to the all of the registered handlers"""
required = False required = False
"""Default value for configure required filters"""
default = None default = None
@classmethod @classmethod
def validate(cls, full_config: typing.Dict[str, typing.Any]) -> typing.Dict[str, typing.Any]: def validate(cls, full_config: typing.Dict[str, typing.Any]) -> typing.Dict[str, typing.Any]:
"""
If ``cls.key`` is not :obj:`None` and that is in config returns config with that argument.
:param full_config:
:return:
"""
if cls.key is not None: if cls.key is not None:
if cls.key in full_config: if cls.key in full_config:
return {cls.key: full_config[cls.key]} return {cls.key: full_config[cls.key]}

View file

@ -4,16 +4,155 @@ Filters
Basics Basics
====== ======
Coming soon...
Filter factory greatly simplifies the reuse of filters when registering handlers.
Filters factory Filters factory
=============== ===============
Coming soon...
.. autoclass:: aiogram.dispatcher.filters.factory.FiltersFactory
:members:
:show-inheritance:
Builtin filters Builtin filters
=============== ===============
Coming soon... ``aiogram`` has some builtin filters. Here you can see all of them:
Command
-------
.. autoclass:: aiogram.dispatcher.filters.builtin.Command
:members:
:show-inheritance:
CommandStart
------------
.. autoclass:: aiogram.dispatcher.filters.builtin.CommandStart
:members:
:show-inheritance:
CommandHelp
-----------
.. autoclass:: aiogram.dispatcher.filters.builtin.CommandHelp
:members:
:show-inheritance:
CommandSettings
---------------
.. autoclass:: aiogram.dispatcher.filters.builtin.CommandSettings
:members:
:show-inheritance:
CommandPrivacy
--------------
.. autoclass:: aiogram.dispatcher.filters.builtin.CommandPrivacy
:members:
:show-inheritance:
Text
----
.. autoclass:: aiogram.dispatcher.filters.builtin.Text
:members:
:show-inheritance:
HashTag
-------
.. autoclass:: aiogram.dispatcher.filters.builtin.HashTag
:members:
:show-inheritance:
Regexp
------
.. autoclass:: aiogram.dispatcher.filters.builtin.Regexp
:members:
:show-inheritance:
RegexpCommandsFilter
--------------------
.. autoclass:: aiogram.dispatcher.filters.builtin.RegexpCommandsFilter
:members:
:show-inheritance:
ContentTypeFilter
-----------------
.. autoclass:: aiogram.dispatcher.filters.builtin.ContentTypeFilter
:members:
:show-inheritance:
StateFilter
-----------
.. autoclass:: aiogram.dispatcher.filters.builtin.StateFilter
:members:
:show-inheritance:
ExceptionsFilter
----------------
.. autoclass:: aiogram.dispatcher.filters.builtin.ExceptionsFilter
:members:
:show-inheritance:
Making own filters (Custom filters) Making own filters (Custom filters)
=================================== ===================================
Coming soon...
Own filter can be:
- any callable object
- any async function
- any anonymous function (Example: ``lambda msg: msg.text == 'spam'``)
- Subclass of :obj:`AbstractFilter`, :obj:`Filter` or :obj:`BoundFilter`
AbstractFilter
--------------
.. autoclass:: aiogram.dispatcher.filters.filters.AbstractFilter
:members:
:show-inheritance:
Filter
------
.. autoclass:: aiogram.dispatcher.filters.filters.Filter
:members:
:show-inheritance:
BoundFilter
-----------
.. autoclass:: aiogram.dispatcher.filters.filters.BoundFilter
:members:
:show-inheritance:
.. code-block:: python
class ChatIdFilter(BoundFilter):
key = 'chat_id'
def __init__(self, chat_id: typing.Union[typing.Iterable, int]):
if isinstance(chat_id, int):
chat_id = [chat_id]
self.chat_id = chat_id
def check(self, message: types.Message) -> bool:
return message.chat.id in self.chat_id
dp.filters_factory.bind(ChatIdFilter, event_handlers=[dp.message_handlers])

View file

@ -22,19 +22,19 @@ Next step: interaction with bots starts with one command. Register your first co
.. literalinclude:: ../../examples/echo_bot.py .. literalinclude:: ../../examples/echo_bot.py
:language: python :language: python
:lines: 21-25 :lines: 20-25
If you want to handle all messages in the chat simply add handler without filters: If you want to handle all messages in the chat simply add handler without filters:
.. literalinclude:: ../../examples/echo_bot.py .. literalinclude:: ../../examples/echo_bot.py
:language: python :language: python
:lines: 28-30 :lines: 35-37
Last step: run long polling. Last step: run long polling.
.. literalinclude:: ../../examples/echo_bot.py .. literalinclude:: ../../examples/echo_bot.py
:language: python :language: python
:lines: 33-34 :lines: 40-41
Summary Summary
------- -------