Ask the atlas to survey npm/express and it charts 68 repositories without complaint. Ask it to survey pypi/prefect or pypi/dagster and the expedition returns empty-handed. This sheet explains why — and why the failure is a fact about Python packaging, not a bug in the instrument.
The atlas resolves dependency closures with Google's deps.dev. For every package version, deps.dev exposes two different answers:
:requirements — the recipe: the dependency constraints the package declares (“anyio <5, ≥4.4”).:dependencies — the meal: a fully resolved graph of concrete versions, the thing the atlas needs.Here is what deps.dev said when we asked (checked 2026‑07‑03; re-check live below):
| Package | :requirements (the recipe) | :dependencies (the meal) |
|---|---|---|
| pypi/flask@3.1.3 | 200 — 9 constraints | 200 — resolved graph |
| pypi/prefect@3.7.7 | 200 — 83 constraints | 404 — declines to answer |
| pypi/dagster@1.13.12 | 200 — 100 constraints | 404 — declines to answer |
deps.dev knows what prefect asks for. It refuses to state what prefect gets. That distinction — recipe versus meal — is the entire subject of this sheet.
In npm, a package's dependencies are data. package.json is a static file; the registry reads it once at publish time and knows, forever and for everyone, what the package depends on. The map is printed on the territory.
Python grew up differently. For most of its history a package declared dependencies in setup.py — an arbitrary program that computes the dependency list at install time, on the installing machine. You cannot know what it will say without running it, and a registry cannot safely run millions of strangers' programs.
The modern ecosystem has a fix — static metadata in pyproject.toml (PEP 621) and reliably-static sdist metadata (PEP 643) — and well-run projects use it. Prefect and Dagster both publish wheels with fully static dependency lists. So this reason alone explains the long tail of PyPI, but not our two packages. For them, the problem is deeper.
Sdist? Wheel? Sheet No. 3 is the field guide to Python packaging — the two boxes opened, filenames decoded.
Even a perfectly static Python dependency list does not name a dependency set. It names a family of dependency sets, indexed by the installing environment. Every line may carry an environment marker — a predicate over the Python version, operating system, interpreter implementation — and an extra gate. The npm question “what does express depend on?” has one answer. The Python question is unanswerable until you finish the sentence: “what does prefect depend on when installed where, on which Python, with which extras?”
This is not hypothetical. The model below evaluates the actual published metadata of prefect 3.7.7, dagster 1.13.12, and flask 3.1.3 (snapshot 2026‑07‑03). Flip the world and watch the dependency set change:
Two details worth savoring in prefect's real metadata: below Python 3.13 it depends on pendulum, but at 3.13+ it swaps that entire subtree for whenever — two different date-time stacks depending on the interpreter you happen to run. And dagster ships a dependency that exists only on Windows. A database that stores “the” dependency graph of these packages must either pick one world, store them all, or stay silent.
Suppose you fix a world: CPython 3.12, Linux, no extras. You still don't have a graph — you have constraints, and someone must now choose concrete versions that satisfy all of them simultaneously. Here the two ecosystems diverge structurally:
node_modules. Conflict is impossible by construction. Resolution is mostly a lookup.“Dependencies are the resolution of the requirements (dependency constraints) specified by a version. The dependency graph should be similar to one produced by installing the package version on a generic 64-bit Linux system, with no other dependencies present.” — deps.dev API documentation, GetDependencies
deps.dev picks one synthetic world and resolves inside it, where it can. Flask — nine constraints, one trivial marker — resolves fine. Prefect and Dagster — 83 and 100 constraints, dozens of markers, sprawling extras matrices, heavyweight transitive closures — do not get a published resolution, and deps.dev returns 404 rather than guess.
Read charitably, the 404 is the honest move. It is the same policy this atlas uses for its own gaps: say plainly that you don't know rather than print a confident wrong number. The recipe is public; the meal is declined.
The atlas's answer combines two moves. First, the one it already made for counting lines: push the ambiguity into the stated boundary. Second, a realization about fidelity: the atlas counts every repository at HEAD, not at a resolved version — so it never needed the version assignment deps.dev can't provide. Package names are the honest resolution for a LOC survey.
So when deps.dev declines a PyPI closure, the atlas now walks the declared requirements by name — recursively, markers evaluated against one declared world (CPython 3.12 · Linux x86‑64 · no root extras, each package at its PyPI-default version), each unique source repo still counted exactly once. The survey result carries that boundary as a visible flag. The number stops pretending to be universal and becomes true relative to its frame — which is the only kind of true this territory offers. Prefect and Dagster, unmappable at the top of this sheet, are surveyable under it.
What's still out of reach: sdist-only packages whose setup.py computes its dependencies (§ 1) — those stay uncharted, and the survey says so: here be dragons.