Repository: aiogram/aiogram
Pull request #659 (2021-08-07 02:07:47)

Add StatesGroup filter
# Description

Added filtering by StatesGroups
There is also a [second](https://github.com/darksidecat/aiogram/commit/0e5285701226cea9d5ba1340d8410d6dcf2693f2) filtering implementation

Usage:
```python3
router.message.filter(StatesGroupName())
```

## Type of change

Please delete options that are not relevant.

- [ ] Documentation (typos, code examples or any documentation update)
- [ ] Bug fix (non-breaking change which fixes an issue)
- [x] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
- [x] This change requires a documentation update

# How Has This Been Tested?

Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce. Please also list any relevant details for your test configuration

- [x] Add tests cases
- [x] Manual testing in bot

**Test Configuration**:
* Operating System: Win10
* Python version: 3.9.1

# Checklist:

- [x] My code follows the style guidelines of this project
- [ ] I have performed a self-review of my own code
- [ ] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [x] I have added tests that prove my fix is effective or that my feature works
- [x] New and existing unit tests pass locally with my changes



Patch:
diff --git a/CHANGES/660.bugfix b/CHANGES/660.bugfix
new file mode 100644
index 00000000..3da5568d
--- /dev/null
+++ b/CHANGES/660.bugfix
@@ -0,0 +1,1 @@
+Prepare parse mode for InputMessageContent in AnswerInlineQuery method
diff --git a/CHANGES/663.misc b/CHANGES/663.misc
new file mode 100644
index 00000000..38bdfb5d
--- /dev/null
+++ b/CHANGES/663.misc
@@ -0,0 +1,1 @@
+add allow_sending_without_reply param to Message reply aliases
diff --git a/aiogram/dispatcher/fsm/state.py b/aiogram/dispatcher/fsm/state.py
index d4ea1974..ced9779a 100644
--- a/aiogram/dispatcher/fsm/state.py
+++ b/aiogram/dispatcher/fsm/state.py
@@ -111,8 +111,8 @@ class StatesGroupMeta(type):
             return item in cls.__all_states_names__
         if isinstance(item, State):
             return item in cls.__all_states__
-        # if isinstance(item, StatesGroup):
-        #     return item in cls.__all_childs__
+        if isinstance(item, StatesGroupMeta):
+            return item in cls.__all_childs__
         return False
 
     def __str__(self) -> str:
@@ -126,8 +126,11 @@ class StatesGroup(metaclass=StatesGroupMeta):
             return cls
         return cls.__parent__.get_root()
 
-    # def __call__(cls, event: TelegramObject, raw_state: Optional[str] = None) -> bool:
-    #     return raw_state in cls.__all_states_names__
+    def __call__(cls, event: TelegramObject, raw_state: Optional[str] = None) -> bool:
+        return raw_state in type(cls).__all_states_names__
+
+    def __str__(self) -> str:
+        return f"StatesGroup {type(self).__full_group_name__}"
 
 
 default_state = State()
diff --git a/aiogram/methods/answer_inline_query.py b/aiogram/methods/answer_inline_query.py
index 95c9b66d..78511403 100644
--- a/aiogram/methods/answer_inline_query.py
+++ b/aiogram/methods/answer_inline_query.py
@@ -37,5 +37,12 @@ class AnswerInlineQuery(TelegramMethod[bool]):
 
     def build_request(self, bot: Bot) -> Request:
         data: Dict[str, Any] = self.dict()
-        prepare_parse_mode(bot, data["results"])
+
+        input_message_contents = []
+        for result in data["results"]:
+            input_message_content = result.get("input_message_content", None)
+            if input_message_content is not None:
+                input_message_contents.append(input_message_content)
+
+        prepare_parse_mode(bot, data["results"] + input_message_contents)
         return Request(method="answerInlineQuery", data=data)
diff --git a/aiogram/types/message.py b/aiogram/types/message.py
index 8d72d0e5..52acd9f6 100644
--- a/aiogram/types/message.py
+++ b/aiogram/types/message.py
@@ -268,6 +268,7 @@ class Message(TelegramObject):
         caption: Optional[str] = None,
         parse_mode: Optional[str] = UNSET,
         disable_notification: Optional[bool] = None,
+        allow_sending_without_reply: Optional[bool] = None,
         reply_markup: Optional[
             Union[InlineKeyboardMarkup, ReplyKeyboardMarkup, ReplyKeyboardRemove, ForceReply]
         ] = None,
@@ -283,6 +284,7 @@ class Message(TelegramObject):
         :param caption:
         :param parse_mode:
         :param disable_notification:
+        :param allow_sending_without_reply:
         :param reply_markup:
         :return:
         """
@@ -299,6 +301,7 @@ class Message(TelegramObject):
             parse_mode=parse_mode,
             disable_notification=disable_notification,
             reply_to_message_id=self.message_id,
+            allow_sending_without_reply=allow_sending_without_reply,
             reply_markup=reply_markup,
         )
 
@@ -356,6 +359,7 @@ class Message(TelegramObject):
         title: Optional[str] = None,
         thumb: Optional[Union[InputFile, str]] = None,
         disable_notification: Optional[bool] = None,
+        allow_sending_without_reply: Optional[bool] = None,
         reply_markup: Optional[
             Union[InlineKeyboardMarkup, ReplyKeyboardMarkup, ReplyKeyboardRemove, ForceReply]
         ] = None,
@@ -371,6 +375,7 @@ class Message(TelegramObject):
         :param title:
         :param thumb:
         :param disable_notification:
+        :param allow_sending_without_reply:
         :param reply_markup:
         :return:
         """
@@ -387,6 +392,7 @@ class Message(TelegramObject):
             thumb=thumb,
             disable_notification=disable_notification,
             reply_to_message_id=self.message_id,
+            allow_sending_without_reply=allow_sending_without_reply,
             reply_markup=reply_markup,
         )
 
