Skip to content

Commit b608829

Browse files
Adam Dobrawyclaude
andcommitted
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 <noreply@anthropic.com>
1 parent ca02a00 commit b608829

2 files changed

Lines changed: 39 additions & 1 deletion

File tree

python_anticaptcha/base.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,12 +67,14 @@ def __repr__(self) -> str:
6767
return f"<Job task_id={self.task_id} status={status!r}>"
6868
return f"<Job task_id={self.task_id}>"
6969

70-
def join(self, maximum_time: int | None = None) -> None:
70+
def join(self, maximum_time: int | None = None, on_check=None) -> None:
7171
elapsed_time = 0
7272
maximum_time = maximum_time or MAXIMUM_JOIN_TIME
7373
while not self.check_is_ready():
7474
time.sleep(SLEEP_EVERY_CHECK_FINISHED)
7575
elapsed_time += SLEEP_EVERY_CHECK_FINISHED
76+
if on_check is not None:
77+
on_check(elapsed_time, self._last_result.get("status"))
7678
if elapsed_time > maximum_time:
7779
raise AnticaptchaException(
7880
None,

tests/test_base.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,42 @@ def test_timeout_raises(self, mock_sleep):
166166
assert "exceeded" in str(exc_info.value).lower()
167167

168168

169+
class TestJobJoinOnCheck:
170+
@patch("python_anticaptcha.base.time.sleep")
171+
def test_on_check_called_each_iteration(self, mock_sleep):
172+
client = MagicMock()
173+
# Return processing twice, then ready
174+
client.getTaskResult.side_effect = [
175+
{"status": "processing"},
176+
{"status": "processing"},
177+
{"status": "ready", "solution": {}},
178+
]
179+
job = Job(client, task_id=1)
180+
callback = MagicMock()
181+
job.join(on_check=callback)
182+
assert callback.call_count == 2
183+
# Verify elapsed time and status passed correctly
184+
callback.assert_any_call(SLEEP_EVERY_CHECK_FINISHED, "processing")
185+
callback.assert_any_call(SLEEP_EVERY_CHECK_FINISHED * 2, "processing")
186+
187+
@patch("python_anticaptcha.base.time.sleep")
188+
def test_on_check_none_by_default(self, mock_sleep):
189+
client = MagicMock()
190+
client.getTaskResult.return_value = {"status": "ready", "solution": {}}
191+
job = Job(client, task_id=1)
192+
# Should not raise when on_check is not provided
193+
job.join()
194+
195+
@patch("python_anticaptcha.base.time.sleep")
196+
def test_on_check_not_called_when_immediately_ready(self, mock_sleep):
197+
client = MagicMock()
198+
client.getTaskResult.return_value = {"status": "ready", "solution": {}}
199+
job = Job(client, task_id=1)
200+
callback = MagicMock()
201+
job.join(on_check=callback)
202+
callback.assert_not_called()
203+
204+
169205
class TestContextManager:
170206
def test_enter_returns_self(self):
171207
client = AnticaptchaClient("key123")

0 commit comments

Comments
 (0)