0
# Dependency Validation
1
2
Validates dependency trees to identify conflicting versions and circular dependencies with detailed reporting and configurable warning levels.
3
4
## Capabilities
5
6
### Main Validation Function
7
8
Primary validation entry point that checks for conflicts and cycles.
9
10
```python { .api }
11
def validate(tree: PackageDAG) -> None:
12
"""
13
Validate the dependency tree for conflicts and circular dependencies.
14
15
Prints warnings to stderr if conflicts or cycles are found and warnings are enabled.
16
The behavior depends on the current WarningPrinter configuration.
17
18
Parameters:
19
- tree: PackageDAG to validate
20
"""
21
```
22
23
### Conflict Detection
24
25
Identifies packages with conflicting version requirements.
26
27
```python { .api }
28
def conflicting_deps(tree: PackageDAG) -> dict[DistPackage, list[ReqPackage]]:
29
"""
30
Return dependencies which are not present or conflict with requirements.
31
32
Finds cases where:
33
- A package requires version X but version Y is installed
34
- A required package is missing entirely
35
36
Parameters:
37
- tree: The requirements tree to analyze
38
39
Returns:
40
Dictionary mapping packages to their conflicting requirements
41
"""
42
```
43
44
### Cycle Detection
45
46
Detects circular dependencies in the dependency graph.
47
48
```python { .api }
49
def cyclic_deps(tree: PackageDAG) -> list[list[Package]]:
50
"""
51
Return cyclic dependencies as list of lists.
52
53
Each inner list represents one dependency cycle, showing the path
54
that creates the circular reference.
55
56
Parameters:
57
- tree: Package dependency tree to analyze
58
59
Returns:
60
List of dependency cycles, where each cycle is a list of Package objects
61
"""
62
```
63
64
### Text Rendering Functions
65
66
Functions to format validation results for display.
67
68
```python { .api }
69
def render_conflicts_text(conflicts: dict[DistPackage, list[ReqPackage]]) -> None:
70
"""
71
Print conflicts in a human-readable format to stderr.
72
73
Output format:
74
* package==version
75
- conflicting_req [required: >=1.0, installed: 0.9]
76
"""
77
78
def render_cycles_text(cycles: list[list[Package]]) -> None:
79
"""
80
Print circular dependencies in a human-readable format to stderr.
81
82
Output format:
83
* package_a => package_b => package_c => package_a
84
"""
85
```
86
87
## Usage Examples
88
89
### Basic Validation
90
91
```python
92
from pipdeptree._discovery import get_installed_distributions
93
from pipdeptree._models import PackageDAG
94
from pipdeptree._validate import validate
95
96
# Create dependency tree and validate
97
distributions = get_installed_distributions()
98
tree = PackageDAG.from_pkgs(distributions)
99
100
# Validate for conflicts and cycles (prints warnings if found)
101
validate(tree)
102
```
103
104
### Manual Conflict Checking
105
106
```python
107
from pipdeptree._validate import conflicting_deps, render_conflicts_text
108
109
# Check for conflicts manually
110
conflicts = conflicting_deps(tree)
111
112
if conflicts:
113
print("Found conflicting dependencies:")
114
render_conflicts_text(conflicts)
115
116
# Process conflicts programmatically
117
for package, conflicting_reqs in conflicts.items():
118
print(f"\nPackage {package.project_name} has conflicts:")
119
for req in conflicting_reqs:
120
print(f" - {req.project_name}: required {req.version_spec}, "
121
f"installed {req.installed_version}")
122
```
123
124
### Manual Cycle Detection
125
126
```python
127
from pipdeptree._validate import cyclic_deps, render_cycles_text
128
129
# Check for circular dependencies
130
cycles = cyclic_deps(tree)
131
132
if cycles:
133
print("Found circular dependencies:")
134
render_cycles_text(cycles)
135
136
# Process cycles programmatically
137
for i, cycle in enumerate(cycles, 1):
138
cycle_names = [pkg.project_name for pkg in cycle]
139
print(f"Cycle {i}: {' -> '.join(cycle_names)}")
140
```
141
142
### Validation with Custom Warning Control
143
144
```python
145
from pipdeptree._warning import get_warning_printer, WarningType
146
from pipdeptree._validate import validate
147
148
# Configure warning behavior
149
warning_printer = get_warning_printer()
150
warning_printer.warning_type = WarningType.FAIL # Exit with error code on warnings
151
152
# Validate (will now fail with exit code 1 if issues found)
153
validate(tree)
154
155
# Check if validation failed
156
if warning_printer.has_warned_with_failure():
157
print("Validation failed with conflicts or cycles")
158
```
159
160
## Conflict Types
161
162
The validation system detects several types of conflicts:
163
164
### Version Conflicts
165
When installed version doesn't satisfy requirement specification:
166
```
167
* Django==3.0.0
168
- requests [required: >=2.25.0, installed: 2.20.0]
169
```
170
171
### Missing Dependencies
172
When a required package is not installed:
173
```
174
* mypackage==1.0.0
175
- missing-dep [required: >=1.0, installed: ?]
176
```
177
178
### Invalid Requirements
179
When package metadata contains malformed requirement strings (logged separately):
180
```
181
Invalid requirement strings found for the following distributions:
182
mypackage
183
Skipping "invalid>=requirement string"
184
```
185
186
## Cycle Detection Algorithm
187
188
The cycle detection uses depth-first search to identify circular dependencies:
189
190
1. **For each package** in the tree, start a DFS traversal
191
2. **Track visited nodes** to detect when we return to a previously seen package
192
3. **Record the path** when a cycle is found
193
4. **Return all unique cycles** found in the dependency graph
194
195
Cycles are returned as lists showing the dependency path that creates the circular reference.
196
197
## Integration with Warning System
198
199
Validation integrates with pipdeptree's warning system:
200
201
- **WarningType.SILENCE**: No validation output, always return 0
202
- **WarningType.SUPPRESS**: Print warnings but return 0
203
- **WarningType.FAIL**: Print warnings and return 1 if any found
204
205
The validation results are automatically printed to stderr when warnings are enabled, and the return code is determined by the warning configuration.