Dependency tree of a Python Module

19 Aug, 2020
Generally, many Python packages are dependent on other packages but how do we know that on which packages is a module dependent?
This is a python built-in module that can help us know the dependent packages but it shows all dependencies as a flat list, finding out which are the top-level packages and which packages do they depend on requires some effort. Let us see an example of how it works:
Type given below command on your command prompt:
$pip freeze

Output:
altair==4.1.0
attrs==19.3.0
docutils==0.15.2
entrypoints==0.3
Jinja2==2.11.2
jmespath==0.10.0
jsonschema==3.2.0
MarkupSafe==1.1.1
numpy==1.18.4
opencv-python==4.2.0.34
pandas==1.0.4
pyrsistent==0.16.0
python-dateutil==2.8.1
pytz==2020.1
six==1.15.0
toolz==0.10.0
urllib3==1.25.9

One easy way of doing so is to use the pipdeptree utility. The pipdeptree works on the command line and shows the installed python packages in the form of a dependency tree.
This module does not come built-in with Python. To install it type the below command in the terminal.
$pip install pipdeptree

This will install the latest version of pipdeptree which requires at least Python 2.7.
Now run this command on command prompt to get a dependency tree of all your Python modules.
Command:
$pipdeptree

Output:
$pipdeptree
altair==4.1.0
 - entrypoints [required: Any, installed: 0.3]
 - jinja2 [required: Any, installed: 2.11.2]
   - MarkupSafe [required: >=0.23, installed: 1.1.1]
 - jsonschema [required: Any, installed: 3.2.0]
   - attrs [required: >=17.4.0, installed: 19.3.0]
   - pyrsistent [required: >=0.14.0, installed: 0.16.0]
     - six [required: Any, installed: 1.15.0]
   - setuptools [required: Any, installed: 41.2.0]
   - six [required: >=1.11.0, installed: 1.15.0]
 - numpy [required: Any, installed: 1.18.4]
 - pandas [required: >=0.18, installed: 1.0.4]
   - numpy [required: >=1.13.3, installed: 1.18.4]
   - python-dateutil [required: >=2.6.1, installed: 2.8.1]
     - six [required: >=1.5, installed: 1.15.0]
   - pytz [required: >=2017.2, installed: 2020.1]
 - toolz [required: Any, installed: 0.10.0]
docutils==0.15.2
jmespath==0.10.0
opencv-python==4.2.0.34
 - numpy [required: >=1.17.3, installed: 1.18.4]
pipdeptree==1.0.0
 - pip [required: >=6.0.0, installed: 20.2.1]
urllib3==1.25.9

Let’s see what happens when we use pipdeptree and freeze altogether,
Command:
$pipdeptree --freeze

Output:
altair==4.1.0
  entrypoints==0.3
  Jinja2==2.11.2
    MarkupSafe==1.1.1
  jsonschema==3.2.0
    attrs==19.3.0
    pyrsistent==0.16.0
      six==1.15.0
    setuptools==41.2.0
    six==1.15.0
  numpy==1.18.4
  pandas==1.0.4
    numpy==1.18.4
    python-dateutil==2.8.1
      six==1.15.0
    pytz==2020.1
  toolz==0.10.0
docutils==0.15.2
jmespath==0.10.0
Mako==1.1.3
  MarkupSafe==1.1.1
opencv-python==4.2.0.34
  numpy==1.18.4
pipdeptree==1.0.0
  pip==20.2.1
scipy==1.5.2
  numpy==1.18.4
urllib3==1.25.9

So, here we see that using pipdeptree along with freeze shows the output by combining the properties of both commands. So it looks like the output of pip freeze indicates which that package installed which another package, similar to pipdeptree but here indentation is used instead of a hyphen(-) to indicate tree.
Commonly there occur two types of warnings while executing pipdeptree command, let us see them one by one.
1. Conflicting Dependencies: As the name suggests “conflicting dependency”, so is its relevance. Sometimes there is/are package(s) that are specified as a dependency of multiple packages with a different version, in this situation possible conflicting dependency warning arises. So, any package that’s specified as a dependency of multiple packages with a different version is considered as a possible conflicting dependency.
pipdeptree by default warns about possible conflicting dependencies.
Let us see one more example of pipdeptree:
Command:
$pipdeptree

