🥥 unladen
[!WARNING] This project is in early alpha. Results are approximate and should be treated as starting points for investigation, not concrete recommendations. Static analysis has inherent limitations — it cannot detect dynamic imports, plugin systems, or runtime-only usage patterns.
"What is the airspeed velocity of an unladen codebase?"
unladen measures the "logical mass" of your Python dependencies.
It doesn't just tell you that you use a library —
it tells you how much of it you actually use.
By mapping your call graphs to a dependency's internal structure,
it provides data-driven recommendations to keep your codebase lean
and avoid carrying unnecessary "coconuts" in your project.
The Problem
Modern Python applications often pull in large libraries for a handful of functions. This "dependency bloat" increases:
- Attack surface — unused code can still contain vulnerabilities.
- Cold boot times — more code to find and load into memory.
- Deployment size — heavier containers and slower CI/CD pipelines.
How It Works
unladen performs a four-phase static analysis:
- Collect — Discovers declared dependencies and locates their installed
source code via
importlib.metadataandsite-packages. - Inspect — Parses your project's source with
astto find import statements, attribute accesses, and string references (e.g. Django settings). - Trace — Indexes each dependency's source, builds a call graph, and walks it transitively from your entry points to compute activated LLOC.
- Report — Renders a table of results with per-dependency recommendations.
The Heft Metric
The core metric is the Heft Ratio: the percentage of a dependency's Logical Lines of Code (LLOC) that your project actually activates.
$$Heft = \frac{\text{Active LLOC}}{\text{Total LLOC}} \times 100%$$
LLOC counts executable statements (via ast),
excluding comments, blank lines, and docstrings.
if TYPE_CHECKING: blocks are excluded
since they never execute at runtime.
Recommendations
| Heft | Mass | Recommendation |
|---|---|---|
| > 25% | Any | Keep — you use a significant portion |
| 5–25% | Any | Review — moderate usage, worth examining |
| 1–5% | Low | Vendor — consider copying the code you need |
| < 1% | High | Rewrite — drop the coconut |
| < 1% | Low | Vendor — small enough to inline |
| Unused | — | Remove? — declared but never imported |
| Native | — | Keep (native) — compiled extensions, LLOC analysis doesn't apply |
Mass threshold: Low < 500 LLOC, High >= 500 LLOC.
Usage
# Analyze all dependencies in the current project
unladen check
# Analyze a specific project path
unladen check /path/to/project
# Drill into a single dependency with file:line detail
unladen show requests
# Use an explicit requirements file
unladen check -r requirements/prod.txt
# Use an explicit site-packages directory
unladen check --site-packages /path/to/site-packages
Configuration
Project-level settings live in pyproject.toml
under [tool.unladen]:
[tool.unladen]
exclude = [
"setuptools",
"pip",
"wheel",
]
| Key | Type | Description |
|---|---|---|
exclude |
list[str] |
Dependencies to skip during analysis. Names are PEP 503 normalized, so "Jinja2", "jinja2", and "jinja-2" all match. |
When dependencies are excluded,
a footnote appears on the check table and treemap
noting the count,
and the show command marks excluded deps in the header.
Example Output
unladen - Dependency Heft Report
┏━━━━━━━━━━━━━━┳━━━━━━━━━┳━━━━━━━┳━━━━━━━┳━━━━━━━━┳━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━┓
┃ ┃ ┃ Names ┃ ┃ ┃ LLOC ┃ ┃
┃ Dependency ┃ Version ┃ Used ┃ Files ┃ Heft % ┃ (active/total) ┃ Recommendation ┃
┡━━━━━━━━━━━━━━╇━━━━━━━━━╇━━━━━━━╇━━━━━━━╇━━━━━━━━╇━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━┩
│ click │ 8.1.7 │ 5 │ 3 │ 32.1% │ 210/654 │ Keep │
├──────────────┼─────────┼───────┼───────┼────────┼────────────────┼────────────────┤
│ requests │ 2.31.0 │ 2 │ 1 │ 5.0% │ 50/1000 │ Review │
├──────────────┼─────────┼───────┼───────┼────────┼────────────────┼────────────────┤
│ nh3 │ 0.3.2 │ 1 │ 1 │ n/a │ 1 extension │ Keep (native) │
└──────────────┴─────────┴───────┴───────┴────────┴────────────────┴────────────────┘
unladen show
The show command provides detailed per-dependency analysis:
- Where the dependency is declared (pyproject.toml, setup.py, setup.cfg)
- A table of every import statement with file:line locations
- String references
(e.g. Django
INSTALLED_APPS,MIDDLEWARE) - Heft computation with LLOC breakdown
- Warnings for dynamic dispatch patterns that may underestimate actual usage
Dependency Source Support
unladen discovers declared dependencies from
(in priority order):
pyproject.toml— PEP 621[project] dependenciespyproject.toml— Poetry[tool.poetry.dependencies]setup.py—install_requiresvia AST (never executed)setup.cfg—[options] install_requiresrequirements.txt— with-r/--requirementinclude traversal
Use -r/--requirements to point at a specific file,
bypassing auto-detection.
This is useful for projects with split requirements
(e.g. requirements/prod.txt, requirements/dev.txt).
What It Detects
- Direct imports:
import requests,from click import echo - Bare import attribute access:
requests.get()resolvesgetas a used name - Submodule imports:
import pygments.lexers; pygments.lexers.get_lexer_by_name() - Aliased imports:
import pandas as pd; pd.DataFrame() - String references:
Django-style activation via
INSTALLED_APPS,MIDDLEWARE, and dotted path strings in settings files - Compiled extensions:
.so,.pyd,.dylibfiles counted as opaque mass - Re-export shims:
packages like
cattrs/cattrwhere the import name differs from the implementation package - Private aliases:
def _init(); init = _initpatterns (common insentry-sdkand similar)
Performance
unladen is designed to be fast on real-world projects:
- Parse-once — each source file is parsed once and the AST tree is shared across all analysis passes.
- Parallel indexing — large dependency trees (100+ files)
are indexed using Python 3.14's
InterpreterPoolExecutor(lightweight subinterpreters, no process overhead). - Bulk computation — heft for all dependencies is computed in a single worker pool rather than per-dependency.
- Fast file discovery — uses
os.walkwith directory pruning instead ofpathlib.rglob.
Limitations
- Static analysis only —
cannot detect
importlib.import_module(),__import__(), or other dynamic imports at runtime. - Direct dependencies only — does not analyze transitive dependency chains.
- Python 3.14+ required — uses modern syntax features.
Requirements
- Python 3.14+
rich(the only runtime dependency)
Installation
Run directly, like:
pipx run unladen check
uvx unladen check
Or install it:
# With pip
pip install unladen
# With uv
uv tool install unladen
Architecture
| Phase | Module | Responsibility |
|---|---|---|
| 1. Collect (Nest) | collector.py |
Discover dependencies and locate installed source |
| 2. Inspect (Flight) | inspector.py |
Parse project code to find imports and usage |
| 3. Trace (Weight) | tracer.py |
Index dependency source, build call graph, compute heft |
| 4. Report (Coconut) | reporter.py |
Format findings as a rich CLI table |
Security
unladen is a read-only analysis tool.
It never executes third-party code —
all source analysis uses ast.parse(),
which parses without compiling or running.
There are no eval(), exec(), subprocess,
or shell invocations anywhere in the codebase.
Known Limitations
- Metadata trust:
unladentruststop_level.txtandRECORDfrom installed distributions, mirroring pip's own behavior. A malicious package could ship metadata claiming import names belonging to another package, which would affect analysis accuracy but not execute code. - Deeply nested files: Some AST traversal functions use recursion. A crafted Python file with extreme nesting (1000+ levels) could hit Python's recursion limit and crash the tool. This cannot cause data loss or code execution.
Authors
License
MIT License. See LICENSE for details.
Release files for unladen 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| unladen-0.1.0.tar.gz | 38.0 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| unladen-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 82.8 kB
Release files / unladen-0.1.0.tar.gz
| Download URL | unladen-0.1.0.tar.gz |
|---|---|
| Size | 38.0 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
9dc46c8b4ed2e697216e12625520342ee584ca5ed824caea30cd4fc929141f02
|
|
BLAKE2b-256 checksum How to use checksums |
e6ca6d5224a11f7942823cacf336353615cef563f855dbc6ea840961dffb8c9f
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.7
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Mar 16, 2026.
Transparency logRelease files / unladen-0.1.0-py3-none-any.whl
| Download URL | unladen-0.1.0-py3-none-any.whl |
|---|---|
| Size | 44.7 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
39682aa04d5ce936673569a2594cfc8ce69ee98e945c2b9b970937500de4ba78
|
|
BLAKE2b-256 checksum How to use checksums |
9a356a1200fb4b387c6dbf9e354a510e8a79143a9b6a347bb14924cecf235ed8
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.7
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Mar 16, 2026.
Transparency log