@@ -441,6 +447,7 @@ class Message(TelegramObject):
         last_name: Optional[str] = None,
         vcard: Optional[str] = None,
         disable_notification: Optional[bool] = None,
+        allow_sending_without_reply: Optional[bool] = None,
         reply_markup: Optional[
             Union[InlineKeyboardMarkup, ReplyKeyboardMarkup, ReplyKeyboardRemove, ForceReply]
         ] = None,
@@ -453,6 +460,7 @@ class Message(TelegramObject):
         :param last_name:
         :param vcard:
         :param disable_notification:
+        :param allow_sending_without_reply:
         :param reply_markup:
         :return:
         """
@@ -466,6 +474,7 @@ class Message(TelegramObject):
             vcard=vcard,
             disable_notification=disable_notification,
             reply_to_message_id=self.message_id,
+            allow_sending_without_reply=allow_sending_without_reply,
             reply_markup=reply_markup,
         )
 
@@ -511,6 +520,7 @@ class Message(TelegramObject):
         caption: Optional[str] = None,
         parse_mode: Optional[str] = UNSET,
         disable_notification: Optional[bool] = None,
+        allow_sending_without_reply: Optional[bool] = None,
         reply_markup: Optional[
             Union[InlineKeyboardMarkup, ReplyKeyboardMarkup, ReplyKeyboardRemove, ForceReply]
         ] = None,
@@ -523,6 +533,7 @@ class Message(TelegramObject):
         :param caption:
         :param parse_mode:
         :param disable_notification:
+        :param allow_sending_without_reply:
         :param reply_markup:
         :return:
         """
@@ -536,6 +547,7 @@ class Message(TelegramObject):
             parse_mode=parse_mode,
             disable_notification=disable_notification,
             reply_to_message_id=self.message_id,
+            allow_sending_without_reply=allow_sending_without_reply,
             reply_markup=reply_markup,
         )
 
@@ -578,6 +590,7 @@ class Message(TelegramObject):
         self,
         game_short_name: str,
         disable_notification: Optional[bool] = None,
