Skip to content

Commit 0ddc760

Browse files
committed
do it better
1 parent 267dc21 commit 0ddc760

File tree

3 files changed

+99
-94
lines changed

3 files changed

+99
-94
lines changed

tests/integration/conftest.py

Lines changed: 49 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
import sys
88
import textwrap
99
from pathlib import Path
10-
from typing import TYPE_CHECKING, Callable, Protocol, cast
10+
from typing import TYPE_CHECKING, Any, Callable, Coroutine, Protocol, cast
1111

1212
import pytest
1313
from filelock import FileLock
@@ -56,6 +56,53 @@ def apify_client_async() -> ApifyClientAsync:
5656
return ApifyClientAsync(api_token, api_url=api_url)
5757

5858

59+
class RunActorFunction(Protocol):
60+
"""A type for the `run_actor` fixture."""
61+
62+
def __call__(
63+
self,
64+
actor: ActorClientAsync,
65+
*,
66+
run_input: Any = None,
67+
) -> Coroutine[None, None, dict]:
68+
"""Initiate an Actor run and wait for its completion.
69+
70+
Args:
71+
actor: Actor async client, in testing context usually created by `make_actor` fixture.
72+
run_input: Optional input for the Actor run.
73+
74+
Returns:
75+
Actor run result.
76+
"""
77+
78+
79+
@pytest.fixture
80+
async def run_actor(apify_client_async: ApifyClientAsync) -> RunActorFunction:
81+
"""Fixture for calling an Actor run and waiting for its completion.
82+
83+
This fixture returns a function that initiates an Actor run with optional run input, waits for its completion,
84+
and retrieves the final result. It uses the `wait_for_finish` method with a timeout of 10 minutes.
85+
86+
Returns:
87+
A coroutine that initiates an Actor run and waits for its completion.
88+
"""
89+
90+
async def _run_actor(actor: ActorClientAsync, *, run_input: Any = None) -> dict:
91+
call_result = await actor.call(run_input=run_input)
92+
93+
assert isinstance(call_result, dict), 'The result of ActorClientAsync.call() is not a dictionary.'
94+
assert 'id' in call_result, 'The result of ActorClientAsync.call() does not contain an ID.'
95+
96+
run_client = apify_client_async.run(call_result['id'])
97+
run_result = await run_client.wait_for_finish(wait_secs=600)
98+
99+
assert isinstance(run_result, dict), 'The result of RunClientAsync.wait_for_finish() is not a dictionary.'
100+
assert 'status' in run_result, 'The result of RunClientAsync.wait_for_finish() does not contain a status.'
101+
return run_result
102+
103+
return _run_actor
104+
105+
59106
# Build the package wheel if it hasn't been built yet, and return the path to the wheel
60107
@pytest.fixture(scope='session')
61108
def sdk_wheel_path(tmp_path_factory: pytest.TempPathFactory, testrun_uid: str) -> Path:
@@ -130,7 +177,7 @@ def actor_base_source_files(sdk_wheel_path: Path) -> dict[str, str | bytes]:
130177
# Just a type for the make_actor result, so that we can import it in tests
131178
class ActorFactory(Protocol):
132179
def __call__(
133-
self: ActorFactory,
180+
self,
134181
actor_label: str,
135182
*,
136183
main_func: Callable | None = None,

tests/integration/test_actor_api_helpers.py

Lines changed: 48 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -12,31 +12,25 @@
1212
if TYPE_CHECKING:
1313
from apify_client import ApifyClientAsync
1414

15-
from .conftest import ActorFactory
15+
from .conftest import ActorFactory, RunActorFunction
1616

1717

1818
async def test_actor_reports_running_on_platform(
19-
apify_client_async: ApifyClientAsync,
19+
run_actor: RunActorFunction,
2020
make_actor: ActorFactory,
2121
) -> None:
2222
async def main() -> None:
2323
async with Actor:
2424
assert Actor.is_at_home() is True
2525

2626
actor = await make_actor('is-at-home', main_func=main)
27+
run_result = await run_actor(actor)
2728

28-
call_result = await actor.call()
29-
assert call_result is not None
30-
31-
run_client = apify_client_async.run(call_result['id'])
32-
run_result = await run_client.wait_for_finish(wait_secs=600)
33-
34-
assert run_result is not None
3529
assert run_result['status'] == 'SUCCEEDED'
3630

3731

3832
async def test_actor_retrieves_env_vars(
39-
apify_client_async: ApifyClientAsync,
33+
run_actor: RunActorFunction,
4034
make_actor: ActorFactory,
4135
) -> None:
4236
async def main() -> None:
@@ -56,19 +50,13 @@ async def main() -> None:
5650
assert len(env_dict.get('default_request_queue_id', '')) == 17
5751

5852
actor = await make_actor('get-env', main_func=main)
53+
run_result = await run_actor(actor)
5954

60-
call_result = await actor.call()
61-
assert call_result is not None
62-
63-
run_client = apify_client_async.run(call_result['id'])
64-
run_result = await run_client.wait_for_finish(wait_secs=600)
65-
66-
assert run_result is not None
6755
assert run_result['status'] == 'SUCCEEDED'
6856

6957

7058
async def test_actor_creates_new_client_instance(
71-
apify_client_async: ApifyClientAsync,
59+
run_actor: RunActorFunction,
7260
make_actor: ActorFactory,
7361
) -> None:
7462
async def main() -> None:
@@ -86,14 +74,8 @@ async def main() -> None:
8674
await kv_store_client.set_record('OUTPUT', 'TESTING-OUTPUT')
8775

8876
actor = await make_actor('new-client', main_func=main)
77+
run_result = await run_actor(actor)
8978

90-
call_result = await actor.call()
91-
assert call_result is not None
92-
93-
run_client = apify_client_async.run(call_result['id'])
94-
run_result = await run_client.wait_for_finish(wait_secs=600)
95-
96-
assert run_result is not None
9779
assert run_result['status'] == 'SUCCEEDED'
9880

9981
output_record = await actor.last_run().key_value_store().get_record('OUTPUT')
@@ -102,7 +84,7 @@ async def main() -> None:
10284

10385

10486
async def test_actor_sets_status_message(
105-
apify_client_async: ApifyClientAsync,
87+
run_actor: RunActorFunction,
10688
make_actor: ActorFactory,
10789
) -> None:
10890
async def main() -> None:
@@ -111,32 +93,21 @@ async def main() -> None:
11193
await Actor.set_status_message('testing-status-message', **actor_input)
11294

11395
actor = await make_actor('set-status-message', main_func=main)
96+
run_result_1 = await run_actor(actor)
11497

115-
call_result = await actor.call()
116-
assert call_result is not None
117-
118-
run_client = apify_client_async.run(call_result['id'])
119-
run_result = await run_client.wait_for_finish(wait_secs=600)
120-
121-
assert run_result is not None
122-
assert run_result['status'] == 'SUCCEEDED'
123-
assert run_result['statusMessage'] == 'testing-status-message'
124-
assert run_result['isStatusMessageTerminal'] is None
125-
126-
call_result_2 = await actor.call(run_input={'is_terminal': True})
127-
assert call_result_2 is not None
98+
assert run_result_1['status'] == 'SUCCEEDED'
99+
assert run_result_1['statusMessage'] == 'testing-status-message'
100+
assert run_result_1['isStatusMessageTerminal'] is None
128101

129-
run_client_2 = apify_client_async.run(call_result_2['id'])
130-
run_result_2 = await run_client_2.wait_for_finish(wait_secs=600)
102+
run_result_2 = await run_actor(actor, run_input={'is_terminal': True})
131103

132-
assert run_result_2 is not None
133104
assert run_result_2['status'] == 'SUCCEEDED'
134105
assert run_result_2['statusMessage'] == 'testing-status-message'
135106
assert run_result_2['isStatusMessageTerminal'] is True
136107

137108

138109
async def test_actor_starts_another_actor_instance(
139-
apify_client_async: ApifyClientAsync,
110+
run_actor: RunActorFunction,
140111
make_actor: ActorFactory,
141112
) -> None:
142113
async def main_inner() -> None:
@@ -166,13 +137,11 @@ async def main_outer() -> None:
166137
inner_actor_id = (await inner_actor.get() or {})['id']
167138
test_value = crypto_random_object_id()
168139

169-
outer_call_result = await outer_actor.call(run_input={'test_value': test_value, 'inner_actor_id': inner_actor_id})
170-
assert outer_call_result is not None
171-
172-
run_client_outer = apify_client_async.run(outer_call_result['id'])
173-
run_result_outer = await run_client_outer.wait_for_finish(wait_secs=600)
140+
run_result_outer = await run_actor(
141+
outer_actor,
142+
run_input={'test_value': test_value, 'inner_actor_id': inner_actor_id},
143+
)
174144

175-
assert run_result_outer is not None
176145
assert run_result_outer['status'] == 'SUCCEEDED'
177146

178147
await inner_actor.last_run().wait_for_finish(wait_secs=600)
@@ -183,7 +152,7 @@ async def main_outer() -> None:
183152

184153

185154
async def test_actor_calls_another_actor(
186-
apify_client_async: ApifyClientAsync,
155+
run_actor: RunActorFunction,
187156
make_actor: ActorFactory,
188157
) -> None:
189158
async def main_inner() -> None:
@@ -213,13 +182,11 @@ async def main_outer() -> None:
213182
inner_actor_id = (await inner_actor.get() or {})['id']
214183
test_value = crypto_random_object_id()
215184

216-
outer_call_result = await outer_actor.call(run_input={'test_value': test_value, 'inner_actor_id': inner_actor_id})
217-
assert outer_call_result is not None
218-
219-
run_client_outer = apify_client_async.run(outer_call_result['id'])
220-
run_result_outer = await run_client_outer.wait_for_finish(wait_secs=600)
185+
run_result_outer = await run_actor(
186+
outer_actor,
187+
run_input={'test_value': test_value, 'inner_actor_id': inner_actor_id},
188+
)
221189

222-
assert run_result_outer is not None
223190
assert run_result_outer['status'] == 'SUCCEEDED'
224191

225192
await inner_actor.last_run().wait_for_finish(wait_secs=600)
@@ -231,6 +198,7 @@ async def main_outer() -> None:
231198

232199
async def test_actor_calls_task(
233200
apify_client_async: ApifyClientAsync,
201+
run_actor: RunActorFunction,
234202
make_actor: ActorFactory,
235203
) -> None:
236204
async def main_inner() -> None:
@@ -265,13 +233,11 @@ async def main_outer() -> None:
265233
task_input={'test_value': test_value},
266234
)
267235

