The virtual engine now detects local members (repo_type=local) and
generates their package index in-memory instead of trying to fetch
from a non-existent upstream.
- MemberIndex gains RepoType field so mergers use correct URL prefix
(/api/v1/local/ vs /api/v1/remote/)
- Virtual engine accepts a LocalIndexGenerator interface for producing
local PyPI indexes
- LocalHandler implements GeneratePyPIPackageHTML for reuse by both
the direct serving path and the virtual merger
- Includes local PyPI upload support (cherry-picked from benvin/local-pypi)
Tested e2e: local wheel upload + virtual merge + uv pip install from
both direct local and virtual URLs
## Summary
- Upload Python wheels/sdists to local PyPI repos with filename validation
- PEP 503 simple index computed on-demand from stored files
- Package names normalized per PEP 503 (lowercase, hyphens)
- Overwrites rejected (409 Conflict)
## Test plan
- [x] Build wheel with `uv build` → upload → verify simple index HTML → `uv pip install` from local repo
- [x] Bad filename rejection (400)
- [x] Overwrite rejection (409)
- [x] Hash integrity verification on download
Reviewed-on: #50
Co-authored-by: Ben Vincent <ben@unkin.net>
Co-committed-by: Ben Vincent <ben@unkin.net>