Bot API 5.7 and some new features (#834)

* Update API, added some new features

* Fixed unknown chat_action value

* Separate events from dispatcher messages

* Disabled cache for I18n LazyProxy

* Rework events isolation

* Added chat member status changed filter, update Bot API 5.7, other small changes

* Improve exceptions in chat member status filter

* Fixed tests, covered flags and events isolation modules

* Try to fix flake8 unused type ignore

* Fixed linter error

* Cover chat member updated filter

* Cover chat action sender

* Added docs for chat action util

* Try to fix tests for python <= 3.9

* Fixed headers

* Added docs for flags functionality

* Added docs for chat_member_updated filter

* Added change notes

* Update dependencies and fix mypy checks

* Bump version
This commit is contained in:
Alex Root Junior 2022-02-19 01:45:59 +02:00 committed by GitHub
parent ac7f2dc408
commit 7776cf9cf6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
77 changed files with 2485 additions and 502 deletions

View file

@ -0,0 +1,103 @@
=================
ChatMemberUpdated
=================
.. autoclass:: aiogram.dispatcher.filters.chat_member_updated.ChatMemberUpdatedFilter
:members:
:member-order: bysource
:undoc-members: False
You can import from :code:`aiogram.dispatcher.filters` all available
variants of `statuses`_, `status groups`_ or `transitions`_:
Statuses
========
+-------------------------+--------------------------------------+
| name | Description |
+=========================+======================================+
| :code:`CREATOR` | Chat owner |
+-------------------------+--------------------------------------+
| :code:`ADMINISTRATOR` | Chat administrator |
+-------------------------+--------------------------------------+
| :code:`MEMBER` | Member of the chat |
+-------------------------+--------------------------------------+
| :code:`RESTRICTED` | Restricted user (can be not member) |
+-------------------------+--------------------------------------+
| :code:`LEFT` | Isn't member of the chat |
+-------------------------+--------------------------------------+
| :code:`KICKED` | Kicked member by administrators |
+-------------------------+--------------------------------------+
Statuses can be extended with `is_member` flag by prefixing with
:code:`+` (for :code:`is_member == True)` or :code:`-` (for :code:`is_member == False`) symbol,
like :code:`+RESTRICTED` or :code:`-RESTRICTED`
Status groups
=============
The particular statuses can be combined via bitwise :code:`or` operator, like :code:`CREATOR | ADMINISTRATOR`
+-------------------------+-----------------------------------------------------------------------------------+
| name | Description |
+=========================+===================================================================================+
| :code:`IS_MEMBER` | Combination of :code:`(CREATOR | ADMINISTRATOR | MEMBER | +RESTRICTED)` statuses. |
+-------------------------+-----------------------------------------------------------------------------------+
| :code:`IS_ADMIN` | Combination of :code:`(CREATOR | ADMINISTRATOR)` statuses. |
+-------------------------+-----------------------------------------------------------------------------------+
| :code:`IS_NOT_MEMBER` | Combination of :code:`(LEFT | KICKED | -RESTRICTED)` statuses. |
+-------------------------+-----------------------------------------------------------------------------------+
Transitions
===========
Transitions can be defined via bitwise shift operators :code:`>>` and :code:`<<`.
Old chat member status should be defined in the left side for :code:`>>` operator (right side for :code:`<<`)
and new status should be specified on the right side for :code:`>>` operator (left side for :code:`<<`)
The direction of transition can be changed via bitwise inversion operator: :code:`~JOIN_TRANSITION`
will produce swap of old and new statuses.
+-----------------------------+-----------------------------------------------------------------------+
| name | Description |
+=============================+=======================================================================+
| :code:`JOIN_TRANSITION` | Means status changed from :code:`IS_NOT_MEMBER` to :code:`IS_MEMBER` |
| | (:code:`IS_NOT_MEMBER >> IS_MEMBER`) |
+-----------------------------+-----------------------------------------------------------------------+
| :code:`LEAVE_TRANSITION` | Means status changed from :code:`IS_MEMBER` to :code:`IS_NOT_MEMBER` |
| | (:code:`~JOIN_TRANSITION`) |
+-----------------------------+-----------------------------------------------------------------------+
| :code:`PROMOTED_TRANSITION` | Means status changed from |
| | :code:`(MEMBER | RESTRICTED | LEFT | KICKED) >> ADMINISTRATOR` |
| | (:code:`(MEMBER | RESTRICTED | LEFT | KICKED) >> ADMINISTRATOR`) |
+-----------------------------+-----------------------------------------------------------------------+
.. note::
Note that if you define the status unions (via :code:`|`) you will need to add brackets for the statement
before use shift operator in due to operator priorities.
Usage
=====
Handle user leave or join events
.. code-block:: python
from aiogram.dispatcher.filters import IS_MEMBER, IS_NOT_MEMBER
@router.chat_member(chat_member_updated=IS_MEMBER >> IS_NOT_MEMBER)
async def on_user_leave(event: ChatMemberUpdated): ...
@router.chat_member(chat_member_updated=IS_NOT_MEMBER >> IS_MEMBER)
async def on_user_join(event: ChatMemberUpdated): ...
Or construct your own terms via using pre-defined set of statuses and transitions.
Allowed handlers
================
Allowed update types for this filter:
- `my_chat_member`
- `chat_member`

View file

@ -18,6 +18,7 @@ Here is list of builtin filters:
command
content_types
text
chat_member_updated
exception
magic_filters
magic_data
@ -83,7 +84,7 @@ Bound Filters with only default arguments will be automatically applied with def
to each handler in the router and nested routers to which this filter is bound.
For example, although we do not specify :code:`chat_type` in the handler filters,
but since the filter has a default value, the filter will be applied to the handler
but since the filter has a default value, the filter will be applied to the handler
with a default value :code:`private`:
.. code-block:: python

89
docs/dispatcher/flags.rst Normal file
View file

@ -0,0 +1,89 @@
=====
Flags
=====
Flags is a markers for handlers that can be used in `middlewares <#use-in-middlewares>`_
or special `utilities <#use-in-utilities>`_ to make classification of the handlers.
Flags can be added to the handler via `decorators <#via-decorators>`_,
`handlers registration <#via-handler-registration-method>`_ or
`filters <via-filters>`_.
Via decorators
==============
For example mark handler with `chat_action` flag
.. code-block:: python
from aiogram import flags
@flags.chat_action
async def my_handler(message: Message)
Or just for rate-limit or something else
.. code-block:: python
from aiogram import flags
@flags.rate_limit(rate=2, key="something")
async def my_handler(message: Message)
Via handler registration method
===============================
.. code-block:: python
@router.message(..., flags={'chat_action': 'typing', 'rate_limit': {'rate': 5}})
Via filters
===========
.. code-block:: python
class Command(BaseFilter):
...
def update_handler_flags(self, flags: Dict[str, Any]) -> None:
commands = flags.setdefault("commands", [])
commands.append(self)
Use in middlewares
==================
.. automodule:: aiogram.dispatcher.flags.getter
:members:
Example in middlewares
----------------------
.. code-block:: python
async def my_middleware(handler, event, data):
typing = get_flag(data, "typing") # Check that handler marked with `typing` flag
if not typing:
return await handler(event, data)
async with ChatActionSender.typing(chat_id=event.chat.id):
return await handler(event, data)
Use in utilities
================
For example you can collect all registered commands with handler description and then it can be used for generating commands help
.. code-block:: python
def collect_commands(router: Router) -> Generator[Tuple[Command, str], None, None]:
for handler in router.message.handlers:
if "commands" not in handler.flags: # ignore all handler without commands
continue
# the Command filter adds the flag with list of commands attached to the handler
for command in handler.flags["commands"]:
yield command, handler.callback.__doc__ or ""
# Recursively extract commands from nested routers
for sub_router in router.sub_routers:
yield from collect_commands(sub_router)

View file

@ -24,3 +24,4 @@ Dispatcher is subclass of router and should be always is root router.
filters/index
middlewares
finite_state_machine/index
flags

View file

@ -0,0 +1,56 @@
==================
Chat action sender
==================
Sender
======
.. autoclass:: aiogram.utils.chat_action.ChatActionSender
:members: __init__,running,typing,upload_photo,record_video,upload_video,record_voice,upload_voice,upload_document,choose_sticker,find_location,record_video_note,upload_video_note
Usage
-----
.. code-block:: python
async with ChatActionSender.typing(bot=bot, chat_id=message.chat.id):
# Do something...
# Perform some long calculations
await message.answer(result)
Middleware
==========
.. autoclass:: aiogram.utils.chat_action.ChatActionMiddleware
Usage
-----
Before usa should be registered for the `message` event
.. code-block:: python
<router or dispatcher>.message.middleware(ChatActionMiddleware())
After this action all handlers which works longer than `initial_sleep` will produce the '`typing`' chat action.
Also sender can be customized via flags feature for particular handler.
Change only action type:
.. code-block:: python
@router.message(...)
@flags.chat_action("sticker")
async def my_handler(message: Message): ...
Change sender configuration:
.. code-block:: python
@router.message(...)
@flags.chat_action(initial_sleep=2, action="upload_document", interval=3)
async def my_handler(message: Message): ...

View file

@ -6,3 +6,4 @@ Utils
i18n
keyboard
chat_action