From 2af930149ce2482547721e2c8755c10307295e48 Mon Sep 17 00:00:00 2001 From: Alex RootJunior Date: Sun, 21 Apr 2019 01:16:42 +0300 Subject: [PATCH] Docs for filters (Not fully) --- aiogram/__init__.py | 2 +- aiogram/dispatcher/filters/builtin.py | 90 ++++++++++++++-- aiogram/dispatcher/filters/factory.py | 2 +- aiogram/dispatcher/filters/filters.py | 41 +++++-- docs/source/dispatcher/filters.rst | 147 +++++++++++++++++++++++++- docs/source/quick_start.rst | 6 +- 6 files changed, 263 insertions(+), 25 deletions(-) diff --git a/aiogram/__init__.py b/aiogram/__init__.py index 6d30f863..b5e647c9 100644 --- a/aiogram/__init__.py +++ b/aiogram/__init__.py @@ -38,5 +38,5 @@ __all__ = [ 'utils' ] -__version__ = '2.1' +__version__ = '2.1.1.dev1' __api_version__ = '4.2' diff --git a/aiogram/dispatcher/filters/builtin.py b/aiogram/dispatcher/filters/builtin.py index 538330c3..011b9b67 100644 --- a/aiogram/dispatcher/filters/builtin.py +++ b/aiogram/dispatcher/filters/builtin.py @@ -10,12 +10,15 @@ from babel.support import LazyProxy from aiogram import types from aiogram.dispatcher.filters.filters import BoundFilter, Filter from aiogram.types import CallbackQuery, Message, InlineQuery, Poll -from aiogram.utils.deprecated import warn_deprecated 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], @@ -23,12 +26,22 @@ class Command(Filter): ignore_case: bool = True, 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 - :param prefixes: - :param ignore_case: - :param ignore_mention: + Examples: + + .. code-block:: python + + @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): commands = (commands,) @@ -43,15 +56,21 @@ class Command(Filter): """ 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: :return: config or empty dict """ config = {} if 'commands' in full_config: 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') - 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') return config @@ -74,17 +93,37 @@ class Command(Filter): @dataclass 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 = '/' + """Command without prefix and mention""" command: str = '' + """Mention (if available)""" mention: str = None + """Command argument""" args: str = field(repr=False, default=None) @property def mentioned(self) -> bool: + """ + This command has mention? + + :return: + """ return bool(self.mention) @property def text(self) -> str: + """ + Generate original text from object + + :return: + """ line = self.prefix + self.command if self.mentioned: line += '@' + self.mention @@ -94,11 +133,32 @@ class Command(Filter): 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): + """ + Also this filter can handle `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']) self.deep_link = deep_link 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) if check and self.deep_link is not None: @@ -114,16 +174,28 @@ class CommandStart(Command): class CommandHelp(Command): + """ + This filter based on :obj:`Command` filter but can handle only ``/help`` command. + """ + def __init__(self): super(CommandHelp, self).__init__(['help']) class CommandSettings(Command): + """ + This filter based on :obj:`Command` filter but can handle only ``/settings`` command. + """ + def __init__(self): super(CommandSettings, self).__init__(['settings']) class CommandPrivacy(Command): + """ + This filter based on :obj:`Command` filter but can handle only ``/privacy`` command. + """ + def __init__(self): super(CommandPrivacy, self).__init__(['privacy']) diff --git a/aiogram/dispatcher/filters/factory.py b/aiogram/dispatcher/filters/factory.py index 099a9b60..89e3e792 100644 --- a/aiogram/dispatcher/filters/factory.py +++ b/aiogram/dispatcher/filters/factory.py @@ -6,7 +6,7 @@ from ..handler import Handler class FiltersFactory: """ - Default filters factory + Filters factory """ def __init__(self, dispatcher): diff --git a/aiogram/dispatcher/filters/filters.py b/aiogram/dispatcher/filters/filters.py index a6d83c62..46e44fc9 100644 --- a/aiogram/dispatcher/filters/filters.py +++ b/aiogram/dispatcher/filters/filters.py @@ -128,24 +128,29 @@ class FilterRecord: class AbstractFilter(abc.ABC): """ - Abstract class for custom filters + Abstract class for custom filters. """ @classmethod @abc.abstractmethod 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: - :return: config + This method will be called by the filters factory when you bind this filter. + Must be overridden. + + :param full_config: dict with arguments passed to handler registrar + :return: Current filter config """ pass @abc.abstractmethod async def check(self, *args) -> bool: """ - Check object + Will be called when filters checks. + + This method must be overridden. :param args: :return: @@ -173,24 +178,46 @@ class AbstractFilter(abc.ABC): 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 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 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 + """If :obj:`True` this filter will be added to the all of the registered handlers""" required = False + """Default value for configure required filters""" default = None @classmethod 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 in full_config: return {cls.key: full_config[cls.key]} diff --git a/docs/source/dispatcher/filters.rst b/docs/source/dispatcher/filters.rst index 2615a0f7..d103ac36 100644 --- a/docs/source/dispatcher/filters.rst +++ b/docs/source/dispatcher/filters.rst @@ -4,16 +4,155 @@ Filters Basics ====== -Coming soon... + +Filter factory greatly simplifies the reuse of filters when registering handlers. Filters factory =============== -Coming soon... + +.. autoclass:: aiogram.dispatcher.filters.factory.FiltersFactory + :members: + :show-inheritance: 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) =================================== -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]) diff --git a/docs/source/quick_start.rst b/docs/source/quick_start.rst index 07224d19..b0724a78 100644 --- a/docs/source/quick_start.rst +++ b/docs/source/quick_start.rst @@ -22,19 +22,19 @@ Next step: interaction with bots starts with one command. Register your first co .. literalinclude:: ../../examples/echo_bot.py :language: python - :lines: 21-25 + :lines: 20-25 If you want to handle all messages in the chat simply add handler without filters: .. literalinclude:: ../../examples/echo_bot.py :language: python - :lines: 28-30 + :lines: 35-37 Last step: run long polling. .. literalinclude:: ../../examples/echo_bot.py :language: python - :lines: 33-34 + :lines: 40-41 Summary -------