+        allow_sending_without_reply: Optional[bool] = None,
         reply_markup: Optional[InlineKeyboardMarkup] = None,
     ) -> SendGame:
         """
@@ -585,6 +598,7 @@ class Message(TelegramObject):
 
         :param game_short_name:
         :param disable_notification:
+        :param allow_sending_without_reply:
         :param reply_markup:
         :return:
         """
@@ -595,6 +609,7 @@ class Message(TelegramObject):
             game_short_name=game_short_name,
             disable_notification=disable_notification,
             reply_to_message_id=self.message_id,
+            allow_sending_without_reply=allow_sending_without_reply,
             reply_markup=reply_markup,
         )
 
@@ -644,6 +659,7 @@ class Message(TelegramObject):
         send_email_to_provider: Optional[bool] = None,
         is_flexible: Optional[bool] = None,
         disable_notification: Optional[bool] = None,
+        allow_sending_without_reply: Optional[bool] = None,
         reply_markup: Optional[InlineKeyboardMarkup] = None,
     ) -> SendInvoice:
         """
@@ -669,6 +685,7 @@ class Message(TelegramObject):
         :param send_email_to_provider:
         :param is_flexible:
         :param disable_notification:
+        :param allow_sending_without_reply:
         :param reply_markup:
         :return:
         """
@@ -697,6 +714,7 @@ class Message(TelegramObject):
             is_flexible=is_flexible,
             disable_notification=disable_notification,
             reply_to_message_id=self.message_id,
+            allow_sending_without_reply=allow_sending_without_reply,
             reply_markup=reply_markup,
         )
 
@@ -784,6 +802,7 @@ class Message(TelegramObject):
         longitude: float,
         live_period: Optional[int] = None,
         disable_notification: Optional[bool] = None,
+        allow_sending_without_reply: Optional[bool] = None,
         reply_markup: Optional[
             Union[InlineKeyboardMarkup, ReplyKeyboardMarkup, ReplyKeyboardRemove, ForceReply]
         ] = None,
@@ -795,6 +814,7 @@ class Message(TelegramObject):
         :param longitude:
         :param live_period:
         :param disable_notification:
+        :param allow_sending_without_reply:
         :param reply_markup:
         :return:
         """
@@ -807,6 +827,7 @@ class Message(TelegramObject):
             live_period=live_period,
             disable_notification=disable_notification,
             reply_to_message_id=self.message_id,
+            allow_sending_without_reply=allow_sending_without_reply,
             reply_markup=reply_markup,
         )
 
@@ -846,12 +867,14 @@ class Message(TelegramObject):
         self,
         media: List[Union[InputMediaPhoto, InputMediaVideo]],
         disable_notification: Optional[bool] = None,
+        allow_sending_without_reply: Optional[bool] = None,
     ) -> SendMediaGroup:
         """
         Reply with media group
 
         :param media:
         :param disable_notification:
+        :param allow_sending_without_reply:
         :return:
         """
         from ..methods import SendMediaGroup
@@ -861,6 +884,7 @@ class Message(TelegramObject):
             media=media,
             disable_notification=disable_notification,
             reply_to_message_id=self.message_id,
+            allow_sending_without_reply=allow_sending_without_reply,
         )
 
     def answer_media_group(
@@ -890,6 +914,7 @@ class Message(TelegramObject):
         parse_mode: Optional[str] = UNSET,
         disable_web_page_preview: Optional[bool] = None,
         disable_notification: Optional[bool] = None,
+        allow_sending_without_reply: Optional[bool] = None,
         reply_markup: Optional[
             Union[InlineKeyboardMarkup, ReplyKeyboardMarkup, ReplyKeyboardRemove, ForceReply]
         ] = None,
@@ -901,6 +926,7 @@ class Message(TelegramObject):
         :param parse_mode:
         :param disable_web_page_preview:
         :param disable_notification:
+        :param allow_sending_without_reply:
         :param reply_markup:
         :return:
         """
@@ -913,6 +939,7 @@ class Message(TelegramObject):
             disable_web_page_preview=disable_web_page_preview,
             disable_notification=disable_notification,
             reply_to_message_id=self.message_id,
