Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions src/apify/_actor.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from __future__ import annotations

import asyncio
import functools
import os
import sys
from contextlib import suppress
Expand Down Expand Up @@ -50,6 +51,26 @@


MainReturnType = TypeVar('MainReturnType')
TFun = TypeVar('TFun', bound=Callable[..., Any])


def _add_local_storage_error_hint(function: TFun) -> TFun:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Couldn't we move the decorator to a dedicated private module, perhaps apify._utils?

"""This decorator adds a local storage error hint in situation where storage was not found locally."""

@functools.wraps(function)
async def wrapper(
self: _ActorType, *, id: str | None = None, name: str | None = None, force_cloud: bool = False
) -> Any:
try:
return await function(self=self, id=id, name=name, force_cloud=force_cloud)
except Exception as e:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we really want to catch all exceptions?

if not force_cloud:
e.args = (
f'{e.args[0]} (If you are trying to retrieve a remote storage, use `force_cloud=True` argument.)',
)
Comment on lines +68 to +70
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Couldn't e.args contain more than one item? If so, am I correct that we are discarding them?

raise

return cast(TFun, wrapper)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we really need the cast? Doesn't functools.wraps already take care of preserving the function's type?



@docs_name('Actor')
Expand Down Expand Up @@ -362,6 +383,7 @@ def new_client(
timeout_secs=int(timeout.total_seconds()) if timeout else None,
)

@_add_local_storage_error_hint
async def open_dataset(
self,
*,
Expand Down Expand Up @@ -398,6 +420,7 @@ async def open_dataset(
storage_client=storage_client,
)

@_add_local_storage_error_hint
async def open_key_value_store(
self,
*,
Expand Down Expand Up @@ -432,6 +455,7 @@ async def open_key_value_store(
storage_client=storage_client,
)

@_add_local_storage_error_hint
async def open_request_queue(
self,
*,
Expand Down
9 changes: 9 additions & 0 deletions tests/unit/actor/test_actor_dataset.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,3 +62,12 @@ async def test_push_data_to_dataset() -> None:

list_page = await dataset.get_data(limit=desired_item_count)
assert {item['id'] for item in list_page.items} == set(range(desired_item_count))


async def test_open_local_non_existent_dataset() -> None:
async with Actor as my_actor:
with pytest.raises(RuntimeError) as e:
await my_actor.open_dataset(id='non-existent')
assert str(e.value).endswith(
'If you are trying to retrieve a remote storage, use `force_cloud=True` argument.)'
)
9 changes: 9 additions & 0 deletions tests/unit/actor/test_actor_key_value_store.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,3 +92,12 @@ async def test_get_input_with_encrypted_secrets(
input = await my_actor.get_input() # noqa: A001
assert input['foo'] == input_with_secret['foo']
assert input['secret'] == secret_string


async def test_open_local_non_existent_kvs() -> None:
async with Actor as my_actor:
with pytest.raises(RuntimeError) as e:
await my_actor.open_key_value_store(id='non-existent')
assert str(e.value).endswith(
'If you are trying to retrieve a remote storage, use `force_cloud=True` argument.)'
)
9 changes: 9 additions & 0 deletions tests/unit/actor/test_actor_request_queue.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,12 @@ async def test_open_returns_same_references() -> None:
rq_by_id_2 = await Actor.open_key_value_store(id=rq_by_name_1._id)
assert rq_by_id_1 is rq_by_name_1
assert rq_by_id_2 is rq_by_id_1


async def test_open_local_non_existent_rq() -> None:
async with Actor as my_actor:
with pytest.raises(RuntimeError) as e:
await my_actor.open_request_queue(id='non-existent')
assert str(e.value).endswith(
'If you are trying to retrieve a remote storage, use `force_cloud=True` argument.)'
)