Check for Python Packages Dependency by pipdeptree (including a Simple Practice with Graphviz)

Jul. 16, 2024

Introduction

Python pipdeptree package is a practical tool to inspect dependencies of installed Python packages1:

pipdeptree is a command line utility for displaying the installed python packages in form of a dependency tree. It works for packages installed globally on a machine as well as in a virtualenv. Since pip freeze 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. It’s also tedious to resolve conflicting dependencies that could have been installed because older version of pip didn’t have true dependency resolution pipdeptree can help here by identifying conflicting dependencies installed in the environment.

Take an example to test pipdeptree.

At first, I newly create a clean Python virtual environment by venv module2, and at the beginning there are two built-in packages, i.e. pip and setuptools. Then, referring to the example from official GitHub repository1, I install Flask, a lightweight web application framework, in the virtual environment. And of course, I install pipdeptree in advance. Anyway, I execute following two install commands after activating the virtual environment:

1
2
pip install pipdeptree
pip install flask

Right now, there are 12 packages in this virtual environment:

Order Packages Version Source
1 blinker 1.8.2 pip install flask
2 click 8.1.7 pip install flask
3 colorama 0.4.6 pip install flask
4 Flask 3.0.3 pip install flask
5 itsdangerous 2.2.0 pip install flask
6 Jinja2 3.1.4 pip install flask
7 MarkupSafe 2.1.5 pip install flask
8 packaging 24.1 pip install pipdeptree
9 pip 24.0 built-in packages in virtual environment created by venv module
10 pipdeptree 2.23.1 pip install pipdeptree
11 setuptools 65.5.0 built-in packages in virtual environment created by venv module
12 Werkzeug 3.0.3 pip install flask

pipdeptree provides a main command, pipdeptree, to inspect dependency relationships of all installed packages in a virtual environment, like my case for example:

1
pipdeptree
1
2
3
4
5
6
7
8
9
10
11
12
13
Flask==3.0.3
├── blinker [required: >=1.6.2, installed: 1.8.2]
├── click [required: >=8.1.3, installed: 8.1.7]
│   └── colorama [required: Any, installed: 0.4.6]
├── itsdangerous [required: >=2.1.2, installed: 2.2.0]
├── Jinja2 [required: >=3.1.2, installed: 3.1.4]
│   └── MarkupSafe [required: >=2.0, installed: 2.1.5]
└── Werkzeug [required: >=3.0.0, installed: 3.0.3]
    └── MarkupSafe [required: >=2.1.1, installed: 2.1.5]
pipdeptree==2.23.1
├── packaging [required: >=23.1, installed: 24.1]
└── pip [required: >=23.1.2, installed: 24.0]
setuptools==65.5.0

Besides, there are some available options for pipdeptree according to the description in GitHub repository1. In next section, I’ll show some of them which I think are most commonly used.


Available options

--packages option

1
pipdeptree --packages flask
1
2
3
4
5
6
7
8
9
Flask==3.0.3
├── blinker [required: >=1.6.2, installed: 1.8.2]
├── click [required: >=8.1.3, installed: 8.1.7]
│   └── colorama [required: Any, installed: 0.4.6]
├── itsdangerous [required: >=2.1.2, installed: 2.2.0]
├── Jinja2 [required: >=3.1.2, installed: 3.1.4]
│   └── MarkupSafe [required: >=2.0, installed: 2.1.5]
└── Werkzeug [required: >=3.0.0, installed: 3.0.3]
    └── MarkupSafe [required: >=2.1.1, installed: 2.1.5]

--reverse option

1
pipdeptree --reverse --packages MarkupSafe
1
2
3
4
5
MarkupSafe==2.1.5
├── Jinja2==3.1.4 [requires: MarkupSafe>=2.0]
│   └── Flask==3.0.3 [requires: Jinja2>=3.1.2]
└── Werkzeug==3.0.3 [requires: MarkupSafe>=2.1.1]
    └── Flask==3.0.3 [requires: Werkzeug>=3.0.0]

--json option

