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

Issue:
Rename `Job.context`
# Current situation

The naming of `Job.context` regularly leads to confusion, because the `context` parameter of job/error/handler callbacks has the same name. Accessing that value in a job callback as `context.job.context` can be even more confusing.

# Proposed solution

Rename `Job.context` to `Job.data` and correspondingly also rename the `context` argument of `JobQueue.run_*` to `data`.

# ToDo

A PR for this should

* rename the parameters of the `JobQueue` methods and the attribute of the `Job` class
* update the docstrings accordingly
* add `.. versionchanged:: 20.0` directives to the documentation
* Update the unit tests accordingly. Letting the test suit run once to see which tests fail after the update should probably already find most tests that need adaption.
* Check if the `timerbot.py` example needs to be updated.

# Additional Info

We have a [contribution guide](https://python-telegram-bot.readthedocs.io/en/v20.0a0/contributing.html)


Discussion:

Resolving patch:
diff --git a/examples/timerbot.py b/examples/timerbot.py
--- a/examples/timerbot.py
+++ b/examples/timerbot.py
@@ -43,7 +43,7 @@ async def start(update: Update, context: CallbackContext.DEFAULT_TYPE) -> None:
 async def alarm(context: CallbackContext.DEFAULT_TYPE) -> None:
     """Send the alarm message."""
     job = context.job
-    await context.bot.send_message(job.chat_id, text=f"Beep! {job.context} seconds are over!")
+    await context.bot.send_message(job.chat_id, text=f"Beep! {job.data} seconds are over!")
 
 
 def remove_job_if_exists(name: str, context: CallbackContext.DEFAULT_TYPE) -> bool:
@@ -67,7 +67,7 @@ async def set_timer(update: Update, context: CallbackContext.DEFAULT_TYPE) -> No
             return
 
         job_removed = remove_job_if_exists(str(chat_id), context)
-        context.job_queue.run_once(alarm, due, chat_id=chat_id, name=str(chat_id), context=due)
+        context.job_queue.run_once(alarm, due, chat_id=chat_id, name=str(chat_id), data=due)
 
         text = "Timer successfully set!"
         if job_removed:
diff --git a/telegram/ext/_conversationhandler.py b/telegram/ext/_conversationhandler.py
--- a/telegram/ext/_conversationhandler.py
+++ b/telegram/ext/_conversationhandler.py
@@ -64,7 +64,7 @@
 @dataclass
 class _ConversationTimeoutContext(Generic[CCT]):
     """Used as a datastore for conversation timeouts. Passed in the
-    :paramref:`JobQueue.run_once.context` parameter. See :meth:`_trigger_timeout`.
+    :paramref:`JobQueue.run_once.data` parameter. See :meth:`_trigger_timeout`.
     """
 
     __slots__ = ("conversation_key", "update", "application", "callback_context")
@@ -666,9 +666,7 @@ def _schedule_job(
             self.timeout_jobs[conversation_key] = j_queue.run_once(
                 self._trigger_timeout,
                 self.conversation_timeout,  # type: ignore[arg-type]
-                context=_ConversationTimeoutContext(
-                    conversation_key, update, application, context
-                ),
+                data=_ConversationTimeoutContext(conversation_key, update, application, context),
             )
         except Exception as exc:
             _logger.exception("Failed to schedule timeout.", exc_info=exc)
@@ -884,7 +882,7 @@ async def _trigger_timeout(self, context: CallbackContext) -> None:
         :obj:`True` is handled.
         """
         job = cast("Job", context.job)
-        ctxt = cast(_ConversationTimeoutContext, job.context)
+        ctxt = cast(_ConversationTimeoutContext, job.data)
 
         _logger.debug(
             "Conversation timeout was triggered for conversation %s!", ctxt.conversation_key
diff --git a/telegram/ext/_jobqueue.py b/telegram/ext/_jobqueue.py
--- a/telegram/ext/_jobqueue.py
+++ b/telegram/ext/_jobqueue.py
@@ -121,7 +121,7 @@ def run_once(
         self,
         callback: JobCallback,
         when: Union[float, datetime.timedelta, datetime.datetime, datetime.time],
-        context: object = None,
+        data: object = None,
         name: str = None,
         chat_id: int = None,
         user_id: int = None,
@@ -163,9 +163,12 @@ async def callback(context: CallbackContext)
                 be available in the callback.
 
                 .. versionadded:: 20.0
-            context (:obj:`object`, optional): Additional data needed for the callback function.
-                Can be accessed through :attr:`Job.context` in the callback. Defaults to
+            data (:obj:`object`, optional): Additional data needed for the callback function.
+                Can be accessed through :attr:`Job.data` in the callback. Defaults to
                 :obj:`None`.
+
+                .. versionchanged:: 20.0
+                    Renamed the parameter ``context`` to :paramref:`data`.
             name (:obj:`str`, optional): The name of the new job. Defaults to
                 :external:attr:`callback.__name__ <definition.__name__>`.
             job_kwargs (:obj:`dict`, optional): Arbitrary keyword arguments to pass to the
@@ -180,7 +183,7 @@ async def callback(context: CallbackContext)
             job_kwargs = {}
 
         name = name or callback.__name__
-        job = Job(callback=callback, context=context, name=name, chat_id=chat_id, user_id=user_id)
+        job = Job(callback=callback, data=data, name=name, chat_id=chat_id, user_id=user_id)
         date_time = self._parse_time_input(when, shift_day=True)
 
         j = self.scheduler.add_job(
@@ -202,7 +205,7 @@ def run_repeating(
         interval: Union[float, datetime.timedelta],
         first: Union[float, datetime.timedelta, datetime.datetime, datetime.time] = None,
         last: Union[float, datetime.timedelta, datetime.datetime, datetime.time] = None,
-        context: object = None,
+        data: object = None,
         name: str = None,
         chat_id: int = None,
         user_id: int = None,
@@ -254,9 +257,12 @@ async def callback(context: CallbackContext)
                 assumed.
 
                 Defaults to :obj:`None`.
-            context (:obj:`object`, optional): Additional data needed for the callback function.
-                Can be accessed through :attr:`Job.context` in the callback. Defaults to
+            data (:obj:`object`, optional): Additional data needed for the callback function.
+                Can be accessed through :attr:`Job.data` in the callback. Defaults to
                 :obj:`None`.
+
+                .. versionchanged:: 20.0
+                    Renamed the parameter ``context`` to :paramref:`data`.
             name (:obj:`str`, optional): The name of the new job. Defaults to
                 :external:attr:`callback.__name__ <definition.__name__>`.
             chat_id (:obj:`int`, optional): Chat id of the chat associated with this job. If
@@ -282,7 +288,7 @@ async def callback(context: CallbackContext)
             job_kwargs = {}
 
         name = name or callback.__name__
-        job = Job(callback=callback, context=context, name=name, chat_id=chat_id, user_id=user_id)
+        job = Job(callback=callback, data=data, name=name, chat_id=chat_id, user_id=user_id)
 
         dt_first = self._parse_time_input(first)
         dt_last = self._parse_time_input(last)
@@ -312,7 +318,7 @@ def run_monthly(
         callback: JobCallback,
         when: datetime.time,
         day: int,
-        context: object = None,
+        data: object = None,
         name: str = None,
         chat_id: int = None,
         user_id: int = None,
@@ -336,9 +342,12 @@ async def callback(context: CallbackContext)
                 be within the range of 1 and 31, inclusive. If a month has fewer days than this
                 number, the job will not run in this month. Passing -1 leads to the job running on
                 the last day of the month.
-            context (:obj:`object`, optional): Additional data needed for the callback function.
-                Can be accessed through :attr:`Job.context` in the callback. Defaults to
+            data (:obj:`object`, optional): Additional data needed for the callback function.
+                Can be accessed through :attr:`Job.data` in the callback. Defaults to
                 :obj:`None`.
+
+                .. versionchanged:: 20.0
+                    Renamed the parameter ``context`` to :paramref:`data`.
             name (:obj:`str`, optional): The name of the new job. Defaults to
                 :external:attr:`callback.__name__ <definition.__name__>`.
             chat_id (:obj:`int`, optional): Chat id of the chat associated with this job. If
@@ -364,7 +373,7 @@ async def callback(context: CallbackContext)
             job_kwargs = {}
 
         name = name or callback.__name__
-        job = Job(callback=callback, context=context, name=name, chat_id=chat_id, user_id=user_id)
+        job = Job(callback=callback, data=data, name=name, chat_id=chat_id, user_id=user_id)
 
         j = self.scheduler.add_job(
             job.run,
@@ -386,7 +395,7 @@ def run_daily(
         callback: JobCallback,
         time: datetime.time,
         days: Tuple[int, ...] = tuple(range(7)),
-        context: object = None,
+        data: object = None,
         name: str = None,
         chat_id: int = None,
         user_id: int = None,
@@ -411,9 +420,12 @@ async def callback(context: CallbackContext)
                 be used.
             days (Tuple[:obj:`int`], optional): Defines on which days of the week the job should
                 run (where ``0-6`` correspond to monday - sunday). Defaults to ``EVERY_DAY``
-            context (:obj:`object`, optional): Additional data needed for the callback function.
-                Can be accessed through :attr:`Job.context` in the callback. Defaults to
+            data (:obj:`object`, optional): Additional data needed for the callback function.
+                Can be accessed through :attr:`Job.data` in the callback. Defaults to
                 :obj:`None`.
+
+                .. versionchanged:: 20.0
+                    Renamed the parameter ``context`` to :paramref:`data`.
             name (:obj:`str`, optional): The name of the new job. Defaults to
                 :external:attr:`callback.__name__ <definition.__name__>`.
             chat_id (:obj:`int`, optional): Chat id of the chat associated with this job. If
@@ -439,7 +451,7 @@ async def callback(context: CallbackContext)
             job_kwargs = {}
 
         name = name or callback.__name__
-        job = Job(callback=callback, context=context, name=name, chat_id=chat_id, user_id=user_id)
+        job = Job(callback=callback, data=data, name=name, chat_id=chat_id, user_id=user_id)
 
         j = self.scheduler.add_job(
             job.run,
@@ -461,7 +473,7 @@ def run_custom(
         self,
         callback: JobCallback,
         job_kwargs: JSONDict,
-        context: object = None,
+        data: object = None,
         name: str = None,
         chat_id: int = None,
         user_id: int = None,
@@ -476,9 +488,12 @@ async def callback(context: CallbackContext)
 
             job_kwargs (:obj:`dict`): Arbitrary keyword arguments. Used as arguments for
                 :meth:`apscheduler.schedulers.base.BaseScheduler.add_job`.
-            context (:obj:`object`, optional): Additional data needed for the callback function.
-                Can be accessed through :attr:`Job.context` in the callback. Defaults to
+            data (:obj:`object`, optional): Additional data needed for the callback function.
+                Can be accessed through :attr:`Job.data` in the callback. Defaults to
                 :obj:`None`.
+
+                .. versionchanged:: 20.0
+                    Renamed the parameter ``context`` to :paramref:`data`.
             name (:obj:`str`, optional): The name of the new job. Defaults to
                 :external:attr:`callback.__name__ <definition.__name__>`.
             chat_id (:obj:`int`, optional): Chat id of the chat associated with this job. If
@@ -499,7 +514,7 @@ async def callback(context: CallbackContext)
 
         """
         name = name or callback.__name__
-        job = Job(callback=callback, context=context, name=name, chat_id=chat_id, user_id=user_id)
+        job = Job(callback=callback, data=data, name=name, chat_id=chat_id, user_id=user_id)
 
         j = self.scheduler.add_job(job.run, args=(self.application,), name=name, **job_kwargs)
 
@@ -564,7 +579,8 @@ class Job:
           this :class:`telegram.ext.Job` to be useful.
 
     .. versionchanged:: 20.0
-        Removed argument and attribute ``job_queue``.
+       * Removed argument and attribute ``job_queue``.
+       * Renamed ``Job.context`` to :attr:`Job.data`.
 
     Args:
         callback (:term:`coroutine function`): The callback function that should be executed by the
@@ -572,8 +588,8 @@ class Job:
 
                 async def callback(context: CallbackContext)
 
-        context (:obj:`object`, optional): Additional data needed for the callback function. Can be
-            accessed through :attr:`Job.context` in the callback. Defaults to :obj:`None`.
+        data (:obj:`object`, optional): Additional data needed for the callback function. Can be
+            accessed through :attr:`Job.data` in the callback. Defaults to :obj:`None`.
         name (:obj:`str`, optional): The name of the new job. Defaults to
             :external:obj:`callback.__name__ <definition.__name__>`.
         job (:class:`apscheduler.job.Job`, optional): The APS Job this job is a wrapper for.
@@ -587,7 +603,7 @@ async def callback(context: CallbackContext)
     Attributes:
         callback (:term:`coroutine function`): The callback function that should be executed by the
             new job.
-        context (:obj:`object`): Optional. Additional data needed for the callback function.
+        data (:obj:`object`): Optional. Additional data needed for the callback function.
         name (:obj:`str`): Optional. The name of the new job.
         job (:class:`apscheduler.job.Job`): Optional. The APS Job this job is a wrapper for.
         chat_id (:obj:`int`): Optional. Chat id of the chat that this job is associated with.
@@ -600,7 +616,7 @@ async def callback(context: CallbackContext)
 
     __slots__ = (
         "callback",
-        "context",
+        "data",
         "name",
         "_removed",
         "_enabled",
@@ -612,7 +628,7 @@ async def callback(context: CallbackContext)
     def __init__(
         self,
         callback: JobCallback,
-        context: object = None,
+        data: object = None,
         name: str = None,
         job: APSJob = None,
         chat_id: int = None,
@@ -620,7 +636,7 @@ def __init__(
     ):
 
         self.callback = callback
-        self.context = context
+        self.data = data
         self.name = name or callback.__name__
         self.chat_id = chat_id
         self.user_id = user_id


Test patch:
diff --git a/tests/test_jobqueue.py b/tests/test_jobqueue.py
--- a/tests/test_jobqueue.py
+++ b/tests/test_jobqueue.py
@@ -65,7 +65,7 @@ async def job_run_once(self, context):
             isinstance(context, CallbackContext)
             and isinstance(context.job, Job)
             and isinstance(context.update_queue, asyncio.Queue)
-            and context.job.context is None
+            and context.job.data is None
             and context.chat_data is None
             and context.user_data is None
             and isinstance(context.bot_data, dict)
@@ -79,8 +79,8 @@ async def job_remove_self(self, context):
         self.result += 1
         context.job.schedule_removal()
 
-    async def job_run_once_with_context(self, context):
-        self.result += context.job.context
+    async def job_run_once_with_data(self, context):
+        self.result += context.job.data
 
     async def job_datetime_tests(self, context):
         self.job_time = time.time()
@@ -121,8 +121,8 @@ async def test_run_once_timezone(self, job_queue, timezone):
         await asyncio.sleep(0.1)
         assert self.result == 1
 
-    async def test_job_with_context(self, job_queue):
-        job_queue.run_once(self.job_run_once_with_context, 0.1, context=5)
+    async def test_job_with_data(self, job_queue):
+        job_queue.run_once(self.job_run_once_with_data, 0.1, data=5)
         await asyncio.sleep(0.2)
         assert self.result == 5