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.
Files changed (73) hide show
  1. {scrapely_python-0.1.24/src/scrapely_python.egg-info → scrapely_python-0.1.26}/PKG-INFO +1 -1
  2. {scrapely_python-0.1.24 → scrapely_python-0.1.26}/pyproject.toml +1 -1
  3. {scrapely_python-0.1.24 → scrapely_python-0.1.26}/src/scrapely/__init__.py +1 -1
  4. {scrapely_python-0.1.24 → scrapely_python-0.1.26}/src/scrapely/actor.py +24 -0
  5. {scrapely_python-0.1.24 → scrapely_python-0.1.26}/src/scrapely/solver.py +6 -5
  6. {scrapely_python-0.1.24 → scrapely_python-0.1.26/src/scrapely_python.egg-info}/PKG-INFO +1 -1
  7. {scrapely_python-0.1.24 → scrapely_python-0.1.26}/tests/test_debug_capture.py +23 -0
  8. {scrapely_python-0.1.24 → scrapely_python-0.1.26}/tests/test_solver.py +1 -1
  9. {scrapely_python-0.1.24 → scrapely_python-0.1.26}/LICENSE +0 -0
  10. {scrapely_python-0.1.24 → scrapely_python-0.1.26}/LICENSE-APACHE +0 -0
  11. {scrapely_python-0.1.24 → scrapely_python-0.1.26}/NOTICE +0 -0
  12. {scrapely_python-0.1.24 → scrapely_python-0.1.26}/README.md +0 -0
  13. {scrapely_python-0.1.24 → scrapely_python-0.1.26}/setup.cfg +0 -0
  14. {scrapely_python-0.1.24 → scrapely_python-0.1.26}/src/scrapely/_browser_locations.py +0 -0
  15. {scrapely_python-0.1.24 → scrapely_python-0.1.26}/src/scrapely/browser_configuration.py +0 -0
  16. {scrapely_python-0.1.24 → scrapely_python-0.1.26}/src/scrapely/cdp_retry.py +0 -0
  17. {scrapely_python-0.1.24 → scrapely_python-0.1.26}/src/scrapely/charging.py +0 -0
  18. {scrapely_python-0.1.24 → scrapely_python-0.1.26}/src/scrapely/charging_client.py +0 -0
  19. {scrapely_python-0.1.24 → scrapely_python-0.1.26}/src/scrapely/configuration.py +0 -0
  20. {scrapely_python-0.1.24 → scrapely_python-0.1.26}/src/scrapely/crypto/__init__.py +0 -0
  21. {scrapely_python-0.1.24 → scrapely_python-0.1.26}/src/scrapely/crypto/_secrets.py +0 -0
  22. {scrapely_python-0.1.24 → scrapely_python-0.1.26}/src/scrapely/errors.py +0 -0
  23. {scrapely_python-0.1.24 → scrapely_python-0.1.26}/src/scrapely/events/__init__.py +0 -0
  24. {scrapely_python-0.1.24 → scrapely_python-0.1.26}/src/scrapely/events/_types.py +0 -0
  25. {scrapely_python-0.1.24 → scrapely_python-0.1.26}/src/scrapely/events/event_manager.py +0 -0
  26. {scrapely_python-0.1.24 → scrapely_python-0.1.26}/src/scrapely/log.py +0 -0
  27. {scrapely_python-0.1.24 → scrapely_python-0.1.26}/src/scrapely/patch_browser_pool.py +0 -0
  28. {scrapely_python-0.1.24 → scrapely_python-0.1.26}/src/scrapely/pricing_types.py +0 -0
  29. {scrapely_python-0.1.24 → scrapely_python-0.1.26}/src/scrapely/proxy_configuration.py +0 -0
  30. {scrapely_python-0.1.24 → scrapely_python-0.1.26}/src/scrapely/py.typed +0 -0
  31. {scrapely_python-0.1.24 → scrapely_python-0.1.26}/src/scrapely/request_loaders/__init__.py +0 -0
  32. {scrapely_python-0.1.24 → scrapely_python-0.1.26}/src/scrapely/request_loaders/_scrapely_request_list.py +0 -0
  33. {scrapely_python-0.1.24 → scrapely_python-0.1.26}/src/scrapely/storage/__init__.py +0 -0
  34. {scrapely_python-0.1.24 → scrapely_python-0.1.26}/src/scrapely/storage/_file_system/__init__.py +0 -0
  35. {scrapely_python-0.1.24 → scrapely_python-0.1.26}/src/scrapely/storage/_file_system/_key_value_store_client.py +0 -0
  36. {scrapely_python-0.1.24 → scrapely_python-0.1.26}/src/scrapely/storage/_file_system/_storage_client.py +0 -0
  37. {scrapely_python-0.1.24 → scrapely_python-0.1.26}/src/scrapely/storage/_metadata.py +0 -0
  38. {scrapely_python-0.1.24 → scrapely_python-0.1.26}/src/scrapely/storage/api_client.py +0 -0
  39. {scrapely_python-0.1.24 → scrapely_python-0.1.26}/src/scrapely/storage/client.py +0 -0
  40. {scrapely_python-0.1.24 → scrapely_python-0.1.26}/src/scrapely/storage/patch_storage.py +0 -0
  41. {scrapely_python-0.1.24 → scrapely_python-0.1.26}/src/scrapely/storage/scrapely_dataset_client.py +0 -0
  42. {scrapely_python-0.1.24 → scrapely_python-0.1.26}/src/scrapely/storage/scrapely_kvs_client.py +0 -0
  43. {scrapely_python-0.1.24 → scrapely_python-0.1.26}/src/scrapely/storage/scrapely_rq_client.py +0 -0
  44. {scrapely_python-0.1.24 → scrapely_python-0.1.26}/src/scrapely/storage/scrapely_storage_client.py +0 -0
  45. {scrapely_python-0.1.24 → scrapely_python-0.1.26}/src/scrapely/storage/smart_scrapely_storage_client.py +0 -0
  46. {scrapely_python-0.1.24 → scrapely_python-0.1.26}/src/scrapely/storage/types.py +0 -0
  47. {scrapely_python-0.1.24 → scrapely_python-0.1.26}/src/scrapely/storage/utils.py +0 -0
  48. {scrapely_python-0.1.24 → scrapely_python-0.1.26}/src/scrapely/storage_clients/__init__.py +0 -0
  49. {scrapely_python-0.1.24 → scrapely_python-0.1.26}/src/scrapely/storages/__init__.py +0 -0
  50. {scrapely_python-0.1.24 → scrapely_python-0.1.26}/src/scrapely/types.py +0 -0
  51. {scrapely_python-0.1.24 → scrapely_python-0.1.26}/src/scrapely/webhook.py +0 -0
  52. {scrapely_python-0.1.24 → scrapely_python-0.1.26}/src/scrapely_python.egg-info/SOURCES.txt +0 -0
  53. {scrapely_python-0.1.24 → scrapely_python-0.1.26}/src/scrapely_python.egg-info/dependency_links.txt +0 -0
  54. {scrapely_python-0.1.24 → scrapely_python-0.1.26}/src/scrapely_python.egg-info/requires.txt +0 -0
  55. {scrapely_python-0.1.24 → scrapely_python-0.1.26}/src/scrapely_python.egg-info/top_level.txt +0 -0
  56. {scrapely_python-0.1.24 → scrapely_python-0.1.26}/tests/test_api_client_retries.py +0 -0
  57. {scrapely_python-0.1.24 → scrapely_python-0.1.26}/tests/test_apify_parity.py +0 -0
  58. {scrapely_python-0.1.24 → scrapely_python-0.1.26}/tests/test_browser_configuration.py +0 -0
  59. {scrapely_python-0.1.24 → scrapely_python-0.1.26}/tests/test_cdp_retry.py +0 -0
  60. {scrapely_python-0.1.24 → scrapely_python-0.1.26}/tests/test_charge_limit_guard.py +0 -0
  61. {scrapely_python-0.1.24 → scrapely_python-0.1.26}/tests/test_charge_retries.py +0 -0
  62. {scrapely_python-0.1.24 → scrapely_python-0.1.26}/tests/test_charging_parity.py +0 -0
  63. {scrapely_python-0.1.24 → scrapely_python-0.1.26}/tests/test_charging_pending.py +0 -0
  64. {scrapely_python-0.1.24 → scrapely_python-0.1.26}/tests/test_input_proxy_aliases.py +0 -0
  65. {scrapely_python-0.1.24 → scrapely_python-0.1.26}/tests/test_lazy_crawlee.py +0 -0
  66. {scrapely_python-0.1.24 → scrapely_python-0.1.26}/tests/test_parity.py +0 -0
  67. {scrapely_python-0.1.24 → scrapely_python-0.1.26}/tests/test_patch_browser_pool.py +0 -0
  68. {scrapely_python-0.1.24 → scrapely_python-0.1.26}/tests/test_proxy_configuration.py +0 -0
  69. {scrapely_python-0.1.24 → scrapely_python-0.1.26}/tests/test_push_idempotency.py +0 -0
  70. {scrapely_python-0.1.24 → scrapely_python-0.1.26}/tests/test_run_control.py +0 -0
  71. {scrapely_python-0.1.24 → scrapely_python-0.1.26}/tests/test_secrets.py +0 -0
  72. {scrapely_python-0.1.24 → scrapely_python-0.1.26}/tests/test_storage.py +0 -0
  73. {scrapely_python-0.1.24 → scrapely_python-0.1.26}/tests/test_terminal_status_message.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: scrapely-python
