Repository: python-telegram-bot/python-telegram-bot

Issue:
[BUG] Object of type InlineQueryResultArticle is not JSON serializable
### Steps to Reproduce

Trying to send data from webapp to bot with 
```
        bot.answerWebAppQuery(
            web_app_query_id=str(payload.query_id),
            result=InlineQueryResultArticle(
                id=str(uuid.uuid4()),
                title="Caps",
                input_message_content=InputTextMessageContent('text'),
            )
        )
```

### Expected behaviour

no error

### Actual behaviour

but got error Object of type InlineQueryResultArticle is not JSON serializable

### Operating System

windows 11 x64

### Version of Python, python-telegram-bot & dependencies

```shell
python-telegram-bot 13.14 (v2.0.1-78-g9483410)
Bot API 6.2
certifi2022.06.15
Python 3.10.5 (tags/v3.10.5:f377153, Jun  6 2022, 16:14:13) [MSC v.1929 64 bit (AMD64)]
```


### Relevant log output

_No response_

### Additional Context

_No response_


Discussion:
Hi. Can you please show the full traceback?
```
Object of type InlineQueryResultArticle is not JSON serializable
  File "C:\dev\gpn\takeaway_app\takeaway\api.py", line 119, in create_order
    bot.answerWebAppQuery(
  File "C:\dev\gpn\takeaway_app\venv\lib\site-packages\telegram\bot.py", line 133, in decorator
    result = func(*args, **kwargs)
  File "C:\dev\gpn\takeaway_app\venv\lib\site-packages\telegram\bot.py", line 3944, in answer_web_app_query
    api_result = self._post(
  File "C:\dev\gpn\takeaway_app\venv\lib\site-packages\telegram\bot.py", line 298, in _post
    return self.request.post(
  File "C:\dev\gpn\takeaway_app\venv\lib\site-packages\telegram\utils\request.py", line 364, in post
    body=json.dumps(data).encode('utf-8'),
  File "C:\Users\Shamil\AppData\Local\Programs\Python\Python310\lib\json\__init__.py", line 231, in dumps
    return _default_encoder.encode(obj)
  File "C:\Users\Shamil\AppData\Local\Programs\Python\Python310\lib\json\encoder.py", line 199, in encode
    chunks = self.iterencode(o, _one_shot=True)
  File "C:\Users\Shamil\AppData\Local\Programs\Python\Python310\lib\json\encoder.py", line 257, in iterencode
    return _iterencode(o, 0)
  File "C:\Users\Shamil\AppData\Local\Programs\Python\Python310\lib\json\encoder.py", line 179, in default
    raise TypeError(f'Object of type {o.__class__.__name__} '
```

