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

Issue:
Deepcopying `telegram.Bot` should raise a clear exception
We already have `Bot.__reduce__` in place which produces an exception if one tries to pickle a Bot, but deepcopying bots does not raise an immediate exception as apparently the superclasses custom `TelegramObject.__deepcopy__` has more priority than `__reduce__`.
Instead deepcopy will with a mysterious error message about being anable to pickle locks, which are buried somewhere in the httpx clients …

It's probably best to just override `__deepcopy__` in analogy to `__reduce__`


Discussion:

Resolving patch:
diff --git a/telegram/_bot.py b/telegram/_bot.py
--- a/telegram/_bot.py
+++ b/telegram/_bot.py
@@ -140,7 +140,8 @@ class Bot(TelegramObject, AbstractAsyncContextManager):
           passing files.
         * Bots should not be serialized since if you for e.g. change the bots token, then your
           serialized instance will not reflect that change. Trying to pickle a bot instance will
-          raise :exc:`pickle.PicklingError`.
+          raise :exc:`pickle.PicklingError`. Trying to deepcopy a bot instance will raise
+          :exc:`TypeError`.
 
     Examples:
         :any:`Raw API Bot <examples.rawapibot>`
@@ -166,6 +167,7 @@ class Bot(TelegramObject, AbstractAsyncContextManager):
           :class:`telegram.ext.Defaults`, please use the subclass :class:`telegram.ext.ExtBot`
           instead.
         * Attempting to pickle a bot instance will now raise :exc:`pickle.PicklingError`.
+        * Attempting to deepcopy a bot instance will now raise :exc:`TypeError`.
         * The following are now keyword-only arguments in Bot methods:
           ``location``, ``filename``, ``venue``, ``contact``,
           ``{read, write, connect, pool}_timeout``, ``api_kwargs``. Use a named argument for those,
@@ -299,9 +301,27 @@ def private_key(self) -> Optional[Any]:
         return self._private_key
 
     def __reduce__(self) -> NoReturn:
-        """Called by pickle.dumps(). Serializing bots is unadvisable, so we forbid pickling."""
+        """Customizes how :func:`copy.deepcopy` processes objects of this type. Bots can not
+        be pickled and this method will always raise an exception.
+
+        .. versionadded:: 20.0
+
+        Raises:
+            :exc:`pickle.PicklingError`
+        """
         raise pickle.PicklingError("Bot objects cannot be pickled!")
 
+    def __deepcopy__(self, memodict: dict) -> NoReturn:
+        """Customizes how :func:`copy.deepcopy` processes objects of this type. Bots can not
+        be deepcopied and this method will always raise an exception.
+
+        .. versionadded:: 20.0
+
+        Raises:
+            :exc:`TypeError`
+        """
+        raise TypeError("Bot objects cannot be deepcopied!")
+
     # TODO: After https://youtrack.jetbrains.com/issue/PY-50952 is fixed, we can revisit this and
     # consider adding Paramspec from typing_extensions to properly fix this. Currently a workaround
     def _log(func: Any):  # type: ignore[no-untyped-def] # skipcq: PY-D0003


Test patch:
diff --git a/tests/test_bot.py b/tests/test_bot.py
--- a/tests/test_bot.py
+++ b/tests/test_bot.py
@@ -426,6 +426,10 @@ def test_bot_pickling_error(self, bot):
         with pytest.raises(pickle.PicklingError, match="Bot objects cannot be pickled"):
             pickle.dumps(bot)
 
+    def test_bot_deepcopy_error(self, bot):
+        with pytest.raises(TypeError, match="Bot objects cannot be deepcopied"):
+            copy.deepcopy(bot)
+
     @bot_methods(ext_bot=False)
     async def test_defaults_handling(
         self, bot_class, bot_method_name, bot_method, bot, raw_bot, monkeypatch