3
- Version: 0.1.24
3
+ Version: 0.1.26
4
4
  Summary: Scrapely Python SDK - A toolkit for building Actors on the Scrapely platform
5
5
  Author-email: Scrapely <support@scrape.ly>
6
6
  License-Expression: MIT AND Apache-2.0
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "scrapely-python"
7
- version = "0.1.24"
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"
@@ -227,7 +227,7 @@ if TYPE_CHECKING: # Static analysers and IDEs see the lazy names as plain impor
227
227
  set_api_storage,
228
228
  )
229
229
 
230
- __version__ = '0.1.24'
230
+ __version__ = '0.1.26'
231
231
  __all__ = [
232
232
  # Core
233
233
  'Actor',
@@ -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 (xhr.dev today) behind
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
- _COMPAT_SUFFIX = "/compat/xhrdev"
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
- if not url.endswith(_COMPAT_SUFFIX):
122
- return None
123
- return url[: -len(_COMPAT_SUFFIX)], key
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 ──────────────────────────────────────────────────────────────────
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: scrapely-python
3
- Version: 0.1.24
3
+ Version: 0.1.26
4
4
  Summary: Scrapely Python SDK - A toolkit for building Actors on the Scrapely platform
5
5
  Author-email: Scrapely <support@scrape.ly>
6
6
  License-Expression: MIT AND Apache-2.0
@@ -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/xhrdev")
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