-
Notifications
You must be signed in to change notification settings - Fork 11
/
Copy pathconftest.py
135 lines (102 loc) · 5.7 KB
/
conftest.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
import asyncio
import inspect
from collections import defaultdict
from pathlib import Path
from typing import Any, Callable, Dict, List, Optional, Tuple, get_type_hints
import pytest
from apify import Actor
from apify._memory_storage import MemoryStorageClient
from apify.config import Configuration
from apify.storages import Dataset, KeyValueStore, RequestQueue, StorageClientManager
from apify_client.client import ApifyClientAsync
from apify_shared.consts import ApifyEnvVars
@pytest.fixture
def reset_default_instances(monkeypatch: pytest.MonkeyPatch) -> Callable[[], None]:
def reset() -> None:
monkeypatch.setattr(Actor, '_default_instance', None)
monkeypatch.setattr(Configuration, '_default_instance', None)
monkeypatch.setattr(Dataset, '_cache_by_id', None)
monkeypatch.setattr(Dataset, '_cache_by_name', None)
monkeypatch.setattr(KeyValueStore, '_cache_by_id', None)
monkeypatch.setattr(KeyValueStore, '_cache_by_name', None)
monkeypatch.setattr(RequestQueue, '_cache_by_id', None)
monkeypatch.setattr(RequestQueue, '_cache_by_name', None)
monkeypatch.setattr(StorageClientManager, '_default_instance', None)
return reset
# To isolate the tests, we need to reset the used singletons before each test case
# We also set the MemoryStorageClient to use a temp path
@pytest.fixture(autouse=True)
def _reset_and_patch_default_instances(monkeypatch: pytest.MonkeyPatch, tmp_path: Path, reset_default_instances: Callable[[], None]) -> None:
reset_default_instances()
# This forces the MemoryStorageClient to use tmp_path for its storage dir
monkeypatch.setenv(ApifyEnvVars.LOCAL_STORAGE_DIR, str(tmp_path))
# This class is used to patch the ApifyClientAsync methods to return a fixed value or be replaced with another method.
class ApifyClientAsyncPatcher:
def __init__(self, monkeypatch: pytest.MonkeyPatch) -> None:
self.monkeypatch = monkeypatch
self.calls: Dict[str, Dict[str, List[Tuple[Any, Any]]]] = defaultdict(lambda: defaultdict(list))
def patch(
self,
method: str,
submethod: str,
*,
return_value: Optional[Any] = None,
replacement_method: Optional[Callable] = None,
is_async: Optional[bool] = None,
) -> None:
"""
Patch a method in ApifyClientAsync.
Patches a submethod in ApifyClientAsync (e.g. `apify_client_async.user().get`)
to either return a fixed value, or be replaced with another method.
The patches will be reverted after the test is over.
One of `return_value` and `replacement_method` arguments must be specified.
Args:
method (str): Which root method to patch in the ApifyClientAsync.
submethod (str): Which submethod to patch in the root method's result.
return_value (optional, Any): What should the patched method return.
replacement_method (optional, Callable): What method should the original method be replaced by.
is_async (optional, bool): Whether the return value or replacement method should be wrapped by an async wrapper,
in order to not break any `await` statements.
If not passed, it is automatically detected from the type of the method which is being replaced.
"""
client_method = getattr(ApifyClientAsync, method, None)
if not client_method:
raise ValueError(f'ApifyClientAsync does not contain method "{method}"!')
client_method_return_type = get_type_hints(client_method)['return']
original_submethod = getattr(client_method_return_type, submethod, None)
if not original_submethod:
raise ValueError(f'apify_client.{client_method_return_type.__name__} does not contain method "{submethod}"!')
if is_async is None:
is_async = inspect.iscoroutinefunction(original_submethod)
if is_async:
if replacement_method:
if not inspect.iscoroutinefunction(replacement_method):
original_replacement_method = replacement_method
async def replacement_method(*args: Any, **kwargs: Any) -> Any:
return original_replacement_method(*args, **kwargs)
else:
original_return_value = return_value
return_value = asyncio.Future()
return_value.set_result(original_return_value)
if not replacement_method:
def replacement_method(*_args: Any, **_kwargs: Any) -> Optional[Any]:
return return_value
def wrapper(*args: Any, **kwargs: Any) -> Any:
self.calls[method][submethod].append((args, kwargs))
assert replacement_method is not None
return replacement_method(*args, **kwargs)
self.monkeypatch.setattr(client_method_return_type, submethod, wrapper, raising=False)
original_getattr = getattr(ApifyClientAsync, '__getattr__', None)
def getattr_override(apify_client_instance: Any, attr_name: str) -> Any:
if attr_name == 'calls':
return self.calls
if original_getattr:
return original_getattr(apify_client_instance, attr_name)
return object.__getattribute__(apify_client_instance, attr_name)
self.monkeypatch.setattr(ApifyClientAsync, '__getattr__', getattr_override, raising=False)
@pytest.fixture
def apify_client_async_patcher(monkeypatch: pytest.MonkeyPatch) -> ApifyClientAsyncPatcher:
return ApifyClientAsyncPatcher(monkeypatch)
@pytest.fixture
def memory_storage_client() -> MemoryStorageClient:
return MemoryStorageClient(write_metadata=True, persist_storage=True)