268-
outer_call_result = await outer_actor.call(run_input={'test_value': test_value, 'inner_task_id': task['id']})
269-
assert outer_call_result is not None
270-
271-
run_client_outer = apify_client_async.run(outer_call_result['id'])
272-
run_result_outer = await run_client_outer.wait_for_finish(wait_secs=600)
236+
run_result_outer = await run_actor(
237+
outer_actor,
238+
run_input={'test_value': test_value, 'inner_task_id': task['id']},
239+
)
273240

274-
assert run_result_outer is not None
275241
assert run_result_outer['status'] == 'SUCCEEDED'
276242

277243
await inner_actor.last_run().wait_for_finish(wait_secs=600)
@@ -284,7 +250,7 @@ async def main_outer() -> None:
284250

285251

286252
async def test_actor_aborts_another_actor_run(
287-
apify_client_async: ApifyClientAsync,
253+
run_actor: RunActorFunction,
288254
make_actor: ActorFactory,
289255
) -> None:
290256
async def main_inner() -> None:
@@ -307,13 +273,11 @@ async def main_outer() -> None:
307273

308274
inner_run_id = (await inner_actor.start())['id']
309275

310-
outer_call_result = await outer_actor.call(run_input={'inner_run_id': inner_run_id})
311-
assert outer_call_result is not None
312-
313-
run_client_outer = apify_client_async.run(outer_call_result['id'])
314-
run_result_outer = await run_client_outer.wait_for_finish(wait_secs=600)
276+
run_result_outer = await run_actor(
277+
outer_actor,
278+
run_input={'inner_run_id': inner_run_id},
279+
)
315280