1
pipdeptree --json
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
[
    {
        "package": {
            "key": "blinker",
            "package_name": "blinker",
            "installed_version": "1.8.2"
        },
        "dependencies": []
    },
    {
        "package": {
            "key": "click",
            "package_name": "click",
            "installed_version": "8.1.7"
        },
        "dependencies": [
            {
                "key": "colorama",
                "package_name": "colorama",
                "installed_version": "0.4.6",
                "required_version": "Any"
            }
        ]
    },
    {
        "package": {
            "key": "colorama",
            "package_name": "colorama",
            "installed_version": "0.4.6"
        },
        "dependencies": []
    },
    {
        "package": {
            "key": "flask",
            "package_name": "Flask",
            "installed_version": "3.0.3"
        },
        "dependencies": [
            {
                "key": "blinker",
                "package_name": "blinker",
                "installed_version": "1.8.2",
                "required_version": ">=1.6.2"
            },
            {
                "key": "click",
                "package_name": "click",
                "installed_version": "8.1.7",
                "required_version": ">=8.1.3"
            },
            {
                "key": "itsdangerous",
                "package_name": "itsdangerous",
                "installed_version": "2.2.0",
                "required_version": ">=2.1.2"
            },
            {
                "key": "jinja2",
                "package_name": "Jinja2",
                "installed_version": "3.1.4",
                "required_version": ">=3.1.2"
            },
            {
                "key": "werkzeug",
                "package_name": "Werkzeug",
                "installed_version": "3.0.3",
                "required_version": ">=3.0.0"
            }
        ]
    },
    {
        "package": {
            "key": "itsdangerous",
            "package_name": "itsdangerous",
            "installed_version": "2.2.0"
        },
        "dependencies": []
    },
    {
        "package": {
            "key": "jinja2",
            "package_name": "Jinja2",
            "installed_version": "3.1.4"
        },
        "dependencies": [
            {
                "key": "markupsafe",
                "package_name": "MarkupSafe",
                "installed_version": "2.1.5",
                "required_version": ">=2.0"
            }
        ]
    },
    {
        "package": {
            "key": "markupsafe",
            "package_name": "MarkupSafe",
            "installed_version": "2.1.5"
        },
        "dependencies": []
    },
    {
        "package": {
            "key": "packaging",
            "package_name": "packaging",
            "installed_version": "24.1"
        },
        "dependencies": []
    },
    {
        "package": {
            "key": "pip",
            "package_name": "pip",
            "installed_version": "24.0"
        },
        "dependencies": []
    },
    {
        "package": {
            "key": "pipdeptree",
            "package_name": "pipdeptree",
            "installed_version": "2.23.1"
        },
        "dependencies": [
            {
                "key": "packaging",
                "package_name": "packaging",
                "installed_version": "24.1",
                "required_version": ">=23.1"
            },
            {
                "key": "pip",
                "package_name": "pip",
                "installed_version": "24.0",
                "required_version": ">=23.1.2"
            }
        ]
    },
    {
        "package": {
            "key": "setuptools",
            "package_name": "setuptools",
            "installed_version": "65.5.0"
        },
        "dependencies": []
    },
    {
        "package": {
            "key": "werkzeug",
            "package_name": "Werkzeug",
            "installed_version": "3.0.3"
        },
        "dependencies": [
            {
                "key": "markupsafe",
                "package_name": "MarkupSafe",
                "installed_version": "2.1.5",
                "required_version": ">=2.1.1"
            }
        ]
    }
]


Generate dependency graph by Graphviz

In addition to above options, pipdeptree also supports generating dependency graph, and this function is realized by Graphviz3. Four corresponding commands are introduced in the repository1:

1
2
3
4
pipdeptree --graph-output dot > dependencies.dot
pipdeptree --graph-output pdf > dependencies.pdf
pipdeptree --graph-output png > dependencies.png
pipdeptree --graph-output svg > dependencies.svg

However, to use these commands to generate dependency graph as expected, some points should be noted.

First, in order to use Graphviz, we should first install Graphviz on computer (installation package links can be found Graphviz documentation4. Installment process is simple, and during which we should check option Add Graphviz to the system PATH for all users. After installment, dot -v can be used to test if it is installed successfully), and then install Graphviz-Python interface5 by command pip install graphviz.

At this time, I use command:

1
pipdeptree --graph-output png > dependencies.png

to get a PNG file dependencies.png, but when I open it, an error occurs, which prompts that the file format is not supported:

image-20240715205636759

Similar message appears when I use command to generate PDF file. On the other hand, when I use command

1
pipdeptree --graph-output dot > dependencies.dot

to output DOT file, I can open it and its content looks normal:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
digraph {
	blinker [label="blinker\n1.8.2"]
	click -> colorama [label=any]
	click [label="click\n8.1.7"]
	colorama [label="colorama\n0.4.6"]
	flask -> blinker [label=">=1.6.2"]
	flask -> click [label=">=8.1.3"]
	flask -> itsdangerous [label=">=2.1.2"]
	flask -> jinja2 [label=">=3.1.2"]
	flask -> werkzeug [label=">=3.0.0"]
	flask [label="Flask\n3.0.3"]
	graphviz [label="graphviz\n0.20.3"]
	itsdangerous [label="itsdangerous\n2.2.0"]
	jinja2 -> markupsafe [label=">=2.0"]
	jinja2 [label="Jinja2\n3.1.4"]
	markupsafe [label="MarkupSafe\n2.1.5"]
	packaging [label="packaging\n24.1"]
	pip [label="pip\n24.0"]
	pipdeptree -> packaging [label=">=23.1"]
	pipdeptree -> pip [label=">=23.1.2"]
	pipdeptree [label="pipdeptree\n2.23.1"]
	setuptools [label="setuptools\n65.5.0"]
	werkzeug -> markupsafe [label=">=2.1.1"]
	werkzeug [label="Werkzeug\n3.0.3"]
}

So, I come up with an indirect method to generate graph, that is rendering this generated DOT file. I find corresponding command to realize this goal:

1
dot -Tpng dependencies.dot -o dependencies.png

However, another error appears:

1
Error: dependencies.dot: syntax error in line 1 near ''

As for this error, some references67 say it is caused by inappropriate encoding, and to correct it, we can open DOT file in text editor, and Save As it in UTF-8 encoding (previous encoding is UTF-16 LE). Then, use dot -Tpng dependencies.dot -o dependencies.png again, I get a PNG file at this time:

dependencies

which can be viewed with no problem. By the way, to enhance image resolution, we can add graph [dpi=1200] in DOT file8:

1
2
3
4
5
digraph {
	graph [dpi=1200]
	...
	...
}

and likewise, use command dot -Tpng dependencies.dot -o dependencies.png to get:

dependencies


References