diff --git a/docs2/dispatcher/class_based_handlers/base.rst b/docs2/dispatcher/class_based_handlers/base.rst new file mode 100644 index 00000000..3c3e910d --- /dev/null +++ b/docs2/dispatcher/class_based_handlers/base.rst @@ -0,0 +1,26 @@ +.. _cbh-base-handler: + +=========== +BaseHandler +=========== + +Base handler is generic abstract class and should be used in all other class-based handlers. + +Import: :code:`from aiogram.hanler import BaseHandler` + +By default you will need to override only method :code:`async def handle(self) -> Any: ...` + +This class is also have an default initializer and you don't need to change it. +Initializer accepts current event and all contextual data and which +can be accessed from the handler through attributes: :code:`event: TelegramEvent` and :code:`data: Dict[Any, str]` + +If instance of the bot is specified in context data or current context it can be accessed through *bot* class attribute. + +Example +======= + +.. code-block:: python + + class MyHandler(BaseHandler[Message]): + async def handle(self) -> Any: + await self.event.answer("Hello!") diff --git a/docs2/dispatcher/class_based_handlers/callback_query.rst b/docs2/dispatcher/class_based_handlers/callback_query.rst new file mode 100644 index 00000000..292f111d --- /dev/null +++ b/docs2/dispatcher/class_based_handlers/callback_query.rst @@ -0,0 +1,27 @@ +==================== +CallbackQueryHandler +==================== + +There is base class for callback query handlers. + +Simple usage +============ +.. code-block:: python + + from aiogram.handlers import CallbackQueryHandler + + ... + + @router.callback_query() + class MyHandler(CallbackQueryHandler): + async def handle(self) -> Any: ... + + +Extension +========= + +This base handler is subclass of :ref:`BaseHandler ` with some extensions: + +- :code:`self.from_user` is alias for :code:`self.event.from_user` +- :code:`self.message` is alias for :code:`self.event.message` +- :code:`self.callback_data` is alias for :code:`self.event.data` diff --git a/docs2/dispatcher/class_based_handlers/chosen_inline_result.rst b/docs2/dispatcher/class_based_handlers/chosen_inline_result.rst new file mode 100644 index 00000000..d4402988 --- /dev/null +++ b/docs2/dispatcher/class_based_handlers/chosen_inline_result.rst @@ -0,0 +1,27 @@ +========================= +ChosenInlineResultHandler +========================= + +There is base class for chosen inline result handlers. + +Simple usage +============ + +.. code-block:: python + + from aiogram.handlers import ChosenInlineResultHandler + + ... + + @router.chosen_inline_result() + class MyHandler(ChosenInlineResultHandler): + async def handle(self) -> Any: ... + + +Extension +========= + +This base handler is subclass of :ref:`BaseHandler ` with some extensions: + +- :code:`self.chat` is alias for :code:`self.event.chat` +- :code:`self.from_user` is alias for :code:`self.event.from_user` diff --git a/docs2/dispatcher/class_based_handlers/error.rst b/docs2/dispatcher/class_based_handlers/error.rst new file mode 100644 index 00000000..dc930e26 --- /dev/null +++ b/docs2/dispatcher/class_based_handlers/error.rst @@ -0,0 +1,32 @@ +============ +ErrorHandler +============ + +There is base class for error handlers. + +Simple usage +============ + + +.. code-block:: python + + from aiogram.handlers import ErrorHandler + + ... + + @router.errors() + class MyHandler(ErrorHandler): + async def handle(self) -> Any: + log.exception( + "Cause unexpected exception %s: %s", + self.exception_name, + self.exception_message + ) + +Extension +========= + +This base handler is subclass of :ref:`BaseHandler ` with some extensions: + +- :code:`self.exception_name` is alias for :code:`self.event.__class__.__name__` +- :code:`self.exception_message` is alias for :code:`str(self.event)` diff --git a/docs2/dispatcher/class_based_handlers/index.rst b/docs2/dispatcher/class_based_handlers/index.rst new file mode 100644 index 00000000..094b127f --- /dev/null +++ b/docs2/dispatcher/class_based_handlers/index.rst @@ -0,0 +1,22 @@ +==================== +Class based handlers +==================== + +A handler is a async callable which takes a event with contextual data and returns a response. + +In **aiogram** it can be more than just an async function, these allow you to use classes +which can be used as Telegram event handlers to structure your event handlers and reuse code by harnessing inheritance and mixins. + +There are some base class based handlers what you need to use in your own handlers: + +.. toctree:: + + base + callback_query + chosen_inline_result + error + inline_query + message + poll + pre_checkout_query + shipping_query diff --git a/docs2/dispatcher/class_based_handlers/inline_query.rst b/docs2/dispatcher/class_based_handlers/inline_query.rst new file mode 100644 index 00000000..f76cfff4 --- /dev/null +++ b/docs2/dispatcher/class_based_handlers/inline_query.rst @@ -0,0 +1,27 @@ +================== +InlineQueryHandler +================== + +There is base class for inline query handlers. + +Simple usage +============ + +.. code-block:: python + + from aiogram.handlers import InlineQueryHandler + + ... + + @router.inline_query() + class MyHandler(InlineQueryHandler): + async def handle(self) -> Any: ... + + +Extension +========= + +This base handler is subclass of :ref:`BaseHandler ` with some extensions: + +- :code:`self.chat` is alias for :code:`self.event.chat` +- :code:`self.query` is alias for :code:`self.event.query` diff --git a/docs2/dispatcher/class_based_handlers/message.rst b/docs2/dispatcher/class_based_handlers/message.rst new file mode 100644 index 00000000..1945e8ba --- /dev/null +++ b/docs2/dispatcher/class_based_handlers/message.rst @@ -0,0 +1,27 @@ +============== +MessageHandler +============== + +There is base class for message handlers. + +Simple usage +============ + +.. code-block:: python + + from aiogram.handlers import MessageHandler + + ... + + @router.message() + class MyHandler(MessageHandler): + async def handle(self) -> Any: + return SendMessage(chat_id=self.chat.id, text="PASS") + +Extension +========= + +This base handler is subclass of [BaseHandler](basics.md#basehandler) with some extensions: + +- :code:`self.chat` is alias for :code:`self.event.chat` +- :code:`self.from_user` is alias for :code:`self.event.from_user` diff --git a/docs2/dispatcher/class_based_handlers/poll.rst b/docs2/dispatcher/class_based_handlers/poll.rst new file mode 100644 index 00000000..3b3b6443 --- /dev/null +++ b/docs2/dispatcher/class_based_handlers/poll.rst @@ -0,0 +1,26 @@ +=========== +PollHandler +=========== + +There is base class for poll handlers. + +Simple usage +============ + +.. code-block:: python + + from aiogram.handlers import PollHandler + + ... + + @router.poll() + class MyHandler(PollHandler): + async def handle(self) -> Any: ... + +Extension +========= + +This base handler is subclass of :ref:`BaseHandler ` with some extensions: + +- :code:`self.question` is alias for :code:`self.event.question` +- :code:`self.options` is alias for :code:`self.event.options` diff --git a/docs2/dispatcher/class_based_handlers/pre_checkout_query.rst b/docs2/dispatcher/class_based_handlers/pre_checkout_query.rst new file mode 100644 index 00000000..e0a8ed4d --- /dev/null +++ b/docs2/dispatcher/class_based_handlers/pre_checkout_query.rst @@ -0,0 +1,25 @@ +======================= +PreCheckoutQueryHandler +======================= + +There is base class for callback query handlers. + +Simple usage +============ + +.. code-block:: python + + from aiogram.handlers import PreCheckoutQueryHandler + + ... + + @router.pre_checkout_query() + class MyHandler(PreCheckoutQueryHandler): + async def handle(self) -> Any: ... + +Extension +========= + +This base handler is subclass of :ref:`BaseHandler ` with some extensions: + +- :code:`self.from_user` is alias for :code:`self.event.from_user` diff --git a/docs2/dispatcher/class_based_handlers/shipping_query.rst b/docs2/dispatcher/class_based_handlers/shipping_query.rst new file mode 100644 index 00000000..111a0d8d --- /dev/null +++ b/docs2/dispatcher/class_based_handlers/shipping_query.rst @@ -0,0 +1,25 @@ +==================== +ShippingQueryHandler +==================== + +There is base class for callback query handlers. + +Simple usage +============ + +.. code-block:: python + + from aiogram.handlers import ShippingQueryHandler + + ... + + @router.shipping_query() + class MyHandler(ShippingQueryHandler): + async def handle(self) -> Any: ... + +Extension +========= + +This base handler is subclass of :ref:`BaseHandler ` with some extensions: + +- :code:`self.from_user` is alias for :code:`self.event.from_user` diff --git a/docs2/dispatcher/filters/command.rst b/docs2/dispatcher/filters/command.rst new file mode 100644 index 00000000..645c6acf --- /dev/null +++ b/docs2/dispatcher/filters/command.rst @@ -0,0 +1,37 @@ +======= +Command +======= + +.. autoclass:: aiogram.dispatcher.filters.command.Command + :members: + :member-order: bysource + :undoc-members: False + +When filter is passed the :class:`aiogram.dispatcher.filters.command.CommandObject` will be passed to the handler argument :code:`command` + +.. autoclass:: aiogram.dispatcher.filters.command.CommandObject + :members: + :member-order: bysource + :undoc-members: False + + +Usage +===== + +1. Filter single variant of commands: :code:`Command(commands=["start"])` or :code:`Command(commands="start")` +2. Handle command by regexp pattern: :code:`Command(commands=[re.compile(r"item_(\d+)")])` +3. Match command by multiple variants: :code:`Command(commands=["item", re.compile(r"item_(\d+)")])` +4. Handle commands in public chats intended for other bots: :code:`Command(commands=["command"], commands)` +5. As keyword argument in registerer: :code:`@router.message(commands=["help"])` + +.. warning:: + + Command cannot include spaces or any whitespace + +Allowed handlers +================ + +Allowed update types for this filter: + +- `message` +- `edited_message` diff --git a/docs2/dispatcher/filters/content_types.rst b/docs2/dispatcher/filters/content_types.rst new file mode 100644 index 00000000..639f2996 --- /dev/null +++ b/docs2/dispatcher/filters/content_types.rst @@ -0,0 +1,39 @@ +================== +ContentTypesFilter +================== + +.. autoclass:: aiogram.dispatcher.filters.content_types.ContentTypesFilter + :members: + :member-order: bysource + :undoc-members: False + +Can be imported: + +- :code:`from aiogram.dispatcher.filters.content_types import ContentTypesFilter` +- :code:`from aiogram.dispatcher.filters import ContentTypesFilter` +- :code:`from aiogram.filters import ContentTypesFilter` + +Or used from filters factory by passing corresponding arguments to handler registration line + +.. warning:: + **Please be patient!** + + If no one content type filter is specified the :code:`["text"]` value is automatically will be used. + +Usage +===== + +1. Single content type: :code:`ContentTypesFilter(content_types=["sticker"])` or :code:`ContentTypesFilter(content_types="sticker")` +2. Multiple content types: :code:`ContentTypesFilter(content_types=["sticker", "photo"])` +3. Recommended: With usage of `ContentType` helper: :code:`ContentTypesFilter(content_types=[ContentType.PHOTO])` +4. Any content type: :code:`ContentTypesFilter(content_types=[ContentType.ANY])` + +Allowed handlers +================ + +Allowed update types for this filter: + +- :code:`message` +- :code:`edited_message` +- :code:`channel_post` +- :code:`edited_channel_post` diff --git a/docs2/dispatcher/filters/exception.rst b/docs2/dispatcher/filters/exception.rst new file mode 100644 index 00000000..6e296658 --- /dev/null +++ b/docs2/dispatcher/filters/exception.rst @@ -0,0 +1,22 @@ +========== +Exceptions +========== + +This filters can be helpful for handling errors from the text messages. + +.. autoclass:: aiogram.dispatcher.filters.exception.ExceptionTypeFilter + :members: + :member-order: bysource + :undoc-members: False + +.. autoclass:: aiogram.dispatcher.filters.exception.ExceptionMessageFilter + :members: + :member-order: bysource + :undoc-members: False + +Allowed handlers +================ + +Allowed update types for this filters: + +- :code:`error` diff --git a/docs2/dispatcher/filters/index.rst b/docs2/dispatcher/filters/index.rst new file mode 100644 index 00000000..010cb0c5 --- /dev/null +++ b/docs2/dispatcher/filters/index.rst @@ -0,0 +1,73 @@ +================ +Filtering events +================ + +Filters is needed for routing updates to the specific handler. +Searching of handler is always stops on first match set of filters are pass. + +*aiogram* has some builtin useful filters. + +Builtin filters +=============== + +Here is list of builtin filters: + +.. toctree:: + :maxdepth: 1 + + command + content_types + text + exception + +Or you can do :ref:`✨ some magic ` + +Own filters specification +========================= + +Filters can be: + +- Asynchronous function (:code:`async def my_filter(*args, **kwargs): pass`) + +- Synchronous function (:code:`def my_filter(*args, **kwargs): pass`) + +- Anonymous function (:code:`lambda event: True`) + +- Any awaitable object + +- Subclass of :ref:`BaseFilter ` + +Filters should return bool or dict. +If the dictionary is passed as result of filter - resulted data will be propagated to the next +filters and handler as keywords arguments. + +Writing bound filters +===================== + +.. autoclass:: aiogram.dispatcher.filters.base.BaseFilter + :members: __call__ + :member-order: bysource + :undoc-members: False + +For example if you need to make simple text filter: + +.. code-block:: python + + from aiogram.filters import BaseFilter + + + class MyText(BaseFilter): + my_text: str + + async def __call__(self, message: Message) -> bool: + return message.text == self.my_text + + + router.message.bind_filter(MyText) + + @router.message(my_text="hello") + async def my_handler(message: Message): ... + +.. note:: + + Bound filters is always recursive propagates to the nested routers. diff --git a/docs2/dispatcher/filters/magic_filters.rst b/docs2/dispatcher/filters/magic_filters.rst new file mode 100644 index 00000000..3a0dd8b7 --- /dev/null +++ b/docs2/dispatcher/filters/magic_filters.rst @@ -0,0 +1,146 @@ +.. _magic-filters: + +============= +Magic filters +============= + +.. note:: + + This page still in progress. Has many incorrectly worded sentences. + +Is external package maintained by *aiogram* core team. + +By default installs with *aiogram* and also is available on `PyPi - magic-filter `_. +That's mean you can install it and use with any other libraries and in own projects without depending *aiogram* installed. + +Usage +===== + +The **magic_filter** package implements class shortly named :class:`magic_filter.F` that's mean :code:`F` can be imported from :code:`magic_filter`. :class:`F` is alias for :class:`MagicFilter`. + +The :class:`MagicFilter` object is callable, supports :ref:`some actions ` +and memorize the attributes chain and the action which should be checked on demand. + +So that's mean you can chain attribute getters, describe simple data validations +and then call the resulted object passing single object as argument, +for example make attributes chain :code:`F.foo.bar.baz` then add +action ':code:`F.foo.bar.baz == 'spam'` and then call the resulted object - :code:`(F.foo.bar.baz == 'spam')(obj)` + +.. _magic-filter-possible-actions: + +Possible actions +================ + +Magic filter object supports some of basic logical operations over object attributes + +Exists or not None +------------------ + +Default actions. + +.. code-block:: python + + F.photo # lambda message: message.photo + +Equals +------ + +.. code-block:: python + + F.text == 'hello' # lambda message: message.text == 'hello' + F.from_user.id == 42 # lambda message: message.from_user.id == 42 + +Is one of +--------- + +Can be used as method named :code:`in_` or as matmul operator :code:`@` with any iterable + +.. code-block:: python + + F.from_user.id.in_(42, 1000, 123123) # lambda query: query.from_user.id in {42, 1000, 123123} + F.data.in_('foo', 'bar', 'baz') # lambda query: query.data in {'foo', 'bar', 'baz'} + F.text @ {'foo', 'bar'} # lambda message: message.text in {'foo', 'bar'} + +Contains +-------- + +.. code-block:: python + + F.text.contains('foo') # lambda message: 'foo' in message.text + +String startswith/endswith +-------------------------- + +Can be applied only for text attributes + +.. code-block:: python + + F.text.startswith('foo') # lambda message: message.text.startswith('foo') + F.text.endswith('bar') # lambda message: message.text.startswith('bar') + +Regexp +------ + +.. code-block:: python + + F.text.regexp(r'Hello, .+') # lambda message: re.match(r'Hello, .+', message.text) + +Custom function +--------------- + +Accepts any callable. Callback will be called when filter checks result + +.. code-block:: python + + F.chat.func(lambda chat: chat.id == -42) # lambda message: (lambda chat: chat.id == -42)(message.chat) + +Inverting result +---------------- + +Any of available operation can be inverted by bitwise inversion - :code:`~` + +.. code-block:: python + + ~(F.text == 'spam') # lambda message: message.text != 'spam' + ~F.text.startswith('spam') # lambda message: not message.text.startswith('spam') + +Combining +--------- + +All operations can be combined via bitwise and/or operators - :code:`&`/:code:`|` + +.. code-block:: python + + (F.from_user.id == 42) & (F.text == 'admin') + F.text.startswith('a') | F.text.endswith('b') + (F.from_user.id @ {42, 777, 911}) & (F.text.startswith('!') | F.text.startswith('/')) & F.text.contains('ban') + + +Attribute modifiers - string manipulations +------------------------------------------ + +Make text upper- or lower-case + +Can be used only with string attributes. + +.. code-block:: python + + F.text__lower == 'test' # lambda message: message.text.lower() == 'test' + F.text__upper.in_('FOO', 'BAR') # lambda message: message.text.upper() in {'FOO', 'BAR'} + F.text__len == 5 # lambda message: len(message.text) == 5 + + +Usage in *aiogram* +================== + +.. code-block:: python + + @router.message(F.text == 'hello') + @router.inline_query(F.data == 'button:1') + @router.message(F.text.startswith('foo')) + @router.message(F.content_type.in_('text', 'sticker')) + @router.message(F.text.regexp(r'\d+')) + + ... + + # Many others cases when you will need to check any of available event attribute diff --git a/docs2/dispatcher/filters/text.rst b/docs2/dispatcher/filters/text.rst new file mode 100644 index 00000000..9aa7b096 --- /dev/null +++ b/docs2/dispatcher/filters/text.rst @@ -0,0 +1,38 @@ +==== +Text +==== + +.. autoclass:: aiogram.dispatcher.filters.text.Text + :members: + :member-order: bysource + :undoc-members: False + +Can be imported: + +- :code:`from aiogram.dispatcher.filters.text import Text` +- :code:`from aiogram.dispatcher.filters import Text` +- :code:`from aiogram.filters import Text` + +Or used from filters factory by passing corresponding arguments to handler registration line + +Usage +===== + +#. Text equals with the specified value: :code:`Text(text="text") # value == 'text'` +#. Text starts with the specified value: :code:`Text(text_startswith="text") # value.startswith('text')` +#. Text ends with the specified value: :code:`Text(text_endswith="text") # value.endswith('text')` +#. Text contains the specified value: :code:`Text(text_contains="text") # value in 'text'` +#. Any of previous listed filters can be list, set or tuple of strings that's mean any of listed value should be equals/startswith/endswith/contains: :code:`Text(text=["text", "spam"])` +#. Ignore case can be combined with any previous listed filter: :code:`Text(text="Text", text_ignore_case=True) # value.lower() == 'text'.lower()` + +Allowed handlers +================ + +Allowed update types for this filter: + +- :code:`message` +- :code:`edited_message` +- :code:`channel_post` +- :code:`edited_channel_post` +- :code:`inline_query` +- :code:`callback_query`