Repository: python-telegram-bot/python-telegram-bot
Pull request: Add docs for ctx managers
closes #3886 
I wonder if we should add docstring for args in `__aexit__`?

Problem statement:
[Documentation]: Context manager dunder Methods
The dunder methods `__aenter__` and `__aexit__` that are implemented in several classes in `tg.(ext)` (Updater, Application, Bot, BaseRequest, BaseUpdateProcessor) should get docstrings that explain how these methods behave.

* Since the behavior is very similar in most cases, usage of sphinx insertions may be beneficial
* The notes that we currently have in the class docstring regarding context manager behavior can possibly be (party) replaced by references to these methods
* Sphinx needs to be configured to show these docstrings

Hints from issue discussion:

Commit message: add docs for ctx managers

Patch to review:
diff --git a/docs/source/conf.py b/docs/source/conf.py
index bcbc33e9e8f..c27eb8ef18c 100644
--- a/docs/source/conf.py
+++ b/docs/source/conf.py
@@ -77,6 +77,13 @@
 # and we document the types anyway
 autodoc_typehints = "none"
 
+# Show docstring for special members
+autodoc_special_members = ["__aenter__", "__aexit__"]
+
+autodoc_default_options = {
+    "special-members": True,
+}
+
 # Fail on warnings & unresolved references etc
 nitpicky = True
 
diff --git a/docs/substitutions/global.rst b/docs/substitutions/global.rst
index 151e347afd7..bc5fc74e130 100644
--- a/docs/substitutions/global.rst
+++ b/docs/substitutions/global.rst
@@ -61,3 +61,5 @@
 .. |removed_thumb_url_note| replace:: Removed the deprecated argument and attribute ``thumb_url``.
 
 .. |removed_thumb_wildcard_note| replace:: Removed the deprecated arguments and attributes ``thumb_*``.