316-
assert run_result_outer is not None
317281
assert run_result_outer['status'] == 'SUCCEEDED'
318282

319283
await inner_actor.last_run().wait_for_finish(wait_secs=600)
@@ -326,7 +290,7 @@ async def main_outer() -> None:
326290

327291

328292
async def test_actor_metamorphs_into_another_actor(
329-
apify_client_async: ApifyClientAsync,
293+
run_actor: RunActorFunction,
330294
make_actor: ActorFactory,
331295
) -> None:
332296
async def main_inner() -> None:
@@ -366,13 +330,11 @@ async def main_outer() -> None:
366330
inner_actor_id = (await inner_actor.get() or {})['id']
367331
test_value = crypto_random_object_id()
368332

369-
outer_call_result = await outer_actor.call(run_input={'test_value': test_value, 'inner_actor_id': inner_actor_id})
370-
assert outer_call_result is not None
371-
372-
run_client_outer = apify_client_async.run(outer_call_result['id'])
373-
run_result_outer = await run_client_outer.wait_for_finish(wait_secs=600)
333+
run_result_outer = await run_actor(
334+
outer_actor,
335+
run_input={'test_value': test_value, 'inner_actor_id': inner_actor_id},
336+
)
374337

375-
assert run_result_outer is not None
376338
assert run_result_outer['status'] == 'SUCCEEDED'
377339

