Documentation

From pypiserver

pypiserver is a Bottle app that serves a directory of your own packages over the simple API, with htpasswd-gated uploads. Its upstream story is a redirect: --fallback-url sends the client to pypi.org for anything the directory lacks, and nothing comes back into a cache. It serves under whichever WSGI server is importable (waitress if installed, otherwise the single-threaded stdlib server), and the project advertises that it is looking for new maintainers.

Comparison against peryx

Overlap

  • Hosting your own packages over the PEP 503 simple API.
  • twine uploads as the write path, authenticated against a credential file.
  • sha256 in file links so installers verify what they download.

Extra: what pypiserver does that peryx does not

  • Per-action authentication. pypiserver's -a download,list,update gates reads, listings, and uploads independently against an htpasswd file. peryx authenticates uploads only; reads are open to the network the port lives on.
  • A hand-editable package directory. You can drop files into pypiserver's directory and it lists them. peryx has no drop-in directory; uploads are the only write path.

Missing: what peryx adds

  • A real cached index. pypiserver's fallback is a 302 redirect to pypi.org; the file never enters its directory, so every machine still needs pypi.org access and every miss pays full upstream latency. peryx's cached layer serves misses through itself and keeps them: one egress point, cold installs at upstream speed, and a content-addressed store that dedupes.
  • Outage resilience. An upstream outage takes pypiserver's fallback installs down with it. peryx serves the last good page while the upstream is unreachable, so a pypi.org blip degrades to stale-but-working.
  • Shadowing. Your uploads shadow upstream names instead of coexisting with a redirect.
  • PEP 658 metadata. pypiserver serves none; peryx serves it by default.

Performance vs peryx

The benchmark suite runs both from their published packages. In the install rows, pypiserver's near-zero server CPU and flat cold-versus-warm columns are the redirect showing through: it does no work on a miss because it caches nothing.

uv: install the top 51 PyPI packages (ratios vs direct)
median over the run's rounds, ± coefficient of variation; a net row is upstream-bound and not a peryx measurement
peryx pypiserver
cold cache net 4.4 s ±6% (1.18x) 4.5 s ±1% (1.20x)
warm cache 3.3 s ±1% (0.90x) 4.5 s ±1% (1.24x)
server CPU 1.8 s ±4% (1.00x) 56 ms ±9% (0.03x)
server peak memory 699 MB ±2% (1.00x) 68 MB ±3% (0.10x)
PEP 691 JSON simple-page requests against a warm cache: peak rate, and p95 latency at 70% of it (ratios vs direct)
median over the run's rounds, ± coefficient of variation; a net row is upstream-bound and not a peryx measurement
peryx pypiserver
1 user: requests/s 7,658 req/s ±2% (128.49x) 65 req/s ±2% (1.09x)
1 user: p95 latency 2.3 ms ±2% (0.06x) 30 ms ±25% (0.75x)
32 users: requests/s 17,707 req/s ±5% (23.84x) 752 req/s ±0% (1.01x)
32 users: p95 latency 3.8 ms ±6% (0.04x) 307 ms ±14% (3.31x)
server CPU per 1k requests 168 ms ±1% (1.00x) 204 ms ±4% (1.21x)
server peak memory 108 MB ±6% (1.00x) 68 MB ±3% (0.63x)

How to migrate

Your package directory does not drop in: re-upload it once with twine, and peryx derives hashes and metadata server-side. Map the flags across:

pypiserverperyx
pypi-server run -p 8080 ~/packagesperyx serve
http://host:8080/simple/http://host:4433/{route}/simple/
-P htpasswd.txt -a updateupload_token on the hosted index
--fallback-url https://pypi.org/simple/ (redirect)a cached layer under the virtual index (served and cached)
--disable-fallbacka hosted-only index, no cached layer
twine upload -r local dist/*the same command, pointed at the virtual route

Re-upload the directory in one pass:

for f in packages/*; do twine upload --repository-url http://host:4433/{route}/ "$f"; done

Gotchas

  • Reads are open. pypiserver's per-action auth (-a download,list,update) has no counterpart; peryx authenticates uploads only, and reads are open to the network the port lives on. Put peryx on a trusted network or behind your own gateway if reads must be restricted.
  • No hand-editing the directory. If you relied on editing files in the package directory by hand, that workflow is gone; uploads are the write path.
  • Clients stop talking to pypi.org. Under pypiserver's redirect every client still reached pypi.org directly; behind peryx they do not, which is the point, but check that nothing downstream assumed direct upstream access.
On this page