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>
6.0 KiB
Plan: modernize hxprobe's Python toolchain
Context
hxprobe (hxprobe/) is a fully standalone Python project (own
pyproject.toml, own venv, own tests — see
docs/summaries/2026-07-02-09-32-hxprobe-standalone-project.md). Its
toolchain is currently bare-minimum: hand-rolled python -m venv + pip install -e ., no linter/formatter, no type checker, no dependency lockfile,
and stdlib unittest with a filename-glob convention
(test_[!i]*.py/test_integration.py) to separate hermetic from live tests.
User confirmed direction (via AskUserQuestion): adopt uv for env/deps
(with a lockfile), add ruff for lint+format, skip mypy for now, and
swap the test runner to pytest (existing unittest.TestCase classes run
unchanged under pytest — no test-code rewrite) using a proper
@pytest.mark.integration marker instead of the filename-glob trick.
Confirmed via inspection: uv (v0.11.19) is already installed globally
(Homebrew); ruff/mypy/pytest only exist under an unrelated pyenv 3.12.3
shim, not on the 3.14 interpreter this project targets — uv sidesteps that
mismatch by installing everything into the project's own venv. Git remote is
a self-hosted Gitea instance, not GitHub, so no CI is being set up here.
Changes
hxprobe/pyproject.toml:
- Add
[dependency-groups]withdev = ["pytest>=8.0", "ruff>=0.8"](PEP 735, the current uv-native way to declare dev-only deps — keeps the install-as-a-librarydependencieslist clean). - Add
[tool.pytest.ini_options]:testpaths = ["tests"]and a registeredintegrationmarker (avoidsPytestUnknownMarkWarning). - Add
[tool.ruff]:target-version = "py311"(matchesrequires-python) andline-length = 100(close to the codebase's existing longest lines, ~103 chars, to minimize reformatting churn). - Leave
[build-system]/[tool.setuptools]untouched — build backend wasn't part of the discussion, setuptools works fine here.
hxprobe/.python-version: new file, 3.14, so uv sync/uv run pin the
same interpreter the rest of the repo uses without relying on $PATH order.
hxprobe/uv.lock: generated by uv lock — first real lockfile pinning
httpx, httpcore, h2, certifi, and friends. Committed (not gitignored;
lockfiles belong in version control).
Test changes (runner swap only, no rewrite):
hxprobe/tests/test_integration.py: addpytestmark = pytest.mark.integrationat module level. This replaces the "namedtest_integration.pyso thetest_[!i]*.pyglob skips it" convention — selection becomes-m "not integration"/-m integration, independent of filename.test_probe.py/test_cli.py: no changes — they're already hermetic (localhttp.serverfixtures only) and need no marker.unittest.TestCaseclasses,_NEEDS_NET = unittest.skipUnless(...), and theif __name__ == "__main__": unittest.main()guards all stay exactly as-is — pytest natively discovers and runs unittest-style tests and respectsunittest.skip*decorators with zero changes required.
Lint fixes: run ruff check --fix / ruff format over hxprobe/ and
review the diff. One known pre-existing issue it will flag: import sys in
cli.py is unused (inherited from the original latprobe/cli.py) — remove
it. Otherwise expect mostly whitespace/quote-style normalization.
Makefile: replace the hx-* section to run through uv instead of a
hand-managed venv:
HX_DIR := hxprobe # (drop HX_VENV / HX_VENV_PYTHON — uv owns this now)
hx-deps: cd $(HX_DIR) && uv sync
hx-run: (deps: hx-deps) cd $(HX_DIR) && uv run python -m hxprobe $(ARGS)
hx-lint: (deps: hx-deps) cd $(HX_DIR) && uv run ruff check .
hx-fmt: (deps: hx-deps) cd $(HX_DIR) && uv run ruff format .
hx-test: (deps: hx-deps) cd $(HX_DIR) && uv run pytest tests -m "not integration" -v $(ARGS)
hx-test-integration: (deps: hx-deps) cd $(HX_DIR) && uv run pytest tests -m integration -v $(ARGS)
hx-check: hx-lint hx-test (test gate now includes lint, mirroring go-check's fmt+vet+test bundling)
hx-clean: also removes .pytest_cache / .ruff_cache alongside __pycache__/egg-info
Umbrella test/check/clean targets keep delegating to hx-test/
hx-check/hx-clean unchanged.
hxprobe/README.md: new, minimal — since this project is meant to be
cp -r-able to its own repo, it should carry its own quick-start
(uv sync, uv run python -m hxprobe <url>, uv run pytest) rather than
relying on the monorepo's root docs.
Docs: update docs/usage/hxprobe.md's "Setup" section (uv sync
instead of manual venv+pip) and "Makefile targets" section (add
hx-lint/hx-fmt, update test invocation description). New
docs/plans/<timestamp>-hxprobe-toolchain-modernization.md and
docs/summaries/<timestamp>-hxprobe-toolchain-modernization.md per
CLAUDE.md convention. New CHANGELOG.md entry.
.gitignore: already covers .venv//__pycache__//*.egg-info/
unanchored; add .pytest_cache/ and .ruff_cache/ (new caches these tools
create).
Verification
cd hxprobe && uv sync— creates.venv, generates/usesuv.lock, installshttpx[http2]+ dev deps (pytest,ruff).make hx-run ARGS="-v http://github.com"— same output as before (HTTP/2, 1 redirect, IP/TLS/cert shown) — confirms the runtime behavior is untouched by the toolchain swap.make hx-lint— clean (after fixing whateverruff checksurfaces, including the unusedimport sys).make hx-test— all hermetic tests pass under pytest; confirm theintegration-marked tests are excluded (test count matches the current 28 hermetic tests).make hx-test-integration— the 9 live tests run and pass under theintegrationmarker selection.make check(top-level) — Go + latprobe + hxprobe (lint + test) all green in one gate.grep -rn "latprobe" hxprobe/— still zero matches (toolchain change must not reintroduce coupling).- Confirm
python/latprobe/is untouched (git diff --stat python/empty) — this is a hxprobe-only change.