+            allow_sending_without_reply=allow_sending_without_reply,
             reply_markup=reply_markup,
         )
 
@@ -954,6 +981,7 @@ class Message(TelegramObject):
         caption: Optional[str] = None,
         parse_mode: Optional[str] = UNSET,
         disable_notification: Optional[bool] = None,
+        allow_sending_without_reply: Optional[bool] = None,
         reply_markup: Optional[
             Union[InlineKeyboardMarkup, ReplyKeyboardMarkup, ReplyKeyboardRemove, ForceReply]
         ] = None,
@@ -965,6 +993,7 @@ class Message(TelegramObject):
         :param caption:
         :param parse_mode:
         :param disable_notification:
+        :param allow_sending_without_reply:
         :param reply_markup:
         :return:
         """
@@ -977,6 +1006,7 @@ class Message(TelegramObject):
             parse_mode=parse_mode,
             disable_notification=disable_notification,
             reply_to_message_id=self.message_id,
+            allow_sending_without_reply=allow_sending_without_reply,
             reply_markup=reply_markup,
         )
 
@@ -1026,6 +1056,7 @@ class Message(TelegramObject):
         close_date: Optional[Union[datetime.datetime, datetime.timedelta, int]] = None,
         is_closed: Optional[bool] = None,
         disable_notification: Optional[bool] = None,
+        allow_sending_without_reply: Optional[bool] = None,
         reply_markup: Optional[
             Union[InlineKeyboardMarkup, ReplyKeyboardMarkup, ReplyKeyboardRemove, ForceReply]
         ] = None,
@@ -1045,6 +1076,7 @@ class Message(TelegramObject):
         :param close_date:
         :param is_closed:
         :param disable_notification:
+        :param allow_sending_without_reply:
         :param reply_markup:
         :return:
         """
@@ -1065,6 +1097,7 @@ class Message(TelegramObject):
             is_closed=is_closed,
             disable_notification=disable_notification,
             reply_to_message_id=self.message_id,
+            allow_sending_without_reply=allow_sending_without_reply,
             reply_markup=reply_markup,
         )
 
@@ -1128,6 +1161,7 @@ class Message(TelegramObject):
         self,
         emoji: Optional[str] = None,
         disable_notification: Optional[bool] = None,
+        allow_sending_without_reply: Optional[bool] = None,
         reply_markup: Optional[
             Union[InlineKeyboardMarkup, ReplyKeyboardMarkup, ReplyKeyboardRemove, ForceReply]
         ] = None,
@@ -1137,6 +1171,7 @@ class Message(TelegramObject):
 
         :param emoji:
         :param disable_notification:
+        :param allow_sending_without_reply:
         :param reply_markup:
         :return:
         """
@@ -1147,6 +1182,7 @@ class Message(TelegramObject):
             emoji=emoji,
             disable_notification=disable_notification,
             reply_to_message_id=self.message_id,
+            allow_sending_without_reply=allow_sending_without_reply,
             reply_markup=reply_markup,
         )
 
@@ -1180,6 +1216,7 @@ class Message(TelegramObject):
         self,
         sticker: Union[InputFile, str],
         disable_notification: Optional[bool] = None,
+        allow_sending_without_reply: Optional[bool] = None,
         reply_markup: Optional[
             Union[InlineKeyboardMarkup, ReplyKeyboardMarkup, ReplyKeyboardRemove, ForceReply]
         ] = None,
@@ -1189,6 +1226,7 @@ class Message(TelegramObject):
 
         :param sticker:
         :param disable_notification:
+        :param allow_sending_without_reply:
         :param reply_markup:
         :return:
         """
@@ -1199,6 +1237,7 @@ class Message(TelegramObject):
             sticker=sticker,
             disable_notification=disable_notification,
             reply_to_message_id=self.message_id,
+            allow_sending_without_reply=allow_sending_without_reply,
             reply_markup=reply_markup,
         )
 
