patchtroy 0.4.5__tar.gz → 0.4.6__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.
- {patchtroy-0.4.5 → patchtroy-0.4.6}/Dockerfile +1 -1
- {patchtroy-0.4.5 → patchtroy-0.4.6}/PKG-INFO +1 -1
- {patchtroy-0.4.5 → patchtroy-0.4.6}/PRD.md +1 -1
- patchtroy-0.4.6/VERSION +1 -0
- {patchtroy-0.4.5 → patchtroy-0.4.6}/benchmarks/BENCHMARKS.md +1 -1
- {patchtroy-0.4.5 → patchtroy-0.4.6}/docs/benchmarks.md +1 -1
- {patchtroy-0.4.5 → patchtroy-0.4.6}/docs/rest-api.md +1 -1
- {patchtroy-0.4.5 → patchtroy-0.4.6}/pyproject.toml +1 -1
- {patchtroy-0.4.5 → patchtroy-0.4.6}/src/patchtroy/__init__.py +1 -1
- {patchtroy-0.4.5 → patchtroy-0.4.6}/src/patchtroy/cli.py +1 -1
- {patchtroy-0.4.5 → patchtroy-0.4.6}/src/patchtroy/pool.py +2 -3
- {patchtroy-0.4.5 → patchtroy-0.4.6}/src/patchtroy/server.py +2 -2
- {patchtroy-0.4.5 → patchtroy-0.4.6}/src/patchtroy/utils.py +28 -26
- {patchtroy-0.4.5 → patchtroy-0.4.6}/tests/test_server.py +1 -1
- {patchtroy-0.4.5 → patchtroy-0.4.6}/uv.lock +1 -1
- patchtroy-0.4.5/VERSION +0 -1
- {patchtroy-0.4.5 → patchtroy-0.4.6}/.dockerignore +0 -0
- {patchtroy-0.4.5 → patchtroy-0.4.6}/.gitattributes +0 -0
- {patchtroy-0.4.5 → patchtroy-0.4.6}/.githooks/pre-commit +0 -0
- {patchtroy-0.4.5 → patchtroy-0.4.6}/.githooks/prepare-commit-msg +0 -0
- {patchtroy-0.4.5 → patchtroy-0.4.6}/.githooks/versionlib.sh +0 -0
- {patchtroy-0.4.5 → patchtroy-0.4.6}/.github/workflows/ci.yml +0 -0
- {patchtroy-0.4.5 → patchtroy-0.4.6}/.github/workflows/docs.yml +0 -0
- {patchtroy-0.4.5 → patchtroy-0.4.6}/.github/workflows/release.yml +0 -0
- {patchtroy-0.4.5 → patchtroy-0.4.6}/.gitignore +0 -0
- {patchtroy-0.4.5 → patchtroy-0.4.6}/LICENSE +0 -0
- {patchtroy-0.4.5 → patchtroy-0.4.6}/README.md +0 -0
- {patchtroy-0.4.5 → patchtroy-0.4.6}/benchmarks/benchmark_results.json +0 -0
- {patchtroy-0.4.5 → patchtroy-0.4.6}/benchmarks/run_benchmark.py +0 -0
- {patchtroy-0.4.5 → patchtroy-0.4.6}/docker-compose.yml +0 -0
- {patchtroy-0.4.5 → patchtroy-0.4.6}/docs/context-pool.md +0 -0
- {patchtroy-0.4.5 → patchtroy-0.4.6}/docs/getting-started.md +0 -0
- {patchtroy-0.4.5 → patchtroy-0.4.6}/docs/index.md +0 -0
- {patchtroy-0.4.5 → patchtroy-0.4.6}/docs/llm-chunking.md +0 -0
- {patchtroy-0.4.5 → patchtroy-0.4.6}/docs/media-capture.md +0 -0
- {patchtroy-0.4.5 → patchtroy-0.4.6}/docs/proxy-rotation.md +0 -0
- {patchtroy-0.4.5 → patchtroy-0.4.6}/docs/stealth-architecture.md +0 -0
- {patchtroy-0.4.5 → patchtroy-0.4.6}/mkdocs.yml +0 -0
- {patchtroy-0.4.5 → patchtroy-0.4.6}/src/patchtroy/chunker.py +0 -0
- {patchtroy-0.4.5 → patchtroy-0.4.6}/src/patchtroy/crawler.py +0 -0
- {patchtroy-0.4.5 → patchtroy-0.4.6}/src/patchtroy/extractors.py +0 -0
- {patchtroy-0.4.5 → patchtroy-0.4.6}/src/patchtroy/models.py +0 -0
- {patchtroy-0.4.5 → patchtroy-0.4.6}/src/patchtroy/proxy.py +0 -0
- {patchtroy-0.4.5 → patchtroy-0.4.6}/tests/test_chunker.py +0 -0
- {patchtroy-0.4.5 → patchtroy-0.4.6}/tests/test_cli.py +0 -0
- {patchtroy-0.4.5 → patchtroy-0.4.6}/tests/test_crawler.py +0 -0
- {patchtroy-0.4.5 → patchtroy-0.4.6}/tests/test_extractors.py +0 -0
- {patchtroy-0.4.5 → patchtroy-0.4.6}/tests/test_media_capture.py +0 -0
- {patchtroy-0.4.5 → patchtroy-0.4.6}/tests/test_models.py +0 -0
- {patchtroy-0.4.5 → patchtroy-0.4.6}/tests/test_pool.py +0 -0
- {patchtroy-0.4.5 → patchtroy-0.4.6}/tests/test_proxy.py +0 -0
|
@@ -8,7 +8,7 @@ FROM python:3.11-slim
|
|
|
8
8
|
LABEL maintainer="Marcus Zou <marcus.zou@icloud.com>"
|
|
9
9
|
LABEL org.opencontainers.image.title="Patchtroy"
|
|
10
10
|
LABEL org.opencontainers.image.description="Undetected stealth web scraper & markdown extractor microservice"
|
|
11
|
-
LABEL org.opencontainers.image.version="0.4.
|
|
11
|
+
LABEL org.opencontainers.image.version="0.4.6"
|
|
12
12
|
|
|
13
13
|
# Python and environment settings
|
|
14
14
|
ENV PYTHONUNBUFFERED=1 \
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: patchtroy
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.6
|
|
4
4
|
Summary: Undetected stealth web scraper & markdown extractor for LLMs, pairing Patchright with Trafilatura.
|
|
5
5
|
Project-URL: Homepage, https://github.com/marcuz-apl/patchtroy
|
|
6
6
|
Project-URL: Repository, https://github.com/marcuz-apl/patchtroy
|
|
@@ -152,7 +152,7 @@ Patchtroy occupies the unoccupied **"Lightweight Stealth Sweet Spot"**:
|
|
|
152
152
|
- [x] Token counter & LLM chunking utility (`result.chunk(max_tokens=2048)`).
|
|
153
153
|
- [x] Full test suite (32 tests passing).
|
|
154
154
|
|
|
155
|
-
### Phase 4 (v0.4.
|
|
155
|
+
### Phase 4 (v0.4.6 — Current)
|
|
156
156
|
- [x] PyPI public release workflows & multi-OS matrix CI (`.github/workflows/`).
|
|
157
157
|
- [x] Comprehensive benchmark publication vs Crawl4AI and Firecrawl (`benchmarks/BENCHMARKS.md`).
|
|
158
158
|
- [x] Official documentation portal (Material for MkDocs).
|
patchtroy-0.4.6/VERSION
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
v0.4.7-260902d
|
|
@@ -19,7 +19,7 @@ Existing open-source solutions force developers to choose between:
|
|
|
19
19
|
|
|
20
20
|
## 2. Feature & Architecture Matrix
|
|
21
21
|
|
|
22
|
-
| Dimension | **Patchtroy** (v0.4.
|
|
22
|
+
| Dimension | **Patchtroy** (v0.4.6) | **Crawl4AI** (v0.9.3) | **Firecrawl** (Self-Hosted) |
|
|
23
23
|
| :--- | :---: | :---: | :---: |
|
|
24
24
|
| **Stealth Engine** | **✅ Native Patchright (C++ CDP masked)** | ❌ Standard Playwright (CDP leaked) | ⚠️ Standard Playwright / Puppeteer |
|
|
25
25
|
| **Dynamic Challenge Resilience** | **✅ Native driver protection** | ❌ Frequently flagged on automated checks | ❌ Blocked unless using paid SaaS proxy |
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# 📊 Benchmarks vs Crawl4AI and Firecrawl
|
|
2
2
|
|
|
3
|
-
A quantitative, reproducible benchmark comparing **Patchtroy (v0.4.
|
|
3
|
+
A quantitative, reproducible benchmark comparing **Patchtroy (v0.4.6)** against **Crawl4AI (v0.9.3)** and **Firecrawl (Self-Hosted)**.
|
|
4
4
|
|
|
5
5
|
---
|
|
6
6
|
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "patchtroy"
|
|
7
|
-
version = "0.4.
|
|
7
|
+
version = "0.4.6"
|
|
8
8
|
description = "Undetected stealth web scraper & markdown extractor for LLMs, pairing Patchright with Trafilatura."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "Apache-2.0"
|
|
@@ -6,7 +6,7 @@ from patchtroy.models import LinkItem, PatchtroyConfig, ScrapeResult
|
|
|
6
6
|
from patchtroy.pool import BrowserContextPool
|
|
7
7
|
from patchtroy.proxy import ProxyItem, ProxyManager
|
|
8
8
|
|
|
9
|
-
__version__ = "0.4.
|
|
9
|
+
__version__ = "0.4.6"
|
|
10
10
|
__author__ = "Marcus Zou"
|
|
11
11
|
__license__ = "Apache-2.0"
|
|
12
12
|
__copyright__ = "Copyright 2026 Alfazen Inc."
|
|
@@ -4,7 +4,6 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
import asyncio
|
|
6
6
|
import logging
|
|
7
|
-
import sys
|
|
8
7
|
from collections.abc import AsyncIterator
|
|
9
8
|
from contextlib import asynccontextmanager
|
|
10
9
|
from typing import Any
|
|
@@ -90,8 +89,8 @@ class BrowserContextPool:
|
|
|
90
89
|
pass
|
|
91
90
|
self._playwright = None
|
|
92
91
|
|
|
93
|
-
|
|
94
|
-
|
|
92
|
+
# Allow event loop to process subprocess termination callbacks before shutdown
|
|
93
|
+
await asyncio.sleep(0.05)
|
|
95
94
|
|
|
96
95
|
@asynccontextmanager
|
|
97
96
|
async def acquire_context(
|
|
@@ -90,7 +90,7 @@ def create_app() -> FastAPI:
|
|
|
90
90
|
app = FastAPI(
|
|
91
91
|
title="Patchtroy REST Microservice",
|
|
92
92
|
description="Undetected stealth web scraper & clean Markdown extractor for LLMs.",
|
|
93
|
-
version="0.4.
|
|
93
|
+
version="0.4.6",
|
|
94
94
|
lifespan=lifespan,
|
|
95
95
|
)
|
|
96
96
|
|
|
@@ -99,7 +99,7 @@ def create_app() -> FastAPI:
|
|
|
99
99
|
"""Microservice health and status check."""
|
|
100
100
|
return {
|
|
101
101
|
"status": "healthy",
|
|
102
|
-
"version": "0.4.
|
|
102
|
+
"version": "0.4.6",
|
|
103
103
|
"engine": "patchright",
|
|
104
104
|
"active": crawler_instance is not None and crawler_instance._pool.is_running,
|
|
105
105
|
}
|
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
import random
|
|
6
|
-
import sys
|
|
7
6
|
from typing import Any
|
|
8
7
|
from urllib.parse import urlparse
|
|
9
8
|
|
|
@@ -56,17 +55,33 @@ def is_valid_url(url: str) -> bool:
|
|
|
56
55
|
return False
|
|
57
56
|
|
|
58
57
|
|
|
59
|
-
def
|
|
60
|
-
"""Silence
|
|
58
|
+
def silence_subprocess_transport_bug() -> None:
|
|
59
|
+
"""Silence Python asyncio BaseSubprocessTransport and pipe transport destructor errors.
|
|
61
60
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
61
|
+
When an event loop finishes and closes (via asyncio.run), underlying child process
|
|
62
|
+
transports may remain in the garbage collector until interpreter exit. During GC,
|
|
63
|
+
their __del__ destructors attempt post-loop cleanup:
|
|
64
|
+
- On Unix/Linux: calls self.close() -> loop.call_soon() -> RuntimeError('Event loop is closed')
|
|
65
|
+
- On Windows: calls _warn() -> __repr__() -> fileno() -> ValueError('I/O operation on closed pipe')
|
|
66
|
+
Wrapping both destructors safely catches and suppresses these post-loop cleanup errors across
|
|
67
|
+
all platforms (Linux, macOS, Windows).
|
|
67
68
|
"""
|
|
68
|
-
|
|
69
|
-
|
|
69
|
+
try:
|
|
70
|
+
from asyncio.base_subprocess import BaseSubprocessTransport
|
|
71
|
+
|
|
72
|
+
orig_sub_del = getattr(BaseSubprocessTransport, "__del__", None)
|
|
73
|
+
if orig_sub_del and not getattr(BaseSubprocessTransport, "_patchtroy_safe", False):
|
|
74
|
+
|
|
75
|
+
def _safe_sub_del(self: Any, *args: Any, **kwargs: Any) -> None:
|
|
76
|
+
try:
|
|
77
|
+
orig_sub_del(self, *args, **kwargs)
|
|
78
|
+
except (RuntimeError, ValueError, OSError):
|
|
79
|
+
pass
|
|
80
|
+
|
|
81
|
+
BaseSubprocessTransport.__del__ = _safe_sub_del # type: ignore[method-assign]
|
|
82
|
+
BaseSubprocessTransport._patchtroy_safe = True # type: ignore[attr-defined]
|
|
83
|
+
except Exception:
|
|
84
|
+
pass
|
|
70
85
|
|
|
71
86
|
try:
|
|
72
87
|
from asyncio.proactor_events import _ProactorBasePipeTransport
|
|
@@ -77,7 +92,7 @@ def silence_windows_proactor_bug() -> None:
|
|
|
77
92
|
def _safe_pipe_del(self: Any, *args: Any, **kwargs: Any) -> None:
|
|
78
93
|
try:
|
|
79
94
|
orig_pipe_del(self, *args, **kwargs)
|
|
80
|
-
except (ValueError, OSError):
|
|
95
|
+
except (RuntimeError, ValueError, OSError):
|
|
81
96
|
pass
|
|
82
97
|
|
|
83
98
|
_ProactorBasePipeTransport.__del__ = _safe_pipe_del # type: ignore[method-assign]
|
|
@@ -85,19 +100,6 @@ def silence_windows_proactor_bug() -> None:
|
|
|
85
100
|
except Exception:
|
|
86
101
|
pass
|
|
87
102
|
|
|
88
|
-
try:
|
|
89
|
-
from asyncio.base_subprocess import BaseSubprocessTransport
|
|
90
|
-
|
|
91
|
-
orig_sub_del = getattr(BaseSubprocessTransport, "__del__", None)
|
|
92
|
-
if orig_sub_del and not getattr(BaseSubprocessTransport, "_patchtroy_safe", False):
|
|
93
|
-
|
|
94
|
-
def _safe_sub_del(self: Any, *args: Any, **kwargs: Any) -> None:
|
|
95
|
-
try:
|
|
96
|
-
orig_sub_del(self, *args, **kwargs)
|
|
97
|
-
except (ValueError, OSError):
|
|
98
|
-
pass
|
|
99
103
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
except Exception:
|
|
103
|
-
pass
|
|
104
|
+
# Backwards-compatible alias
|
|
105
|
+
silence_windows_proactor_bug = silence_subprocess_transport_bug
|
patchtroy-0.4.5/VERSION
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
v0.4.5-260902b
|
|
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
|
|
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
|