Skip to content
Latchkey
Published June 2026

The State of Python CI 2026

The shape of a Python pipeline barely changed in 2026, but uv rewrote the dependency layer underneath it, and the teams that noticed have pipelines that feel an order of magnitude faster.

49%
of Python developers run their tests in a CI service
JetBrains Developer Ecosystem Survey
10-100x
faster dependency resolution with uv vs pip in a cold CI environment
Latchkey analysis (modeled)
43%
of a typical uncached Python CI run spent on dependency install alone
Latchkey analysis (modeled)

Executive summary

Python CI in 2026 looks different than it did even two years ago, though not where you might expect. The pipeline shape is remarkably stable: lint, type-check, then test across a version matrix is still the canonical structure, and it has not needed reinventing. What changed is the dependency layer underneath that shape. The rise of uv has pulled install-and-resolve time from the slowest stage of a Python pipeline toward a near-non-event when the cache is warm, and that single shift is the biggest story in Python CI this year.

The data points at one dominant cost and one dominant lever. The cost is dependency install, which on an uncached run routinely consumes the largest single slice of wall-clock time, ahead of the tests themselves. The reason is structural: the test suite is yours and roughly fixed in size, but the dependency tree is large, frequently re-downloaded, and re-resolved from scratch on every run unless something deliberately stops it. The lever is caching, and crucially, which tool you use changes how much that lever is worth.

pip, poetry, and uv each cache differently, and they do not cache equally. pip with a requirements file caches wheels but re-resolves; poetry caches its resolved environment but pays a resolver cost; uv's resolver is dramatically faster cold and, with a warm cache, collapses the dependency stage from dominant to rounding error. This report measures those differences directly, because the gap between a cold pip install and a warm uv install is the difference between a Python pipeline that drags and one that feels instant.

Then there is the matrix. Testing across Python 3.10 through 3.13 is good practice, and many teams add an operating-system dimension on top. Every cell of that matrix pays the full pipeline cost, which means an uncached install tax does not just slow one job, it gets multiplied by the width of the grid. A wide matrix turns a moderate per-cell inefficiency into a large per-run number, which is exactly where a fast, well-cached, cheap-per-minute runner pays for itself.

The throughline for Python teams is that the speedups are mechanical and available today. Cache the dependency layer per version, adopt uv where the resolver win matters, order the cheap fail-fast checks before the expensive matrix, and run on a runner that preserves the cache reliably and auto-heals the transient failures that otherwise force a re-run of the whole grid. None of that requires touching a single test, and together it is the difference between a Python pipeline that feels fast and one that taxes every push.

Where a Python CI minute goes
Dependency install 43%
Test execution 31%
Lint + type-check 14%
Env setup + checkout 12%

Estimated wall-clock split of a typical uncached Python CI run. · Source: Latchkey analysis (modeled)

Python dependency tool usage
pip / requirements46%poetry27%uv21%pipenv / other6%

Share of Python projects using each tool to manage CI dependencies. · Source: Synthesized from public Python ecosystem surveys

Email me the report

The full report is right here on this page, free. Want the link in your inbox to read later or share, plus new Latchkey reports as they drop? Drop your email and we will send it over.

Sent! Check your inbox for the report link.

No spam. Unsubscribe anytime.

Dependency install is the slowest stage in most Python pipelines

On an uncached run, installing and resolving dependencies is consistently the single largest consumer of wall-clock time in a Python pipeline, ahead of the test suite itself. The split is lopsided enough that a team optimizing test execution while ignoring dependency install is polishing the wrong stage entirely.

The reason is structural rather than incidental. A team's test suite grows roughly with the code and runs the same way each time, but the dependency tree is large, transitively deep, and re-fetched and re-resolved from scratch every run unless something explicitly stops it. A modest application can pull hundreds of transitive packages, and resolving a valid set of versions across them is real computational work that an uncached pipeline repeats on every single push.

This makes the dependency layer the highest-leverage speedup available to a Python team, and it is the first thing the fastest pipelines get right. Caching that layer does not just shave a few seconds, it removes the largest single slice of the run, which is why the modeled split puts dependency install well ahead of every other stage on an uncached run.

uv has rewritten the caching math

pip, poetry, and uv all cache, but they cache to very different effect. pip caches downloaded wheels yet still re-resolves the dependency set, so a warm pip cache helps with download time but not with resolution. poetry caches its resolved environment and is faster on a warm run, but pays a notable resolver cost cold. uv changes the shape of the whole stage: its resolver is dramatically faster cold, and with a warm cache it collapses dependency install from the dominant stage to a near rounding error.

