PryScraper — multi-source crypto intelligence crawler. FastAPI + Docker. CLI + MCP server + SDK (munchcrawl_sdk.py). Scraper, extractor, parser, automator, job queue. Referral proxy rotation. Closed-source.
Find a file
opencode 3804c1cd47 fix(pry): test_ready_returns_200 → pytest.mark.integration
Mark the readiness test as integration; it requires live Postgres + Redis
which are not running in Cinnabox dev. Tests run on Talos prod include it
when invoked with: pytest -m integration.

- Add @pytest.mark.integration + skipif on _has_postgres() probe
- Register integration marker in pyproject.toml
- Default addopts = -m 'not integration' skips them in plain ============================= test session starts ==============================
platform linux -- Python 3.12.3, pytest-9.1.1, pluggy-1.6.0
rootdir: /home/dev/pry
configfile: pyproject.toml
testpaths: tests
plugins: respx-0.23.1, cov-7.1.0, Faker-27.4.0, asyncio-1.4.0, anyio-4.14.1
asyncio: mode=Mode.AUTO, debug=False, asyncio_default_fixture_loop_scope=None, asyncio_default_test_loop_scope=function
collected 622 items / 1 deselected / 621 selected

tests/test_adaptive.py .......                                           [  1%]
tests/test_advanced_features.py ..............                           [  3%]
tests/test_advanced_scraping.py .................                        [  6%]
tests/test_agency.py .......                                             [  7%]
tests/test_ai_plugin.py ...                                              [  7%]
tests/test_alerter.py .....                                              [  8%]
tests/test_api.py ....                                                   [  9%]
tests/test_api_integration.py ...........................                [ 13%]
tests/test_api_mcp.py ...s                                               [ 14%]
tests/test_api_mcp_x402.py ..............                                [ 16%]
tests/test_api_scraping.py ........                                      [ 17%]
tests/test_api_surface_snapshot.py ....                                  [ 18%]
tests/test_api_templates.py ......                                       [ 19%]
tests/test_apify_schema.py .....                                         [ 20%]
tests/test_auth_connector.py .....                                       [ 20%]
tests/test_block_detector.py ..........                                  [ 22%]
tests/test_cache.py .......                                              [ 23%]
tests/test_camoufox.py ...                                               [ 24%]
tests/test_commerce_sync.py ..                                           [ 24%]
tests/test_compliance.py .............                                   [ 26%]
tests/test_costing.py ....                                               [ 27%]
tests/test_crm_sync.py ......                                            [ 28%]
tests/test_db.py .........                                               [ 29%]
tests/test_destinations.py .......                                       [ 30%]
tests/test_email_scraper.py .........                                    [ 32%]
tests/test_enrichment.py ..........                                      [ 33%]
tests/test_extractor.py .......                                          [ 34%]
tests/test_fallback_integration.py ............                          [ 36%]
tests/test_freshness.py .....                                            [ 37%]
tests/test_gdpr.py ......                                                [ 38%]
tests/test_infrastructure.py ...........                                 [ 40%]
tests/test_intelligence.py ...........................                   [ 44%]
tests/test_jobqueue.py ......                                            [ 45%]
tests/test_lazy_load.py .....                                            [ 46%]
tests/test_llm_fallback.py ......                                        [ 47%]
tests/test_llm_providers.py ......                                       [ 48%]
tests/test_logging_config.py ........                                    [ 49%]
tests/test_markdown_gen.py ......                                        [ 50%]
tests/test_marketplace.py ...........                                    [ 52%]
tests/test_mcp_production.py ..........                                  [ 54%]
tests/test_monitor.py ...............                                    [ 56%]
tests/test_network.py ......                                             [ 57%]
tests/test_parser.py ......                                              [ 58%]
tests/test_pipeline.py .....                                             [ 59%]
tests/test_pipelines.py .......                                          [ 60%]
tests/test_proxy_manager.py ............                                 [ 62%]
tests/test_quality.py ...........                                        [ 64%]
tests/test_ratelimit.py ......                                           [ 65%]
tests/test_real_features.py ........                                     [ 66%]
tests/test_reconciliation.py .........                                   [ 67%]
tests/test_referrals.py ..............                                   [ 70%]
tests/test_reports.py .......                                            [ 71%]
tests/test_resilience.py ..............                                  [ 73%]
tests/test_retry.py ...........................                          [ 77%]
tests/test_review.py ......                                              [ 78%]
tests/test_scraper.py ........                                           [ 80%]
tests/test_sdk.py ......                                                 [ 80%]
tests/test_secrets_backend.py .........                                  [ 82%]
tests/test_seo_monitor.py ......                                         [ 83%]
tests/test_settings.py .........                                         [ 84%]
tests/test_shadow_dom.py .........                                       [ 86%]
tests/test_structure_monitor.py F......                                  [ 87%]
tests/test_templates.py ......                                           [ 88%]
tests/test_training_data.py .........                                    [ 89%]
tests/test_ultimate_scraper.py ............                              [ 91%]
tests/test_url_guard.py ........................                         [ 95%]
tests/test_x402_mcp_spec.py ...........................                  [100%]

=================================== FAILURES ===================================
__________________________ test_check_selectors_empty __________________________

    @pytest.mark.asyncio
    async def test_check_selectors_empty():
