scrapely-python 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- scrapely/__init__.py +246 -0
- scrapely/_browser_locations.py +5470 -0
- scrapely/actor.py +1836 -0
- scrapely/browser_configuration.py +146 -0
- scrapely/charging.py +1143 -0
- scrapely/charging_client.py +107 -0
- scrapely/configuration.py +375 -0
- scrapely/crypto/__init__.py +21 -0
- scrapely/crypto/_secrets.py +210 -0
- scrapely/errors.py +153 -0
- scrapely/events/__init__.py +57 -0
- scrapely/events/_types.py +194 -0
- scrapely/events/event_manager.py +292 -0
- scrapely/log.py +39 -0
- scrapely/patch_browser_pool.py +861 -0
- scrapely/pricing_types.py +172 -0
- scrapely/proxy_configuration.py +741 -0
- scrapely/py.typed +2 -0
- scrapely/request_loaders/__init__.py +5 -0
- scrapely/request_loaders/_scrapely_request_list.py +169 -0
- scrapely/storage/__init__.py +48 -0
- scrapely/storage/_file_system/__init__.py +9 -0
- scrapely/storage/_file_system/_key_value_store_client.py +155 -0
- scrapely/storage/_file_system/_storage_client.py +50 -0
- scrapely/storage/_metadata.py +138 -0
- scrapely/storage/api_client.py +350 -0
- scrapely/storage/client.py +618 -0
- scrapely/storage/patch_storage.py +257 -0
- scrapely/storage/scrapely_dataset_client.py +305 -0
- scrapely/storage/scrapely_kvs_client.py +220 -0
- scrapely/storage/scrapely_rq_client.py +496 -0
- scrapely/storage/scrapely_storage_client.py +192 -0
- scrapely/storage/smart_scrapely_storage_client.py +109 -0
- scrapely/storage/types.py +601 -0
- scrapely/storage/utils.py +38 -0
- scrapely/storage_clients/__init__.py +25 -0
- scrapely/storages/__init__.py +5 -0
- scrapely/types.py +269 -0
- scrapely/webhook.py +82 -0
- scrapely_python-0.1.0.dist-info/METADATA +83 -0
- scrapely_python-0.1.0.dist-info/RECORD +43 -0
- scrapely_python-0.1.0.dist-info/WHEEL +5 -0
- scrapely_python-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
"""Charging-aware API client for Scrapely.
|
|
2
|
+
|
|
3
|
+
Creates a ``scrapely_client`` ``ApifyClientAsync`` configured with charging
|
|
4
|
+
hooks, so pushes to the run's default dataset are intercepted and charged
|
|
5
|
+
pay-per-event by the client itself. The charging logic (pricing, budgets,
|
|
6
|
+
ledger) stays in the SDK's ``ChargingManager``; the client only routes
|
|
7
|
+
``push_items()`` through the hooks.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
from typing import TYPE_CHECKING, Any, Callable, Dict, Optional, Sequence
|
|
13
|
+
|
|
14
|
+
from scrapely_client import ApifyClientAsync
|
|
15
|
+
|
|
16
|
+
# Re-exported so the SDK and the client share one marker attribute and one
|
|
17
|
+
# context var. Identity matters: the client's intercepted ``push_items()``
|
|
18
|
+
# reads/writes these, and the SDK detects interception via the attribute.
|
|
19
|
+
from scrapely_client import USES_PUSH_DATA_INTERCEPTION, push_data_charging_context
|
|
20
|
+
|
|
21
|
+
if TYPE_CHECKING:
|
|
22
|
+
from .charging import ChargingManager
|
|
23
|
+
|
|
24
|
+
__all__ = [
|
|
25
|
+
'USES_PUSH_DATA_INTERCEPTION',
|
|
26
|
+
'push_data_charging_context',
|
|
27
|
+
'create_charging_client',
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class _ChargingManagerHooks:
|
|
32
|
+
"""Adapts the SDK's ``ChargingManager`` to ``scrapely_client``'s ``ChargingHooks`` protocol."""
|
|
33
|
+
|
|
34
|
+
def __init__(self, charging_manager: 'ChargingManager') -> None:
|
|
35
|
+
self._charging_manager = charging_manager
|
|
36
|
+
|
|
37
|
+
def should_charge_dataset_pushes(self, dataset_id: str) -> bool:
|
|
38
|
+
"""Whether default-dataset-item event pricing is configured.
|
|
39
|
+
|
|
40
|
+
Called by the client at ``dataset()`` time, so pricing is consulted
|
|
41
|
+
lazily, at the same moment the former subclass override did.
|
|
42
|
+
"""
|
|
43
|
+
from .charging import has_default_dataset_item_event_in_pricing
|
|
44
|
+
|
|
45
|
+
pricing_info = self._charging_manager.get_pricing_info()
|
|
46
|
+
return has_default_dataset_item_event_in_pricing(pricing_info.per_event_prices or {})
|
|
47
|
+
|
|
48
|
+
async def push_and_charge(
|
|
49
|
+
self,
|
|
50
|
+
*,
|
|
51
|
+
items: Sequence[Dict[str, Any]],
|
|
52
|
+
event_name: Optional[str],
|
|
53
|
+
push_fn: Callable[[Sequence[Dict[str, Any]]], Any],
|
|
54
|
+
) -> Any:
|
|
55
|
+
from .charging import push_data_and_charge
|
|
56
|
+
|
|
57
|
+
# The client only intercepts the default dataset.
|
|
58
|
+
return await push_data_and_charge(
|
|
59
|
+
self._charging_manager,
|
|
60
|
+
list(items),
|
|
61
|
+
event_name,
|
|
62
|
+
True,
|
|
63
|
+
push_fn,
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
def merge_charge_results(self, a: Any, b: Any) -> Any:
|
|
67
|
+
from .charging import merge_charge_results
|
|
68
|
+
|
|
69
|
+
return merge_charge_results(a, b)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def create_charging_client(
|
|
73
|
+
*,
|
|
74
|
+
token: Optional[str] = None,
|
|
75
|
+
api_url: Optional[str] = None,
|
|
76
|
+
api_public_url: Optional[str] = None,
|
|
77
|
+
charging_manager: Optional['ChargingManager'] = None,
|
|
78
|
+
default_dataset_id: Optional[str] = None,
|
|
79
|
+
**kwargs: Any,
|
|
80
|
+
) -> ApifyClientAsync:
|
|
81
|
+
"""Create an ``ApifyClientAsync`` configured with the SDK's charging hooks.
|
|
82
|
+
|
|
83
|
+
Args:
|
|
84
|
+
token: API token
|
|
85
|
+
api_url: Base URL for the API
|
|
86
|
+
api_public_url: Publicly accessible base URL for signed/public resource URLs.
|
|
87
|
+
When omitted, the client's own default is used.
|
|
88
|
+
charging_manager: Optional charging manager for pay-per-event
|
|
89
|
+
default_dataset_id: The default dataset ID for this actor run
|
|
90
|
+
**kwargs: Additional arguments passed to ApifyClientAsync
|
|
91
|
+
|
|
92
|
+
Returns:
|
|
93
|
+
A charging-configured ``ApifyClientAsync``
|
|
94
|
+
"""
|
|
95
|
+
if api_url:
|
|
96
|
+
kwargs['api_url'] = api_url
|
|
97
|
+
if api_public_url:
|
|
98
|
+
kwargs['api_public_url'] = api_public_url
|
|
99
|
+
|
|
100
|
+
charging_hooks = _ChargingManagerHooks(charging_manager) if charging_manager is not None else None
|
|
101
|
+
|
|
102
|
+
return ApifyClientAsync(
|
|
103
|
+
token=token,
|
|
104
|
+
charging_hooks=charging_hooks,
|
|
105
|
+
default_dataset_id=default_dataset_id,
|
|
106
|
+
**kwargs,
|
|
107
|
+
)
|
|
@@ -0,0 +1,375 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Configuration management for the Scrapely Python SDK.
|
|
3
|
+
|
|
4
|
+
Handles environment variables and configuration options for Actors.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import json
|
|
8
|
+
import os
|
|
9
|
+
from datetime import datetime
|
|
10
|
+
from typing import Any, Dict, Optional, cast
|
|
11
|
+
|
|
12
|
+
from .types import ActorPermissionLevel, ApifyEnv
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class Configuration:
|
|
17
|
+
"""Configuration management for Scrapely Actors.
|
|
18
|
+
|
|
19
|
+
Handles environment variables and provides default values.
|
|
20
|
+
Compatible with Apify's configuration system.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
# Environment variable to configuration key mapping
|
|
24
|
+
ENV_MAP = {
|
|
25
|
+
# APIFY_* variables
|
|
26
|
+
'APIFY_TOKEN': 'token',
|
|
27
|
+
'APIFY_API_BASE_URL': 'api_base_url',
|
|
28
|
+
'APIFY_API_PUBLIC_BASE_URL': 'api_public_base_url',
|
|
29
|
+
'APIFY_IS_AT_HOME': 'is_at_home',
|
|
30
|
+
'APIFY_ACTOR_EVENTS_WS_URL': 'actor_events_ws_url',
|
|
31
|
+
'APIFY_ACTOR_ID': 'actor_id',
|
|
32
|
+
'APIFY_ACTOR_RUN_ID': 'actor_run_id',
|
|
33
|
+
'APIFY_ACTOR_TASK_ID': 'actor_task_id',
|
|
34
|
+
'APIFY_CONTAINER_PORT': 'container_port',
|
|
35
|
+
'APIFY_CONTAINER_URL': 'container_url',
|
|
36
|
+
'APIFY_USER_ID': 'user_id',
|
|
37
|
+
'APIFY_USER_IS_PAYING': 'user_is_paying',
|
|
38
|
+
'APIFY_PROXY_HOSTNAME': 'proxy_hostname',
|
|
39
|
+
'APIFY_PROXY_PASSWORD': 'proxy_password',
|
|
40
|
+
'APIFY_PROXY_STATUS_URL': 'proxy_status_url',
|
|
41
|
+
'APIFY_PROXY_PORT': 'proxy_port',
|
|
42
|
+
'APIFY_INPUT_SECRETS_PRIVATE_KEY_FILE': 'input_secrets_private_key_file',
|
|
43
|
+
'APIFY_INPUT_SECRETS_PRIVATE_KEY_PASSPHRASE': 'input_secrets_private_key_passphrase',
|
|
44
|
+
'APIFY_META_ORIGIN': 'meta_origin',
|
|
45
|
+
'APIFY_DEFAULT_DATASET_ID': 'default_dataset_id',
|
|
46
|
+
'APIFY_DEFAULT_KEY_VALUE_STORE_ID': 'default_key_value_store_id',
|
|
47
|
+
'APIFY_DEFAULT_REQUEST_QUEUE_ID': 'default_request_queue_id',
|
|
48
|
+
'APIFY_MEMORY_MBYTES': 'memory_mbytes',
|
|
49
|
+
'APIFY_INPUT_KEY': 'input_key',
|
|
50
|
+
'APIFY_ACTOR_PRICING_INFO': 'actor_pricing_info',
|
|
51
|
+
'APIFY_CHARGED_ACTOR_EVENT_COUNTS': 'charged_event_counts',
|
|
52
|
+
'APIFY_USER_PRICING_TIER': 'user_pricing_tier',
|
|
53
|
+
'APIFY_LOG_LEVEL': 'log_level',
|
|
54
|
+
'APIFY_LOG_FORMAT': 'log_format',
|
|
55
|
+
'APIFY_LOCAL_STORAGE_DIR': 'local_storage_dir',
|
|
56
|
+
'APIFY_HEADLESS': 'headless',
|
|
57
|
+
'APIFY_SDK_LATEST_VERSION': 'sdk_latest_version',
|
|
58
|
+
'APIFY_SYSTEM_INFO_INTERVAL_MILLIS': 'system_info_interval_millis',
|
|
59
|
+
'APIFY_WORKFLOW_KEY': 'workflow_key',
|
|
60
|
+
'APIFY_DEDICATED_CPUS': 'dedicated_cpus',
|
|
61
|
+
'APIFY_DISABLE_OUTDATED_WARNING': 'disable_outdated_warning',
|
|
62
|
+
'APIFY_FACT': 'fact',
|
|
63
|
+
'APIFY_CHROME_EXECUTABLE_PATH': 'chrome_executable_path',
|
|
64
|
+
'APIFY_PURGE_ON_START': 'purge_on_start',
|
|
65
|
+
'APIFY_XVFB': 'xvfb',
|
|
66
|
+
'APIFY_METAMORPH_AFTER_SLEEP_MILLIS': 'metamorph_after_sleep_millis',
|
|
67
|
+
'APIFY_PERSIST_STATE_INTERVAL_MILLIS': 'persist_state_interval_millis',
|
|
68
|
+
'APIFY_ACTOR_BUILD_ID': 'actor_build_id',
|
|
69
|
+
'APIFY_ACTOR_BUILD_NUMBER': 'actor_build_number',
|
|
70
|
+
'APIFY_ACTOR_MAX_PAID_DATASET_ITEMS': 'actor_max_paid_dataset_items',
|
|
71
|
+
'APIFY_STARTED_AT': 'started_at',
|
|
72
|
+
'APIFY_TIMEOUT_AT': 'timeout_at',
|
|
73
|
+
|
|
74
|
+
# SCRAPELY_* variables
|
|
75
|
+
|
|
76
|
+
'SCRAPELY_TOKEN': 'token',
|
|
77
|
+
'SCRAPELY_API_URL': 'api_base_url',
|
|
78
|
+
'SCRAPELY_IS_AT_HOME': 'is_at_home',
|
|
79
|
+
|
|
80
|
+
# ACTOR_* variables
|
|
81
|
+
'ACTOR_ID': 'actor_id',
|
|
82
|
+
'ACTOR_RUN_ID': 'actor_run_id',
|
|
83
|
+
'ACTOR_TASK_ID': 'actor_task_id',
|
|
84
|
+
'ACTOR_DEFAULT_DATASET_ID': 'default_dataset_id',
|
|
85
|
+
'ACTOR_DEFAULT_KEY_VALUE_STORE_ID': 'default_key_value_store_id',
|
|
86
|
+
'ACTOR_DEFAULT_REQUEST_QUEUE_ID': 'default_request_queue_id',
|
|
87
|
+
'ACTOR_EVENTS_WEBSOCKET_URL': 'actor_events_ws_url',
|
|
88
|
+
'ACTOR_INPUT_KEY': 'input_key',
|
|
89
|
+
'ACTOR_MEMORY_MBYTES': 'memory_mbytes',
|
|
90
|
+
'ACTOR_PERMISSION_LEVEL': 'actor_permission_level',
|
|
91
|
+
'ACTOR_WEB_SERVER_PORT': 'container_port',
|
|
92
|
+
'ACTOR_WEB_SERVER_URL': 'container_url',
|
|
93
|
+
'ACTOR_MAX_TOTAL_CHARGE_USD': 'max_total_charge_usd',
|
|
94
|
+
'ACTOR_TEST_PAY_PER_EVENT': 'test_pay_per_event',
|
|
95
|
+
'ACTOR_USE_CHARGING_LOG_DATASET': 'use_charging_log_dataset',
|
|
96
|
+
'ACTOR_STORAGES_JSON': 'actor_storages_json',
|
|
97
|
+
'ACTOR_META_ORIGIN': 'meta_origin',
|
|
98
|
+
'ACTOR_TIMEOUT_AT': 'timeout_at',
|
|
99
|
+
'ACTOR_STARTED_AT': 'started_at',
|
|
100
|
+
'ACTOR_BUILD_ID': 'actor_build_id',
|
|
101
|
+
'ACTOR_BUILD_NUMBER': 'actor_build_number',
|
|
102
|
+
'ACTOR_BUILD_TAGS': 'actor_build_tags',
|
|
103
|
+
'ACTOR_FULL_NAME': 'actor_full_name',
|
|
104
|
+
'ACTOR_INPUT': 'actor_input',
|
|
105
|
+
'ACTOR_METAMORPH_COUNT': 'metamorph_count',
|
|
106
|
+
'ACTOR_MAX_PAID_DATASET_ITEMS': 'actor_max_paid_dataset_items',
|
|
107
|
+
'ACTOR_RESTART_ON_ERROR': 'restart_on_error',
|
|
108
|
+
'ACTOR_STANDBY_PORT': 'standby_port',
|
|
109
|
+
'ACTOR_STANDBY_URL': 'standby_url',
|
|
110
|
+
|
|
111
|
+
# CRAWLEE_* variables
|
|
112
|
+
'CRAWLEE_STORAGE_DIR': 'local_storage_dir',
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
# Variables that should be parsed as integers
|
|
117
|
+
INTEGER_VARS = {
|
|
118
|
+
'container_port', 'proxy_port', 'memory_mbytes',
|
|
119
|
+
'persist_state_interval_millis',
|
|
120
|
+
'metamorph_after_sleep_millis', 'actor_max_paid_dataset_items',
|
|
121
|
+
'standby_port', 'dedicated_cpus', 'disable_outdated_warning',
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
# Variables that should be parsed as floats (USD amounts etc.)
|
|
125
|
+
FLOAT_VARS = {
|
|
126
|
+
'max_total_charge_usd',
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
# Variables that should be parsed as booleans
|
|
130
|
+
BOOLEAN_VARS = {
|
|
131
|
+
'is_at_home', 'test_pay_per_event', 'use_charging_log_dataset',
|
|
132
|
+
'user_is_paying', 'purge_on_start', 'headless', 'restart_on_error',
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
# Default values
|
|
137
|
+
DEFAULTS = {
|
|
138
|
+
'default_key_value_store_id': 'default',
|
|
139
|
+
'default_dataset_id': 'default',
|
|
140
|
+
'default_request_queue_id': 'default',
|
|
141
|
+
'input_key': 'INPUT',
|
|
142
|
+
'api_base_url': 'https://api-staging.scrape.ly',
|
|
143
|
+
'api_public_base_url': 'https://api-staging.scrape.ly',
|
|
144
|
+
'proxy_status_url': 'http://proxy.scrape.ly',
|
|
145
|
+
'proxy_hostname': 'proxy.scrape.ly',
|
|
146
|
+
'proxy_port': 8000,
|
|
147
|
+
'container_port': 4321,
|
|
148
|
+
'container_url': 'http://localhost:4321',
|
|
149
|
+
'metamorph_after_sleep_millis': 300000,
|
|
150
|
+
'persist_state_interval_millis': 60000,
|
|
151
|
+
'test_pay_per_event': False,
|
|
152
|
+
'use_charging_log_dataset': False,
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
_instance: Optional['Configuration'] = None
|
|
156
|
+
|
|
157
|
+
def __init__(self, options: Optional[Dict[str, Any]] = None):
|
|
158
|
+
"""Initialize configuration with optional overrides.
|
|
159
|
+
|
|
160
|
+
Args:
|
|
161
|
+
options: Configuration options to override defaults
|
|
162
|
+
"""
|
|
163
|
+
self._config: Dict[str, Any] = {}
|
|
164
|
+
self._options = options or {}
|
|
165
|
+
|
|
166
|
+
# Load from environment and options
|
|
167
|
+
self._load_from_env()
|
|
168
|
+
self._load_from_options()
|
|
169
|
+
|
|
170
|
+
@classmethod
|
|
171
|
+
def get_global_config(cls) -> 'Configuration':
|
|
172
|
+
"""Get or create the global configuration instance."""
|
|
173
|
+
if cls._instance is None:
|
|
174
|
+
cls._instance = cls()
|
|
175
|
+
return cls._instance
|
|
176
|
+
|
|
177
|
+
@classmethod
|
|
178
|
+
def get_global_configuration(cls) -> 'Configuration':
|
|
179
|
+
"""Apify/crawlee-compatible alias for get_global_config()."""
|
|
180
|
+
return cls.get_global_config()
|
|
181
|
+
|
|
182
|
+
@classmethod
|
|
183
|
+
def reset_global_state(cls) -> None:
|
|
184
|
+
"""Reset the global configuration instance."""
|
|
185
|
+
cls._instance = None
|
|
186
|
+
|
|
187
|
+
def _load_from_env(self) -> None:
|
|
188
|
+
"""Load configuration from environment variables."""
|
|
189
|
+
for env_key, config_key in self.ENV_MAP.items():
|
|
190
|
+
value = os.environ.get(env_key)
|
|
191
|
+
if value is not None:
|
|
192
|
+
self._config[config_key] = self._parse_value(config_key, value)
|
|
193
|
+
|
|
194
|
+
def _load_from_options(self) -> None:
|
|
195
|
+
"""Load configuration from provided options."""
|
|
196
|
+
for key, value in self._options.items():
|
|
197
|
+
if value is not None:
|
|
198
|
+
self._config[key] = value
|
|
199
|
+
|
|
200
|
+
def _parse_value(self, key: str, value: str) -> Any:
|
|
201
|
+
"""Parse a string value based on the key type."""
|
|
202
|
+
if key in self.INTEGER_VARS:
|
|
203
|
+
try:
|
|
204
|
+
return int(value)
|
|
205
|
+
except ValueError:
|
|
206
|
+
return value
|
|
207
|
+
elif key in self.FLOAT_VARS:
|
|
208
|
+
try:
|
|
209
|
+
return float(value)
|
|
210
|
+
except ValueError:
|
|
211
|
+
return value
|
|
212
|
+
elif key in self.BOOLEAN_VARS:
|
|
213
|
+
return value.lower() in ('1', 'true', 'yes')
|
|
214
|
+
elif key in ('timeout_at', 'started_at'):
|
|
215
|
+
try:
|
|
216
|
+
return datetime.fromisoformat(value.replace('Z', '+00:00'))
|
|
217
|
+
except (ValueError, TypeError):
|
|
218
|
+
return value
|
|
219
|
+
return value
|
|
220
|
+
|
|
221
|
+
def get(self, key: str, default: Any = None) -> Any:
|
|
222
|
+
"""Get a configuration value.
|
|
223
|
+
|
|
224
|
+
Args:
|
|
225
|
+
key: Configuration key
|
|
226
|
+
default: Default value if not found
|
|
227
|
+
|
|
228
|
+
Returns:
|
|
229
|
+
Configuration value or default
|
|
230
|
+
"""
|
|
231
|
+
# Check config first (from env/options)
|
|
232
|
+
if key in self._config:
|
|
233
|
+
return self._config[key]
|
|
234
|
+
# Check defaults
|
|
235
|
+
if key in self.DEFAULTS:
|
|
236
|
+
return self.DEFAULTS[key]
|
|
237
|
+
return default
|
|
238
|
+
|
|
239
|
+
def set(self, key: str, value: Any) -> None:
|
|
240
|
+
"""Set a configuration value.
|
|
241
|
+
|
|
242
|
+
Args:
|
|
243
|
+
key: Configuration key
|
|
244
|
+
value: Value to set
|
|
245
|
+
"""
|
|
246
|
+
self._config[key] = value
|
|
247
|
+
|
|
248
|
+
@property
|
|
249
|
+
def token(self) -> Optional[str]:
|
|
250
|
+
"""Get the authentication token."""
|
|
251
|
+
return cast(Optional[str], self.get('token'))
|
|
252
|
+
|
|
253
|
+
@property
|
|
254
|
+
def api_base_url(self) -> str:
|
|
255
|
+
"""Get the API base URL."""
|
|
256
|
+
return cast(str, self.get('api_base_url', self.DEFAULTS['api_base_url']))
|
|
257
|
+
|
|
258
|
+
@property
|
|
259
|
+
def actor_run_id(self) -> Optional[str]:
|
|
260
|
+
"""Get the current Actor run ID."""
|
|
261
|
+
return cast(Optional[str], self.get('actor_run_id'))
|
|
262
|
+
|
|
263
|
+
@property
|
|
264
|
+
def actor_id(self) -> Optional[str]:
|
|
265
|
+
"""Get the current Actor ID."""
|
|
266
|
+
return cast(Optional[str], self.get('actor_id'))
|
|
267
|
+
|
|
268
|
+
@property
|
|
269
|
+
def is_at_home(self) -> bool:
|
|
270
|
+
"""Check if running on the Scrapely platform."""
|
|
271
|
+
return cast(bool, self.get('is_at_home', False))
|
|
272
|
+
|
|
273
|
+
@property
|
|
274
|
+
def default_dataset_id(self) -> str:
|
|
275
|
+
"""Get the default dataset ID."""
|
|
276
|
+
return cast(str, self.get('default_dataset_id', self.DEFAULTS['default_dataset_id']))
|
|
277
|
+
|
|
278
|
+
@property
|
|
279
|
+
def default_key_value_store_id(self) -> str:
|
|
280
|
+
"""Get the default key-value store ID."""
|
|
281
|
+
return cast(str, self.get('default_key_value_store_id', self.DEFAULTS['default_key_value_store_id']))
|
|
282
|
+
|
|
283
|
+
@property
|
|
284
|
+
def default_request_queue_id(self) -> str:
|
|
285
|
+
"""Get the default request queue ID."""
|
|
286
|
+
return cast(str, self.get('default_request_queue_id', self.DEFAULTS['default_request_queue_id']))
|
|
287
|
+
|
|
288
|
+
@property
|
|
289
|
+
def input_key(self) -> str:
|
|
290
|
+
"""Get the input key."""
|
|
291
|
+
return cast(str, self.get('input_key', self.DEFAULTS['input_key']))
|
|
292
|
+
|
|
293
|
+
def get_env(self) -> ApifyEnv:
|
|
294
|
+
"""Get all environment variables as an ApifyEnv object.
|
|
295
|
+
|
|
296
|
+
Returns:
|
|
297
|
+
ApifyEnv object with all parsed environment variables
|
|
298
|
+
"""
|
|
299
|
+
return ApifyEnv(
|
|
300
|
+
actor_id=self.get('actor_id'),
|
|
301
|
+
actor_build_id=self.get('actor_build_id'),
|
|
302
|
+
actor_build_number=self.get('actor_build_number'),
|
|
303
|
+
actor_build_tags=self.get('actor_build_tags'),
|
|
304
|
+
actor_events_ws_url=self.get('actor_events_ws_url'),
|
|
305
|
+
actor_full_name=self.get('actor_full_name'),
|
|
306
|
+
actor_max_paid_dataset_items=self.get('actor_max_paid_dataset_items'),
|
|
307
|
+
actor_permission_level=self._parse_permission_level(self.get('actor_permission_level')),
|
|
308
|
+
actor_pricing_info=(
|
|
309
|
+
json.dumps(self.get('actor_pricing_info'))
|
|
310
|
+
if isinstance(self.get('actor_pricing_info'), dict)
|
|
311
|
+
else self.get('actor_pricing_info')
|
|
312
|
+
),
|
|
313
|
+
actor_run_id=self.get('actor_run_id'),
|
|
314
|
+
actor_storages_json=self.get('actor_storages_json'),
|
|
315
|
+
actor_task_id=self.get('actor_task_id'),
|
|
316
|
+
api_base_url=self.get('api_base_url'),
|
|
317
|
+
api_public_base_url=self.get('api_public_base_url'),
|
|
318
|
+
charged_event_counts=(
|
|
319
|
+
json.dumps(self.get('charged_event_counts'))
|
|
320
|
+
if isinstance(self.get('charged_event_counts'), dict)
|
|
321
|
+
else self.get('charged_event_counts')
|
|
322
|
+
),
|
|
323
|
+
|
|
324
|
+
chrome_executable_path=self.get('chrome_executable_path'),
|
|
325
|
+
container_port=self.get('container_port'),
|
|
326
|
+
container_url=self.get('container_url'),
|
|
327
|
+
dedicated_cpus=str(self.get('dedicated_cpus')) if self.get('dedicated_cpus') is not None else None,
|
|
328
|
+
default_dataset_id=self.get('default_dataset_id'),
|
|
329
|
+
default_key_value_store_id=self.get('default_key_value_store_id'),
|
|
330
|
+
default_request_queue_id=self.get('default_request_queue_id'),
|
|
331
|
+
disable_outdated_warning=self.get('disable_outdated_warning'),
|
|
332
|
+
fact=self.get('fact'),
|
|
333
|
+
headless='1' if self.get('headless') else None,
|
|
334
|
+
input_key=self.get('input_key'),
|
|
335
|
+
input_secrets_private_key_file=self.get('input_secrets_private_key_file'),
|
|
336
|
+
input_secrets_private_key_passphrase=self.get('input_secrets_private_key_passphrase'),
|
|
337
|
+
is_at_home='1' if self.get('is_at_home') else None,
|
|
338
|
+
local_storage_dir=self.get('local_storage_dir'),
|
|
339
|
+
log_format=self.get('log_format'),
|
|
340
|
+
log_level=self.get('log_level'),
|
|
341
|
+
memory_mbytes=self.get('memory_mbytes'),
|
|
342
|
+
meta_origin=self.get('meta_origin'),
|
|
343
|
+
metamorph_after_sleep_millis=self.get('metamorph_after_sleep_millis'),
|
|
344
|
+
persist_state_interval_millis=self.get('persist_state_interval_millis'),
|
|
345
|
+
proxy_hostname=self.get('proxy_hostname'),
|
|
346
|
+
proxy_password=self.get('proxy_password'),
|
|
347
|
+
proxy_port=str(self.get('proxy_port')) if self.get('proxy_port') is not None else None,
|
|
348
|
+
proxy_status_url=self.get('proxy_status_url'),
|
|
349
|
+
purge_on_start='1' if self.get('purge_on_start') else None,
|
|
350
|
+
sdk_latest_version=self.get('sdk_latest_version'),
|
|
351
|
+
started_at=self.get('started_at'),
|
|
352
|
+
standby_port=self.get('standby_port'),
|
|
353
|
+
standby_url=self.get('standby_url'),
|
|
354
|
+
system_info_interval_millis=self.get('system_info_interval_millis'),
|
|
355
|
+
timeout_at=self.get('timeout_at'),
|
|
356
|
+
token=self.get('token'),
|
|
357
|
+
user_id=self.get('user_id'),
|
|
358
|
+
user_is_paying='1' if self.get('user_is_paying') else None,
|
|
359
|
+
user_pricing_tier=self.get('user_pricing_tier'),
|
|
360
|
+
workflow_key=self.get('workflow_key'),
|
|
361
|
+
xvfb=self.get('xvfb'),
|
|
362
|
+
max_total_charge_usd=self.get('max_total_charge_usd'),
|
|
363
|
+
user_plan=self.get('user_plan'),
|
|
364
|
+
restart_on_error=self.get('restart_on_error'),
|
|
365
|
+
)
|
|
366
|
+
|
|
367
|
+
|
|
368
|
+
def _parse_permission_level(self, value: Optional[str]) -> Optional[ActorPermissionLevel]:
|
|
369
|
+
"""Parse permission level string to enum."""
|
|
370
|
+
if value is None:
|
|
371
|
+
return None
|
|
372
|
+
try:
|
|
373
|
+
return ActorPermissionLevel(value.upper())
|
|
374
|
+
except ValueError:
|
|
375
|
+
return None
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"""Crypto helpers for the Scrapely Python SDK.
|
|
2
|
+
|
|
3
|
+
Currently contains the secret Actor input decryption ported from the Node SDK's
|
|
4
|
+
``src/crypto/secrets.ts``.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from ._secrets import (
|
|
8
|
+
decrypt_all_secrets,
|
|
9
|
+
decrypt_private_key_file,
|
|
10
|
+
decrypt_secret_inputs,
|
|
11
|
+
decrypt_secret_value,
|
|
12
|
+
merge_decrypted_input,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
__all__ = [
|
|
16
|
+
'decrypt_all_secrets',
|
|
17
|
+
'decrypt_private_key_file',
|
|
18
|
+
'decrypt_secret_inputs',
|
|
19
|
+
'decrypt_secret_value',
|
|
20
|
+
'merge_decrypted_input',
|
|
21
|
+
]
|