378340
outer_run_key_value_store = outer_actor.last_run().key_value_store()
@@ -388,7 +350,7 @@ async def main_outer() -> None:
388350

389351

390352
async def test_actor_reboots_successfully(
391-
apify_client_async: ApifyClientAsync,
353+
run_actor: RunActorFunction,
392354
make_actor: ActorFactory,
393355
) -> None:
394356
async def main() -> None:
@@ -406,13 +368,11 @@ async def main() -> None:
406368

407369
actor = await make_actor('actor_rebooter', main_func=main)
408370

409-
call_result = await actor.call(run_input={'counter_key': 'reboot_counter'})
410-
assert call_result is not None
411-
412-
run_client = apify_client_async.run(call_result['id'])
413-
run_result = await run_client.wait_for_finish(wait_secs=600)
371+
run_result = await run_actor(
372+
actor,
373+
run_input={'counter_key': 'reboot_counter'},
374+
)
414375

415-
assert run_result is not None
416376
assert run_result['status'] == 'SUCCEEDED'
417377

418378
not_written_value = await actor.last_run().key_value_store().get_record('THIS_KEY_SHOULD_NOT_BE_WRITTEN')
@@ -424,7 +384,7 @@ async def main() -> None:
424384

425385

426386
async def test_actor_adds_webhook_and_receives_event(
427-
apify_client_async: ApifyClientAsync,
387+
run_actor: RunActorFunction,
428388
make_actor: ActorFactory,
429389
) -> None:
430390
async def main_server() -> None:
@@ -487,13 +447,11 @@ async def main_client() -> None:
487447
server_actor_initialized = await server_actor.last_run().key_value_store().get_record('INITIALIZED')
488448
await asyncio.sleep(1)
489449

490-
ac_call_result = await client_actor.call(run_input={'server_actor_container_url': server_actor_container_url})
491-
assert ac_call_result is not None
492-
493-
ac_run_client = apify_client_async.run(ac_call_result['id'])
494-
ac_run_result = await ac_run_client.wait_for_finish(wait_secs=600)
450+
ac_run_result = await run_actor(
451+
client_actor,
452+
run_input={'server_actor_container_url': server_actor_container_url},
453+
)
495454

496-
assert ac_run_result is not None
497455
assert ac_run_result['status'] == 'SUCCEEDED'
498456

499457
sa_run_result = await server_actor.last_run().wait_for_finish(wait_secs=600)

tests/unit/conftest.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,12 +64,12 @@ def _reset_and_patch_default_instances(
6464

6565
# This class is used to patch the ApifyClientAsync methods to return a fixed value or be replaced with another method.
6666
class ApifyClientAsyncPatcher:
67-
def __init__(self: ApifyClientAsyncPatcher, monkeypatch: pytest.MonkeyPatch) -> None:
67+
def __init__(self, monkeypatch: pytest.MonkeyPatch) -> None:
6868
self.monkeypatch = monkeypatch
6969
self.calls: dict[str, dict[str, list[tuple[Any, Any]]]] = defaultdict(lambda: defaultdict(list))
7070

7171
def patch(
72-
self: ApifyClientAsyncPatcher,
72+
self,
7373
method: str,
7474
submethod: str,
7575
*,

0 commit comments

Comments
 (0)