The modeled numbers make the gap concrete. A cold pip install in the tens-of-seconds-to-a-minute-and-a-half range, a cold poetry install somewhat faster, a cold uv install a fraction of either, and a warm uv install down in the single-digit seconds. That is the span between a dependency stage that dominates the run and one that effectively disappears from the critical path, which is why teams that adopt uv describe their pipelines as simply feeling fast.

This is the engine behind uv's climbing share even in pipelines that kept pip everywhere else. A team does not have to rewrite its packaging philosophy to capture the win; it can adopt uv at the CI install step and keep the rest of its workflow unchanged. The one requirement is a runner that preserves the cache reliably across runs, because uv's warm-cache advantage only materializes if the cache actually survives from one run to the next.

  • pip caches wheels but still re-resolves, so a warm pip cache helps download time, not resolution.
  • poetry is faster warm but pays a resolver cost cold.
  • uv is dramatically faster cold and collapses the install stage to single-digit seconds warm.
  • The warm-cache win only materializes if the runner preserves the cache reliably across runs.
Dependency install time by tool
pip, cold94 spoetry, cold71 suv, cold18 suv, warm cache4 s

Modeled install + resolve seconds in CI, cold vs warm cache. · Source: Latchkey analysis (modeled)

Version matrices multiply everything, including the install tax

Testing across Python 3.10 through 3.13 is sound practice, because Python releases do change behavior and a library that works on 3.11 can break on 3.13. But every cell of that matrix pays the full pipeline cost, and an uncached install tax is therefore multiplied by the width of the grid rather than paid once.

The modeled minutes show the curve plainly. A single-version run is cheap, two versions roughly double it, the full 3.10-through-3.13 span quadruples it, and adding a three-way operating-system dimension multiplies again into a grid that consumes an order of magnitude more minutes than the single-version baseline. A wide matrix is good for correctness and brutal for an uncached pipeline, because the slowest stage is paid in every cell.

The teams that keep wide matrices affordable do two specific things. They cache per version so each cell starts warm instead of cold, which means the install tax that the matrix multiplies is a small number rather than a large one. And they run on runners cheap enough per minute that a wide grid does not dominate the bill, so correctness and cost stop pulling against each other. Per-version caching plus a low per-minute rate is what lets a team test the full matrix on every push without flinching at the invoice.

CI minutes by Python version matrix width
Single version6 min3.11 + 3.1212 min3.10 - 3.1324 min+ OS matrix (x3)72 min

Modeled total CI minutes per run as the version matrix grows. · Source: Latchkey analysis (modeled)

Lint and type-check are cheap, but their position is the lever

Lint and type-check together are a small slice of total pipeline time, but where they sit in the pipeline matters more than how long they take. Running them before the matrix as a fail-fast gate stops the most expensive stage, the full version grid, from running on code that was never going to pass anyway.

The economics are asymmetric in the team's favor. A lint failure or a type error caught in a few seconds before the matrix fans out saves the entire cost of the grid for that run. A team that runs the matrix first and lints inside each cell pays the full multiplied cost only to fail on something a single cheap check would have caught immediately.

The fastest Python pipelines therefore put the cheap, high-signal checks first and only fan out to the full version matrix once those are green. This keeps the average run short even when the worst case is wide, because most failing runs fail at the cheap gate and never reach the expensive grid at all. Ordering is free, and it is one of the highest-return changes a Python pipeline can make.

Transient failures re-run the whole grid

In a matrix build a single transient failure, a PyPI timeout, a flaky network fetch while resolving a dependency, an out-of-memory kill in one cell, often fails the job and drags a re-run of the surrounding capacity. The failure is mechanical, not a defect in the code, and it passes cleanly on retry, but the cost of that retry is multiplied by the matrix.

This is the matrix-specific version of the flaky-test tax. One bad cell out of a dozen can cost a full re-run of the grid if the failure is handled by re-running the job rather than recovering the cell, which means a transient hiccup in a single Python version costs the minutes of every version. On a wide matrix run repeatedly across a busy day, that adds up fast and erodes trust in the pipeline.

Runners that auto-heal transient failures stop one bad cell from costing a full re-run of the matrix. When a step fails on a known-transient signal, the platform retries it on a fresh environment before the failure propagates, so a PyPI blip in the 3.12 cell does not red the whole grid. For Python teams running wide matrices, this is where the reliability win and the cost win compound, because the matrix that multiplies the install tax also multiplies the flaky tax, and auto-healing addresses both.

Test execution is the stage caching cannot fix

Once dependency install is cached down to near nothing, test execution becomes the new dominant stage, and unlike install it cannot be cached away because the whole point is to run the tests. This is where Python teams hit the limit of the caching lever and have to reach for parallelism instead.

