mirror of
https://github.com/yogeshojha/rengine.git
synced 2026-10-20 23:31:31 +02:00
The comment rule in CLAUDE.md is one line maximum, never narration: no multi-line blocks, no module-header explainers, no sentences that explain or justify the code to the reader. The tree had drifted a long way from it, and most of the drift is mine. Removed: 232 lines of multi-line comment blocks 203 single-line comments that argued instead of stating 154 comments joined by so/because/otherwise/rather-than 143 narrating comments in the frontend 112 test docstrings (the test name already says what it asserts) 25 test module docstrings and 25 decorative section dividers Collapsed: 324 multi-line docstrings to one factual line 271 one-line docstrings that had a justifying clause bolted on Kept: functional directives, section dividers, short field labels, alembic revision headers, and the eight FastAPI route docstrings that render in /docs. Where a fact was load-bearing it is in CLAUDE.md, which is where it belongs. 284 python tests and 34 frontend tests pass, ruff and ruff-format clean across ten packages, svelte-check 0 errors, prettier and eslint clean, app.main imports. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013dKUV96WkQQETZBMoAG98C
156 lines
4.6 KiB
Python
156 lines
4.6 KiB
Python
from __future__ import annotations
|
|
|
|
import contextlib
|
|
from collections.abc import Iterator
|
|
|
|
from shared.logging import get_logger
|
|
from tools.runner import CLIToolRunner, StreamOutcome, ToolNotFoundError
|
|
from tools.runner.models import CommandRecorder
|
|
|
|
logger = get_logger(__name__)
|
|
|
|
HTTPX_BINARY = "httpx"
|
|
DEFAULT_TIMEOUT = 900
|
|
_IDLE_FLOOR = 120
|
|
_IDLE_TIMEOUT_FACTOR = 6
|
|
_CAPTURE_IDLE_FLOOR = 300
|
|
CAPTURE_SECONDS_PER_TARGET = 6
|
|
MAX_CAPTURE_SECONDS = 7200
|
|
|
|
_RESPONSE_SIZE_CAP = 131072 # 128 KiB
|
|
|
|
_ENRICH_FLAGS = [
|
|
"-status-code",
|
|
"-title",
|
|
"-tech-detect",
|
|
"-web-server",
|
|
"-content-length",
|
|
"-content-type",
|
|
"-location",
|
|
"-ip",
|
|
"-cname",
|
|
"-cdn",
|
|
"-tls-grab",
|
|
"-jarm",
|
|
"-favicon",
|
|
"-hash",
|
|
"sha256",
|
|
"-http2",
|
|
"-pipeline",
|
|
"-include-response",
|
|
"-body-preview",
|
|
"-response-size-to-read",
|
|
str(_RESPONSE_SIZE_CAP),
|
|
"-no-color",
|
|
]
|
|
|
|
|
|
class HttpxError(Exception):
|
|
pass
|
|
|
|
|
|
class HttpxClient:
|
|
def __init__(
|
|
self,
|
|
*,
|
|
rate_limit: int | None = None,
|
|
threads: int = 50,
|
|
timeout: int = 10,
|
|
proxy_url: str | None = None,
|
|
headers: dict[str, str] | None = None,
|
|
follow_redirects: bool = True,
|
|
store_dir: str | None = None,
|
|
recorder: CommandRecorder | None = None,
|
|
extra_args: list[str] | None = None,
|
|
) -> None:
|
|
self.rate_limit = rate_limit
|
|
self.threads = threads
|
|
self.timeout = timeout
|
|
self.proxy_url = proxy_url
|
|
self.headers = headers or {}
|
|
self.follow_redirects = follow_redirects
|
|
self.store_dir = store_dir
|
|
self.recorder = recorder
|
|
self.extra_args = extra_args or []
|
|
|
|
try:
|
|
self._runner = CLIToolRunner(HTTPX_BINARY, default_timeout=DEFAULT_TIMEOUT)
|
|
except ToolNotFoundError as e:
|
|
raise HttpxError(str(e)) from e
|
|
|
|
@contextlib.contextmanager
|
|
def stream_probe(self, targets: list[str]) -> Iterator[StreamOutcome]:
|
|
"""Probe targets, streaming parsed httpx records one at a time (memory-bounded)."""
|
|
if not targets:
|
|
yield StreamOutcome(records=iter(()), return_code=0)
|
|
return
|
|
args = list(_ENRICH_FLAGS)
|
|
if self.follow_redirects:
|
|
args.append("-follow-redirects")
|
|
if self.rate_limit:
|
|
args += ["-rate-limit", str(self.rate_limit)]
|
|
args += ["-threads", str(self.threads), "-timeout", str(self.timeout)]
|
|
if self.proxy_url:
|
|
args += ["-proxy", self.proxy_url]
|
|
for key, value in self.headers.items():
|
|
args += ["-header", f"{key}: {value}"]
|
|
|
|
with self._runner.stream_json(
|
|
args=args,
|
|
input_data=targets,
|
|
input_flag="-l",
|
|
json_flag="-json",
|
|
silent=True,
|
|
silent_flag="-silent",
|
|
timeout=0,
|
|
idle_timeout=max(_IDLE_FLOOR, self.timeout * _IDLE_TIMEOUT_FACTOR),
|
|
recorder=self.recorder,
|
|
tool=HTTPX_BINARY,
|
|
extra_args=self.extra_args,
|
|
) as stream:
|
|
yield stream
|
|
|
|
def _capture_args(self) -> list[str]:
|
|
args = [
|
|
"-status-code",
|
|
"-screenshot",
|
|
"-system-chrome",
|
|
"-exclude-screenshot-bytes",
|
|
"-no-color",
|
|
]
|
|
if self.store_dir:
|
|
args += ["-store-response-dir", self.store_dir]
|
|
if self.follow_redirects:
|
|
args.append("-follow-redirects")
|
|
args += ["-threads", str(self.threads), "-timeout", str(max(self.timeout, 20))]
|
|
if self.proxy_url:
|
|
args += ["-proxy", self.proxy_url]
|
|
for key, value in self.headers.items():
|
|
args += ["-header", f"{key}: {value}"]
|
|
return args
|
|
|
|
@contextlib.contextmanager
|
|
def stream_capture(self, targets: list[str]) -> Iterator[StreamOutcome]:
|
|
"""The same render, streaming each image as the browser finishes it."""
|
|
if not targets:
|
|
yield StreamOutcome(records=iter(()), return_code=0)
|
|
return
|
|
ceiling = min(
|
|
MAX_CAPTURE_SECONDS,
|
|
max(DEFAULT_TIMEOUT, len(targets) * CAPTURE_SECONDS_PER_TARGET),
|
|
)
|
|
with self._runner.stream_json(
|
|
args=self._capture_args(),
|
|
input_data=targets,
|
|
input_flag="-l",
|
|
json_flag="-json",
|
|
silent=True,
|
|
silent_flag="-silent",
|
|
timeout=ceiling,
|
|
idle_timeout=max(_CAPTURE_IDLE_FLOOR, self.timeout * _IDLE_TIMEOUT_FACTOR),
|
|
recorder=self.recorder,
|
|
tool=HTTPX_BINARY,
|
|
extra_args=self.extra_args,
|
|
) as stream:
|
|
yield stream
|