# PePy: a DDD and CQS service that counts Python package downloads

> The pepy repository is the Flask backend behind pepy.tech, a site that reports download statistics for Python packages, with the numbers coming from the public BigQuery dataset of PyPI downloads. What matters for anyone reading it now is the notice at the top: this is the legacy public version, the current one is maintained privately, and the repository exists to track issues and keep historical references.

**psincraian/pepy** — pepy is a site to get statistics information about any Python package.

- Repository: https://github.com/psincraian/pepy
- Website: https://pepy.tech
- Stars: 902 · Forks: 38
- Language: Python
- License: MIT
- Published: 2026-10-20 · Updated: 2026-10-20 · Language: en
- Canonical page: https://hysenlabs.com/projects/psincraian-pepy

## This repository is a historical copy, not the live backend

The first thing to read is the notice near the top of the README, because it changes what the repository is for. It states that this repository contains the legacy public version of PePy, that the latest version is now maintained privately, and that the project continues to use this repository solely to track issues and preserve historical references. Everything else in the README describes the service as it was built. PePy is a website that provides statistics about Python packages, and this repo housed the backend service for it, with the site itself at pepy.tech. So there are two things to keep apart: the running service, which anyone can visit today, and this code, which is a snapshot of how the service was put together. The last push to the repository was on 2026-04-28, and the project has no GitHub releases, so there is no version history to consult either. Reading this as a live codebase you could deploy would be a mistake, and reading it as a reference implementation with a well drawn architectural map is the better use of it.

## The numbers come from BigQuery, refreshed once a day

The FAQ answers the two questions that decide whether you can trust the figures. The downloads are retrieved from the official BigQuery repository of PyPI package download data, and the page linked is the packaging.python.org guide on analysing PyPI package downloads. The refresh schedule is a cron that runs every day at 5 pm UTC and pulls all the new downloads from the previous day. That single daily import is the whole data pipeline, and it has consequences worth understanding. The counts are not real time, they are a day behind at best, and a package that publishes several releases in a day has all of them attributed to one bucket. The same dataset is public, which means the raw numbers are not secret and could in principle be recomputed by anyone, so the value PePy adds is the presentation, the history and the per-package pages rather than the data itself. If you want the same numbers without the service, the FAQ hands you the source. If you want a chart per package with history, that is what this backend was built to produce.

## Three layers, and a deliberate split between commands and queries

The architecture section says the author used some DDD concepts, value objects and entities among them, and also CQS, whose objective is to separate commands from queries. The directory structure makes that concrete. In pepy/application sit all the commands and the queries. In pepy/domain are the domain objects, the entities, the exceptions and the value objects. In pepy/infrastructure are the infrastructure components, which includes the implementation of the repository class for the database or BigQuery, the Flask web application and the container. Below that sit three more directories with clear roles: pepy/infrastructure/cli for the command line programs, pepy/infrastructure/container for the config files and the dependency injection manager, and pepy/infrastructure/api for the api endpoints controller. The dependency inversion is the point. The application layer holds the use cases without naming BigQuery, Flask or the database, and the infrastructure layer supplies the implementations and wires them together in one place. For a project this size, that is a defensible amount of ceremony, and the interesting question for a reader is whether the boundary holds.

## One make target starts the whole stack

The contributing guide makes a point of making the environment easy to set up, and the prerequisites list is unusually specific about versions: bash at 4.3 or newer, docker at 17.05 or newer, docker-compose at 1.16.1 or newer, docker-py at 2.2.1 or newer, and ansible at 2.3 or newer. With those in place, the entire environment starts with a single command:

```bash
make start-containers
```

The Makefile shows what that target actually does, which is more informative than the one-liner. It sets a DOCKER_TAG variable defaulting to latest, passes that as a PEPY_VERSION environment variable into docker-compose, points at the compose file in the infrastructure directory while keeping the project directory at the repository root, and brings the stack up detached. It then polls a health check endpoint on port 5200 in a loop, sleeping a second between attempts, until the service answers. So the start command is not just starting containers, it is waiting for the application to become healthy, which is the difference between a usable local environment and a race. Two sibling targets stop the containers without removing them and remove them with the volumes, and the compose file is invoked with a variable project directory so the container names and paths are predictable.

## Three test layers, each with its own runner

