From b6088291068e84e776f413eddf413121c1172e22 Mon Sep 17 00:00:00 2001 From: Adam Dobrawy Date: Sat, 7 Mar 2026 14:22:16 +0100 Subject: [PATCH 1/2] Add optional on_check callback to Job.join() Allow users to monitor progress during the blocking join() call by passing an on_check callback that receives (elapsed_time, status) after each polling iteration. Co-Authored-By: Claude Opus 4.6 --- python_anticaptcha/base.py | 4 +++- tests/test_base.py | 36 ++++++++++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/python_anticaptcha/base.py b/python_anticaptcha/base.py index 7db0c38..a5400f2 100644 --- a/python_anticaptcha/base.py +++ b/python_anticaptcha/base.py @@ -67,12 +67,14 @@ def __repr__(self) -> str: return f"" return f"" - def join(self, maximum_time: int | None = None) -> None: + def join(self, maximum_time: int | None = None, on_check=None) -> None: elapsed_time = 0 maximum_time = maximum_time or MAXIMUM_JOIN_TIME while not self.check_is_ready(): time.sleep(SLEEP_EVERY_CHECK_FINISHED) elapsed_time += SLEEP_EVERY_CHECK_FINISHED + if on_check is not None: + on_check(elapsed_time, self._last_result.get("status")) if elapsed_time > maximum_time: raise AnticaptchaException( None, diff --git a/tests/test_base.py b/tests/test_base.py index 71afd6b..5c97223 100644 --- a/tests/test_base.py +++ b/tests/test_base.py @@ -166,6 +166,42 @@ def test_timeout_raises(self, mock_sleep): assert "exceeded" in str(exc_info.value).lower() +class TestJobJoinOnCheck: + @patch("python_anticaptcha.base.time.sleep") + def test_on_check_called_each_iteration(self, mock_sleep): + client = MagicMock() + # Return processing twice, then ready + client.getTaskResult.side_effect = [ + {"status": "processing"}, + {"status": "processing"}, + {"status": "ready", "solution": {}}, + ] + job = Job(client, task_id=1) + callback = MagicMock() + job.join(on_check=callback) + assert callback.call_count == 2 + # Verify elapsed time and status passed correctly + callback.assert_any_call(SLEEP_EVERY_CHECK_FINISHED, "processing") + callback.assert_any_call(SLEEP_EVERY_CHECK_FINISHED * 2, "processing") + + @patch("python_anticaptcha.base.time.sleep") + def test_on_check_none_by_default(self, mock_sleep): + client = MagicMock() + client.getTaskResult.return_value = {"status": "ready", "solution": {}} + job = Job(client, task_id=1) + # Should not raise when on_check is not provided + job.join() + + @patch("python_anticaptcha.base.time.sleep") + def test_on_check_not_called_when_immediately_ready(self, mock_sleep): + client = MagicMock() + client.getTaskResult.return_value = {"status": "ready", "solution": {}} + job = Job(client, task_id=1) + callback = MagicMock() + job.join(on_check=callback) + callback.assert_not_called() + + class TestContextManager: def test_enter_returns_self(self): client = AnticaptchaClient("key123") From 6a6018300801064aabed0ec137e3f611c21d4a3f Mon Sep 17 00:00:00 2001 From: Adam Dobrawy Date: Sat, 7 Mar 2026 14:29:48 +0100 Subject: [PATCH 2/2] Add docstring to Job.join() Co-Authored-By: Claude Opus 4.6 --- python_anticaptcha/base.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/python_anticaptcha/base.py b/python_anticaptcha/base.py index a5400f2..dee0b96 100644 --- a/python_anticaptcha/base.py +++ b/python_anticaptcha/base.py @@ -68,6 +68,15 @@ def __repr__(self) -> str: return f"" def join(self, maximum_time: int | None = None, on_check=None) -> None: + """Poll for task completion, blocking until ready or timeout. + + :param maximum_time: Maximum seconds to wait (default: ``MAXIMUM_JOIN_TIME``). + :param on_check: Optional callback invoked after each poll with + ``(elapsed_time, status)`` where *elapsed_time* is the total seconds + waited so far and *status* is the last task status string + (e.g. ``"processing"``). + :raises AnticaptchaException: If *maximum_time* is exceeded. + """ elapsed_time = 0 maximum_time = maximum_time or MAXIMUM_JOIN_TIME while not self.check_is_ready():