Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 47 additions & 5 deletions src/python_inspector/resolution.py
Original file line number Diff line number Diff line change
Expand Up @@ -627,13 +627,52 @@ def format_resolution(
return dependencies


def pdt_dfs(mapping, graph, src):
"""
Return a nested mapping of dependencies.
Comment thread
pombredanne marked this conversation as resolved.

This takes ``mapping`` and ``graph`` as input. And do a dfs
Comment thread
TG1999 marked this conversation as resolved.
(aka. depth-first search see https://en.wikipedia.org/wiki/Depth-first_search)
on the ``graph`` to get the dependencies of the given ``src``.
And use the ``mapping`` to get the version of the given dependency.
"""
children = list(graph.iter_children(src))
if not children:
return dict(
key=src, package_name=src, installed_version=str(mapping[src].version), dependencies=[]
)
# recurse
dependencies = [pdt_dfs(mapping, graph, c) for c in children]
dependencies.sort(key=lambda d: d["key"])
return dict(
key=src,
package_name=src,
installed_version=str(mapping[src].version),
dependencies=dependencies,
)


def format_pdt_tree(results):
"""
Return a formatted tree of dependencies in the style of pipdeptree.
"""
mapping = results.mapping
graph = results.graph
dependencies = []
for src in get_all_srcs(mapping=mapping, graph=graph):
dependencies.append(pdt_dfs(mapping=mapping, graph=graph, src=src))
dependencies.sort(key=lambda d: d["key"])
Comment thread
TG1999 marked this conversation as resolved.
return dependencies


def get_resolved_dependencies(
requirements: List[Requirement],
environment: Environment = None,
repos: Sequence[PypiSimpleRepository] = tuple(),
as_tree: bool = False,
max_rounds: int = 200000,
debug: bool = False,
verbose: bool = False,
pdt_output: bool = False,
):
"""
Return resolved dependencies of a ``requirements`` list of Requirement for
Expand All @@ -648,11 +687,14 @@ def get_resolved_dependencies(
provider=PythonInputProvider(environment=environment, repos=repos),
reporter=BaseReporter(),
)
results = resolver.resolve(requirements=requirements, max_rounds=max_rounds)
results = format_resolution(results, as_tree=as_tree, environment=environment, repos=repos)
return results
resolver_results = resolver.resolve(requirements=requirements, max_rounds=max_rounds)
if pdt_output:
return format_pdt_tree(resolver_results)
return format_resolution(
resolver_results, as_tree=as_tree, environment=environment, repos=repos
)
except Exception as e:
if debug:
if verbose:
import click

click.secho(f"{e!r}", err=True)
95 changes: 67 additions & 28 deletions src/python_inspector/resolve_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
#

import json
import sys
from typing import List

import click
Expand All @@ -34,6 +33,7 @@


@click.command()
@click.pass_context
@click.option(
"-r",
"--requirement",
Expand Down Expand Up @@ -99,11 +99,20 @@
"--json",
"json_output",
type=FileOptionType(mode="w", encoding="utf-8", lazy=True),
required=True,
required=False,
metavar="FILE",
help="Write output as pretty-printed JSON to FILE. "
"Use the special '-' file name to print results on screen/stdout.",
)
@click.option(
"--json-pdt",
"pdt_output",
type=FileOptionType(mode="w", encoding="utf-8", lazy=True),
required=False,
metavar="FILE",
help="Write output as pretty-printed JSON to FILE as a tree in the style of pipdeptree. "
"Use the special '-' file name to print results on screen/stdout.",
)
@click.option(
"--max-rounds",
"max_rounds",
Expand All @@ -124,24 +133,26 @@
"--index-url are ignored when this option is active.",
)
@click.option(
"--debug",
"--verbose",
is_flag=True,
hidden=True,
help="Enable debug output.",
)
@click.help_option("-h", "--help")
def resolve_dependencies(
ctx,
requirement_files,
netrc_file,
specifiers,
python_version,
operating_system,
index_urls,
json_output,
pdt_output,
max_rounds,
use_cached_index=False,
use_pypi_json_api=False,
debug=TRACE,
verbose=TRACE,
):
"""
Resolve the dependencies of the packages listed in REQUIREMENT-FILE(s) file
Expand All @@ -161,7 +172,15 @@ def resolve_dependencies(

dad --spec "flask==2.1.2" --json -
"""
if debug:
if not (json_output or pdt_output):
click.secho("No output file specified. Use --json or --json-pdt.", err=True)
ctx.exit(1)

if json_output and pdt_output:
click.secho("Only one of --json or --json-pdt can be used.", err=True)
ctx.exit(1)

if verbose:
click.secho(f"Resolving dependencies...")

netrc = None
Expand All @@ -184,11 +203,10 @@ def resolve_dependencies(
direct_dependencies.append(dep)

if not direct_dependencies:
if debug:
click.secho("Error: no requirements requested.")
sys.exit(1)
click.secho("Error: no requirements requested.")
ctx.exit(1)

if debug:
if verbose:
click.secho("direct_dependencies:")
for dep in direct_dependencies:
click.secho(f" {dep}")
Expand All @@ -198,7 +216,7 @@ def resolve_dependencies(
python_version=python_version, operating_system=operating_system
)

if debug:
if verbose:
click.secho(f"environment: {environment}")

repos = []
Expand All @@ -224,7 +242,7 @@ def resolve_dependencies(
)
repos.append(repo)

if debug:
if verbose:
click.secho("repos:")
for repo in repos:
click.secho(f" {repo}")
Expand All @@ -236,7 +254,8 @@ def resolve_dependencies(
repos=repos,
as_tree=False,
max_rounds=max_rounds,
debug=debug,
verbose=verbose,
pdt_output=pdt_output,
)

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

write_output(
headers=headers,
requirements=requirements,
resolved_dependencies=resolved_dependencies,
json_output=json_output,
)

if debug:
if json_output:
write_output(
headers=headers,
requirements=requirements,
resolved_dependencies=resolved_dependencies,
json_output=json_output,
)

else:
write_output(
headers=headers,
requirements=requirements,
resolved_dependencies=resolved_dependencies,
json_output=pdt_output,
pdt_output=True,
)

if verbose:
click.secho("done!")


def resolve(
direct_dependencies, environment, repos=tuple(), as_tree=False, max_rounds=200000, debug=False
direct_dependencies,
environment,
repos=tuple(),
as_tree=False,
max_rounds=200000,
verbose=False,
pdt_output=False,
):
"""
Resolve dependencies given a ``direct_dependencies`` list of
Expand All @@ -292,7 +327,8 @@ def resolve(
repos=repos,
as_tree=as_tree,
max_rounds=max_rounds,
debug=debug,
verbose=verbose,
pdt_output=pdt_output,
)

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


def write_output(headers, requirements, resolved_dependencies, json_output):
def write_output(headers, requirements, resolved_dependencies, json_output, pdt_output=False):
"""
Write headers, requirements and resolved_dependencies as JSON to ``json_output``.
Return the output data.
"""
output = dict(
headers=headers,
requirements=requirements,
resolved_dependencies=resolved_dependencies,
)
if not pdt_output:
output = dict(
headers=headers,
requirements=requirements,
resolved_dependencies=resolved_dependencies,
)
else:
output = resolved_dependencies

json.dump(output, json_output, indent=2)
return output
Expand Down
Loading