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,updategates 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
302redirect 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.
| 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) |
| 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:
| pypiserver | peryx |
|---|---|
pypi-server run -p 8080 ~/packages | peryx serve |
http://host:8080/simple/ | http://host:4433/{route}/simple/ |
-P htpasswd.txt -a update | upload_token on the hosted index |
--fallback-url https://pypi.org/simple/ (redirect) | a cached layer under the virtual index (served and cached) |
--disable-fallback | a 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"; doneGotchas
- 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.