Output:
Warning!!! Possibly conflicting dependencies found:
* impacket==0.9.20
 - ldap3 [required: ==2.5.1, installed: ?]
 - ldapdomaindump [required: >=0.9.0, installed: ?]
------------------------------------------------------------------------
alembic==1.0.11.dev0
attrs==18.2.0
dulwich==0.20.2
  - certifi [required: Any, installed: 2018.11.29]
  - urllib3 [required: >=1.24.1, installed: 1.24.1]
EditorConfig==0.12.1
Flask-Cors==3.0.8
  - Flask [required: >=0.9, installed: 1.1.1]
  - Six [required: Any, installed: 1.13.0]
Flask-Session==0.3.1
Flask-SocketIO==4.2.1
  - Flask [required: >=0.9, installed: 1.1.1]
  - python-socketio [required: >=4.3.0, installed: 4.5.1]
    - python-engineio [required: >=3.9.0, installed: 3.12.1]
      - six [required: >=1.9.0, installed: 1.13.0]
    - six [required: >=1.9.0, installed: 1.13.0]
google==2.0.1
  - beautifulsoup4 [required: Any, installed: 4.8.0]
html2text==2019.8.11
impacket==0.9.20
  - ldap3 [required: ==2.5.1, installed: ?]
  - ldapdomaindump [required: >=0.9.0, installed: ?]
ipython==5.8.0
  - backports.shutil-get-terminal-size [required: Any, installed: 1.0.0]
  - pathlib2 [required: Any, installed: 2.3.5]
    - scandir [required: Any, installed: 1.10.0]
  - pexpect [required: Any, installed: 4.6.0]

 pip doesn’t have a true dependency resolution yet. The warning is printed to stderr (Standard error) instead of stdout (Standard Output). To completely silence this warning use the -w silence or –warn silence flag It can also be made mode strict with –warn fail in which case the command will not only print the warnings to stderr but also exit with a non-zero status code. This could be useful if you want to fit this tool into your CI pipeline.
Note: The –warn flag was added in version 0.6.0. For older version, use –nowarn flag.
2. Circular Dependencies: This dependency occurs when two packages depend on each other. Suppose, package A depends upon package B and package B depends upon package A.
For this let us see one more example:
Command:
$pipdeptree

Output:
Warning!!! Cyclic dependencies found:
- CircularDependencyA => CircularDependencyB => CircularDependencyA
- CircularDependencyB => CircularDependencyA => CircularDependencyB
------------------------------------------------------------------------
wsgiref==0.1.2
argparse==1.2.1

Note: They are also printed to stderr and can be controlled using the –warn flag.
Now, we may sometimes want to know why a particular package is installed. Then we can use –reverse (or simply -r) flag for this. To find out what all packages require a particular package(s), it can be combined with –packages
flag as shown in following example:
Command:
$pipdeptree --reverse --packages MarkupSafe,numpy

Output:
MarkupSafe==1.1.1
  - Jinja2==2.11.2 [requires: MarkupSafe>=0.23]
    - altair==4.1.0 [requires: jinja2]
  - Mako==1.1.3 [requires: MarkupSafe>=0.9.2]
numpy==1.18.4
  - altair==4.1.0 [requires: numpy]
  - opencv-python==4.2.0.34 [requires: numpy>=1.17.3]
  - pandas==1.0.4 [requires: numpy>=1.13.3]
    - altair==4.1.0 [requires: pandas>=0.18]
  - scipy==1.5.2 [requires: numpy>=1.14.5]

If you wish to track only the top-level packages in your requirements.txt file, it’s possible to do so using pipdeptree by grep-ing only the top-level lines from the output,
Command:
$pipdeptree | grep -P '^\w+'

