johnnydep: a readable dependency tree for a Python distribution
Display dependency tree of Python distribution
At a glance
- What is it?
- johnnydep pretty-prints the dependency tree of a Python distribution, showing each dependency with its version specifier and summary in a tree drawn with box characters. It can also resolve the tree into a pinned list, which makes it useful for auditing what a package actually pulls in before you install it.
- Who is it for?
- johnnydep fits a reviewer who wants to see what a candidate dependency drags in before installing it, and a packager who needs a readable tree rather than a flat requirements list, since the tree shows the same package appearing at several levels with different constraints.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 3 days ago.
- What is it written in?
- Mainly Python, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.
Editorial analysis
It answers what does this package actually drag in
johnnydep's job is to pretty-print a dependency tree for a Python distribution. The problem it addresses is mundane and frequent. When you are about to add a dependency to a project, the metadata in the package's own description tells you what it requires, but not what those requirements transitively pull in, and a flat requirements file hides the shape entirely. The README's first example is the smallest case:
$ johnnydep requests
name summary
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
requests Python HTTP for Humans.
βββ certifi>=2023.5.7 Python package for providing Mozilla's CA Bundle.
βββ charset_normalizer<4,>=2 The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet.
βββ idna<4,>=2.5 Internationalized Domain Names in Applications (IDNA)
βββ urllib3<3,>=1.26 HTTP library with thread-safe connection pooling, file post, and more.Note what is in each line. The name column carries the requirement specifier rather than a bare name, so `charset_normalizer<4,>=2` shows the upper and lower bounds the parent asked for, and the summary column repeats the short description from the package metadata. That combination is what makes the output readable: you can see at a glance that a dependency is bounded and why it is present.
The tree shows the same package at several levels with different constraints
The boto3 example is the interesting one, because it shows what a real tree looks like:
$ johnnydep boto3
name summary
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
boto3 The AWS SDK for Python
βββ botocore<1.44.0,>=1.43.12 Low-level, data-driven core of boto 3.
β βββ jmespath<2.0.0,>=0.7.1 JSON Matching Expressions
β βββ python-dateutil<3.0.0,>=2.1 Extensions to the standard Python datetime module
β β βββ six>=1.5 Python 2 and 3 compatibility utilities
β βββ urllib3!=2.2.0,<3,>=1.25.4 HTTP library with thread-safe connection pooling, file post, and more.
βββ jmespath<2.0.0,>=0.7.1 JSON Matching Expressions
βββ s3transfer<0.18.0,>=0.17.0 An Amazon S3 Transfer Manager
βββ botocore<2.0a.0,>=1.37.4 Low-level, data-driven core of boto 3.
βββ jmespath<2.0.0,>=0.7.1 JSON Matching Expressions
βββ python-dateutil<3.0.0,>=2.1 Extensions to the standard Python datetime module
β βββ six>=1.5 Python 2 and 3 compatibility utilities
βββ urllib3!=2.2.0,<3,>=1.25.4 HTTP library with thread-safe connection pooling, file post, and more.Several things stand out. `botocore` appears twice, once as a direct dependency of boto3 with the constraint `<1.44.0,>=1.43.12`, and again under `s3transfer` with a much looser `<2.0a.0,>=1.37.4`, which is the kind of conflict a reviewer wants to see before installing rather than after a resolver picks a version. The tree also shows the shared leaves: `jmespath`, `python-dateutil` and `urllib3` are pulled in through two different paths. The drawing uses box characters to make the parent and child relationship explicit, with the vertical bar continuing down the sibling lines. The tree is generated from the distribution metadata rather than from an installed environment, which is why it can be run against a package you have never installed.
The pinned output turns the tree into a flat list you can diff
Johnnydep can also attempt to resolve the dependency tree, and the output format for that is a flat pinned list:
$ johnnydep ipython --output-format pinned
ipython==9.13.0
decorator==5.3.1
ipython_pygments_lexers==1.1.1
jedi==0.20.0
matplotlib-inline==0.2.2
pexpect==4.9.0
prompt_toolkit==3.0.52
psutil==7.2.2
Pygments==2.20.0
stack-data==0.6.3
traitlets==5.15.0
parso==0.8.7
ptyprocess==0.7.0
wcwidth==0.7.0
asttokens==3.0.1
executing==2.2.1
pure_eval==0.2.3This is the mode that answers a different question. The tree says what the metadata allows; the pinned list says what a resolver would actually choose right now, with every package at an exact version. A flat list is easier to diff between two points in time, easier to paste into a lockfile or a constraints file, and easier to scan for a package you did not expect to see. The README makes a point here about the resolver's history, noting that `pip install` lacked a working solver for many years but that pip v20.3 has a new solver, which has really improved matters. That context matters for reading the output, because the versions shown are the ones the current resolver picks, not a fixed answer.
It is built on packaging metadata, and the docs point at the specs
The implementation is a thin layer over the standard metadata and the resolution libraries rather than a reimplementation of dependency logic. The dependencies in pyproject.toml include `packaging`, with a floor of 17 and a specific exclusion of version 22, plus `unearth` for finding distributions, `uv` for resolution, `rich` for the table rendering, `loguru` for logging, `PyYAML` and `tomli-w` for configuration and output, and `colorama` on Windows only, with `importlib_metadata` for Python versions below 3.10. The README closes with a list of the specifications this all rests on: the core metadata specifications, PEP 427 for the wheel format, PEP 426 which is now revoked but still commonly seen in the wild, PEP 566 for metadata version 2.1, and PEP 503 for the simple repository API. The presence of a revoked spec in that list is a fair warning that older packages on PyPI still carry metadata written against it.
The packaging metadata is the only source of truth, and that has a limit
Because the tree comes from metadata, everything you see is a statement the publisher made. The README's own list of metadata versions explains why that can be awkward: PEP 426 metadata is revoked but still commonly encountered, and PEP 566 defines the 2.1 format. A package whose metadata is incomplete or wrong will produce a wrong tree, and johnnydep has no way to detect that beyond what the metadata itself says. There is a related operational point. The tool queries the index to find distributions, so running it against a package requires network access to the repository the package lives in, and the test configuration reflects this, since pytest is run with `--disable-socket` and `--allow-unix-socket`, meaning the test suite blocks network access by default and the tests work against local fixtures.
Where johnnydep is the wrong tool
Three limits are worth being concrete about. The first is that it reports rather than installs. Running `johnnydep boto3` tells you what would be pulled in; it does not create an environment, and it does not validate that the combination you are shown can actually be installed together. The `--output-format pinned` mode is closer, but the README's wording is careful that johnnydep can attempt to resolve the tree, which is not the same promise. The second is that the tree reflects the index at the moment you run it, so a package that publishes new metadata changes what you see, and there is no snapshot mechanism. The third is that this is a reporting tool for one distribution at a time. If you want to audit an entire environment, or find which of your direct dependencies pulled in a particular package, you want a tool that works from an installed environment and supports queries across it. The README's advice to check `johnnydep --help` for other features and options is where those extra modes are described, since the README itself only demonstrates three.
pipdeptree works from your environment instead of the index
The common alternative is pipdeptree, and the difference in approach is where the data comes from. johnnydep resolves metadata from the package index, so it answers what a package would bring in on a fresh machine, and it works for packages you have never installed. pipdeptree inspects the environment that is already active, so it answers what is installed right now, which is the question you have when something in production behaves differently from what you expected. The two therefore suit different moments: johnnydep for a pre-install review of a candidate dependency, pipdeptree for a post-install audit of a machine. There is also the plain `pip show` and `pip list` route, which gives you a flat list of installed packages with no tree at all, and `pip install --dry-run` for a resolver's own view of what an install would fetch, which is closer in spirit to the pinned mode here.
Editorial conclusion
johnnydep fits a reviewer who wants to see what a candidate dependency drags in before installing it, and a packager who needs a readable tree rather than a flat requirements list, since the tree shows the same package appearing at several levels with different constraints. It is the wrong tool if you want to install anything, since it reports rather than resolves and installs, and it is the wrong tool for an offline environment where the metadata it queries cannot be fetched. Before you adopt it, run `johnnydep --help` as the README suggests, since the output formats and options beyond the default tree are only documented there, and check that the pinned output matches what your own resolver would produce, because the two can disagree on the version chosen.
Frequently asked questions
How do I install johnnydep?
It is published on PyPI as the `johnnydep` package and installs as a command line tool named `johnnydep`, with the console script entry point `johnnydep = "johnnydep.cli:main"`. The project requires Python 3.9 or newer.
How do I show the resolved dependency list of a package with johnnydep?
Run `johnnydep ipython --output-format pinned`, which prints one exact version per line instead of a tree. The README notes this is an attempt to resolve the tree, and that the versions shown come from the current pip resolver.
Does johnnydep install the dependencies it lists?
No. It reports the dependency tree of a distribution and can attempt to resolve it into pinned versions, but it does not create an environment or perform an install.
Why does the same package appear several times in the johnnydep tree?
Because each parent declares its own requirement, and those can differ. In the boto3 example, `botocore` appears as a direct dependency with the constraint `<1.44.0,>=1.43.12` and again under `s3transfer` with `<2.0a.0,>=1.37.4`.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/wimglenn-johnnydep)Was this useful?