Plans (docs/plans/): - 2026-07-01-23-47-py-hxprobe-httpx.md — initial httpx probe design - 2026-07-02-09-32 through 14-05 — standalone project, toolchain, usage doc + Makefile, file input (-f), simplification pass, run-summary footer Summaries (docs/summaries/): one per completed feature, recording what was actually built, deviations from the plan, and verification steps Explanations (docs/explanations/): two deep-dives written during review — hxprobe concurrency model and worst-exit-code + render-loop analysis Usage (docs/usage/hxprobe.md): overview with pointer to hxprobe/USAGE.md for the full runnable reference Walkthrough (docs/py-latprobe-walkthrough.md): narrative tour of the latprobe Python package for interview / code-review context CHANGELOG.md: entries for all hxprobe features (toolchain, usage doc, file input, simplification, run-summary footer) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
118 lines
6.7 KiB
Markdown
118 lines
6.7 KiB
Markdown
# Summary: extract `hxprobe` into a standalone top-level project
|
|
|
|
Plan: [docs/plans/2026-07-02-09-32-hxprobe-standalone-project.md](../plans/2026-07-02-09-32-hxprobe-standalone-project.md)
|
|
|
|
## What was built
|
|
|
|
`hxprobe` moved from `python/hxprobe/` to a new top-level `hxprobe/`
|
|
directory (sibling of `go/` and `python/`), structured as a genuinely
|
|
independent project: own `pyproject.toml`, own venv (`hxprobe/.venv`), own
|
|
`tests/` directory. It imports nothing from `python/latprobe` — verified with
|
|
`grep -rn "latprobe" hxprobe/` returning zero matches in code (a few comments
|
|
mention Go's `http.DefaultClient` for context, which is fine; no comment
|
|
references `latprobe` as a concrete path/module anymore either).
|
|
|
|
Files:
|
|
- `hxprobe/pyproject.toml` — own manifest, `httpx[http2]` dependency,
|
|
`packages = ["hxprobe"]`
|
|
- `hxprobe/hxprobe/probe.py` — moved from `python/hxprobe/probe.py`, with
|
|
`Options`, `Phase`, `Result`, `VerboseDetail`, `CertInfo` dataclasses and
|
|
`_parse_cert`/`_parse_cert_date` helpers now defined locally (copied from
|
|
`latprobe/probe.py`) instead of imported; all the httpx-instrumentation
|
|
logic (`_Trace`, `_TimingStream`, `_TimingBackend`, `_TimingTransport`,
|
|
`measure()`) is unchanged
|
|
- `hxprobe/hxprobe/aggregate.py`, `duration.py` — verbatim copies of
|
|
`latprobe`'s; their imports were already package-relative (`from .probe
|
|
import Result`), so copying required zero edits
|
|
- `hxprobe/hxprobe/cli.py` — rewritten as a full standalone CLI. Previously a
|
|
27-line wrapper delegating to `latprobe.cli.run()` via an injected
|
|
`measure_fn`; now has its own exit codes, argparse, and text/JSON
|
|
rendering (copied from the shared `latprobe/cli.py`, then stripped of the
|
|
`measure_fn`/`prog`/`description`/`protocol_flags` parameterization that
|
|
only existed to let two packages share one `run()`)
|
|
- `hxprobe/tests/{test_probe,test_cli,test_integration}.py` — moved from
|
|
`python/tests/test_hx_*.py` / `test_integration_hx.py`, `hx`-prefix
|
|
dropped, imports repointed from `latprobe.*` to `hxprobe.*`
|
|
- `python/latprobe/{cli.py,probe.py}` — reverted via `git checkout --` to
|
|
their exact pre-`hxprobe` committed state (confirmed zero diff afterward)
|
|
- `Makefile` — `py-deps`/`PY_VENV*`/shared `hx-run`/`hx-test-integration`
|
|
removed from the Python section; new standalone `hx-deps`/`hx-run`/
|
|
`hx-test`/`hx-test-integration`/`hx-check`/`hx-clean` targets under
|
|
`HX_DIR`/`HX_VENV*`; umbrella `test`/`check`/`clean` now run all three
|
|
(`go-*`/`py-*`/`hx-*`)
|
|
- `docs/usage/py-hxprobe.md` → `docs/usage/hxprobe.md` (renamed + rewritten
|
|
for the new structure)
|
|
- Old `python/hxprobe/`, `python/pyproject.toml`, `python/.venv`,
|
|
`python/latprobe_python.egg-info/` deleted
|
|
|
|
## Key design decisions
|
|
|
|
- **Move, don't rewrite, the already-debugged files.** `probe.py` and the
|
|
three test files carry real bug fixes found during the original
|
|
implementation (the `mark_dns` present-on-failure bug, verbose detail not
|
|
populated on the failure path, `Content-Length` needed once the test
|
|
fixtures switched to HTTP/1.1 keep-alive). Rewriting from scratch would
|
|
have risked reintroducing them.
|
|
- **`aggregate.py`/`duration.py` needed zero import changes** — their
|
|
existing relative imports (`from .probe import ...`) already resolve
|
|
correctly once copied into a new package with its own `probe.py`. Not
|
|
every file needed the same treatment as `probe.py`/`cli.py`.
|
|
- **`cli.py`'s starting point was the *current* shared `latprobe/cli.py`**,
|
|
not a from-scratch rewrite — it already contained 100% of the needed
|
|
logic (including the `--no-http2`/`--no-follow-redirects` flags and the
|
|
verbose Protocol row/JSON keys, both added earlier specifically for
|
|
hxprobe). The only work was deleting the generalization scaffolding
|
|
(`measure_fn`, `MeasureFn`, `prog`/`description`/`protocol_flags` params)
|
|
that existed solely to let two packages share one `run()`.
|
|
- **Reverting `latprobe` via `git checkout --` rather than hand-editing** —
|
|
confirmed first via `git log`/`git diff --stat` that `cli.py`/`probe.py`
|
|
had no changes besides the hxprobe-sharing scaffolding since the last
|
|
commit, making this a safe, exact, zero-risk revert.
|
|
- **Comments referencing `latprobe` by file path were reworded**, not just
|
|
the imports. A comment like "already used by latprobe/probe.py" becomes a
|
|
dangling reference once this directory is genuinely portable to another
|
|
repo. Reworded ~5 comments/docstrings to describe the technique generically
|
|
(e.g., "ports the getaddrinfo → connect split" → "splits DNS and TCP
|
|
connect into two timed steps") instead of naming the other project.
|
|
|
|
## Deviations from the plan
|
|
|
|
None of substance. One judgment call not spelled out in the plan: the old
|
|
`test_cli.py`'s docstring said its tests "focus on what's different... since
|
|
run() delegates almost entirely to latprobe.cli.run()" — no longer true now
|
|
that `cli.py` is a full standalone reimplementation. Rewrote that docstring
|
|
for accuracy rather than leaving a stale claim, without expanding the test
|
|
suite itself (that would be a larger, separate scope-creep beyond what was
|
|
asked — see the coverage note below).
|
|
|
|
## Notable follow-up worth flagging
|
|
|
|
`hxprobe/tests/test_cli.py` has ~11 tests versus `latprobe/tests/test_cli.py`'s
|
|
~23. That gap was fine when hxprobe's `cli.py` was a thin wrapper around
|
|
already-tested shared code; it's a real gap now that `cli.py` is an
|
|
independent ~440-line reimplementation with its own copy of every rendering
|
|
branch. Existing tests do exercise the core paths (single URL, `--fail`,
|
|
JSON, DNS/connect failures) so this isn't uncovered, but reaching
|
|
`latprobe`-level depth (multi-URL separator, sampling aggregate output, JSON
|
|
error grouping, all-failed multi-header) would be a reasonable next step if
|
|
full independent confidence in the standalone project matters. Not done here
|
|
— out of scope for a decoupling/move task.
|
|
|
|
## Verification
|
|
|
|
- `grep -rn "latprobe" hxprobe/` — zero matches in code; comment mentions
|
|
reworded to be self-contained
|
|
- `git diff --stat python/latprobe/` — empty (exact revert to last commit)
|
|
- `make hx-deps` — creates `hxprobe/.venv`, installs `httpx[http2]` from
|
|
`hxprobe/pyproject.toml`
|
|
- `make hx-run ARGS="-v http://github.com"` — same output as before the
|
|
move (HTTP/2, 1 redirect followed, IP/TLS/cert shown)
|
|
- `make hx-test` — all hermetic hxprobe tests pass standalone (own venv,
|
|
own `PYTHONPATH=hxprobe`)
|
|
- `make hx-test-integration` — live HTTP/2 + redirect tests still pass
|
|
- `make py-test` — `latprobe`'s own suite passes with zero setup (no
|
|
`py-deps`/venv needed), confirming the revert didn't leave residue
|
|
- `make check` (top-level) — Go + latprobe + hxprobe all green in one gate
|
|
- `python -m latprobe --help` output confirmed unchanged from before the
|
|
whole hxprobe feature existed (no leftover `--no-http2` etc.)
|