scrapely-python 0.1.24__tar.gz → 0.1.26__tar.gz
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_python-0.1.24/src/scrapely_python.egg-info → scrapely_python-0.1.26}/PKG-INFO +1 -1
- {scrapely_python-0.1.24 → scrapely_python-0.1.26}/pyproject.toml +1 -1
- {scrapely_python-0.1.24 → scrapely_python-0.1.26}/src/scrapely/__init__.py +1 -1
- {scrapely_python-0.1.24 → scrapely_python-0.1.26}/src/scrapely/actor.py +24 -0
- {scrapely_python-0.1.24 → scrapely_python-0.1.26}/src/scrapely/solver.py +6 -5
- {scrapely_python-0.1.24 → scrapely_python-0.1.26/src/scrapely_python.egg-info}/PKG-INFO +1 -1
- {scrapely_python-0.1.24 → scrapely_python-0.1.26}/tests/test_debug_capture.py +23 -0
- {scrapely_python-0.1.24 → scrapely_python-0.1.26}/tests/test_solver.py +1 -1
- {scrapely_python-0.1.24 → scrapely_python-0.1.26}/LICENSE +0 -0
- {scrapely_python-0.1.24 → scrapely_python-0.1.26}/LICENSE-APACHE +0 -0
- {scrapely_python-0.1.24 → scrapely_python-0.1.26}/NOTICE +0 -0
- {scrapely_python-0.1.24 → scrapely_python-0.1.26}/README.md +0 -0
- {scrapely_python-0.1.24 → scrapely_python-0.1.26}/setup.cfg +0 -0
- {scrapely_python-0.1.24 → scrapely_python-0.1.26}/src/scrapely/_browser_locations.py +0 -0
- {scrapely_python-0.1.24 → scrapely_python-0.1.26}/src/scrapely/browser_configuration.py +0 -0
- {scrapely_python-0.1.24 → scrapely_python-0.1.26}/src/scrapely/cdp_retry.py +0 -0
- {scrapely_python-0.1.24 → scrapely_python-0.1.26}/src/scrapely/charging.py +0 -0
- {scrapely_python-0.1.24 → scrapely_python-0.1.26}/src/scrapely/charging_client.py +0 -0
- {scrapely_python-0.1.24 → scrapely_python-0.1.26}/src/scrapely/configuration.py +0 -0
- {scrapely_python-0.1.24 → scrapely_python-0.1.26}/src/scrapely/crypto/__init__.py +0 -0
- {scrapely_python-0.1.24 → scrapely_python-0.1.26}/src/scrapely/crypto/_secrets.py +0 -0
- {scrapely_python-0.1.24 → scrapely_python-0.1.26}/src/scrapely/errors.py +0 -0
- {scrapely_python-0.1.24 → scrapely_python-0.1.26}/src/scrapely/events/__init__.py +0 -0
- {scrapely_python-0.1.24 → scrapely_python-0.1.26}/src/scrapely/events/_types.py +0 -0
- {scrapely_python-0.1.24 → scrapely_python-0.1.26}/src/scrapely/events/event_manager.py +0 -0
- {scrapely_python-0.1.24 → scrapely_python-0.1.26}/src/scrapely/log.py +0 -0
- {scrapely_python-0.1.24 → scrapely_python-0.1.26}/src/scrapely/patch_browser_pool.py +0 -0
- {scrapely_python-0.1.24 → scrapely_python-0.1.26}/src/scrapely/pricing_types.py +0 -0
- {scrapely_python-0.1.24 → scrapely_python-0.1.26}/src/scrapely/proxy_configuration.py +0 -0
- {scrapely_python-0.1.24 → scrapely_python-0.1.26}/src/scrapely/py.typed +0 -0
- {scrapely_python-0.1.24 → scrapely_python-0.1.26}/src/scrapely/request_loaders/__init__.py +0 -0
- {scrapely_python-0.1.24 → scrapely_python-0.1.26}/src/scrapely/request_loaders/_scrapely_request_list.py +0 -0
- {scrapely_python-0.1.24 → scrapely_python-0.1.26}/src/scrapely/storage/__init__.py +0 -0
- {scrapely_python-0.1.24 → scrapely_python-0.1.26}/src/scrapely/storage/_file_system/__init__.py +0 -0
- {scrapely_python-0.1.24 → scrapely_python-0.1.26}/src/scrapely/storage/_file_system/_key_value_store_client.py +0 -0
- {scrapely_python-0.1.24 → scrapely_python-0.1.26}/src/scrapely/storage/_file_system/_storage_client.py +0 -0
- {scrapely_python-0.1.24 → scrapely_python-0.1.26}/src/scrapely/storage/_metadata.py +0 -0
- {scrapely_python-0.1.24 → scrapely_python-0.1.26}/src/scrapely/storage/api_client.py +0 -0
- {scrapely_python-0.1.24 → scrapely_python-0.1.26}/src/scrapely/storage/client.py +0 -0
- {scrapely_python-0.1.24 → scrapely_python-0.1.26}/src/scrapely/storage/patch_storage.py +0 -0
- {scrapely_python-0.1.24 → scrapely_python-0.1.26}/src/scrapely/storage/scrapely_dataset_client.py +0 -0
- {scrapely_python-0.1.24 → scrapely_python-0.1.26}/src/scrapely/storage/scrapely_kvs_client.py +0 -0
- {scrapely_python-0.1.24 → scrapely_python-0.1.26}/src/scrapely/storage/scrapely_rq_client.py +0 -0
- {scrapely_python-0.1.24 → scrapely_python-0.1.26}/src/scrapely/storage/scrapely_storage_client.py +0 -0
- {scrapely_python-0.1.24 → scrapely_python-0.1.26}/src/scrapely/storage/smart_scrapely_storage_client.py +0 -0
- {scrapely_python-0.1.24 → scrapely_python-0.1.26}/src/scrapely/storage/types.py +0 -0
- {scrapely_python-0.1.24 → scrapely_python-0.1.26}/src/scrapely/storage/utils.py +0 -0
- {scrapely_python-0.1.24 → scrapely_python-0.1.26}/src/scrapely/storage_clients/__init__.py +0 -0
- {scrapely_python-0.1.24 → scrapely_python-0.1.26}/src/scrapely/storages/__init__.py +0 -0
- {scrapely_python-0.1.24 → scrapely_python-0.1.26}/src/scrapely/types.py +0 -0
- {scrapely_python-0.1.24 → scrapely_python-0.1.26}/src/scrapely/webhook.py +0 -0
- {scrapely_python-0.1.24 → scrapely_python-0.1.26}/src/scrapely_python.egg-info/SOURCES.txt +0 -0
- {scrapely_python-0.1.24 → scrapely_python-0.1.26}/src/scrapely_python.egg-info/dependency_links.txt +0 -0
- {scrapely_python-0.1.24 → scrapely_python-0.1.26}/src/scrapely_python.egg-info/requires.txt +0 -0
- {scrapely_python-0.1.24 → scrapely_python-0.1.26}/src/scrapely_python.egg-info/top_level.txt +0 -0
- {scrapely_python-0.1.24 → scrapely_python-0.1.26}/tests/test_api_client_retries.py +0 -0
- {scrapely_python-0.1.24 → scrapely_python-0.1.26}/tests/test_apify_parity.py +0 -0
- {scrapely_python-0.1.24 → scrapely_python-0.1.26}/tests/test_browser_configuration.py +0 -0
- {scrapely_python-0.1.24 → scrapely_python-0.1.26}/tests/test_cdp_retry.py +0 -0
- {scrapely_python-0.1.24 → scrapely_python-0.1.26}/tests/test_charge_limit_guard.py +0 -0
- {scrapely_python-0.1.24 → scrapely_python-0.1.26}/tests/test_charge_retries.py +0 -0
- {scrapely_python-0.1.24 → scrapely_python-0.1.26}/tests/test_charging_parity.py +0 -0
- {scrapely_python-0.1.24 → scrapely_python-0.1.26}/tests/test_charging_pending.py +0 -0
- {scrapely_python-0.1.24 → scrapely_python-0.1.26}/tests/test_input_proxy_aliases.py +0 -0
- {scrapely_python-0.1.24 → scrapely_python-0.1.26}/tests/test_lazy_crawlee.py +0 -0
- {scrapely_python-0.1.24 → scrapely_python-0.1.26}/tests/test_parity.py +0 -0
- {scrapely_python-0.1.24 → scrapely_python-0.1.26}/tests/test_patch_browser_pool.py +0 -0
- {scrapely_python-0.1.24 → scrapely_python-0.1.26}/tests/test_proxy_configuration.py +0 -0
- {scrapely_python-0.1.24 → scrapely_python-0.1.26}/tests/test_push_idempotency.py +0 -0
- {scrapely_python-0.1.24 → scrapely_python-0.1.26}/tests/test_run_control.py +0 -0
- {scrapely_python-0.1.24 → scrapely_python-0.1.26}/tests/test_secrets.py +0 -0
- {scrapely_python-0.1.24 → scrapely_python-0.1.26}/tests/test_storage.py +0 -0
- {scrapely_python-0.1.24 → scrapely_python-0.1.26}/tests/test_terminal_status_message.py +0 -0
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "scrapely-python"
|
|
7
|
-
version = "0.1.
|
|
7
|
+
version = "0.1.26"
|
|
8
8
|
description = "Scrapely Python SDK - A toolkit for building Actors on the Scrapely platform"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "MIT AND Apache-2.0"
|
|
@@ -360,6 +360,7 @@ class Actor(metaclass=_ActorMeta):
|
|
|
360
360
|
|
|
361
361
|
@classmethod
|
|
362
362
|
async def init(cls, options: Optional[InitOptions] = None) -> None:
|
|
363
|
+
cls._main_loop = asyncio.get_running_loop() # for debug_capture_sync from worker threads
|
|
363
364
|
if cls._initialized:
|
|
364
365
|
return
|
|
365
366
|
|
|
@@ -934,6 +935,29 @@ class Actor(metaclass=_ActorMeta):
|
|
|
934
935
|
import hashlib
|
|
935
936
|
return 'DEBUG_CAPTURE_' + hashlib.sha1(str(key).strip().encode('utf-8')).hexdigest()[:24]
|
|
936
937
|
|
|
938
|
+
_main_loop: Optional[asyncio.AbstractEventLoop] = None
|
|
939
|
+
|
|
940
|
+
@classmethod
|
|
941
|
+
def debug_capture_sync(cls, key: str, payload: Any) -> None:
|
|
942
|
+
"""debug_capture for synchronous code (also from worker threads, e.g. asyncio.to_thread).
|
|
943
|
+
Does nothing unless the platform enabled capture for this run."""
|
|
944
|
+
if os.environ.get('SCRAPELY_DEBUG_CAPTURE') != '1' or not key:
|
|
945
|
+
return
|
|
946
|
+
loop = cls._main_loop
|
|
947
|
+
if loop is None or loop.is_closed():
|
|
948
|
+
return
|
|
949
|
+
try:
|
|
950
|
+
try:
|
|
951
|
+
running = asyncio.get_running_loop()
|
|
952
|
+
except RuntimeError:
|
|
953
|
+
running = None
|
|
954
|
+
if running is loop:
|
|
955
|
+
loop.create_task(cls.debug_capture(key, payload))
|
|
956
|
+
else:
|
|
957
|
+
asyncio.run_coroutine_threadsafe(cls.debug_capture(key, payload), loop).result(timeout=15)
|
|
958
|
+
except Exception as err: # never break the run
|
|
959
|
+
logging.getLogger('scrapely').debug('debug_capture_sync failed: %s', err)
|
|
960
|
+
|
|
937
961
|
@classmethod
|
|
938
962
|
async def debug_capture(cls, key: str, payload: Any) -> None:
|
|
939
963
|
"""Save the raw source payload (API JSON or HTML) an item was extracted from, keyed by
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"""Anti-bot solver helpers for Scrapely Actors.
|
|
2
2
|
|
|
3
|
-
The platform runs the challenge solver
|
|
3
|
+
The platform runs the challenge solver behind
|
|
4
4
|
``/v2/solver``. Actors never hold a solver key: the platform injects
|
|
5
5
|
``SCRAPELY_SOLVER_URL`` and ``SCRAPELY_SOLVER_API_KEY`` (the run's own token)
|
|
6
6
|
into runs of Actors that may use it.
|
|
@@ -88,7 +88,7 @@ DEGRADED_STATUS_MESSAGE = "Site protection temporarily unavailable, results may
|
|
|
88
88
|
# Codes the platform answers with when a vendor cannot be solved right now.
|
|
89
89
|
UNAVAILABLE_CODES = ("vendor_degraded", "vendor_down", "provider_unavailable", "vendor_unsupported", "solver_not_enabled")
|
|
90
90
|
|
|
91
|
-
|
|
91
|
+
_COMPAT_SUFFIXES = ("/compat/v1", "/compat/xhrdev")
|
|
92
92
|
_STATUS_TTL_SECS = 15.0
|
|
93
93
|
_MAX_BODY = 4 * 1024 * 1024
|
|
94
94
|
|
|
@@ -118,9 +118,10 @@ def _gateway() -> tuple[str, str] | None:
|
|
|
118
118
|
if env is None:
|
|
119
119
|
return None
|
|
120
120
|
url, key = env
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
121
|
+
for suffix in _COMPAT_SUFFIXES:
|
|
122
|
+
if url.endswith(suffix):
|
|
123
|
+
return url[: -len(suffix)], key
|
|
124
|
+
return None
|
|
124
125
|
|
|
125
126
|
|
|
126
127
|
# ─── errors ──────────────────────────────────────────────────────────────────
|
|
@@ -29,3 +29,26 @@ def test_debug_capture_writes_keyed_capped_record(monkeypatch):
|
|
|
29
29
|
assert key.startswith('DEBUG_CAPTURE_') and len(key) == len('DEBUG_CAPTURE_') + 24
|
|
30
30
|
assert saved[key]['key'] == 'https://x/item/1'
|
|
31
31
|
assert len(saved[key]['payload']) == Actor._DEBUG_CAPTURE_MAX_BYTES
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def test_debug_capture_sync_from_thread(monkeypatch):
|
|
35
|
+
monkeypatch.setenv('SCRAPELY_DEBUG_CAPTURE', '1')
|
|
36
|
+
saved = {}
|
|
37
|
+
|
|
38
|
+
async def fake_set_value(key, value, content_type=None):
|
|
39
|
+
saved[key] = value
|
|
40
|
+
|
|
41
|
+
monkeypatch.setattr(Actor, 'set_value', fake_set_value)
|
|
42
|
+
monkeypatch.setattr(Actor, '_debug_capture_count', 0)
|
|
43
|
+
|
|
44
|
+
async def main():
|
|
45
|
+
Actor._main_loop = asyncio.get_running_loop()
|
|
46
|
+
await asyncio.to_thread(Actor.debug_capture_sync, 'https://x/item/2', {'a': 1})
|
|
47
|
+
|
|
48
|
+
asyncio.run(main())
|
|
49
|
+
assert saved[Actor.debug_capture_record_key('https://x/item/2')]['key'] == 'https://x/item/2'
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def test_debug_capture_sync_noop_without_flag(monkeypatch):
|
|
53
|
+
monkeypatch.delenv('SCRAPELY_DEBUG_CAPTURE', raising=False)
|
|
54
|
+
Actor.debug_capture_sync('k', {'a': 1}) # no loop, no flag: nothing happens
|
|
@@ -177,7 +177,7 @@ class FakeSession:
|
|
|
177
177
|
@pytest.fixture
|
|
178
178
|
def gateway(monkeypatch):
|
|
179
179
|
gw = Gateway()
|
|
180
|
-
monkeypatch.setenv("SCRAPELY_SOLVER_URL", gw.base + "/v2/solver/compat/
|
|
180
|
+
monkeypatch.setenv("SCRAPELY_SOLVER_URL", gw.base + "/v2/solver/compat/v1")
|
|
181
181
|
monkeypatch.setenv("SCRAPELY_SOLVER_API_KEY", "st_test_token")
|
|
182
182
|
S._status_cache.clear()
|
|
183
183
|
yield gw
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{scrapely_python-0.1.24 → scrapely_python-0.1.26}/src/scrapely/storage/_file_system/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{scrapely_python-0.1.24 → scrapely_python-0.1.26}/src/scrapely/storage/scrapely_dataset_client.py
RENAMED
|
File without changes
|
{scrapely_python-0.1.24 → scrapely_python-0.1.26}/src/scrapely/storage/scrapely_kvs_client.py
RENAMED
|
File without changes
|
{scrapely_python-0.1.24 → scrapely_python-0.1.26}/src/scrapely/storage/scrapely_rq_client.py
RENAMED
|
File without changes
|
{scrapely_python-0.1.24 → scrapely_python-0.1.26}/src/scrapely/storage/scrapely_storage_client.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{scrapely_python-0.1.24 → scrapely_python-0.1.26}/src/scrapely_python.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
{scrapely_python-0.1.24 → scrapely_python-0.1.26}/src/scrapely_python.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|