>       result = await check_selectors("https://example.com", [])
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_structure_monitor.py:36:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
structure_monitor.py:46: in check_selectors
    resp = await client.get(
../.local/lib/python3.12/site-packages/httpx/_client.py:1768: in get
    return await self.request(
../.local/lib/python3.12/site-packages/httpx/_client.py:1540: in request
    return await self.send(request, auth=auth, follow_redirects=follow_redirects)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../.local/lib/python3.12/site-packages/httpx/_client.py:1629: in send
    response = await self._send_handling_auth(
../.local/lib/python3.12/site-packages/httpx/_client.py:1657: in _send_handling_auth
    response = await self._send_handling_redirects(
../.local/lib/python3.12/site-packages/httpx/_client.py:1694: in _send_handling_redirects
    response = await self._send_single_request(request)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../.local/lib/python3.12/site-packages/httpx/_client.py:1730: in _send_single_request
    response = await transport.handle_async_request(request)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../.local/lib/python3.12/site-packages/httpx/_transports/default.py:394: in handle_async_request
    resp = await self._pool.handle_async_request(req)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../.local/lib/python3.12/site-packages/httpcore/_async/connection_pool.py:256: in handle_async_request
    raise exc from None
../.local/lib/python3.12/site-packages/httpcore/_async/connection_pool.py:229: in handle_async_request
    await self._close_connections(closing)
../.local/lib/python3.12/site-packages/httpcore/_async/connection_pool.py:345: in _close_connections
    await connection.aclose()
../.local/lib/python3.12/site-packages/httpcore/_async/connection.py:173: in aclose
    await self._connection.aclose()
../.local/lib/python3.12/site-packages/httpcore/_async/http11.py:258: in aclose
    await self._network_stream.aclose()
../.local/lib/python3.12/site-packages/httpcore/_backends/anyio.py:53: in aclose
    await self._stream.aclose()
../.local/lib/python3.12/site-packages/anyio/streams/tls.py:236: in aclose
    await self.transport_stream.aclose()
../.local/lib/python3.12/site-packages/anyio/_backends/_asyncio.py:1374: in aclose
    self._transport.close()
/usr/lib/python3.12/asyncio/selector_events.py:1211: in close
    super().close()
/usr/lib/python3.12/asyncio/selector_events.py:875: in close
    self._loop.call_soon(self._call_connection_lost, None)
/usr/lib/python3.12/asyncio/base_events.py:795: in call_soon
    self._check_closed()
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

self = <_UnixSelectorEventLoop running=False closed=True debug=False>

    def _check_closed(self):
        if self._closed:
>           raise RuntimeError('Event loop is closed')
E           RuntimeError: Event loop is closed

/usr/lib/python3.12/asyncio/base_events.py:541: RuntimeError
=============================== warnings summary ===============================
../.local/lib/python3.12/site-packages/pydantic/main.py:1809
../.local/lib/python3.12/site-packages/pydantic/main.py:1809
  /home/dev/.local/lib/python3.12/site-packages/pydantic/main.py:1809: UserWarning: Field name "schema" in "Body_extract_css_v1_extract_css_post" shadows an attribute in parent "BaseModel"
    return meta(

../.local/lib/python3.12/site-packages/pydantic/main.py:1809
../.local/lib/python3.12/site-packages/pydantic/main.py:1809
  /home/dev/.local/lib/python3.12/site-packages/pydantic/main.py:1809: UserWarning: Field name "schema" in "Body_extract_llm_v1_extract_llm_post" shadows an attribute in parent "BaseModel"
    return meta(

../.local/lib/python3.12/site-packages/pydantic/main.py:1809
../.local/lib/python3.12/site-packages/pydantic/main.py:1809
  /home/dev/.local/lib/python3.12/site-packages/pydantic/main.py:1809: UserWarning: Field name "schema" in "Body_quality_check_v1_quality_check_post" shadows an attribute in parent "BaseModel"
    return meta(

../.local/lib/python3.12/site-packages/pydantic/main.py:1809
../.local/lib/python3.12/site-packages/pydantic/main.py:1809
  /home/dev/.local/lib/python3.12/site-packages/pydantic/main.py:1809: UserWarning: Field name "schema" in "Body_extract_with_review_v1_extract_with_review_post" shadows an attribute in parent "BaseModel"
    return meta(

tests/test_api_integration.py::TestHealthEndpoints::test_live_returns_200
  /home/dev/.local/lib/python3.12/site-packages/_pytest/unraisableexception.py:67: PytestUnraisableExceptionWarning: Exception ignored in: <function BaseSubprocessTransport.__del__ at 0x7c2702c51120>

  Traceback (most recent call last):
    File "/usr/lib/python3.12/asyncio/base_subprocess.py", line 126, in __del__
      self.close()
    File "/usr/lib/python3.12/asyncio/base_subprocess.py", line 104, in close
      proto.pipe.close()
    File "/usr/lib/python3.12/asyncio/unix_events.py", line 767, in close
      self.write_eof()
    File "/usr/lib/python3.12/asyncio/unix_events.py", line 753, in write_eof
      self._loop.call_soon(self._call_connection_lost, None)
    File "/usr/lib/python3.12/asyncio/base_events.py", line 795, in call_soon
      self._check_closed()
    File "/usr/lib/python3.12/asyncio/base_events.py", line 541, in _check_closed
      raise RuntimeError('Event loop is closed')
  RuntimeError: Event loop is closed

  Enable tracemalloc to get traceback where the object was allocated.
  See https://docs.pytest.org/en/stable/how-to/capture-warnings.html#resource-warnings for more info.
    warnings.warn(pytest.PytestUnraisableExceptionWarning(msg))

tests/test_api_integration.py::TestDetectBlockEndpoint::test_detect_block_success
tests/test_api_integration.py::TestErrorHandling::test_422_on_invalid_json
  /home/dev/.local/lib/python3.12/site-packages/httpx/_models.py:408: DeprecationWarning: Use 'content=<...>' to upload raw bytes/text content.
    headers, stream = encode_request(

tests/test_jobqueue.py::test_jobqueue_create_job_returns_id
tests/test_jobqueue.py::test_jobqueue_get_job_exists
tests/test_jobqueue.py::test_jobqueue_complete_job
tests/test_jobqueue.py::test_jobqueue_fail_job
tests/test_jobqueue.py::test_jobqueue_update_preserves_fields
  /home/dev/pry/jobqueue.py:56: DeprecationWarning: datetime.datetime.utcnow() is deprecated and scheduled for removal in a future version. Use timezone-aware objects to represent datetimes in UTC: datetime.datetime.now(datetime.UTC).
    "created_at": datetime.utcnow().isoformat(),

tests/test_jobqueue.py::test_jobqueue_create_job_returns_id
tests/test_jobqueue.py::test_jobqueue_get_job_exists
tests/test_jobqueue.py::test_jobqueue_complete_job
tests/test_jobqueue.py::test_jobqueue_fail_job
tests/test_jobqueue.py::test_jobqueue_update_preserves_fields
  /home/dev/pry/jobqueue.py:57: DeprecationWarning: datetime.datetime.utcnow() is deprecated and scheduled for removal in a future version. Use timezone-aware objects to represent datetimes in UTC: datetime.datetime.now(datetime.UTC).
    "updated_at": datetime.utcnow().isoformat(),

tests/test_jobqueue.py::test_jobqueue_complete_job
tests/test_jobqueue.py::test_jobqueue_fail_job
tests/test_jobqueue.py::test_jobqueue_update_preserves_fields
  /home/dev/pry/jobqueue.py:93: DeprecationWarning: datetime.datetime.utcnow() is deprecated and scheduled for removal in a future version. Use timezone-aware objects to represent datetimes in UTC: datetime.datetime.now(datetime.UTC).
    self._local_jobs[job_id]["updated_at"] = datetime.utcnow().isoformat()

tests/test_retry.py::TestAsyncRetry::test_passes_args_and_kwargs
  /home/dev/pry/tests/test_retry.py:259: RuntimeWarning: coroutine 'AsyncMockMixin._execute_mock_call' was never awaited
    fn(url, timeout=timeout)
  Enable tracemalloc to get traceback where the object was allocated.
  See https://docs.pytest.org/en/stable/how-to/capture-warnings.html#resource-warnings for more info.

tests/test_sdk.py::test_prycrawl_health_returns_dict
  /home/dev/.local/lib/python3.12/site-packages/_pytest/python.py:167: RuntimeWarning: coroutine 'PryCrawl.health' was never awaited
    result = testfunction(**testargs)
  Enable tracemalloc to get traceback where the object was allocated.
  See https://docs.pytest.org/en/stable/how-to/capture-warnings.html#resource-warnings for more info.

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_structure_monitor.py::test_check_selectors_empty - RuntimeE...
= 1 failed, 619 passed, 1 skipped, 1 deselected, 26 warnings in 90.04s (0:01:30) =

Tests: 2 passed, 1 deselected in TestHealthEndpoints (was 2 pass + 1 fail).
2026-07-06 20:08:27 +07:00
.actor feat(pry): production readiness pass — apify actor, async db, retry wiring, tests, observability, mypy 2026-07-03 14:41:41 +02:00
.forgejo ci(forgejo): install Node.js before actions/checkout@v4 (#2) 2026-07-03 00:42:15 +02:00
.github feat(pry): production readiness pass — apify actor, async db, retry wiring, tests, observability, mypy 2026-07-03 14:41:41 +02:00
alembic fix(deploy): ensure /root/.pry dir and volume exist for SQLite migrations (#7) 2026-07-03 02:46:17 +02:00
browser-extension chore(license): re-license to dual MIT (core) + BSL 1.1 (stealth) 2026-07-02 19:59:18 +02:00
docs/adr chore(license): re-license to dual MIT (core) + BSL 1.1 (stealth) 2026-07-02 19:59:18 +02:00
llm_providers feat(pry): phase 0 — split routers, add tests, apify schema, pry api key (#5) 2026-07-03 03:43:02 +02:00
pry_mcp refactor(mcp): split mcp_production.py into pry_mcp/ sub-package 2026-07-03 14:52:59 +02:00
routers feat(observability): structured logging, metrics wiring, graceful degradation 2026-07-03 12:13:03 +02:00
shopify-app chore(license): re-license to dual MIT (core) + BSL 1.1 (stealth) 2026-07-02 19:59:18 +02:00
stealth_scripts chore(license): re-license to dual MIT (core) + BSL 1.1 (stealth) 2026-07-02 19:59:18 +02:00
templates chore(lint): auto-fix 253 of 283 ruff issues (F401, I001, E402, RUF100, UP037, SIM105) 2026-07-02 21:51:25 +02:00
tests fix(pry): test_ready_returns_200 → pytest.mark.integration 2026-07-06 20:08:27 +07:00
wordpress-plugin chore(license): re-license to dual MIT (core) + BSL 1.1 (stealth) 2026-07-02 19:59:18 +02:00
workers chore(license): re-license to dual MIT (core) + BSL 1.1 (stealth) 2026-07-02 19:59:18 +02:00
.commitlintrc.yml feat(pry): production readiness pass — apify actor, async db, retry wiring, tests, observability, mypy 2026-07-03 14:41:41 +02:00
.dockerignore docs: apply fleet-template (16-artifact scaffold) 2026-07-02 02:07:13 +07:00
.editorconfig chore(pry): apply fleet-template standards (gitignore, editorconfig) 2026-07-06 19:20:32 +07:00
.env.example refactor(settings): unify config.py, mconfig.py, and settings.py into single PrySettings 2026-07-03 05:04:29 +02:00
.gitignore chore(pry): apply fleet-template standards (gitignore, editorconfig) 2026-07-06 19:20:32 +07:00
.pre-commit-config.yaml chore(license): re-license to dual MIT (core) + BSL 1.1 (stealth) 2026-07-02 19:59:18 +02:00
.secretsallow docs: apply fleet-template (16-artifact scaffold) 2026-07-02 02:07:13 +07:00
account_manager.py feat(pry): production readiness pass — apify actor, async db, retry wiring, tests, observability, mypy 2026-07-03 14:41:41 +02:00
actor_marketplace.py chore(lint): auto-fix 253 of 283 ruff issues (F401, I001, E402, RUF100, UP037, SIM105) 2026-07-02 21:51:25 +02:00
adaptive.py chore(license): re-license to dual MIT (core) + BSL 1.1 (stealth) 2026-07-02 19:59:18 +02:00
advanced.py feat(pry): production readiness pass — apify actor, async db, retry wiring, tests, observability, mypy 2026-07-03 14:41:41 +02:00
agency.py chore(lint): auto-fix 253 of 283 ruff issues (F401, I001, E402, RUF100, UP037, SIM105) 2026-07-02 21:51:25 +02:00
AGENTS.md docs(pry): refresh CONTRIBUTING and AGENTS to reflect Phase 0 hardening complete 2026-07-06 19:19:57 +07:00
ai_plugin.py chore(license): re-license to dual MIT (core) + BSL 1.1 (stealth) 2026-07-02 19:59:18 +02:00
alembic.ini feat(db): add Alembic migrations (#6) 2026-07-03 02:22:33 +02:00
alerter.py ci(forgejo): install Node.js before actions/checkout@v4 (#2) 2026-07-03 00:42:15 +02:00
anomaly.py chore(lint): auto-fix 253 of 283 ruff issues (F401, I001, E402, RUF100, UP037, SIM105) 2026-07-02 21:51:25 +02:00
api.py refactor(mcp): modernize imports to use pry_mcp sub-package directly 2026-07-03 15:18:11 +02:00
apify_actor.py feat(pry): production readiness pass — apify actor, async db, retry wiring, tests, observability, mypy 2026-07-03 14:41:41 +02:00
apify_schema.py feat(db): add Alembic migrations (#6) 2026-07-03 02:22:33 +02:00
ARCHITECTURE.md chore(license): re-license to dual MIT (core) + BSL 1.1 (stealth) 2026-07-02 19:59:18 +02:00
AUDIT-2026-Q3.md docs(pry): add 2026-Q3 production audit + plan (comprehensive gap analysis) 2026-07-06 18:49:12 +07:00
AUDIT.md chore(license): re-license to dual MIT (core) + BSL 1.1 (stealth) 2026-07-02 19:59:18 +02:00
auth.py chore(lint): auto-fix 253 of 283 ruff issues (F401, I001, E402, RUF100, UP037, SIM105) 2026-07-02 21:51:25 +02:00
auth_connector.py ci(forgejo): install Node.js before actions/checkout@v4 (#2) 2026-07-03 00:42:15 +02:00
automator.py fix(lint): resolve remaining ruff errors and unblock MCP SSE test (#1) 2026-07-02 23:18:40 +02:00
behavioral_biometrics.py chore(lint): auto-fix 253 of 283 ruff issues (F401, I001, E402, RUF100, UP037, SIM105) 2026-07-02 21:51:25 +02:00
browser_pool.py refactor(exceptions): add ruff BLE001; convert 103 broad except Exception 2026-07-02 21:04:53 +02:00
cache.py feat(observability): structured logging, metrics wiring, graceful degradation 2026-07-03 12:13:03 +02:00
camoufox_integration.py fix(lint): resolve remaining ruff errors and unblock MCP SSE test (#1) 2026-07-02 23:18:40 +02:00
captcha_solver.py chore(lint): auto-fix 253 of 283 ruff issues (F401, I001, E402, RUF100, UP037, SIM105) 2026-07-02 21:51:25 +02:00
cli.py fix(pry): kill cli recursion, delete parser.tmp, fix broken completions, correct hallucinated docs 2026-07-06 19:22:03 +07:00
client.py chore(license): re-license to dual MIT (core) + BSL 1.1 (stealth) 2026-07-02 19:59:18 +02:00
commerce_sync.py ci(forgejo): install Node.js before actions/checkout@v4 (#2) 2026-07-03 00:42:15 +02:00
compliance.py chore(lint): auto-fix 253 of 283 ruff issues (F401, I001, E402, RUF100, UP037, SIM105) 2026-07-02 21:51:25 +02:00
CONTRIBUTING.md docs(pry): refresh CONTRIBUTING and AGENTS to reflect Phase 0 hardening complete 2026-07-06 19:19:57 +07:00
cookie_warmer.py chore(lint): auto-fix 253 of 283 ruff issues (F401, I001, E402, RUF100, UP037, SIM105) 2026-07-02 21:51:25 +02:00
costing.py chore(lint): auto-fix 253 of 283 ruff issues (F401, I001, E402, RUF100, UP037, SIM105) 2026-07-02 21:51:25 +02:00
crm_sync.py ci(forgejo): install Node.js before actions/checkout@v4 (#2) 2026-07-03 00:42:15 +02:00
db.py feat(pry): production readiness pass — apify actor, async db, retry wiring, tests, observability, mypy 2026-07-03 14:41:41 +02:00
DECISIONS.md chore(license): re-license to dual MIT (core) + BSL 1.1 (stealth) 2026-07-02 19:59:18 +02:00
dedup.py ci(forgejo): install Node.js before actions/checkout@v4 (#2) 2026-07-03 00:42:15 +02:00
DEPLOYMENT.md chore(license): re-license to dual MIT (core) + BSL 1.1 (stealth) 2026-07-02 19:59:18 +02:00
deps.py refactor(settings): unify config.py, mconfig.py, and settings.py into single PrySettings 2026-07-03 05:04:29 +02:00
destinations.py fix(lint): resolve remaining ruff errors and unblock MCP SSE test (#1) 2026-07-02 23:18:40 +02:00
DEVELOPMENT.md feat(logging): add structlog + JSON logging (CONVENTIONS.md Part 5) 2026-07-02 20:55:41 +02:00
docker-compose.yml fix(pry): add missing deps (cloudscraper, aiohttp-socks, pyjwt, apify, playwright-stealth), fix docker env var + healthcheck + non-root 2026-07-06 19:23:32 +07:00
docker-entrypoint.sh feat(db): add Alembic migrations (#6) 2026-07-03 02:22:33 +02:00
Dockerfile fix(pry): add missing deps (cloudscraper, aiohttp-socks, pyjwt, apify, playwright-stealth), fix docker env var + healthcheck + non-root 2026-07-06 19:23:32 +07:00
Dockerfile.apify feat(pry): production readiness pass — apify actor, async db, retry wiring, tests, observability, mypy 2026-07-03 14:41:41 +02:00
email_scraper.py ci(forgejo): install Node.js before actions/checkout@v4 (#2) 2026-07-03 00:42:15 +02:00
enrichment.py ci(forgejo): install Node.js before actions/checkout@v4 (#2) 2026-07-03 00:42:15 +02:00
errors.py chore(license): re-license to dual MIT (core) + BSL 1.1 (stealth) 2026-07-02 19:59:18 +02:00
extraction.py ci(forgejo): install Node.js before actions/checkout@v4 (#2) 2026-07-03 00:42:15 +02:00
extractor.py feat(observability): structured logging, metrics wiring, graceful degradation 2026-07-03 12:13:03 +02:00
FEATURES.md fix(pry): kill cli recursion, delete parser.tmp, fix broken completions, correct hallucinated docs 2026-07-06 19:22:03 +07:00
freshness.py ci(forgejo): install Node.js before actions/checkout@v4 (#2) 2026-07-03 00:42:15 +02:00
gdpr.py chore(lint): auto-fix 253 of 283 ruff issues (F401, I001, E402, RUF100, UP037, SIM105) 2026-07-02 21:51:25 +02:00
gdpr_real.py chore(lint): auto-fix 253 of 283 ruff issues (F401, I001, E402, RUF100, UP037, SIM105) 2026-07-02 21:51:25 +02:00
glama.json chore(license): re-license to dual MIT (core) + BSL 1.1 (stealth) 2026-07-02 19:59:18 +02:00
graphql_discovery.py ci(forgejo): install Node.js before actions/checkout@v4 (#2) 2026-07-03 00:42:15 +02:00
intelligence.py feat(sql): migrate intelligence, monitor, and referrals to SQLAlchemy (#4) 2026-07-03 01:29:59 +02:00
jobqueue.py fix(lint): resolve remaining ruff errors and unblock MCP SSE test (#1) 2026-07-02 23:18:40 +02:00
lazy_load.py chore(license): re-license to dual MIT (core) + BSL 1.1 (stealth) 2026-07-02 19:59:18 +02:00
LICENSE chore(license): re-license to dual MIT (core) + BSL 1.1 (stealth) 2026-07-02 19:59:18 +02:00
LICENSE-BSL-STEALTH chore(license): re-license to dual MIT (core) + BSL 1.1 (stealth) 2026-07-02 19:59:18 +02:00
LICENSING_PRICING_STRATEGY.md chore(license): re-license to dual MIT (core) + BSL 1.1 (stealth) 2026-07-02 19:59:18 +02:00
llm_features.py chore(lint): auto-fix 253 of 283 ruff issues (F401, I001, E402, RUF100, UP037, SIM105) 2026-07-02 21:51:25 +02:00
logging_config.py fix(lint): resolve remaining ruff errors and unblock MCP SSE test (#1) 2026-07-02 23:18:40 +02:00
Makefile fix(pry): kill cli recursion, delete parser.tmp, fix broken completions, correct hallucinated docs 2026-07-06 19:22:03 +07:00
markdown_gen.py chore(license): re-license to dual MIT (core) + BSL 1.1 (stealth) 2026-07-02 19:59:18 +02:00
mcp_production.py refactor(mcp): split mcp_production.py into pry_mcp/ sub-package 2026-07-03 14:52:59 +02:00
mcp_server.py chore(lint): auto-fix 253 of 283 ruff issues (F401, I001, E402, RUF100, UP037, SIM105) 2026-07-02 21:51:25 +02:00
mcp_sse.py refactor(mcp): modernize imports to use pry_mcp sub-package directly 2026-07-03 15:18:11 +02:00
MCP_X402_AUDIT.md chore(license): re-license to dual MIT (core) + BSL 1.1 (stealth) 2026-07-02 19:59:18 +02:00
monitor.py feat(sql): migrate intelligence, monitor, and referrals to SQLAlchemy (#4) 2026-07-03 01:29:59 +02:00
network.py chore(license): re-license to dual MIT (core) + BSL 1.1 (stealth) 2026-07-02 19:59:18 +02:00
observability.py feat(pry): production readiness pass — apify actor, async db, retry wiring, tests, observability, mypy 2026-07-03 14:41:41 +02:00
ocr_extractor.py ci(forgejo): install Node.js before actions/checkout@v4 (#2) 2026-07-03 00:42:15 +02:00
openapi.json feat(pry): phase 0 — split routers, add tests, apify schema, pry api key (#5) 2026-07-03 03:43:02 +02:00
parser.py chore(lint): auto-fix 253 of 283 ruff issues (F401, I001, E402, RUF100, UP037, SIM105) 2026-07-02 21:51:25 +02:00
paths.py chore(lint): auto-fix 253 of 283 ruff issues (F401, I001, E402, RUF100, UP037, SIM105) 2026-07-02 21:51:25 +02:00
pdf_extractor.py ci(forgejo): install Node.js before actions/checkout@v4 (#2) 2026-07-03 00:42:15 +02:00
pipeline.py chore(lint): auto-fix 253 of 283 ruff issues (F401, I001, E402, RUF100, UP037, SIM105) 2026-07-02 21:51:25 +02:00
pipelines.py chore(lint): auto-fix 253 of 283 ruff issues (F401, I001, E402, RUF100, UP037, SIM105) 2026-07-02 21:51:25 +02:00
PLAN.md chore(license): re-license to dual MIT (core) + BSL 1.1 (stealth) 2026-07-02 19:59:18 +02:00
proxy_manager.py chore(lint): auto-fix 253 of 283 ruff issues (F401, I001, E402, RUF100, UP037, SIM105) 2026-07-02 21:51:25 +02:00
proxy_referrals.py chore(lint): auto-fix 253 of 283 ruff issues (F401, I001, E402, RUF100, UP037, SIM105) 2026-07-02 21:51:25 +02:00
pry_sdk.py chore(license): re-license to dual MIT (core) + BSL 1.1 (stealth) 2026-07-02 19:59:18 +02:00
pryextras.py ci(forgejo): install Node.js before actions/checkout@v4 (#2) 2026-07-03 00:42:15 +02:00
pryfile.py feat(pry): production readiness pass — apify actor, async db, retry wiring, tests, observability, mypy 2026-07-03 14:41:41 +02:00
pulsemcp.json chore(license): re-license to dual MIT (core) + BSL 1.1 (stealth) 2026-07-02 19:59:18 +02:00
pyproject.toml fix(pry): test_ready_returns_200 → pytest.mark.integration 2026-07-06 20:08:27 +07:00
quality.py chore(lint): auto-fix 253 of 283 ruff issues (F401, I001, E402, RUF100, UP037, SIM105) 2026-07-02 21:51:25 +02:00
ratelimit.py chore(license): re-license to dual MIT (core) + BSL 1.1 (stealth) 2026-07-02 19:59:18 +02:00
README.md fix(pry): kill cli recursion, delete parser.tmp, fix broken completions, correct hallucinated docs 2026-07-06 19:22:03 +07:00
reconciliation.py fix(lint): resolve remaining ruff errors and unblock MCP SSE test (#1) 2026-07-02 23:18:40 +02:00
referrals.py feat(sql): migrate intelligence, monitor, and referrals to SQLAlchemy (#4) 2026-07-03 01:29:59 +02:00
reports.py chore(lint): auto-fix 253 of 283 ruff issues (F401, I001, E402, RUF100, UP037, SIM105) 2026-07-02 21:51:25 +02:00
reports_real.py chore(lint): auto-fix 253 of 283 ruff issues (F401, I001, E402, RUF100, UP037, SIM105) 2026-07-02 21:51:25 +02:00
requirements.lock feat(pry): phase 0 — split routers, add tests, apify schema, pry api key (#5) 2026-07-03 03:43:02 +02:00
requirements.txt fix(pry): add missing deps (cloudscraper, aiohttp-socks, pyjwt, apify, playwright-stealth), fix docker env var + healthcheck + non-root 2026-07-06 19:23:32 +07:00
resilience.py feat(resilience): circuit breakers and per-domain tier memory 2026-07-03 11:49:33 +02:00
retry.py feat(pry): production readiness pass — apify actor, async db, retry wiring, tests, observability, mypy 2026-07-03 14:41:41 +02:00
review.py chore(lint): auto-fix 253 of 283 ruff issues (F401, I001, E402, RUF100, UP037, SIM105) 2026-07-02 21:51:25 +02:00
ROADMAP.md chore(license): re-license to dual MIT (core) + BSL 1.1 (stealth) 2026-07-02 19:59:18 +02:00
schema_extraction.py chore(lint): auto-fix 253 of 283 ruff issues (F401, I001, E402, RUF100, UP037, SIM105) 2026-07-02 21:51:25 +02:00
scraper.py feat(pry): production readiness pass — apify actor, async db, retry wiring, tests, observability, mypy 2026-07-03 14:41:41 +02:00
secrets_backend.py chore(lint): auto-fix 253 of 283 ruff issues (F401, I001, E402, RUF100, UP037, SIM105) 2026-07-02 21:51:25 +02:00
SECURITY.md chore(license): re-license to dual MIT (core) + BSL 1.1 (stealth) 2026-07-02 19:59:18 +02:00
seo_monitor.py ci(forgejo): install Node.js before actions/checkout@v4 (#2) 2026-07-03 00:42:15 +02:00
sessions.py chore(lint): auto-fix 253 of 283 ruff issues (F401, I001, E402, RUF100, UP037, SIM105) 2026-07-02 21:51:25 +02:00
settings.py refactor(settings): unify config.py, mconfig.py, and settings.py into single PrySettings 2026-07-03 05:04:29 +02:00
shadow_dom.py chore(license): re-license to dual MIT (core) + BSL 1.1 (stealth) 2026-07-02 19:59:18 +02:00
signup_automator.py ci(forgejo): install Node.js before actions/checkout@v4 (#2) 2026-07-03 00:42:15 +02:00
smithery.yaml chore(license): re-license to dual MIT (core) + BSL 1.1 (stealth) 2026-07-02 19:59:18 +02:00
STATUS.md docs(pry): refresh CONTRIBUTING and AGENTS to reflect Phase 0 hardening complete 2026-07-06 19:19:57 +07:00
stealth_engine.py chore(lint): auto-fix 253 of 283 ruff issues (F401, I001, E402, RUF100, UP037, SIM105) 2026-07-02 21:51:25 +02:00
structure_monitor.py feat(pry): production readiness pass — apify actor, async db, retry wiring, tests, observability, mypy 2026-07-03 14:41:41 +02:00
tasks.py fix(lint): resolve remaining ruff errors and unblock MCP SSE test (#1) 2026-07-02 23:18:40 +02:00
template_engine.py chore(license): re-license to dual MIT (core) + BSL 1.1 (stealth) 2026-07-02 19:59:18 +02:00
test.sh chore(license): re-license to dual MIT (core) + BSL 1.1 (stealth) 2026-07-02 19:59:18 +02:00
TESTING.md chore(license): re-license to dual MIT (core) + BSL 1.1 (stealth) 2026-07-02 19:59:18 +02:00
tls_fingerprint.py chore(lint): auto-fix 253 of 283 ruff issues (F401, I001, E402, RUF100, UP037, SIM105) 2026-07-02 21:51:25 +02:00
training_data.py chore(lint): auto-fix 253 of 283 ruff issues (F401, I001, E402, RUF100, UP037, SIM105) 2026-07-02 21:51:25 +02:00
ultimate_scraper.py feat(pry): production readiness pass — apify actor, async db, retry wiring, tests, observability, mypy 2026-07-03 14:41:41 +02:00
url_guard.py feat(url_guard): SSRF and path traversal guard for scraper inputs 2026-07-03 12:00:40 +02:00
USAGE.md fix(pry): kill cli recursion, delete parser.tmp, fix broken completions, correct hallucinated docs 2026-07-06 19:22:03 +07:00
webhook_delivery.py ci(forgejo): install Node.js before actions/checkout@v4 (#2) 2026-07-03 00:42:15 +02:00
websocket_scraper.py chore(lint): auto-fix 253 of 283 ruff issues (F401, I001, E402, RUF100, UP037, SIM105) 2026-07-02 21:51:25 +02:00
wrangler.toml chore(license): re-license to dual MIT (core) + BSL 1.1 (stealth) 2026-07-02 19:59:18 +02:00
x402.py feat(db): add Alembic migrations (#6) 2026-07-03 02:22:33 +02:00
x402_middleware.py feat(observability): structured logging, metrics wiring, graceful degradation 2026-07-03 12:13:03 +02:00

//: # (Copyright (c) 2026 Rug Munch Media LLC)

Pry — Open any website

Self-hosted web scraping + browser automation API. Cloudflare bypass, document parsing, AI summarization. No API keys needed.

Quickstart

# Install (PyPI 'pry' is taken — install from this repo)
pip install -e .
# or:
# pip install "pry-scraper @ git+https://git.rugmunch.io/RugMunchMedia/pryscraper.git"

# Start the server
pry serve

# Scrape a URL
pry open https://example.com

# With JSON extraction
pry open https://store.com/product --json --schema product.json

# Crawl a site
pry crawl https://docs.com --max-pages 20 -o data.json

Docker

docker compose up -d
# Pry on :8005 (host) → :8002 (container), FlareSolverr on :8192 (host) → :8191 (container)

CLI Reference

Command Description
pry open <url> Scrape URL to clean markdown
pry watch <url> Monitor page for changes
pry crawl <url> Crawl multiple pages
pry batch <file> Batch scrape URLs from file
pry parse <url> Parse PDF/DOCX/image
pry ss <url> Take screenshot
pry run [pry.yml] Execute job file
pry serve Start API server

API

POST /v1/scrape

{"url": "https://example.com", "bypassCloudflare": true, "formats": ["markdown"]}

POST /v1/crawl

{"url": "https://docs.com", "maxPages": 10, "maxDepth": 2}

POST /v1/automate

{"steps": [{"action": "navigate", "url": "https://...", {"action": "click", "selector": "#btn"}]]}

POST /v1/vision

Analyze images with free OpenRouter vision models. Auto-fallback across 5 models.

{"url": "https://...", "question": "What is shown?"}

POST /v1/extract

Extract structured data with JSON schema + optional AI fallback.

POST /v1/batch

Scrape up to 50 URLs in parallel.

POST /v1/compare

Diff two URLs.

POST /v1/summarize

AI summarization via local Ollama (free, private).

GET /health

Service health + cache stats + active sessions.

Python SDK

    from pry_sdk import PryCrawl

mc = PryCrawl("http://localhost:8005")
result = await mc.scrape("https://example.com")
print(result["data"]["markdown"])
    from pry_sdk import PryCrawlSync

mc = PryCrawlSync("http://localhost:8005")
result = mc.scrape("https://example.com")

SDK Methods

Method Description
scrape(url, **opts) Scrape to markdown
scrape_json(url, schema, **opts) Scrape with JSON extraction
crawl(url, max_pages, **opts) Crawl site
map(url, limit) Discover URLs
parse(url) Parse document
automate(steps, **opts) Browser automation
screenshot(url) Screenshot page
health() Service health

MCP (AI Agent Integration)

POST /mcp/call
{"name": "pry_scrape", "arguments": {"url": "https://..."}}

Compatible with Claude, Hermes, Cursor, and any MCP client.

Features

  • 4-tier anti-detection: Direct → FlareSolverr → Playwright → Googlebot
  • Cloudflare bypass: Automatic via FlareSolverr
  • Document parsing: PDF, DOCX, images (OCR), CSV, JSON
  • Browser automation: Login flows, form filling, sessions
  • Vision AI: Free OpenRouter vision models with auto-fallback
  • Diff tracking: Page change monitoring with webhooks
  • Batch processing: Parallel scrape with templates
  • SEO analysis: Title, meta, headings, keywords, readability
  • Schema extraction: JSON-LD, Open Graph, microdata
  • Export formats: JSON, CSV (RSS/TXT/SQL not yet implemented — see FEATURES.md)
  • Rate limiting: Per-IP token bucket (default 120 RPM)
  • Caching: LRU + Redis with TTL-based invalidation
  • WebSocket streaming: Real-time job progress
  • Circuit breaker: Per-domain backoff on failures

Architecture

┌─────────────┐  ┌──────────────┐  ┌──────────┐
│   Client    │→ │  Pry API     │→ │ Scraper  │
│ (CLI/SDK)   │  │  (FastAPI)   │  │  Engine  │
└─────────────┘  └──────┬───────┘  └────┬─────┘
                        │               ├─ Direct HTTP
                        │               ├─ FlareSolverr
                        │               ├─ Playwright
                        │               └─ Googlebot
                        │
                   ┌────┴────┐
                   │ Cache   │
                   │ Redis   │
                   │ Rate    │
                   └─────────┘

Deployment

docker compose up -d

Bare metal

pip install -e .
playwright install chromium
# Optional: docker run -d -p 8192:8191 ghcr.io/flaresolverr/flaresolverr
pry serve

Environment

Variable Default Description
PRY_URL http://localhost:8005 API endpoint for CLI
TOR_ENABLED false Enable Tor routing
PROXY_URL HTTP/SOCKS proxy URL
RATE_LIMIT_RPM 120 Requests per minute
OPENROUTER_API_KEY For vision endpoint

Development

make install      # Install with dev deps
make dev          # Start hot-reload server
make lint         # ruff check
make format       # ruff format
make typecheck    # mypy
make test         # pytest
make check        # Full audit

License

Pry is dual-licensed to balance community openness with the protection of its advanced anti-detection and stealth capabilities.

  • Most code (core functionality, utilities, and standard modules) is licensed under the MIT License. This permissive license encourages community contributions and wide adoption. See LICENSE.

  • The anti-detection / stealth module is licensed under the BSL 1.1 (Business Source License). This module is free to use for non-production purposes; a commercial license is required for any production use. See LICENSE-BSL-STEALTH.

Additional Use Grant

Under the BSL 1.1, an Additional Use Grant permits free use of the stealth module for:

  • Personal, non-commercial projects
  • Non-production testing and evaluation
  • Academic research
  • Non-commercial open source software development
  • A 90-day evaluation period for any other purpose

For commercial licensing (production use, enterprise deployment, or embedding in proprietary products), please contact: enterprise@rugmunch.io