The top finding in pip isn’t a function under active churn — it’s structural debt that has sat untouched for months. The riskiest function I found, render in the vendored rich markup parser, carries an activity-weighted risk score of 17.5 and hasn’t been changed in 136 days, which means whoever touches it next inherits 45 branching paths and 6 levels of nesting with zero recent context to work from. Pip totals 4,536 functions, of which 449 land in the critical band, and four of my top five hotspots share this same profile: complex, dormant, and vendored. I’d start triage there, not because these functions are misbehaving, but because their next edit carries the highest blast radius in the repository.
The table below ranks functions by activity-weighted risk — a score that multiplies structural complexity by recent commit frequency. A function that is both hard to understand (high cyclomatic complexity) and actively changing is a higher priority than one that is complex but untouched. CC = cyclomatic complexity (independent execution paths); ND = max nesting depth; FO = fan-out (distinct callees).
Top 5 Hotspots
| Function | File | Risk | CC | ND | FO |
|---|---|---|---|---|---|
render | src/pip/_vendor/rich/markup.py | 17.5 | 45 | 6 | 30 |
extract | src/pip/_vendor/rich/traceback.py | 17.0 | 58 | 5 | 29 |
_install_wheel | src/pip/_internal/operations/install/wheel.py | 17.0 | 34 | 5 | 84 |
_traverse | src/pip/_vendor/rich/pretty.py | 16.8 | 113 | 6 | 35 |
urlopen | src/pip/_vendor/urllib3/connectionpool.py | 16.7 | 63 | 4 | 38 |
Codemod / Tooling Files in Results
Four of my five hotspots live in pip’s vendored dependencies — src/pip/_vendor/rich/ (markup.py, traceback.py, pretty.py) and src/pip/_vendor/urllib3/connectionpool.py. These are bundled copies of third-party libraries that pip ships to avoid runtime dependency conflicts, and they score high because pip’s own team doesn’t maintain their internals, so the code accumulates complexity without local review pressure. I’d exclude vendored paths from active review scope with { "exclude": ["src/pip/_vendor/"] } in .hotspotsrc.json, then re-run the analysis to see pip’s own code — like _install_wheel — ranked on its own terms.
Where the risk actually sits
4,536 functions analyzed
Out of 4,536 functions, only 6 fall into the ‘fire’ quadrant — complex and being actively changed right now. Compare that to 1,445 functions sitting in ‘debt’: complex, but quiet. That ratio is the real story here. Pip’s acute regression risk is small and contained; its structural debt is not.
Calls an unusually large number of distinct functions (high fan-out), making it the structural centre of gravity for a subsystem.Long Function×6Long Function
Function body is too long to review in a single pass; likely contains multiple distinct responsibilities.Exit Heavy×5Exit Heavy
Multiple return or throw paths dispersed through the body — each exit needs separate test coverage.Complex Branching×5Complex Branching
High cyclomatic complexity — many independent execution paths, each a potential bug surface and required test case.Deeply Nested×4Deeply Nested
Control structures nested 4+ levels deep, making it hard to reason about the full execution state at inner branches.
Seven of my flagged functions are tagged as god functions and six as long functions. That’s the dominant shape of risk in this codebase: not tangled logic so much as functions that took on too many responsibilities and grew past the point where a reviewer can hold the whole thing in their head.
render — src/pip/_vendor/rich/markup.py
This is the markup-to-Text renderer inside the vendored rich library. The excerpt shows a tag-parsing loop with a nested closing-tag branch (explicit close vs. implicit close, each with its own exception path) plus a separate pop_style helper that walks a style stack in reverse. That’s consistent with the 45 branching paths and 6 levels of nesting the metrics report, and with the ‘exit_heavy’ tag — I count at least three distinct raise points in the excerpt alone, each a required test case. Fan-out of 30 means this function reaches into 30 other functions or constructors, which in a duck-typed codebase means more coupling than the number implies, since none of those call sites are checked at compile time. It hasn’t been touched in 136 days, and the single historical commit shows no bug-linked activity, so this reads as inherited complexity from upstream rich, not something pip’s own maintainers are actively wrestling with. My recommendation: if pip doesn’t need to patch rich internals, this is a candidate for exclusion from active review scope entirely — see the vendor note below.
extract — src/pip/_vendor/rich/traceback.py
Same vendored library, same dormancy window: 136 days untouched, one historical commit, zero authors in the last 90 days. The excerpt shows a while True loop building a chain of Stack objects for exception groups and causes, with a nested safe_str guard to keep __str__ failures from propagating, plus a version-gated branch for BaseExceptionGroup handling on Python 3.11+. A cyclomatic complexity of 58 is high even by god-function standards — that’s 58 independent paths a test suite would need to cover to fully exercise this traceback extractor. Fan-out of 29 confirms broad coupling to the rest of the rich rendering machinery. This is textbook structural debt: nobody’s actively breaking it, but if a Python version bump or exception-handling change ever forces a touch, whoever does it is working against 58 paths with no recent commit history to lean on.
_install_wheel — src/pip/_internal/operations/install/wheel.py
This is the one function in my top five that actually lives in pip’s own code, not vendored, and it’s the standout on fan-out: 84 distinct functions called, nearly triple the next-highest entry in this list. The source is annotated noqa: C901, PLR0915 function is too long — pip’s own maintainers have already flagged this with linter suppressions. The excerpt shows the function defining a cluster of nested closures (record_installed, is_dir_path, assert_no_path_traversal, root_scheme_file_maker, data_scheme_file_maker) inline, each capturing shared state like lib_dir and installed — a pattern that inflates both nesting depth and the effective surface area of the function even though each closure is individually simple. It hasn’t been touched in 62 days, but 3 distinct authors worked on it across 8 total commits, so this isn’t abandoned code — it’s actively-owned code that happens to be quiet right now. Given the path-traversal check and the wheel-file-writing logic, this is exactly the kind of function I’d want covered by characterization tests before extracting the closures into named module-level helpers.
_traverse — src/pip/_vendor/rich/pretty.py
This is the highest cyclomatic complexity in the entire dataset — 113 independent paths, more than double the next-closest function I found. It’s a recursive object-tree walker for rich’s pretty-printer, and the excerpt shows why the number is so extreme: a nested iter_rich_args generator with three arity-dependent branches, a try/except guard for attribute probing, conditional formatting for angular versus parenthesized reprs, and recursive calls back into _traverse itself for each child. Recursion plus generator-driven branching is exactly the kind of invisible control-flow layering that inflates path count without necessarily inflating visible line count. It’s been dormant for 136 days with a single historical commit — this is vendored debt pip inherited, not a function pip’s team is iterating on. If pip ever needs to debug pretty-printing behavior, this is the function that will eat the most review time relative to its apparent size.
urlopen — src/pip/_vendor/urllib3/connectionpool.py
The core HTTP request path in vendored urllib3. Sixty-three branching paths against a nesting depth of only 4 tells me the complexity here comes from breadth, not depth — lots of sibling conditionals for retries, redirects, timeouts, and connection-release behavior rather than deeply staircased logic, which the docstring’s own list of parameters (retries, redirect, assert_same_host, timeout, pool_timeout, chunked, body_pos, preload_content, decode_content) partly explains. Two historical commits, no authors active in the last 90 days, 92 days since the last change. This is the connection-pooling engine underneath every pip network call, so its fan-out of 38 matters even in dormancy — if pip ever needs to patch retry or redirect behavior for a networking edge case, this function is where that patch lands, and it will land in unfamiliar territory.
Patterns Found
Antipatterns detected across the top functions in this snapshot:
| Pattern | Occurrences |
|---|---|
god_function | 7 |
long_function | 6 |
exit_heavy | 5 |
complex_branching | 5 |
deeply_nested | 4 |
These labels belong to two tiers — Tier 1 (structural): complex_branching, deeply_nested, exit_heavy, long_function, god_function. Tier 2 (relational/temporal): hub_function, cyclic_hub, middle_man, neighbor_risk, stale_complex, churn_magnet, shotgun_target, volatile_god.
See more analyses with these patterns: complex_branching, deeply_nested, exit_heavy, god_function, long_function.
Reproduce This Analysis
git clone https://github.com/pypa/pip
cd pip
git checkout 2b28a816d043826f2ba10ff1d22ec3d94d2ed7c5
hotspots analyze . --mode snapshot --explain-patterns --force
To run the same analysis on your own codebase, run hotspots analyze . --mode snapshot in any local git repo — no configuration required.
I use Hotspots to highlight structural and activity risk — not “bad code.” I treat these findings as a prioritization aid, not a bug predictor. Editorial policy →