Documentation

From pypicloud

pypicloud was the closest thing to peryx in Python: a Pyramid application offering private hosting on S3/GCS/Azure/local storage with a fallback = cache mode that downloaded misses from PyPI, stored them, and served them. Its repository was archived on August 27, 2023 ("Pypicloud has transitioned to maintenance mode"), with the last release in December 2022. It runs today only under Python 3.10 with SQLAlchemy pinned below 2.

Comparison against peryx

Overlap

  • Read-through cache-on-miss. pypicloud's fallback = cache is peryx's default cached-index behavior: fetch a miss, store it, serve it.
  • Private hosting of your own packages, private names taking precedence over public ones.
  • Token or user-authenticated uploads.

Extra: what pypicloud does that peryx does not

  • Cloud storage backends. pypicloud stores artifacts on S3, GCS, Azure Blob, or local disk. peryx stores on local disk only.
  • Pluggable cache and access backends. pypicloud keeps its package index in SQLAlchemy, Redis, or DynamoDB, and drives access through config, SQL, or LDAP user/group systems. peryx embeds its metadata store (redb, nothing to provision) and authenticates uploads with one token per index.
  • Horizontal scale-out. Several stateless pypicloud web servers can share one storage backend and cache DB. peryx is one process per data directory.

Missing: what peryx adds

  • It is maintained. pypicloud is archived and pinned to a pre-2.0 SQLAlchemy stack.
  • A streaming cold path. pypicloud buffers a missed wheel fully into a TemporaryFile, writes it to storage and a cache row, and only then serves it, so the client waits for the whole download plus the disk write plus the DB commit. peryx streams the bytes to the client and into the store at once.
  • Concurrency correctness. A cold burst of clients asking pypicloud for the same wheel each download it and race to insert the same primary key into single-writer SQLite; the losers surface as HTTP 500. peryx single-flights the fetch, so all waiters tail one download.
  • Content-addressed dedup and PEP 658 metadata, neither of which pypicloud offers (it stores files by name/version/filename and serves no .metadata sibling).

Performance vs peryx

The benchmark suite runs both from their published packages. Cold and warm installs through uv:

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 pypicloud
cold cache net 33.4 s ±29% (9.26x) 9.8 s ±5% (2.72x)
warm cache 8.4 s ±30% (2.37x) 4.7 s ±3% (1.32x)
server CPU 40.0 s ±16% (1.00x) 3.4 s ±3% (0.09x)
server peak memory 570 MB ±11% (1.00x) 367 MB ±8% (0.64x)

The throughput workload includes the cold burst that pypicloud answers with HTTP 500: four clients ask for one large wheel the instant it lands.

moving one large wheel (torch): cold under contention, hot at speed (ratios vs direct)
median over the run's rounds, ± coefficient of variation; a net row is upstream-bound and not a peryx measurement
peryx pypicloud
cold cache: 4 clients, one wheel net 819 ms ±1% (0.27x) error (n/a)
hot cache: single download 4,306 MB/s ±6% (36.97x) error (n/a)
hot cache: 8 parallel downloads 7,653 MB/s ±4% (65.51x) error (n/a)
server CPU 294 ms ±13% (1.00x) 565 ms ±5% (1.92x)
server peak memory 44 MB ±4% (1.00x) 260 MB ±22% (5.85x)

How to migrate

Feature-wise this is the most direct migration: peryx's read-through cached index is pypicloud's fallback = cache made the default. Your cached-index state refills on first use; only hosted uploads need to move. Map the config across:

pypicloudperyx
ppc-make-config + pserve config.inia TOML file + peryx serve
pypi.fallback = cachethe default cached-index behavior
pypi.fallback = redirect / nonenot offered; misses serve through the cache or 404 on hosted-only indexes
storage = s3 / gcs / azurelocal data_dir only
db = sqlalchemy / redis / dynamo cacheembedded (redb), nothing to provision
access backends (config / SQL / LDAP)one upload_token per hosted index
/simple/ and /pypi/ routes/{route}/simple/

Gotchas

  • No object-storage backend. If your deployment depended on S3 durability, put data_dir on a durable volume and back it up (plain files; rsync works), or wait for a storage backend seam.
  • No per-user permissions. peryx authenticates uploads with a token per index and leaves reads open; see the devpi page for the same caveat.
  • One process per data directory. Multiple stateless web servers sharing one cache was a valid pypicloud shape; it is not a peryx shape. Run one peryx per site.
On this page