@@ -1237,6 +1276,7 @@ class Message(TelegramObject):
         foursquare_id: Optional[str] = None,
         foursquare_type: Optional[str] = None,
         disable_notification: Optional[bool] = None,
+        allow_sending_without_reply: Optional[bool] = None,
         reply_markup: Optional[
             Union[InlineKeyboardMarkup, ReplyKeyboardMarkup, ReplyKeyboardRemove, ForceReply]
         ] = None,
@@ -1251,6 +1291,7 @@ class Message(TelegramObject):
         :param foursquare_id:
         :param foursquare_type:
         :param disable_notification:
+        :param allow_sending_without_reply:
         :param reply_markup:
         :return:
         """
@@ -1266,6 +1307,7 @@ class Message(TelegramObject):
             foursquare_type=foursquare_type,
             disable_notification=disable_notification,
             reply_to_message_id=self.message_id,
+            allow_sending_without_reply=allow_sending_without_reply,
             reply_markup=reply_markup,
         )
 
@@ -1321,6 +1363,7 @@ class Message(TelegramObject):
         parse_mode: Optional[str] = UNSET,
         supports_streaming: Optional[bool] = None,
         disable_notification: Optional[bool] = None,
+        allow_sending_without_reply: Optional[bool] = None,
         reply_markup: Optional[
             Union[InlineKeyboardMarkup, ReplyKeyboardMarkup, ReplyKeyboardRemove, ForceReply]
         ] = None,
@@ -1337,6 +1380,7 @@ class Message(TelegramObject):
         :param parse_mode:
         :param supports_streaming:
         :param disable_notification:
+        :param allow_sending_without_reply:
         :param reply_markup:
         :return:
         """
@@ -1354,6 +1398,7 @@ class Message(TelegramObject):
             supports_streaming=supports_streaming,
             disable_notification=disable_notification,
             reply_to_message_id=self.message_id,
+            allow_sending_without_reply=allow_sending_without_reply,
             reply_markup=reply_markup,
         )
 
@@ -1411,6 +1456,7 @@ class Message(TelegramObject):
         length: Optional[int] = None,
         thumb: Optional[Union[InputFile, str]] = None,
         disable_notification: Optional[bool] = None,
+        allow_sending_without_reply: Optional[bool] = None,
         reply_markup: Optional[
             Union[InlineKeyboardMarkup, ReplyKeyboardMarkup, ReplyKeyboardRemove, ForceReply]
         ] = None,
@@ -1423,6 +1469,7 @@ class Message(TelegramObject):
         :param length:
         :param thumb:
         :param disable_notification:
+        :param allow_sending_without_reply:
         :param reply_markup:
         :return:
         """
@@ -1436,6 +1483,7 @@ class Message(TelegramObject):
             thumb=thumb,
             disable_notification=disable_notification,
             reply_to_message_id=self.message_id,
+            allow_sending_without_reply=allow_sending_without_reply,
             reply_markup=reply_markup,
         )
 
@@ -1481,6 +1529,7 @@ class Message(TelegramObject):
         parse_mode: Optional[str] = UNSET,
         duration: Optional[int] = None,
         disable_notification: Optional[bool] = None,
+        allow_sending_without_reply: Optional[bool] = None,
         reply_markup: Optional[
             Union[InlineKeyboardMarkup, ReplyKeyboardMarkup, ReplyKeyboardRemove, ForceReply]
         ] = None,
@@ -1493,6 +1542,7 @@ class Message(TelegramObject):
         :param parse_mode:
         :param duration:
         :param disable_notification:
+        :param allow_sending_without_reply:
         :param reply_markup:
         :return:
         """
@@ -1506,6 +1556,7 @@ class Message(TelegramObject):
             duration=duration,
             disable_notification=disable_notification,
             reply_to_message_id=self.message_id,
+            allow_sending_without_reply=allow_sending_without_reply,
             reply_markup=reply_markup,
         )
 


