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 33.4 s ±29% (9.26x) 3.9 s ±2% (1.07x)
warm cache 8.4 s ±30% (2.37x) 3.9 s ±18% (1.10x)
server CPU 40.0 s ±16% (1.00x) 46 ms ±9% (0.00x)
server peak memory 570 MB ±11% (1.00x) 68 MB ±3% (0.12x)
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 573 req/s ±3% (2.16x) 244 req/s ±2% (0.92x)
1 user: p95 latency 9 ms ±1% (0.86x) 11 ms ±4% (1.01x)
32 users: requests/s 2,512 req/s ±2% (2.87x) 884 req/s ±0% (1.01x)
32 users: p95 latency 16 ms ±1% (0.36x) 106 ms ±2% (2.41x)
server CPU 2m 06.1s ±2% (1.00x) 2.9 s ±1% (0.02x)
server peak memory 336 MB ±4% (1.00x) 68 MB ±3% (0.20x)

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