Skip to main content

pypi-winnow-downloads

PyPI version Python versions License CI Coverage pip*/uv/poetry/pdm downloads

Self-hosted PyPI download badge service that winnows CI traffic out of download counts. Produces shields.io-compatible endpoint badges filtered by BigQuery's details.ci flag and an interactive-installer allowlist (pip, uv, poetry, pdm, pipenv, pipx) — more honest than any existing alternative for small or young Python packages.

Eating our own dogfood — the download-count badge in the row above and the breakdown below are produced by this project itself; the reference deployment at pypi-badges.intfar.com refreshes them daily via systemd timer. Both views track the same pypi-winnow-downloads package over a 30-day non-CI window.

By installer (30d, non-CI):

pip downloads pipenv downloads pipx downloads uv downloads poetry downloads pdm downloads

By OS (30d, non-CI):

linux downloads macos downloads windows downloads

What these badges actually count

The hero badge — labelled pip*/uv/poetry/pdm (Nd) (N=30 in the reference deployment, configurable per-package via window_days) — counts downloads that meet all of these conditions over the configured rolling window:

  • details.ci != True (BigQuery's CI-detection flag is not set)
  • details.installer.name is one of the interactive Python packaging tools: pip, uv, poetry, pdm, pipenv, or pipx (the asterisk in pip* covers pip itself plus pipenv and pipx, which delegate to pip and inherit its installer telemetry pattern)

Excluded (the things that inflate other badges):

  • Mirrors: bandersnatch, Nexus, devpi, Artifactory, z3c.pypimirror
  • Browser fetches via the PyPI web UI (installer_name == "Browser")
  • Generic HTTP UAs used by scrapers and scanners (requests, curl, etc.)
  • Unknown installer (installer_name == "None") — uncategorised traffic that in practice is dominated by automated scanners

For context on how much these can dwarf real installs: at v1 deploy time, one of the seed packages had 2,771 "non-CI" downloads in 30 days under a naïve mirror-and-all-installers query, of which 1,325 (48%) was bandersnatch alone and only 14 came from pip + uv + poetry + pdm. The honest signal is the 14.

The filter is fail-closed: a future pypinfo emitting a new mainstream installer will be excluded until the allowlist in src/pypi_winnow_downloads/collector.py is updated explicitly. That's a feature for a project whose pitch is honesty.

Per-installer breakdown. Alongside the hero, the reference deployment also produces one badge per installer in the allowlist (pip, pipenv, pipx, uv, poetry, pdm) plus a pip* aggregate (pip + pipenv + pipx). Each applies the same details.ci != True filter as the hero — so they answer "non-CI downloads broken down by which packaging tool the user was running." Useful for spotting installer-mix shifts (e.g., uv overtaking pip on a young package). See Use this service for your own package below for the per-installer URL pattern.

By OS breakdown. Each per-OS badge applies the same details.ci != True filter as the hero — they answer "non-CI downloads on that OS." Darwin is pypinfo's emission for what users call macOS; the badge filename and label use macos. The per-OS sum can be less than the hero count: rows whose user-agent didn't expose a system_name (or exposed one outside Linux/Darwin/Windows) drop out of the per-OS aggregation but still count toward the hero — same pattern as the per-installer-sum ≤ hero gap.

Install

uv tool install pypi-winnow-downloads

uv tool drops the winnow-collect console-script onto your PATH in an isolated environment without touching your system Python. If you don't have uv installed, plain pip also works:

pip install pypi-winnow-downloads

The collector queries Google's public PyPI BigQuery dataset via pypinfo, so before the first run you'll need a Google Cloud service account JSON key. Pypinfo's installation guide walks the full setup (create a GCP project, enable the BigQuery API, generate the JSON key) and recommends the broad BigQuery User role; the narrower pair BigQuery Job User + BigQuery Data Viewer also works and is what config.example.yaml and the reference deploy document. Then point service.credential_file in your config at the resulting file.

Run with a YAML config — copy config.example.yaml and edit:

winnow-collect --config /path/to/config.yaml

To deploy as a daily systemd timer plus a Caddy HTTPS service serving the output directory, see deploy/README.md.

Use this service for your own package

The reference deployment at pypi-badges.intfar.com produces eight badge JSON files per configured package per window, all under https://pypi-badges.intfar.com/<package>/:

File Label What it counts
downloads-30d-non-ci.json pip*/uv/poetry/pdm (30d) All six allowlisted installers summed (the v1 hero)
installer-pip-30d-non-ci.json pip (30d) pip only
installer-pipenv-30d-non-ci.json pipenv (30d) pipenv only
installer-pipx-30d-non-ci.json pipx (30d) pipx only
installer-uv-30d-non-ci.json uv (30d) uv only
installer-poetry-30d-non-ci.json poetry (30d) poetry only
installer-pdm-30d-non-ci.json pdm (30d) pdm only
installer-pip-family-30d-non-ci.json pip* (30d) pip + pipenv + pipx aggregate
os-linux-30d-non-ci.json linux (30d) Per-OS, Linux
os-macos-30d-non-ci.json macos (30d) Per-OS, macOS (Darwin)
os-windows-30d-non-ci.json windows (30d) Per-OS, Windows

All files exclude CI traffic (BigQuery's details.ci != True). Each is a shields.io endpoint badge JSON.

To embed any of these in your own README, wrap the file URL in shields.io's /endpoint?url= form, URL-encoding the inner URL (/ becomes %2F, : becomes %3A):

[![pip downloads](https://img.shields.io/endpoint?url=https%3A%2F%2Fpypi-badges.intfar.com%2F<your-package>%2Finstaller-pip-30d-non-ci.json)](https://pypi.org/project/<your-package>/)

Replace <your-package> with your PyPI package name. The same template works for any of the eight files — substitute the filename. The window length (30d in the examples) reflects the reference deployment's window_days: 30 setting; if you self-host, your own deployment's window_days substitutes here.

To get your package added to the reference deployment's config.yaml, open an issue or run your own collector — see deploy/README.md.

Status

Beta as of v0.2.0. Self-hosted reference deployment running at pypi-badges.intfar.com since 2026-04-25, producing daily badges for four target packages (the three seed packages in config.example.yaml plus pypi-winnow-downloads itself for the dogfood badge). Test suite holds 100% line coverage on src/. The v1 hero badge JSON shape and filename are stable and won't change before 1.0; new badge files may be added (the v2 installer-mix breakdown landed in v0.2.0 alongside the unchanged hero). Expect occasional breaking changes elsewhere in the 0.x series — the Changed/Removed sections of CHANGELOG.md are where to look on each release.

Acknowledgments

This project rests on three pieces of upstream work:

  • pypinfo by Ofek Lev: the BigQuery query layer for the PyPI download dataset. pypi-winnow-downloads is essentially a filter and badge writer wrapped around pypinfo.
  • shields.io renders the endpoint badges. The collector emits the JSON shape that shields.io's endpoint badge consumes, so badges inherit its caching, theming, and SVG rendering.
  • The bigquery-public-data.pypi.file_downloads dataset, hosted by Google as a public BigQuery dataset and populated by the PyPI Linehaul pipeline, is the underlying data source. Without it, no installer-level breakdown of PyPI downloads would be possible.

Designed and built collaboratively with Claude Code (Anthropic) across planning, implementation, review, and QA. Significant subsystems (the pypinfo XDG_DATA_HOME isolation, the sys.executable-based pypinfo resolver, the installer-allowlist filter, the deploy/ examples) emerged through that planner / Dev / QA loop.

License

Licensed under the Apache License, Version 2.0.

© 2026 Chris Means.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

pypi_winnow_downloads-0.3.0.tar.gz (186.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

pypi_winnow_downloads-0.3.0-py3-none-any.whl (19.5 kB view details)

Uploaded Python 3

File details

Details for the file pypi_winnow_downloads-0.3.0.tar.gz.

File metadata

  • Download URL: pypi_winnow_downloads-0.3.0.tar.gz
  • Upload date:
  • Size: 186.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for pypi_winnow_downloads-0.3.0.tar.gz
Algorithm Hash digest
SHA256 2a5012f5b9b129486a05b07e84741c4a2e25251d6059fa1e95f6ece5d5c5a99f
MD5 c42d8cffad0144abc017d787bb2680cf
BLAKE2b-256 7e5c57b8352e2275cdd7b9c430506d5da0ed342dc2c25b933cd7a83dbdc88a96

See more details on using hashes here.

Provenance

The following attestation bundles were made for pypi_winnow_downloads-0.3.0.tar.gz:

Publisher: publish.yml on cmeans/pypi-winnow-downloads

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file pypi_winnow_downloads-0.3.0-py3-none-any.whl.

File metadata

File hashes

Hashes for pypi_winnow_downloads-0.3.0-py3-none-any.whl
Algorithm Hash digest
SHA256 13caded7b1b260891cd329bcf39feb3ed7cbd77281c7520eea99020919e07cd0
MD5 d7601fee0c4b18fd54f6c4bbf72f95c1
BLAKE2b-256 3d711f74510cb79adc9dbbd1128a9d6878b41a220469dd19e506b2c2e5a9b18d

See more details on using hashes here.

Provenance

The following attestation bundles were made for pypi_winnow_downloads-0.3.0-py3-none-any.whl:

Publisher: publish.yml on cmeans/pypi-winnow-downloads

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

This release

0.3.0 This release

2 files

0.2.0

2 files

0.1.3

2 files

0.1.2

2 files

0.1.1

2 files

0.1.0

2 files

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page