The Makefile also lays out how the project tests itself, and the three tiers use three different tools, which tells you the kinds of test each layer holds. The unit tests run pytest against tests/unit, the integration tests run pytest with verbose output against tests/integration, and the acceptance tests run behave against tests/acceptance. Behave is a behaviour driven testing tool, and pairing it with pytest for the lower two tiers is a deliberate split between testing units of logic, testing the assembled pieces against real collaborators, and testing the system through described behaviour. The combined target runs the three in sequence. All of them execute inside the compose environment, in a container named pepy-test, which means the tests run against the same service definitions the development environment uses rather than against a separately configured stack. That is the arrangement that catches the class of problem where tests pass locally and fail in the environment. The tests directory is at the repository root next to the pepy package, and the Pipfile and its lock file are there for the Python dependencies, with a separate requirements-style file for the container build in the infrastructure directory.

## Code formatting is a make target too, at 120 columns

A small detail that says something about how the project was run. The format-code target runs black through pipenv, with a line length of 120, excluding node_modules, over the pepy directory and the tests directory. The line length of 120 is a deliberate choice, wider than black's default of 88, and it is worth knowing if you intend to contribute or to read the code against a different standard. The Pipfile and Pipfile.lock are present at the root, so the Python environment is managed with pipenv rather than a bare requirements file, and there is a sonar-project.properties file, which points at static analysis configuration for the project. The test tooling, the static analysis configuration, the code formatter and the container orchestration are all declared as make targets, which is a coherent way to run a project: one entry point, with the same commands a contributor and a CI run would use. The repository layout is otherwise plain, with a docs directory for the logo and supporting pages, a tests directory, a pepy package, and an infrastructure directory that holds the container and compose definitions.

## MIT licensed code, a hosted service, and what to check before using it

The code is available under the MIT licence, with the terms in the LICENSE.md file at the repository root, so the code itself is permissively licensed and can be read, copied and modified without the usual friction. The running service is a different matter in one respect: it depends on a hosted BigQuery dataset and on paid hosting, and the README acknowledges both. The site is kept alive by sponsors, and the sponsor list names individual backers and companies including a DigitalOcean referral badge, which is the usual way a small independent service covers its running costs. The authors behind it are also visible, with the maintainer of the tool and two well known figures from the Python packaging world listed among the backers. The practical question for an engineer is what the code is for. It is a reference implementation of a clean layering in a small domain, with a data source that anyone can query, so the value is in the structure and the daily import pattern rather than in the service itself, which is a better reason to read it than to run it.

## Conclusion

pepy is worth reading as a worked example of domain driven design and command query separation applied to a small, well-defined problem, and the layering in pepy/application, pepy/domain and pepy/infrastructure is clear enough to borrow as a structure. It is not a codebase to deploy today, because the repository states that the latest version is maintained privately and that this one is kept only to track issues and preserve historical references. Before you base anything on it, read that notice, and if you want the live service rather than the code, the numbers behind it are the same public BigQuery dataset that the FAQ points to, which you can query yourself.

## FAQ

### Is the pepy repository still the live backend for pepy.tech?

No. The README states that this repository holds the legacy public version of PePy, that the latest version is maintained privately, and that the repository is kept solely to track issues and preserve historical references. The last push was on 2026-04-28 and the project has no GitHub releases.

### Where does PePy get its download counts from?

From the official BigQuery repository of PyPI package download data, the dataset described in the packaging.python.org guide on analysing PyPI package downloads. The same public dataset can be queried directly if you need the raw numbers.

### How do I start the PePy development environment?

With the prerequisites in place, run make start-containers from the repository root. The target brings up the compose stack in the infrastructure directory and then polls a health check endpoint on port 5200 until the service answers, so the command returns only when the environment is usable.

### What is the layering in the pepy source code?

pepy/application holds the commands and the queries, pepy/domain holds the entities, exceptions and value objects, and pepy/infrastructure holds the implementations such as the repository for the database or BigQuery, the Flask application and the container, with subdirectories for the cli, the container configuration and the api endpoints controller.

## Sources

- [Issues](https://github.com/psincraian/pepy/issues)
- [License: MIT](https://github.com/psincraian/pepy/blob/master/LICENSE)
- [Project website](https://pepy.tech)
- [psincraian/pepy on GitHub](https://github.com/psincraian/pepy)
- [README](https://github.com/psincraian/pepy/blob/master/README.md)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/psincraian-pepy