+
+.. |async_context_manager| replace:: Asynchronous context manager which
\ No newline at end of file
diff --git a/telegram/_bot.py b/telegram/_bot.py
index 799c57c520e..de79755339a 100644
--- a/telegram/_bot.py
+++ b/telegram/_bot.py
@@ -310,6 +310,18 @@ def __init__(
         self._freeze()
 
     async def __aenter__(self: BT) -> BT:
+        """
+        |async_context_manager| initializes the Bot.
+
+        If no exceptions are raised during initialization, returns the Bot instance.
+        If an exception is raised, shuts down the Bot and re-raises the exception.
+
+        Returns:
+            The initialized Bot instance.
+
+        Raises:
+            :exc:`Exception`: If an exception is raised during initialization.
+        """
         try:
             await self.initialize()
             return self
@@ -323,6 +335,12 @@ async def __aexit__(
         exc_val: Optional[BaseException],
         exc_tb: Optional[TracebackType],
     ) -> None:
+        """
+        |async_context_manager| shuts down the Bot.
+
+        This function is called when exiting the context manager.
+        It shuts down the Bot and does not suppress any exceptions.
+        """
         # Make sure not to return `True` so that exceptions are not suppressed
         # https://docs.python.org/3/reference/datamodel.html?#object.__aexit__
         await self.shutdown()
diff --git a/telegram/ext/_application.py b/telegram/ext/_application.py
index e7970975ce9..5b4bd059670 100644
--- a/telegram/ext/_application.py
+++ b/telegram/ext/_application.py
@@ -345,7 +345,17 @@ def __init__(
         self.__create_task_tasks: Set[asyncio.Task] = set()  # Used for awaiting tasks upon exit
 
     async def __aenter__(self: _AppType) -> _AppType:  # noqa: PYI019
-        """Simple context manager which initializes the App."""
+        """|async_context_manager| initializes the App.
+
+        If no exceptions are raised during initialization, returns the App instance.
+        If an exception is raised, shuts down the App and re-raises the exception.
+
+        Returns:
+            The initialized App instance.
+
+        Raises:
+            :exc:`Exception`: If an exception is raised during initialization.
+        """
         try:
             await self.initialize()
             return self
@@ -359,7 +369,11 @@ async def __aexit__(
         exc_val: Optional[BaseException],
         exc_tb: Optional[TracebackType],
     ) -> None:
-        """Shutdown the App from the context manager."""
+        """|async_context_manager| shuts down the App.
+
+        This function is called when exiting the context manager.
+        It shuts down the App and does not suppress any exceptions.
+        """
         # Make sure not to return `True` so that exceptions are not suppressed
         # https://docs.python.org/3/reference/datamodel.html?#object.__aexit__
         await self.shutdown()
diff --git a/telegram/ext/_baseupdateprocessor.py b/telegram/ext/_baseupdateprocessor.py
index 42baa02da9d..32470156066 100644
--- a/telegram/ext/_baseupdateprocessor.py
+++ b/telegram/ext/_baseupdateprocessor.py
@@ -49,7 +49,17 @@ def __init__(self, max_concurrent_updates: int):
         self._semaphore = BoundedSemaphore(self.max_concurrent_updates)
 
     async def __aenter__(self) -> "BaseUpdateProcessor":
-        """Simple context manager which initializes the Processor."""
+        """|async_context_manager| initializes the Processor.
+
+        If no exceptions are raised during initialization, returns the Processor instance.
+        If an exception is raised, shuts down the Processor and re-raises the exception.
+
+        Returns:
+            The initialized Processor instance.
+
+        Raises:
+            :exc:`Exception`: If an exception is raised during initialization.
+        """
         try:
             await self.initialize()
             return self
@@ -63,7 +73,11 @@ async def __aexit__(
         exc_val: Optional[BaseException],
         exc_tb: Optional[TracebackType],
     ) -> None:
-        """Simple context manager which shuts down the Processor."""
+        """|async_context_manager| shuts down the Processor.
+
+        This function is called when exiting the context manager.
+        It shuts down the Processor and does not suppress any exceptions.
+        """
         await self.shutdown()
 
     @property
diff --git a/telegram/ext/_updater.py b/telegram/ext/_updater.py
index 740ca60d4ea..4490144cfcd 100644
--- a/telegram/ext/_updater.py
+++ b/telegram/ext/_updater.py
@@ -126,7 +126,18 @@ def __init__(
         self.__polling_cleanup_cb: Optional[Callable[[], Coroutine[Any, Any, None]]] = None
 
     async def __aenter__(self: _UpdaterType) -> _UpdaterType:  # noqa: PYI019
-        """Simple context manager which initializes the Updater."""
+        """
+        |async_context_manager| initializes the Updater.
+
+        If no exceptions are raised during initialization, returns the Updater instance.
+        If an exception is raised, shuts down the Updater and re-raises the exception.
+
+        Returns:
+            The initialized Updater instance.
+
+        Raises:
+            :exc:`Exception`: If an exception is raised during initialization.
+        """
         try:
             await self.initialize()
             return self
@@ -140,7 +151,12 @@ async def __aexit__(
         exc_val: Optional[BaseException],
         exc_tb: Optional[TracebackType],
     ) -> None:
-        """Shutdown the Updater from the context manager."""
+        """
+        |async_context_manager| shuts down the Updater.
+
+        This function is called when exiting the context manager.
+        It shuts down the Updater and does not suppress any exceptions.
+        """
         # Make sure not to return `True` so that exceptions are not suppressed
         # https://docs.python.org/3/reference/datamodel.html?#object.__aexit__
         await self.shutdown()
diff --git a/telegram/request/_baserequest.py b/telegram/request/_baserequest.py
index 28404ca2347..4ab1144eac0 100644
--- a/telegram/request/_baserequest.py
+++ b/telegram/request/_baserequest.py
@@ -99,6 +99,17 @@ class BaseRequest(
     """
 
     async def __aenter__(self: RT) -> RT:
+        """|async_context_manager| initializes the Request.
+
+        If no exceptions are raised during initialization, returns the Request instance.
+        If an exception is raised, shuts down the Request and re-raises the exception.
+
+        Returns:
+            The initialized Request instance.
+
+        Raises:
+            :exc:`Exception`: If an exception is raised during initialization.
+        """
         try:
             await self.initialize()
             return self
@@ -112,6 +123,12 @@ async def __aexit__(
         exc_val: Optional[BaseException],
         exc_tb: Optional[TracebackType],
     ) -> None:
+        """
+        |async_context_manager| shuts down the Request.
+
+        This function is called when exiting the context manager.
+        It shuts down the Request and does not suppress any exceptions.
+        """
         # Make sure not to return `True` so that exceptions are not suppressed
         # https://docs.python.org/3/reference/datamodel.html?#object.__aexit__
         await self.shutdown()


Code review comments:
diff_hunk: @@ -310,6 +310,18 @@ def __init__(
         self._freeze()
 
     async def __aenter__(self: BT) -> BT:
+        """
+        |async_context_manager| initializes the Bot.
+
+        If no exceptions are raised during initialization, returns the Bot instance.
path: telegram/_bot.py
text: @user1:
```suggestion
```
superflous imo, since mentioned in Returns block

diff_hunk: @@ -310,6 +310,18 @@ def __init__(
         self._freeze()
 
     async def __aenter__(self: BT) -> BT:
+        """
+        |async_context_manager| initializes the Bot.
+
+        If no exceptions are raised during initialization, returns the Bot instance.
+        If an exception is raised, shuts down the Bot and re-raises the exception.
path: telegram/_bot.py
text: @user1:
```suggestion
```
no need of that since it is mentioned in the raises block

diff_hunk: @@ -323,6 +335,12 @@ async def __aexit__(
         exc_val: Optional[BaseException],
         exc_tb: Optional[TracebackType],
     ) -> None:
+        """
+        |async_context_manager| shuts down the Bot.
+
+        This function is called when exiting the context manager.
+        It shuts down the Bot and does not suppress any exceptions.
path: telegram/_bot.py
text: @user1:
again no need of this imo