Test patch:
diff --git a/tests/test_api/test_methods/test_answer_inline_query.py b/tests/test_api/test_methods/test_answer_inline_query.py
index 6b15cb8c..98227b57 100644
--- a/tests/test_api/test_methods/test_answer_inline_query.py
+++ b/tests/test_api/test_methods/test_answer_inline_query.py
@@ -2,7 +2,7 @@ import pytest
 
 from aiogram import Bot
 from aiogram.methods import AnswerInlineQuery, Request
-from aiogram.types import InlineQueryResult, InlineQueryResultPhoto
+from aiogram.types import InlineQueryResult, InlineQueryResultPhoto, InputTextMessageContent
 from tests.mocked_bot import MockedBot
 
 pytestmark = pytest.mark.asyncio
@@ -40,3 +40,22 @@ class TestAnswerInlineQuery:
         new_bot = Bot(token="42:TEST", parse_mode="HTML")
         request = query.build_request(new_bot)
         assert request.data["results"][0]["parse_mode"] == "HTML"
+
+    def test_parse_mode_input_message_content(self, bot: MockedBot):
+        query = AnswerInlineQuery(
+            inline_query_id="query id",
+            results=[
+                InlineQueryResultPhoto(
+                    id="result id",
+                    photo_url="photo",
+                    thumb_url="thumb",
+                    input_message_content=InputTextMessageContent(message_text="test"),
+                )
+            ],
+        )
+        request = query.build_request(bot)
+        assert request.data["results"][0]["input_message_content"]["parse_mode"] is None
+
+        new_bot = Bot(token="42:TEST", parse_mode="HTML")
+        request = query.build_request(new_bot)
+        assert request.data["results"][0]["input_message_content"]["parse_mode"] == "HTML"
diff --git a/tests/test_dispatcher/test_fsm/test_state.py b/tests/test_dispatcher/test_fsm/test_state.py
index 07037e86..04c8e448 100644
--- a/tests/test_dispatcher/test_fsm/test_state.py
+++ b/tests/test_dispatcher/test_fsm/test_state.py
@@ -139,8 +139,7 @@ class TestStatesGroup:
         assert MyGroup.state1 not in MyGroup.MyNestedGroup
         assert MyGroup.state1 in MyGroup
 
-        # Not working as well
-        # assert MyGroup.MyNestedGroup in MyGroup
+        assert MyGroup.MyNestedGroup in MyGroup
 
         assert "MyGroup.MyNestedGroup:state1" in MyGroup
         assert "MyGroup.MyNestedGroup:state1" in MyGroup.MyNestedGroup
@@ -150,3 +149,36 @@ class TestStatesGroup:
         assert 42 not in MyGroup
 
         assert MyGroup.MyNestedGroup.get_root() is MyGroup
+
+    def test_empty_filter(self):
+        class MyGroup(StatesGroup):
+            pass
+
+        assert str(MyGroup()) == "StatesGroup MyGroup"
+
+    def test_with_state_filter(self):
+        class MyGroup(StatesGroup):
+            state1 = State()
+            state2 = State()
+
+        assert MyGroup()(None, "MyGroup:state1")
+        assert MyGroup()(None, "MyGroup:state2")
+        assert not MyGroup()(None, "MyGroup:state3")
+
+        assert str(MyGroup()) == "StatesGroup MyGroup"
+
+    def test_nested_group_filter(self):
+        class MyGroup(StatesGroup):
+            state1 = State()
+
+            class MyNestedGroup(StatesGroup):
+                state1 = State()
+
+        assert MyGroup()(None, "MyGroup:state1")
+        assert MyGroup()(None, "MyGroup.MyNestedGroup:state1")
+        assert not MyGroup()(None, "MyGroup:state2")
+        assert MyGroup.MyNestedGroup()(None, "MyGroup.MyNestedGroup:state1")
+        assert not MyGroup.MyNestedGroup()(None, "MyGroup:state1")
+
+        assert str(MyGroup()) == "StatesGroup MyGroup"
+        assert str(MyGroup.MyNestedGroup()) == "StatesGroup MyGroup.MyNestedGroup"