Files
http-latency-prober/docs/plans/2026-07-02-09-57-hxprobe-toolchain-modernization.md
Jan Novak f487a4b1bd docs: hxprobe plans, summaries, explanations, usage, and changelog
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>
2026-07-02 13:22:59 +02:00

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] with dev = ["pytest>=8.0", "ruff>=0.8"] (PEP 735, the current uv-native way to declare dev-only deps — keeps the install-as-a-library dependencies list clean).
  • Add [tool.pytest.ini_options]: testpaths = ["tests"] and a registered integration marker (avoids PytestUnknownMarkWarning).
  • Add [tool.ruff]: target-version = "py311" (matches requires-python) and line-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: add pytestmark = pytest.mark.integration at module level. This replaces the "named test_integration.py so the test_[!i]*.py glob 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 (local http.server fixtures only) and need no marker.
  • unittest.TestCase classes, _NEEDS_NET = unittest.skipUnless(...), and the if __name__ == "__main__": unittest.main() guards all stay exactly as-is — pytest natively discovers and runs unittest-style tests and respects unittest.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

  1. cd hxprobe && uv sync — creates .venv, generates/uses uv.lock, installs httpx[http2] + dev deps (pytest, ruff).
  2. 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.
  3. make hx-lint — clean (after fixing whatever ruff check surfaces, including the unused import sys).
  4. make hx-test — all hermetic tests pass under pytest; confirm the integration-marked tests are excluded (test count matches the current 28 hermetic tests).
  5. make hx-test-integration — the 9 live tests run and pass under the integration marker selection.
  6. make check (top-level) — Go + latprobe + hxprobe (lint + test) all green in one gate.
  7. grep -rn "latprobe" hxprobe/ — still zero matches (toolchain change must not reintroduce coupling).
  8. Confirm python/latprobe/ is untouched (git diff --stat python/ empty) — this is a hxprobe-only change.