Skip to content

Commit 226260d

Browse files
authored
Merge pull request #44 from nexB/pdt
Add a new option --json-pdt
2 parents 93df5a6 + 33d4229 commit 226260d

8 files changed

+1285
-37
lines changed

src/python_inspector/resolution.py

+47-5
Original file line numberDiff line numberDiff line change
@@ -627,13 +627,52 @@ def format_resolution(
627627
return dependencies
628628

629629

630+
def pdt_dfs(mapping, graph, src):
631+
"""
632+
Return a nested mapping of dependencies.
633+
634+
This takes ``mapping`` and ``graph`` as input. And do a dfs
635+
(aka. depth-first search see https://en.wikipedia.org/wiki/Depth-first_search)
636+
on the ``graph`` to get the dependencies of the given ``src``.
637+
And use the ``mapping`` to get the version of the given dependency.
638+
"""
639+
children = list(graph.iter_children(src))
640+
if not children:
641+
return dict(
642+
key=src, package_name=src, installed_version=str(mapping[src].version), dependencies=[]
643+
)
644+
# recurse
645+
dependencies = [pdt_dfs(mapping, graph, c) for c in children]
646+
dependencies.sort(key=lambda d: d["key"])
647+
return dict(
648+
key=src,
649+
package_name=src,
650+
installed_version=str(mapping[src].version),
651+
dependencies=dependencies,
652+
)
653+
654+
655+
def format_pdt_tree(results):
656+
"""
657+
Return a formatted tree of dependencies in the style of pipdeptree.
658+
"""
659+
mapping = results.mapping
660+
graph = results.graph
661+
dependencies = []
662+
for src in get_all_srcs(mapping=mapping, graph=graph):
663+
dependencies.append(pdt_dfs(mapping=mapping, graph=graph, src=src))
664+
dependencies.sort(key=lambda d: d["key"])
665+
return dependencies
666+
667+
630668
def get_resolved_dependencies(
631669
requirements: List[Requirement],
632670
environment: Environment = None,
633671
repos: Sequence[PypiSimpleRepository] = tuple(),
634672
as_tree: bool = False,
635673
max_rounds: int = 200000,
636-
debug: bool = False,
674+
verbose: bool = False,
675+
pdt_output: bool = False,
637676
):
638677
"""
639678
Return resolved dependencies of a ``requirements`` list of Requirement for
@@ -648,11 +687,14 @@ def get_resolved_dependencies(
648687
provider=PythonInputProvider(environment=environment, repos=repos),
649688
reporter=BaseReporter(),
650689
)
651-
results = resolver.resolve(requirements=requirements, max_rounds=max_rounds)
652-
results = format_resolution(results, as_tree=as_tree, environment=environment, repos=repos)
653-
return results
690+
resolver_results = resolver.resolve(requirements=requirements, max_rounds=max_rounds)
691+
if pdt_output:
692+
return format_pdt_tree(resolver_results)
693+
return format_resolution(
694+
resolver_results, as_tree=as_tree, environment=environment, repos=repos
695+
)
654696
except Exception as e:
655-
if debug:
697+
if verbose:
656698
import click
657699

658700
click.secho(f"{e!r}", err=True)

src/python_inspector/resolve_cli.py

+67-28
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010
#
1111

1212
import json
13-
import sys
1413
from typing import List
1514

1615
import click
@@ -34,6 +33,7 @@
3433

3534

3635
@click.command()
36+
@click.pass_context
3737
@click.option(
3838
"-r",
3939
"--requirement",
@@ -99,11 +99,20 @@
9999
"--json",
100100
"json_output",
101101
type=FileOptionType(mode="w", encoding="utf-8", lazy=True),
102-
required=True,
102+
required=False,
103103
metavar="FILE",
104104
help="Write output as pretty-printed JSON to FILE. "
105105
"Use the special '-' file name to print results on screen/stdout.",
106106
)
107+
@click.option(
108+
"--json-pdt",
109+
"pdt_output",
110+
type=FileOptionType(mode="w", encoding="utf-8", lazy=True),
111+
required=False,
112+
metavar="FILE",
113+
help="Write output as pretty-printed JSON to FILE as a tree in the style of pipdeptree. "
114+
"Use the special '-' file name to print results on screen/stdout.",
115+
)
107116
@click.option(
108117
"--max-rounds",
109118
"max_rounds",
@@ -124,24 +133,26 @@
124133
"--index-url are ignored when this option is active.",
125134
)
126135
@click.option(
127-
"--debug",
136+
"--verbose",
128137
is_flag=True,
129138
hidden=True,
130139
help="Enable debug output.",
131140
)
132141
@click.help_option("-h", "--help")
133142
def resolve_dependencies(
143+
ctx,
134144
requirement_files,
135145
netrc_file,
136146
specifiers,
137147
python_version,
138148
operating_system,
139149
index_urls,
140150
json_output,
151+
pdt_output,
141152
max_rounds,
142153
use_cached_index=False,
143154
use_pypi_json_api=False,
144-
debug=TRACE,
155+
verbose=TRACE,
145156
):
146157
"""
147158
Resolve the dependencies of the packages listed in REQUIREMENT-FILE(s) file
@@ -161,7 +172,15 @@ def resolve_dependencies(
161172
162173
dad --spec "flask==2.1.2" --json -
163174
"""
164-
if debug:
175+
if not (json_output or pdt_output):
176+
click.secho("No output file specified. Use --json or --json-pdt.", err=True)
177+
ctx.exit(1)
178+
179+
if json_output and pdt_output:
180+
click.secho("Only one of --json or --json-pdt can be used.", err=True)
181+
ctx.exit(1)
182+
183+
if verbose:
165184
click.secho(f"Resolving dependencies...")
166185

167186
netrc = None
@@ -184,11 +203,10 @@ def resolve_dependencies(
184203
direct_dependencies.append(dep)
185204

186205
if not direct_dependencies:
187-
if debug:
188-
click.secho("Error: no requirements requested.")
189-
sys.exit(1)
206+
click.secho("Error: no requirements requested.")
207+
ctx.exit(1)
190208

191-
if debug:
209+
if verbose:
192210
click.secho("direct_dependencies:")
193211
for dep in direct_dependencies:
194212
click.secho(f" {dep}")
@@ -198,7 +216,7 @@ def resolve_dependencies(
198216
python_version=python_version, operating_system=operating_system
199217
)
200218

201-
if debug:
219+
if verbose:
202220
click.secho(f"environment: {environment}")
203221

204222
repos = []
@@ -224,7 +242,7 @@ def resolve_dependencies(
224242
)
225243
repos.append(repo)
226244

227-
if debug:
245+
if verbose:
228246
click.secho("repos:")
229247
for repo in repos:
230248
click.secho(f" {repo}")
@@ -236,7 +254,8 @@ def resolve_dependencies(
236254
repos=repos,
237255
as_tree=False,
238256
max_rounds=max_rounds,
239-
debug=debug,
257+
verbose=verbose,
258+
pdt_output=pdt_output,
240259
)
241260

242261
cli_options = [f"--requirement {rf}" for rf in requirement_files]
@@ -262,19 +281,35 @@ def resolve_dependencies(
262281
errors=[],
263282
)
264283

265-
write_output(
266-
headers=headers,
267-
requirements=requirements,
268-
resolved_dependencies=resolved_dependencies,
269-
json_output=json_output,
270-
)
271-
272-
if debug:
284+
if json_output:
285+
write_output(
286+
headers=headers,
287+
requirements=requirements,
288+
resolved_dependencies=resolved_dependencies,
289+
json_output=json_output,
290+
)
291+
292+
else:
293+
write_output(
294+
headers=headers,
295+
requirements=requirements,
296+
resolved_dependencies=resolved_dependencies,
297+
json_output=pdt_output,
298+
pdt_output=True,
299+
)
300+
301+
if verbose:
273302
click.secho("done!")
274303

275304

276305
def resolve(
277-
direct_dependencies, environment, repos=tuple(), as_tree=False, max_rounds=200000, debug=False
306+
direct_dependencies,
307+
environment,
308+
repos=tuple(),
309+
as_tree=False,
310+
max_rounds=200000,
311+
verbose=False,
312+
pdt_output=False,
278313
):
279314
"""
280315
Resolve dependencies given a ``direct_dependencies`` list of
@@ -292,7 +327,8 @@ def resolve(
292327
repos=repos,
293328
as_tree=as_tree,
294329
max_rounds=max_rounds,
295-
debug=debug,
330+
verbose=verbose,
331+
pdt_output=pdt_output,
296332
)
297333

298334
initial_requirements = [d.to_dict() for d in direct_dependencies]
@@ -312,16 +348,19 @@ def get_requirements_from_direct_dependencies(
312348
yield Requirement(requirement_string=dependency.extracted_requirement)
313349

314350

315-
def write_output(headers, requirements, resolved_dependencies, json_output):
351+
def write_output(headers, requirements, resolved_dependencies, json_output, pdt_output=False):
316352
"""
317353
Write headers, requirements and resolved_dependencies as JSON to ``json_output``.
318354
Return the output data.
319355
"""
320-
output = dict(
321-
headers=headers,
322-
requirements=requirements,
323-
resolved_dependencies=resolved_dependencies,
324-
)
356+
if not pdt_output:
357+
output = dict(
358+
headers=headers,
359+
requirements=requirements,
360+
resolved_dependencies=resolved_dependencies,
361+
)
362+
else:
363+
output = resolved_dependencies
325364

326365
json.dump(output, json_output, indent=2)
327366
return output

0 commit comments

Comments
 (0)