The structure of a Python test suite makes this tractable. pytest distributes cleanly across workers, and splitting a suite across processes or across sharded jobs turns a serial test stage into a parallel one bounded by the slowest shard rather than the sum of all tests. Combined with a version matrix, sharding within each cell keeps the per-cell wall-clock down even as the suite grows.

The teams that have already solved dependency install with uv and caching find their next bottleneck is exactly here, and the move is the same one CI teams make everywhere: do the work in parallel rather than in sequence, and make the work proportional to what changed where test impact analysis is available. A warm cache makes the pipeline fast to start; sharded execution keeps it fast to finish.

What the fastest Python pipelines do differently

The Python teams with the fastest pipelines are not running exotic tooling. They run the same lint, type-check, test-matrix shape as everyone else, but they have made a short set of mechanical choices that compound, and none of those choices touch a single test.

They also measure the pipeline the way they measure their code. They watch the cache hit rate, the per-stage wall-clock, and the matrix cost over time, and they treat a regression in any of them as a bug rather than as weather. Because they can see the curve, they catch a cache that silently stopped working before it slows every push, rather than discovering it months later when the pipeline mysteriously feels sluggish.

  • Cache the dependency layer per Python version so every matrix cell starts warm.
  • Adopt uv at the install step to collapse the dependency stage, even if the rest of the workflow stays on pip.
  • Order lint and type-check before the matrix as a fail-fast gate.
  • Shard test execution once install is cached, since tests are the stage caching cannot remove.
  • Auto-heal transient failures so one bad cell never costs a full re-run of the grid.

Recommendations

Cache the dependency layer per Python version

Dependency install is the largest single slice of an uncached Python run, and a version matrix multiplies it across every cell. Cache per version, keyed to the lockfile and the Python version, so each cell starts warm instead of paying the install tax from scratch.

Adopt uv at the CI install step

uv is dramatically faster cold than pip or poetry and collapses dependency install to single-digit seconds warm. You do not have to rewrite your packaging workflow to capture this; adopting uv at the install step alone moves the dependency stage off the critical path.

Order fail-fast checks before the matrix

Run lint and type-check as a cheap gate before the version grid fans out. A failure caught in seconds saves the full multiplied cost of the matrix, keeping the average run short even when the worst-case matrix is wide.

Shard test execution once install is solved

Caching cannot remove test execution, so once dependency install is near zero, parallelism is the next lever. Split the pytest suite across workers or sharded jobs so the test stage is bounded by the slowest shard rather than the sum of every test.

Auto-heal transient matrix failures instead of re-running the grid

A PyPI timeout or an OOM in one cell is mechanical and passes on retry, but handling it by re-running the job costs the minutes of every cell. Retry the failing cell on a fresh environment so one bad cell never reds the whole matrix.

Outlook

Expect uv's share to keep climbing and the dependency-install stage to keep shrinking as a share of Python CI time, because the win is large, the adoption cost is low, and it does not require abandoning pip or poetry elsewhere in a team's workflow. The teams still running uncached pip in 2026 are paying the largest stage of the pipeline on every push for no reason, and that gap will become more visible as the fast-pipeline cohort grows.

As dependency install collapses, the bottleneck moves to test execution and to the matrix, which means the next phase of Python CI optimization is about parallelism and about making the matrix cost proportional to the change rather than to the full grid. The teams that have already solved install with uv and caching are the ones discovering this next frontier first, and the answers, sharding and impact analysis, are the same ones the broader CI world has converged on.

The practical takeaway is that a fast Python pipeline in 2026 is a solved problem mechanically. Cache per version, adopt uv, order the cheap checks first, shard the tests, and run on a runner that preserves the cache and auto-heals transient failures. None of it touches a test, all of it is configuration, and together it turns the canonical lint-type-test-matrix shape from a tax on every push into a pipeline that simply feels fast.

Methodology

This report synthesizes publicly available Python ecosystem data, including the JetBrains Developer Ecosystem Survey and developer surveys, with Latchkey's own analysis of Python CI pipeline economics across managed runner usage. Figures labeled modeled are illustrative estimates derived from typical Python pipeline shapes, observed dependency-tool benchmarks, and runner caching behavior, not a primary survey; figures attributed to a named source reflect that source. Tool performance figures reflect representative cold-versus-warm cache behavior and should be verified against current tool versions. Figures labeled "modeled" are illustrative estimates derived from public pricing and typical pipeline shapes, not a primary survey; figures attributed to a named source reflect that source. Pricing reflects published rates at time of writing and should be verified against current provider pricing.

Sources

More Latchkey reports

See what you would save with Latchkey managed runners and self-healing. Start free →