![image](https://user-images.githubusercontent.com/55530995/201461730-acfa5975-e376-4f57-b4f1-e740dff531e9.png)

Finally, I found the working state, but I'm not sure about the correct logic for interacting with ptb
```
        bot.answerWebAppQuery(
            web_app_query_id=str(payload.query_id),
            result=InlineQueryResultArticle(
                id=str(uuid.uuid4()),
                title="Caps",
                input_message_content=InputTextMessageContent(
                    message_text='text',
                    parse_mode=telegram.ParseMode.MARKDOWN, #   default values not working, need to set manual
                    disable_web_page_preview=True   #   default values not working, need to set manual
                ),
            ).to_json() # convert to json here 
        )
```
Thanks for the updates. I can reproduce. The method is indeed faulty both on v13 and on v20.

## Missing serialization on v13

https://github.com/python-telegram-bot/python-telegram-bot/blob/b818dadbad7e1f70f11a6c384bc032fc413c3b84/telegram/bot.py#L3942

In this line, we should have `result.to_dict()`. The corresponding unit test is also missing that.

## Defaults handling on both versions

Both versions miss setting the default values for the inline result (and even removing the `DEFAULT_NONE` references). We have corresponding code already in place that we can reuse. On v13, that's function `_set_defaults` currently defined as local function within `answer_inline_query`. On v20, we have `_insert_defaults_for_ilq_results`. We'd have to call these methods and also add corresponding uni tests

## Actions items

We definitely need a fix for the defaults behavior on v20.
Since these bugs are related to a bot method, I'd also vote to fix both on v13, as that can be seen to fall under "API compatibility" IMO.


Definitely fix it on V13.

Resolving patch:
diff --git a/telegram/bot.py b/telegram/bot.py
--- a/telegram/bot.py
+++ b/telegram/bot.py
@@ -2200,6 +2200,36 @@ def _effective_inline_results(  # pylint: disable=R0201
 
         return effective_results, next_offset
 
+    @no_type_check
+    def _set_ilq_result_defaults(self, res):
+        # pylint: disable=W0212
+        if hasattr(res, 'parse_mode') and res.parse_mode == DEFAULT_NONE:
+            if self.defaults:
+                res.parse_mode = self.defaults.parse_mode
+            else:
+                res.parse_mode = None
+        if hasattr(res, 'input_message_content') and res.input_message_content:
+            if (
+                hasattr(res.input_message_content, 'parse_mode')
+                and res.input_message_content.parse_mode == DEFAULT_NONE
+            ):
+                if self.defaults:
+                    res.input_message_content.parse_mode = DefaultValue.get_value(
+                        self.defaults.parse_mode
+                    )
+                else:
+                    res.input_message_content.parse_mode = None
+            if (
+                hasattr(res.input_message_content, 'disable_web_page_preview')
+                and res.input_message_content.disable_web_page_preview == DEFAULT_NONE
+            ):
+                if self.defaults:
+                    res.input_message_content.disable_web_page_preview = DefaultValue.get_value(
+                        self.defaults.disable_web_page_preview
+                    )
+                else:
+                    res.input_message_content.disable_web_page_preview = None
+
     @log
     def answer_inline_query(
         self,
@@ -2274,43 +2304,13 @@ def answer_inline_query(
 
         """
 
-        @no_type_check
-        def _set_defaults(res):
-            # pylint: disable=W0212
-            if hasattr(res, 'parse_mode') and res.parse_mode == DEFAULT_NONE:
-                if self.defaults:
-                    res.parse_mode = self.defaults.parse_mode
-                else:
-                    res.parse_mode = None
-            if hasattr(res, 'input_message_content') and res.input_message_content:
-                if (
-                    hasattr(res.input_message_content, 'parse_mode')
-                    and res.input_message_content.parse_mode == DEFAULT_NONE
-                ):
-                    if self.defaults:
-                        res.input_message_content.parse_mode = DefaultValue.get_value(
-                            self.defaults.parse_mode
-                        )
-                    else:
-                        res.input_message_content.parse_mode = None
-                if (
-                    hasattr(res.input_message_content, 'disable_web_page_preview')
-                    and res.input_message_content.disable_web_page_preview == DEFAULT_NONE
-                ):
-                    if self.defaults:
-                        res.input_message_content.disable_web_page_preview = (
-                            DefaultValue.get_value(self.defaults.disable_web_page_preview)
-                        )
-                    else:
-                        res.input_message_content.disable_web_page_preview = None
-
         effective_results, next_offset = self._effective_inline_results(
             results=results, next_offset=next_offset, current_offset=current_offset
         )
 
         # Apply defaults
         for result in effective_results:
-            _set_defaults(result)
+            self._set_ilq_result_defaults(result)
 
         results_dicts = [res.to_dict() for res in effective_results]
 
@@ -3939,7 +3939,8 @@ def answer_web_app_query(
         Raises:
             :class:`telegram.error.TelegramError`
         """
-        data: JSONDict = {'web_app_query_id': web_app_query_id, 'result': result}
+        self._set_ilq_result_defaults(result)
+        data: JSONDict = {'web_app_query_id': web_app_query_id, 'result': result.to_dict()}
 
         api_result = self._post(
             'answerWebAppQuery',


Test patch:
diff --git a/tests/conftest.py b/tests/conftest.py
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -44,6 +44,8 @@
     ChosenInlineResult,
     File,
     ChatPermissions,
+    InlineQueryResultArticle,
+    InputTextMessageContent,
 )
 from telegram.ext import (
     Dispatcher,
@@ -547,6 +549,12 @@ def build_kwargs(signature: inspect.Signature, default_kwargs, dfv: Any = DEFAUL
                 # Some special casing
                 if name == 'permissions':
                     kws[name] = ChatPermissions()
+                elif name == 'result':
+                    kws[name] = InlineQueryResultArticle(
+                        id='id',
+                        title='title',
+                        input_message_content=InputTextMessageContent('content'),
+                    )
                 elif name in ['prices', 'media', 'results', 'commands', 'errors']:
                     kws[name] = []
                 elif name == 'ok':
diff --git a/tests/test_bot.py b/tests/test_bot.py
--- a/tests/test_bot.py
+++ b/tests/test_bot.py
@@ -769,7 +769,14 @@ def make_assertion(url, data, *args, **kwargs):
             nonlocal params
             params = data == {
                 'web_app_query_id': '12345',
-                'result': result,
+                'result': {
+                    "title": "title",
+                    "input_message_content": {
+                        "message_text": "text",
+                    },
+                    "type": "article",
+                    "id": "1",
+                },
             }
             web_app_msg = SentWebAppMessage('321').to_dict()
             return web_app_msg
@@ -825,6 +832,96 @@ def test(url, data, *args, **kwargs):
         )
         monkeypatch.delattr(bot.request, 'post')
 
+    @pytest.mark.parametrize(
+        "default_bot",
+        [{"parse_mode": "Markdown", "disable_web_page_preview": True}],
+        indirect=True,
+    )
+    @pytest.mark.parametrize(
+        "ilq_result,expected_params",
+        [
+            (
+                InlineQueryResultArticle("1", "title", InputTextMessageContent("text")),
+                {
+                    "web_app_query_id": "12345",
+                    "result": {
+                        "title": "title",
+                        "input_message_content": {
+                            "message_text": "text",
+                            "parse_mode": "Markdown",
+                            "disable_web_page_preview": True,
+                        },
+                        "type": "article",
+                        "id": "1",
+                    },
+                },
+            ),
+            (
+                InlineQueryResultArticle(
+                    "1",
+                    "title",
+                    InputTextMessageContent(
+                        "text", parse_mode="HTML", disable_web_page_preview=False
+                    ),
+                ),
+                {
+                    "web_app_query_id": "12345",
+                    "result": {
+                        "title": "title",
+                        "input_message_content": {
+                            "message_text": "text",
+                            "parse_mode": "HTML",
+                            "disable_web_page_preview": False,
+                        },
+                        "type": "article",
+                        "id": "1",
+                    },
+                },
+            ),
+            (
+                InlineQueryResultArticle(
+                    "1",
+                    "title",
+                    InputTextMessageContent(
+                        "text", parse_mode=None, disable_web_page_preview="False"
+                    ),
+                ),
+                {
+                    "web_app_query_id": "12345",
+                    "result": {
+                        "title": "title",
+                        "input_message_content": {
+                            "message_text": "text",
+                            "disable_web_page_preview": "False",
+                        },
+                        "type": "article",
+                        "id": "1",
+                    },
+                },
+            ),
+        ],
+    )
+    def test_answer_web_app_query_defaults(
+        self, default_bot, ilq_result, expected_params, monkeypatch
+    ):
+        bot = default_bot
+        params = False
+
+        # For now just test that our internals pass the correct data
+
+        def make_assertion(url, data, *args, **kwargs):
+            nonlocal params
+            params = data == expected_params
+            web_app_msg = SentWebAppMessage("321").to_dict()
+            return web_app_msg
+
+        monkeypatch.setattr(bot.request, "post", make_assertion)
+
+        web_app_msg = bot.answer_web_app_query("12345", ilq_result)
+        assert params, "something went wrong with passing arguments to the request"
+        assert isinstance(web_app_msg, SentWebAppMessage)
+        assert web_app_msg.inline_message_id == "321"
+
     def test_answer_inline_query_no_default_parse_mode(self, monkeypatch, bot):
         def test(url, data, *args, **kwargs):
             return data == {
diff --git a/tests/test_constants.py b/tests/test_constants.py
--- a/tests/test_constants.py
+++ b/tests/test_constants.py
@@ -44,6 +44,6 @@ def test_max_caption_length(self, bot, chat_id):
         bad_caption = good_caption + 'Z'
         with pytest.raises(
             BadRequest,
-            match="Media_caption_too_long",
+            match="Message caption is too long",
         ), open('tests/data/telegram.png', 'rb') as f:
             bot.send_photo(photo=f, caption=bad_caption, chat_id=chat_id)