|
3 | 3 | import logging
|
4 | 4 | import time
|
5 | 5 | from collections.abc import AsyncIterator, Iterator
|
6 |
| -from datetime import datetime |
| 6 | +from datetime import datetime, timedelta |
7 | 7 | from unittest.mock import patch
|
8 | 8 |
|
9 | 9 | import httpx
|
|
14 | 14 |
|
15 | 15 | from apify_client import ApifyClient, ApifyClientAsync
|
16 | 16 | from apify_client._logging import RedirectLogFormatter
|
17 |
| -from apify_client.clients.resource_clients.log import StreamedLog |
| 17 | +from apify_client.clients.resource_clients.log import StreamedLog, StatusMessageRedirector |
18 | 18 |
|
19 | 19 | _MOCKED_API_URL = 'https://example.com'
|
20 | 20 | _MOCKED_RUN_ID = 'mocked_run_id'
|
|
57 | 57 |
|
58 | 58 | @pytest.fixture
|
59 | 59 | def mock_api() -> None:
|
60 |
| - actor_runs_responses = iter( |
61 |
| - ( |
62 |
| - httpx.Response( |
63 |
| - content=json.dumps( |
64 |
| - {'data': {'id': _MOCKED_RUN_ID, 'actId': _MOCKED_ACTOR_ID, 'status': ActorJobStatus.RUNNING}} |
65 |
| - ), |
66 |
| - status_code=200, |
67 |
| - ), |
68 |
| - httpx.Response( |
| 60 | + |
| 61 | + def create_status_responses_generator(): |
| 62 | + for i in range(10): |
| 63 | + yield httpx.Response( |
69 | 64 | content=json.dumps(
|
70 |
| - {'data': {'id': _MOCKED_RUN_ID, 'actId': _MOCKED_ACTOR_ID, 'status': ActorJobStatus.RUNNING}} |
| 65 | + {'data': {'id': _MOCKED_RUN_ID, 'actId': _MOCKED_ACTOR_ID, 'status': ActorJobStatus.RUNNING, |
| 66 | + 'statusMessage': f'Status {i}', 'isStatusMessageTerminal': False}} |
71 | 67 | ),
|
72 | 68 | status_code=200,
|
73 |
| - ), |
74 |
| - httpx.Response( |
| 69 | + ) |
| 70 | + while True: |
| 71 | + yield httpx.Response( |
75 | 72 | content=json.dumps(
|
76 |
| - {'data': {'id': _MOCKED_RUN_ID, 'actId': _MOCKED_ACTOR_ID, 'status': ActorJobStatus.SUCCEEDED}} |
| 73 | + {'data': {'id': _MOCKED_RUN_ID, 'actId': _MOCKED_ACTOR_ID, 'status': ActorJobStatus.SUCCEEDED, |
| 74 | + 'statusMessage': f'Status 101', 'isStatusMessageTerminal': True}} |
77 | 75 | ),
|
78 | 76 | status_code=200,
|
79 |
| - ), |
80 |
| - ) |
81 |
| - ) |
| 77 | + ) |
| 78 | + |
| 79 | + response_generator = create_status_responses_generator() |
82 | 80 |
|
83 | 81 | def actor_runs_side_effect(_: httpx.Request) -> httpx.Response:
|
84 | 82 | time.sleep(0.1)
|
85 |
| - return next(actor_runs_responses) |
| 83 | + return next(response_generator) |
86 | 84 |
|
87 | 85 | respx.get(url=f'{_MOCKED_API_URL}/v2/actor-runs/{_MOCKED_RUN_ID}').mock(side_effect=actor_runs_side_effect)
|
88 | 86 |
|
@@ -129,7 +127,9 @@ def close(self) -> None:
|
129 | 127 |
|
130 | 128 | @pytest.fixture
|
131 | 129 | def propagate_stream_logs() -> None:
|
132 |
| - StreamedLog._force_propagate = True # Enable propagation of logs to the caplog fixture |
| 130 | + # Enable propagation of logs to the caplog fixture |
| 131 | + StreamedLog._force_propagate = True |
| 132 | + StatusMessageRedirector._force_propagate = True |
133 | 133 | logging.getLogger(f'apify.{_MOCKED_ACTOR_NAME}-{_MOCKED_RUN_ID}').setLevel(logging.DEBUG)
|
134 | 134 |
|
135 | 135 |
|
@@ -332,3 +332,47 @@ def test_actor_call_redirect_logs_to_custom_logger_sync(
|
332 | 332 | for expected_message_and_level, record in zip(_EXPECTED_MESSAGES_AND_LEVELS, caplog.records):
|
333 | 333 | assert expected_message_and_level[0] == record.message
|
334 | 334 | assert expected_message_and_level[1] == record.levelno
|
| 335 | + |
| 336 | +@respx.mock |
| 337 | +async def test_redirect_status_message_async( |
| 338 | + *, |
| 339 | + caplog: LogCaptureFixture, |
| 340 | + mock_api: None, # noqa: ARG001, fixture |
| 341 | + propagate_stream_logs: None, # noqa: ARG001, fixture |
| 342 | +) -> None: |
| 343 | + """Test redirected status and status messages.""" |
| 344 | + |
| 345 | + run_client = ApifyClientAsync(token='mocked_token', api_url=_MOCKED_API_URL).run(run_id=_MOCKED_RUN_ID) |
| 346 | + |
| 347 | + logger_name = f'apify.{_MOCKED_ACTOR_NAME}-{_MOCKED_RUN_ID}' |
| 348 | + |
| 349 | + status_message_redirector = await run_client.get_status_message_redirector(check_period=timedelta(seconds =0.1)) |
| 350 | + with caplog.at_level(logging.DEBUG, logger=logger_name): |
| 351 | + async with status_message_redirector: |
| 352 | + # Do stuff while the status from the other actor is being redirected to the logs. |
| 353 | + await asyncio.sleep(2) |
| 354 | + |
| 355 | + assert caplog.records[0].message == 'Status: RUNNING, Message: Status 1' |
| 356 | + assert caplog.records[1].message == 'Status: SUCCEEDED, Message: Status 2' |
| 357 | + |
| 358 | +@respx.mock |
| 359 | +def test_redirect_status_message_sync( |
| 360 | + *, |
| 361 | + caplog: LogCaptureFixture, |
| 362 | + mock_api: None, # noqa: ARG001, fixture |
| 363 | + propagate_stream_logs: None, # noqa: ARG001, fixture |
| 364 | +) -> None: |
| 365 | + """Test redirected status and status messages.""" |
| 366 | + |
| 367 | + run_client = ApifyClient(token='mocked_token', api_url=_MOCKED_API_URL).run(run_id=_MOCKED_RUN_ID) |
| 368 | + |
| 369 | + logger_name = f'apify.{_MOCKED_ACTOR_NAME}-{_MOCKED_RUN_ID}' |
| 370 | + |
| 371 | + status_message_redirector = run_client.get_status_message_redirector(check_period=timedelta(seconds =0.1)) |
| 372 | + with caplog.at_level(logging.DEBUG, logger=logger_name): |
| 373 | + with status_message_redirector: |
| 374 | + # Do stuff while the status from the other actor is being redirected to the logs. |
| 375 | + time.sleep(2) |
| 376 | + |
| 377 | + assert caplog.records[0].message == 'Status: RUNNING, Message: Status 1' |
| 378 | + assert caplog.records[1].message == 'Status: SUCCEEDED, Message: Status 2' |
0 commit comments