Output:
Lookupy==0.1
wsgiref==0.1.2
argparse==1.2.1
psycopg2==2.5.2
Flask-Script==0.6.6
alembic==0.6.2
ipython==2.0.0
slugify==0.0.1
redis==2.9.1


There is a problem here though. The output doesn’t mention anything about Lookupy being installed as an editable package (refer to the output of pip freeze above) and information about its source is lost. To fix this, pipdeptree must be run with a -f or –freeze flag
Command:
$pipdeptree -f --warn silence | grep -P '^[\w0-9\-=.]+'

Output:
-e git+git@github.com:naiquevin/lookupy.git@cdbe30c160e1c29802df75e145ea4ad903c05386#egg=Lookupy-master
wsgiref==0.1.2
argparse==1.2.1
psycopg2==2.5.2
Flask-Script==0.6.6
alembic==0.6.2
ipython==2.0.0
slugify==0.0.1
redis==2.9.1

Command:
$ pipdeptree -f --warn silence | grep -P '^[\w0-9\-=.]+' > requirements.txt

The freeze flag will also not output the hyphens for child dependencies, so you could dump the complete output of pipdeptree -f to the requirements.txt file making the file human-friendly (due to indentations) as well as pip-friendly. (Take care of duplicate dependencies though)
pipdeptree uses flag –json to show output in JSON representation, as shown below:
Command:
$pipdeptree --json

Output:
[
    {
        "package": {
            "key": "werkzeug",
            "package_name": "Werkzeug",
            "installed_version": "1.0.1"
        },
        "dependencies": []
    },
    {
        "package": {
            "key": "urllib3",
            "package_name": "urllib3",
            "installed_version": "1.25.9"
        },
        "dependencies": []
    },
    
    {
        "package": {
            "key": "pytz",
            "package_name": "pytz",
            "installed_version": "2020.1"
        },
        "dependencies": []
    },
    {
        "package": {
            "key": "python-dateutil",
            "package_name": "python-dateutil",
            "installed_version": "2.8.1"
        },
        "dependencies": [
            {
                "key": "six",
                "package_name": "six",
                "installed_version": "1.15.0",
                "required_version": ">=1.5"
            }
        ]
    },
    {
        "package": {
            "key": "pyrsistent",
            "package_name": "pyrsistent",
            "installed_version": "0.16.0"
        },
        "dependencies": [
            {
                "key": "six",
                "package_name": "six",
                "installed_version": "1.15.0",
                "required_version": null
            }
        ]
    },
    {
        "package": {
            "key": "pipdeptree",
            "package_name": "pipdeptree",
            "installed_version": "1.0.0"
        },
        "dependencies": [
            {
                "key": "pip",
                "package_name": "pip",
                "installed_version": "20.2.1",
                "required_version": ">=6.0.0"
            }
        ]
    },
    
    
]



Note: –json will output a flat list of all packages with their immediate dependencies. To obtain nested JSON, use –-json-tree (added in version 0.11.0).
Command:
$pipdeptree --json-tree

Output:
 [
    {
        "key": "altair",
        "package_name": "altair",
        "installed_version": "4.1.0",
        "required_version": "4.1.0",
        "dependencies": [
            {
                "key": "entrypoints",
                "package_name": "entrypoints",
                "installed_version": "0.3",
                "required_version": "Any",
                "dependencies": []
            },
            {
                "key": "jinja2",
                "package_name": "jinja2",
                "installed_version": "2.11.2",
                "required_version": "Any",
                "dependencies": [
                    {
                        "key": "markupsafe",
                        "package_name": "MarkupSafe",
                        "installed_version": "1.1.1",
                        "required_version": ">=0.23",
                        "dependencies": []
                    }
                ]
            },
            
    {
        "key": "urllib3",
        "package_name": "urllib3",
        "installed_version": "1.25.9",
        "required_version": "1.25.9",
        "dependencies": []
    }
]

python-utility
Python
Writing code in comment?
Please use ide.geeksforgeeks.org,
generate link and share the link here.