Repository: apache/airflow

Issue:
Support telegram-bot v20+
### Body

Currently our telegram integration uses Telegram v13 telegram-bot library. On 1st of Jan 2023 a new, backwards incompatible version of Telegram-bot has been released : https://pypi.org/project/python-telegram-bot/20.0/#history and at least as reported by MyPy and our test suite test failures, Telegram 20 needs some changes to work:

Here is a transition guide that might be helpful. 

Transition guide is here: https://github.com/python-telegram-bot/python-telegram-bot/wiki/Transition-guide-to-Version-20.0

In the meantime we limit telegram to < 20.0.0

### Committer

- [X] I acknowledge that I am a maintainer/committer of the Apache Airflow project.


Discussion:
@potiuk happy to take a look and work on this
Assigned you !

Resolving patch:
diff --git a/airflow/providers/telegram/hooks/telegram.py b/airflow/providers/telegram/hooks/telegram.py
--- a/airflow/providers/telegram/hooks/telegram.py
+++ b/airflow/providers/telegram/hooks/telegram.py
@@ -18,6 +18,8 @@
 """Hook for Telegram"""
 from __future__ import annotations
 
+import asyncio
+
 import telegram
 import tenacity
 
@@ -67,13 +69,13 @@ def __init__(
         self.chat_id = self.__get_chat_id(chat_id, telegram_conn_id)
         self.connection = self.get_conn()
 
-    def get_conn(self) -> telegram.bot.Bot:
+    def get_conn(self) -> telegram.Bot:
         """
         Returns the telegram bot client
 
         :return: telegram bot client
         """
-        return telegram.bot.Bot(token=self.token)
+        return telegram.Bot(self.token)
 
     def __get_token(self, token: str | None, telegram_conn_id: str | None) -> str:
         """
@@ -126,7 +128,7 @@ def send_message(self, api_params: dict) -> None:
         """
         kwargs = {
             "chat_id": self.chat_id,
-            "parse_mode": telegram.parsemode.ParseMode.HTML,
+            "parse_mode": telegram.constants.ParseMode.HTML,
             "disable_web_page_preview": True,
         }
         kwargs.update(api_params)
@@ -137,5 +139,5 @@ def send_message(self, api_params: dict) -> None:
         if kwargs["chat_id"] is None:
             raise AirflowException("'chat_id' must be provided for telegram message")
 
-        response = self.connection.send_message(**kwargs)
+        response = asyncio.run(self.connection.send_message(**kwargs))
         self.log.debug(response)


Test patch:
diff --git a/tests/providers/telegram/hooks/test_telegram.py b/tests/providers/telegram/hooks/test_telegram.py
--- a/tests/providers/telegram/hooks/test_telegram.py
+++ b/tests/providers/telegram/hooks/test_telegram.py
@@ -30,6 +30,11 @@
 TELEGRAM_TOKEN = "dummy token"
 
 
+class AsyncMock(mock.MagicMock):
+    async def __call__(self, *args, **kwargs):
+        return super(AsyncMock, self).__call__(*args, **kwargs)
+
+
 class TestTelegramHook:
     def setup_method(self):
         db.merge_conn(
@@ -90,7 +95,7 @@ def test_should_raise_exception_if_message_text_is_not_provided(self, mock_get_c
 
     @mock.patch("airflow.providers.telegram.hooks.telegram.TelegramHook.get_conn")
     def test_should_send_message_if_all_parameters_are_correctly_provided(self, mock_get_conn):
-        mock_get_conn.return_value = mock.Mock(password="some_token")
+        mock_get_conn.return_value = AsyncMock(password="some_token")
 
         hook = TelegramHook(telegram_conn_id="telegram_default")
         hook.send_message({"chat_id": -420913222, "text": "test telegram message"})
@@ -109,7 +114,7 @@ def test_should_send_message_if_all_parameters_are_correctly_provided(self, mock
 
     @mock.patch("airflow.providers.telegram.hooks.telegram.TelegramHook.get_conn")
     def test_should_send_message_if_chat_id_is_provided_through_constructor(self, mock_get_conn):
-        mock_get_conn.return_value = mock.Mock(password="some_token")
+        mock_get_conn.return_value = AsyncMock(password="some_token")
 
         hook = TelegramHook(telegram_conn_id="telegram_default", chat_id=-420913222)
         hook.send_message({"text": "test telegram message"})
@@ -128,7 +133,7 @@ def test_should_send_message_if_chat_id_is_provided_through_constructor(self, mo
 
     @mock.patch("airflow.providers.telegram.hooks.telegram.TelegramHook.get_conn")
     def test_should_send_message_if_chat_id_is_provided_in_connection(self, mock_get_conn):
-        mock_get_conn.return_value = mock.Mock(password="some_token")
+        mock_get_conn.return_value = AsyncMock(password="some_token")
 
         hook = TelegramHook(telegram_conn_id="telegram-webhook-with-chat_id")
         hook.send_message({"text": "test telegram message"})
@@ -148,7 +153,7 @@ def test_should_send_message_if_chat_id_is_provided_in_connection(self, mock_get
     @mock.patch("airflow.providers.telegram.hooks.telegram.TelegramHook.get_conn")
     def test_should_retry_when_any_telegram_error_is_encountered(self, mock_get_conn):
         excepted_retry_count = 5
-        mock_get_conn.return_value = mock.Mock(password="some_token")
+        mock_get_conn.return_value = AsyncMock(password="some_token")
 
         def side_effect(*args, **kwargs):
             raise telegram.error.TelegramError("cosmic rays caused bit flips")
@@ -175,7 +180,7 @@ def side_effect(*args, **kwargs):
 
     @mock.patch("airflow.providers.telegram.hooks.telegram.TelegramHook.get_conn")
     def test_should_send_message_if_token_is_provided(self, mock_get_conn):
-        mock_get_conn.return_value = mock.Mock(password="some_token")
+        mock_get_conn.return_value = AsyncMock(password="some_token")
 
         hook = TelegramHook(token=TELEGRAM_TOKEN, chat_id=-420913222)
         hook.send_message({"text": "test telegram message"})