Changed the included 'pipdeptree' to an external dependency. eric7

Thu, 31 Aug 2023 14:49:37 +0200

author
Detlev Offenbach <detlev@die-offenbachs.de>
date
Thu, 31 Aug 2023 14:49:37 +0200
branch
eric7
changeset 10177
27a6e35c64ed
parent 10176
5e26785b93b8
child 10178
d13545cfafa1

Changed the included 'pipdeptree' to an external dependency.

docs/ThirdParty.md file | annotate | diff | comparison | revisions
pyproject.toml file | annotate | diff | comparison | revisions
scripts/install.py file | annotate | diff | comparison | revisions
src/eric7/PipInterface/Pip.py file | annotate | diff | comparison | revisions
src/eric7/PipInterface/pipdeptree.py file | annotate | diff | comparison | revisions
--- a/docs/ThirdParty.md	Thu Aug 31 14:07:24 2023 +0200
+++ b/docs/ThirdParty.md	Thu Aug 31 14:49:37 2023 +0200
@@ -7,7 +7,6 @@
 |:------------:|:---------:|:----------------------------|
 | eradicate    |   2.3.0   | MIT License (Expat License) |
 | mccabe       |   0.7.0   | MIT License (Expat License) |
-| pipdeptree   |   2.7.1   | MIT License (MIT)           |
 | pip-licenses |   4.3.1   | MIT License (MIT)           |
 | pycodestyle  |   2.11.0  | MIT License (Expat License) |
 | pyflakes     |   3.1.0   | MIT License (MIT)           |
--- a/pyproject.toml	Thu Aug 31 14:07:24 2023 +0200
+++ b/pyproject.toml	Thu Aug 31 14:49:37 2023 +0200
@@ -80,6 +80,7 @@
     "isort>=5.10.0",
     "coverage>=6.5.0",
     "semver",
+    "pipdeptree",
     "importlib-metadata; python_version<'3.8'",
     "pywin32>=1.0;platform_system=='Windows'",
 ]
--- a/scripts/install.py	Thu Aug 31 14:07:24 2023 +0200
+++ b/scripts/install.py	Thu Aug 31 14:49:37 2023 +0200
@@ -1728,6 +1728,7 @@
         "isort": ("isort", ">=5.10.0"),
         "coverage": ("coverage", ">=6.5.0"),
         "semver": ("semver", ""),
+        "pipdeptree": ("pipdeptree", ""),
     }
     optionalModulesList = {
         # key is pip project name
--- a/src/eric7/PipInterface/Pip.py	Thu Aug 31 14:07:24 2023 +0200
+++ b/src/eric7/PipInterface/Pip.py	Thu Aug 31 14:49:37 2023 +0200
@@ -1019,10 +1019,7 @@
         if envName:
             interpreter = self.getVirtualenvInterpreter(envName)
             if interpreter:
-                args = [
-                    os.path.join(os.path.dirname(__file__), "pipdeptree.py"),
-                    "--json-tree",
-                ]
+                args = ["-m", "pipdeptree", "--python", interpreter, "--json-tree"]
                 if localPackages:
                     args.append("--local-only")
                 if usersite:
@@ -1031,7 +1028,7 @@
                     args.append("--reverse")
 
                 proc = QProcess()
-                proc.start(interpreter, args)
+                proc.start(PythonUtilities.getPythonExecutable(), args)
                 if proc.waitForStarted(15000) and proc.waitForFinished(30000):
                     output = str(
                         proc.readAllStandardOutput(),
--- a/src/eric7/PipInterface/pipdeptree.py	Thu Aug 31 14:07:24 2023 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,1053 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-
-#
-# eric-ide modification: Copyright from file "LICENSE"
-#
-
-"""
-Copyright (c) The pipdeptree developers
-
-Permission is hereby granted, free of charge, to any person obtaining
-a copy of this software and associated documentation files (the
-"Software"), to deal in the Software without restriction, including
-without limitation the rights to use, copy, modify, merge, publish,
-distribute, sublicense, and/or sell copies of the Software, and to
-permit persons to whom the Software is furnished to do so, subject to
-the following conditions:
-
-The above copyright notice and this permission notice shall be
-included in all copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
-NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
-LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
-OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
-WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-"""
-
-#
-# Slightly modified to be used within the eric-ide project.
-#
-# Copyright (c) 2022 - 2023 Detlev Offenbach <detlev@die-offenbachs.de>
-#
-
-import argparse
-import fnmatch
-import inspect
-import json
-import os
-import shutil
-import subprocess
-import sys
-import tempfile
-from collections import defaultdict, deque
-from collections.abc import Mapping
-from importlib import import_module
-from itertools import chain
-from textwrap import dedent
-
-from pip._vendor import pkg_resources
-
-try:
-    from pip._internal.operations.freeze import FrozenRequirement
-except ImportError:
-    from pip import FrozenRequirement
-
-
-__version__ = '2.7.1'
-__version_tuple__ = (2, 7, 1)
-# eric-ide modification: from version.py
-
-flatten = chain.from_iterable
-
-
-def sorted_tree(tree):
-    """
-    Sorts the dict representation of the tree. The root packages as well as the intermediate packages are sorted in the
-    alphabetical order of the package names.
-
-    :param dict tree: the pkg dependency tree obtained by calling `construct_tree` function
-    :returns: sorted tree
-    :rtype: dict
-    """
-    return {k: sorted(v) for k, v in sorted(tree.items())}
-
-
-def guess_version(pkg_key, default="?"):
-    """Guess the version of a pkg when pip doesn't provide it
-
-    :param str pkg_key: key of the package
-    :param str default: default version to return if unable to find
-    :returns: version
-    :rtype: string
-    """
-    try:
-        import importlib.metadata
-        return importlib.metadata.version(pkg_key)
-    except ImportError:
-        pass
-    # Avoid AssertionError with setuptools, see https://github.com/tox-dev/pipdeptree/issues/162
-    if pkg_key in {"setuptools"}:
-        return default
-    try:
-        m = import_module(pkg_key)
-    except ImportError:
-        return default
-    else:
-        v = getattr(m, "__version__", default)
-        if inspect.ismodule(v):
-            return getattr(v, "__version__", default)
-        else:
-            return v
-
-
-def frozen_req_from_dist(dist):
-    # The `pip._internal.metadata` modules were introduced in 21.1.1
-    # and the `pip._internal.operations.freeze.FrozenRequirement`
-    # class now expects dist to be a subclass of
-    # `pip._internal.metadata.BaseDistribution`, however the
-    # `pip._internal.utils.misc.get_installed_distributions` continues
-    # to return objects of type
-    # pip._vendor.pkg_resources.DistInfoDistribution.
-    #
-    # This is a hacky backward compatible (with older versions of pip)
-    # fix.
-    try:
-        from pip._internal import metadata
-    except ImportError:
-        pass
-    else:
-        dist = metadata.pkg_resources.Distribution(dist)
-
-    try:
-        return FrozenRequirement.from_dist(dist)
-    except TypeError:
-        return FrozenRequirement.from_dist(dist, [])
-
-
-class Package:
-    """
-    Abstract class for wrappers around objects that pip returns. This class needs to be subclassed with implementations
-    for `render_as_root` and `render_as_branch` methods.
-    """
-
-    def __init__(self, obj):
-        self._obj = obj
-        self.project_name = obj.project_name
-        self.key = obj.key
-
-    def render_as_root(self, frozen):  # noqa: U100
-        return NotImplementedError
-
-    def render_as_branch(self, frozen):  # noqa: U100
-        return NotImplementedError
-
-    def render(self, parent=None, frozen=False):
-        if not parent:
-            return self.render_as_root(frozen)
-        else:
-            return self.render_as_branch(frozen)
-
-    @staticmethod
-    def frozen_repr(obj):
-        fr = frozen_req_from_dist(obj)
-        return str(fr).strip()
-
-    def __getattr__(self, key):
-        return getattr(self._obj, key)
-
-    def __repr__(self):
-        return f'<{self.__class__.__name__}("{self.key}")>'
-
-    def __lt__(self, rhs):
-        return self.key < rhs.key
-
-
-class DistPackage(Package):
-    """
-    Wrapper class for pkg_resources.Distribution instances
-
-    :param obj: pkg_resources.Distribution to wrap over
-    :param req: optional ReqPackage object to associate this DistPackage with. This is useful for displaying the tree
-        in reverse
-    """
-
-    def __init__(self, obj, req=None):
-        super().__init__(obj)
-        self.version_spec = None
-        self.req = req
-
-    def render_as_root(self, frozen):
-        if not frozen:
-            return f"{self.project_name}=={self.version}"
-        else:
-            return self.__class__.frozen_repr(self._obj)
-
-    def render_as_branch(self, frozen):
-        assert self.req is not None
-        if not frozen:
-            parent_ver_spec = self.req.version_spec
-            parent_str = self.req.project_name
-            if parent_ver_spec:
-                parent_str += parent_ver_spec
-            return f"{self.project_name}=={self.version} [requires: {parent_str}]"
-        else:
-            return self.render_as_root(frozen)
-
-    def as_requirement(self):
-        """Return a ReqPackage representation of this DistPackage"""
-        return ReqPackage(self._obj.as_requirement(), dist=self)
-
-    def as_parent_of(self, req):
-        """
-        Return a DistPackage instance associated to a requirement. This association is necessary for reversing the
-        PackageDAG.
-
-        If `req` is None, and the `req` attribute of the current instance is also None, then the same instance will be
-        returned.
-
-        :param ReqPackage req: the requirement to associate with
-        :returns: DistPackage instance
-        """
-        if req is None and self.req is None:
-            return self
-        return self.__class__(self._obj, req)
-
-    def as_dict(self):
-        return {"key": self.key, "package_name": self.project_name, "installed_version": self.version}
-
-
-class ReqPackage(Package):
-    """
-    Wrapper class for Requirements instance
-
-    :param obj: The `Requirements` instance to wrap over
-    :param dist: optional `pkg_resources.Distribution` instance for this requirement
-    """
-
-    UNKNOWN_VERSION = "?"
-
-    def __init__(self, obj, dist=None):
-        super().__init__(obj)
-        self.dist = dist
-
-    @property
-    def version_spec(self):
-        specs = sorted(self._obj.specs, reverse=True)  # `reverse` makes '>' prior to '<'
-        return ",".join(["".join(sp) for sp in specs]) if specs else None
-
-    @property
-    def installed_version(self):
-        if not self.dist:
-            return guess_version(self.key, self.UNKNOWN_VERSION)
-        return self.dist.version
-
-    @property
-    def is_missing(self):
-        return self.installed_version == self.UNKNOWN_VERSION
-
-    def is_conflicting(self):
-        """If installed version conflicts with required version"""
-        # unknown installed version is also considered conflicting
-        if self.installed_version == self.UNKNOWN_VERSION:
-            return True
-        ver_spec = self.version_spec if self.version_spec else ""
-        req_version_str = f"{self.project_name}{ver_spec}"
-        req_obj = pkg_resources.Requirement.parse(req_version_str)
-        return self.installed_version not in req_obj
-
-    def render_as_root(self, frozen):
-        if not frozen:
-            return f"{self.project_name}=={self.installed_version}"
-        elif self.dist:
-            return self.__class__.frozen_repr(self.dist._obj)
-        else:
-            return self.project_name
-
-    def render_as_branch(self, frozen):
-        if not frozen:
-            req_ver = self.version_spec if self.version_spec else "Any"
-            return f"{self.project_name} [required: {req_ver}, installed: {self.installed_version}]"
-        else:
-            return self.render_as_root(frozen)
-
-    def as_dict(self):
-        return {
-            "key": self.key,
-            "package_name": self.project_name,
-            "installed_version": self.installed_version,
-            "required_version": self.version_spec,
-        }
-
-
-class PackageDAG(Mapping):
-    """
-    Representation of Package dependencies as directed acyclic graph using a dict (Mapping) as the underlying
-    datastructure.
-
-    The nodes and their relationships (edges) are internally stored using a map as follows,
-
-    {a: [b, c],
-     b: [d],
-     c: [d, e],
-     d: [e],
-     e: [],
-     f: [b],
-     g: [e, f]}
-
-    Here, node `a` has 2 children nodes `b` and `c`. Consider edge direction from `a` -> `b` and `a` -> `c`
-    respectively.
-
-    A node is expected to be an instance of a subclass of `Package`. The keys are must be of class `DistPackage` and
-    each item in values must be of class `ReqPackage`. (See also ReversedPackageDAG where the key and value types are
-    interchanged).
-    """
-
-    @classmethod
-    def from_pkgs(cls, pkgs):
-        pkgs = [DistPackage(p) for p in pkgs]
-        idx = {p.key: p for p in pkgs}
-        m = {p: [ReqPackage(r, idx.get(r.key)) for r in p.requires()] for p in pkgs}
-        return cls(m)
-
-    def __init__(self, m):
-        """Initialize the PackageDAG object
-
-        :param dict m: dict of node objects (refer class docstring)
-        :returns: None
-        :rtype: NoneType
-
-        """
-        self._obj = m
-        self._index = {p.key: p for p in list(self._obj)}
-
-    def get_node_as_parent(self, node_key):
-        """
-        Get the node from the keys of the dict representing the DAG.
-
-        This method is useful if the dict representing the DAG contains different kind of objects in keys and values.
-        Use this method to look up a node obj as a parent (from the keys of the dict) given a node key.
-
-        :param node_key: identifier corresponding to key attr of node obj
-        :returns: node obj (as present in the keys of the dict)
-        :rtype: Object
-        """
-        try:
-            return self._index[node_key]
-        except KeyError:
-            return None
-
-    def get_children(self, node_key):
-        """
-        Get child nodes for a node by its key
-
-        :param str node_key: key of the node to get children of
-        :returns: list of child nodes
-        :rtype: ReqPackage[]
-        """
-        node = self.get_node_as_parent(node_key)
-        return self._obj[node] if node else []
-
-    def filter(self, include, exclude):
-        """
-        Filters nodes in a graph by given parameters
-
-        If a node is included, then all it's children are also included.
-
-        :param set include: set of node keys to include (or None)
-        :param set exclude: set of node keys to exclude (or None)
-        :returns: filtered version of the graph
-        :rtype: PackageDAG
-        """
-        # If neither of the filters are specified, short circuit
-        if include is None and exclude is None:
-            return self
-
-        # Note: In following comparisons, we use lower cased values so
-        # that user may specify `key` or `project_name`. As per the
-        # documentation, `key` is simply
-        # `project_name.lower()`. Refer:
-        # https://setuptools.readthedocs.io/en/latest/pkg_resources.html#distribution-objects
-        if include:
-            include = {s.lower() for s in include}
-        if exclude:
-            exclude = {s.lower() for s in exclude}
-        else:
-            exclude = set()
-
-        # Check for mutual exclusion of show_only and exclude sets
-        # after normalizing the values to lowercase
-        if include and exclude:
-            assert not (include & exclude)
-
-        # Traverse the graph in a depth first manner and filter the
-        # nodes according to `show_only` and `exclude` sets
-        stack = deque()
-        m = {}
-        seen = set()
-        for node in self._obj.keys():
-            if any(fnmatch.fnmatch(node.key, e) for e in exclude):
-                continue
-            if include is None or any(fnmatch.fnmatch(node.key, i) for i in include):
-                stack.append(node)
-            while True:
-                if len(stack) > 0:
-                    n = stack.pop()
-                    cldn = [c for c in self._obj[n] if not any(fnmatch.fnmatch(c.key, e) for e in exclude)]
-                    m[n] = cldn
-                    seen.add(n.key)
-                    for c in cldn:
-                        if c.key not in seen:
-                            cld_node = self.get_node_as_parent(c.key)
-                            if cld_node:
-                                stack.append(cld_node)
-                            else:
-                                # It means there's no root node corresponding to the child node i.e.
-                                # a dependency is missing
-                                continue
-                else:
-                    break
-
-        return self.__class__(m)
-
-    def reverse(self):
-        """
-        Reverse the DAG, or turn it upside-down.
-
-        In other words, the directions of edges of the nodes in the DAG will be reversed.
-
-        Note that this function purely works on the nodes in the graph. This implies that to perform a combination of
-        filtering and reversing, the order in which `filter` and `reverse` methods should be applied is important. For
-        e.g., if reverse is called on a filtered graph, then only the filtered nodes and it's children will be
-        considered when reversing. On the other hand, if filter is called on reversed DAG, then the definition of
-        "child" nodes is as per the reversed DAG.
-
-        :returns: DAG in the reversed form
-        :rtype: ReversedPackageDAG
-        """
-        m = defaultdict(list)
-        child_keys = {r.key for r in chain.from_iterable(self._obj.values())}
-        for k, vs in self._obj.items():
-            for v in vs:
-                # if v is already added to the dict, then ensure that
-                # we are using the same object. This check is required
-                # as we're using array mutation
-                try:
-                    node = [p for p in m.keys() if p.key == v.key][0]
-                except IndexError:
-                    node = v
-                m[node].append(k.as_parent_of(v))
-            if k.key not in child_keys:
-                m[k.as_requirement()] = []
-        return ReversedPackageDAG(dict(m))
-
-    def sort(self):
-        """
-        Return sorted tree in which the underlying _obj dict is an dict, sorted alphabetically by the keys.
-
-        :returns: Instance of same class with dict
-        """
-        return self.__class__(sorted_tree(self._obj))
-
-    # Methods required by the abstract base class Mapping
-    def __getitem__(self, *args):
-        return self._obj.get(*args)
-
-    def __iter__(self):
-        return self._obj.__iter__()
-
-    def __len__(self):
-        return len(self._obj)
-
-
-class ReversedPackageDAG(PackageDAG):
-    """Representation of Package dependencies in the reverse order.
-
-    Similar to it's super class `PackageDAG`, the underlying datastructure is a dict, but here the keys are expected to
-    be of type `ReqPackage` and each item in the values of type `DistPackage`.
-
-    Typically, this object will be obtained by calling `PackageDAG.reverse`.
-    """
-
-    def reverse(self):
-        """
-        Reverse the already reversed DAG to get the PackageDAG again
-
-        :returns: reverse of the reversed DAG
-        :rtype: PackageDAG
-        """
-        m = defaultdict(list)
-        child_keys = {r.key for r in chain.from_iterable(self._obj.values())}
-        for k, vs in self._obj.items():
-            for v in vs:
-                try:
-                    node = [p for p in m.keys() if p.key == v.key][0]
-                except IndexError:
-                    node = v.as_parent_of(None)
-                m[node].append(k)
-            if k.key not in child_keys:
-                m[k.dist] = []
-        return PackageDAG(dict(m))
-
-
-def render_text(tree, list_all=True, frozen=False):
-    """Print tree as text on console
-
-    :param dict tree: the package tree
-    :param bool list_all: whether to list all the pgks at the root level or only those that are the sub-dependencies
-    :param bool frozen: show the names of the pkgs in the output that's favourable to pip --freeze
-    :returns: None
-
-    """
-    tree = tree.sort()
-    nodes = tree.keys()
-    branch_keys = {r.key for r in chain.from_iterable(tree.values())}
-    use_bullets = not frozen
-
-    if not list_all:
-        nodes = [p for p in nodes if p.key not in branch_keys]
-
-    def aux(node, parent=None, indent=0, cur_chain=None):
-        cur_chain = cur_chain or []
-        node_str = node.render(parent, frozen)
-        if parent:
-            prefix = " " * indent + ("- " if use_bullets else "")
-            node_str = prefix + node_str
-        result = [node_str]
-        children = [
-            aux(c, node, indent=indent + 2, cur_chain=cur_chain + [c.project_name])
-            for c in tree.get_children(node.key)
-            if c.project_name not in cur_chain
-        ]
-        result += list(chain.from_iterable(children))
-        return result
-
-    lines = chain.from_iterable([aux(p) for p in nodes])
-    print("\n".join(lines))
-
-
-def render_json(tree, indent):
-    """
-    Converts the tree into a flat json representation.
-
-    The json repr will be a list of hashes, each hash having 2 fields:
-      - package
-      - dependencies: list of dependencies
-
-    :param dict tree: dependency tree
-    :param int indent: no. of spaces to indent json
-    :returns: json representation of the tree
-    :rtype: str
-    """
-    tree = tree.sort()
-    return json.dumps(
-        [{"package": k.as_dict(), "dependencies": [v.as_dict() for v in vs]} for k, vs in tree.items()], indent=indent
-    )
-
-
-def render_json_tree(tree, indent):
-    """
-    Converts the tree into a nested json representation.
-
-    The json repr will be a list of hashes, each hash having the following fields:
-
-      - package_name
-      - key
-      - required_version
-      - installed_version
-      - dependencies: list of dependencies
-
-    :param dict tree: dependency tree
-    :param int indent: no. of spaces to indent json
-    :returns: json representation of the tree
-    :rtype: str
-    """
-    tree = tree.sort()
-    branch_keys = {r.key for r in chain.from_iterable(tree.values())}
-    nodes = [p for p in tree.keys() if p.key not in branch_keys]
-
-    def aux(node, parent=None, cur_chain=None):
-        if cur_chain is None:
-            cur_chain = [node.project_name]
-
-        d = node.as_dict()
-        if parent:
-            d["required_version"] = node.version_spec if node.version_spec else "Any"
-        else:
-            d["required_version"] = d["installed_version"]
-
-        d["dependencies"] = [
-            aux(c, parent=node, cur_chain=cur_chain + [c.project_name])
-            for c in tree.get_children(node.key)
-            if c.project_name not in cur_chain
-        ]
-
-        return d
-
-    return json.dumps([aux(p) for p in nodes], indent=indent)
-
-
-def render_mermaid(tree) -> str:
-    """Produce a Mermaid flowchart from the dependency graph.
-
-    :param dict tree: dependency graph
-    """
-    # List of reserved keywords in Mermaid that cannot be used as node names.
-    # See: https://github.com/mermaid-js/mermaid/issues/4182#issuecomment-1454787806
-    reserved_ids: set[str] = {
-        "C4Component",
-        "C4Container",
-        "C4Deployment",
-        "C4Dynamic",
-        "_blank",
-        "_parent",
-        "_self",
-        "_top",
-        "call",
-        "class",
-        "classDef",
-        "click",
-        "end",
-        "flowchart",
-        "flowchart-v2",
-        "graph",
-        "interpolate",
-        "linkStyle",
-        "style",
-        "subgraph",
-    }
-    node_ids_map: dict[str:str] = {}
-
-    def mermaid_id(key: str) -> str:
-        """Returns a valid Mermaid node ID from a string."""
-        # If we have already seen this key, return the canonical ID.
-        canonical_id = node_ids_map.get(key)
-        if canonical_id is not None:
-            return canonical_id
-        # If the key is not a reserved keyword, return it as is, and update the map.
-        if key not in reserved_ids:
-            node_ids_map[key] = key
-            return key
-        # If the key is a reserved keyword, append a number to it.
-        number = 0
-        while True:
-            new_id = f"{key}_{number}"
-            if new_id not in node_ids_map:
-                node_ids_map[key] = new_id
-                return new_id
-            number += 1
-
-    # Use a sets to avoid duplicate entries.
-    nodes: set[str] = set()
-    edges: set[str] = set()
-
-    if isinstance(tree, ReversedPackageDAG):
-        for package, reverse_dependencies in tree.items():
-            package_label = "\\n".join(
-                (package.project_name, "(missing)" if package.is_missing else package.installed_version)
-            )
-            package_key = mermaid_id(package.key)
-            nodes.add(f'{package_key}["{package_label}"]')
-            for reverse_dependency in reverse_dependencies:
-                edge_label = reverse_dependency.req.version_spec or "any"
-                reverse_dependency_key = mermaid_id(reverse_dependency.key)
-                edges.add(f'{package_key} -- "{edge_label}" --> {reverse_dependency_key}')
-    else:
-        for package, dependencies in tree.items():
-            package_label = "\\n".join((package.project_name, package.version))
-            package_key = mermaid_id(package.key)
-            nodes.add(f'{package_key}["{package_label}"]')
-            for dependency in dependencies:
-                edge_label = dependency.version_spec or "any"
-                dependency_key = mermaid_id(dependency.key)
-                if dependency.is_missing:
-                    dependency_label = f"{dependency.project_name}\\n(missing)"
-                    nodes.add(f'{dependency_key}["{dependency_label}"]:::missing')
-                    edges.add(f"{package_key} -.-> {dependency_key}")
-                else:
-                    edges.add(f'{package_key} -- "{edge_label}" --> {dependency_key}')
-
-    # Produce the Mermaid Markdown.
-    indent = " " * 4
-    output = dedent(
-        f"""\
-        flowchart TD
-        {indent}classDef missing stroke-dasharray: 5
-        """
-    )
-    # Sort the nodes and edges to make the output deterministic.
-    output += indent
-    output += f"\n{indent}".join(node for node in sorted(nodes))
-    output += "\n" + indent
-    output += f"\n{indent}".join(edge for edge in sorted(edges))
-    output += "\n"
-    return output
-
-
-def dump_graphviz(tree, output_format="dot", is_reverse=False):
-    """Output dependency graph as one of the supported GraphViz output formats.
-
-    :param dict tree: dependency graph
-    :param string output_format: output format
-    :param bool is_reverse: reverse or not
-    :returns: representation of tree in the specified output format
-    :rtype: str or binary representation depending on the output format
-
-    """
-    try:
-        from graphviz import Digraph
-    except ImportError:
-        print("graphviz is not available, but necessary for the output " "option. Please install it.", file=sys.stderr)
-        sys.exit(1)
-
-    try:
-        from graphviz import parameters
-    except ImportError:
-        from graphviz import backend
-
-        valid_formats = backend.FORMATS
-        print(
-            "Deprecation warning! Please upgrade graphviz to version >=0.18.0 "
-            "Support for older versions will be removed in upcoming release",
-            file=sys.stderr,
-        )
-    else:
-        valid_formats = parameters.FORMATS
-
-    if output_format not in valid_formats:
-        print(f"{output_format} is not a supported output format.", file=sys.stderr)
-        print(f"Supported formats are: {', '.join(sorted(valid_formats))}", file=sys.stderr)
-        sys.exit(1)
-
-    graph = Digraph(format=output_format)
-
-    if not is_reverse:
-        for pkg, deps in tree.items():
-            pkg_label = f"{pkg.project_name}\\n{pkg.version}"
-            graph.node(pkg.key, label=pkg_label)
-            for dep in deps:
-                edge_label = dep.version_spec or "any"
-                if dep.is_missing:
-                    dep_label = f"{dep.project_name}\\n(missing)"
-                    graph.node(dep.key, label=dep_label, style="dashed")
-                    graph.edge(pkg.key, dep.key, style="dashed")
-                else:
-                    graph.edge(pkg.key, dep.key, label=edge_label)
-    else:
-        for dep, parents in tree.items():
-            dep_label = f"{dep.project_name}\\n{dep.installed_version}"
-            graph.node(dep.key, label=dep_label)
-            for parent in parents:
-                # req reference of the dep associated with this
-                # particular parent package
-                req_ref = parent.req
-                edge_label = req_ref.version_spec or "any"
-                graph.edge(dep.key, parent.key, label=edge_label)
-
-    # Allow output of dot format, even if GraphViz isn't installed.
-    if output_format == "dot":
-        # Emulates graphviz.dot.Dot.__iter__() to force the sorting of graph.body.
-        # Fixes https://github.com/tox-dev/pipdeptree/issues/188
-        # That way we can guarantee the output of the dot format is deterministic
-        # and stable.
-        return "".join([tuple(graph)[0]] + sorted(graph.body) + [graph._tail])
-
-    # As it's unknown if the selected output format is binary or not, try to
-    # decode it as UTF8 and only print it out in binary if that's not possible.
-    try:
-        return graph.pipe().decode("utf-8")
-    except UnicodeDecodeError:
-        return graph.pipe()
-
-
-def print_graphviz(dump_output):
-    """
-    Dump the data generated by GraphViz to stdout.
-
-    :param dump_output: The output from dump_graphviz
-    """
-    if hasattr(dump_output, "encode"):
-        print(dump_output)
-    else:
-        with os.fdopen(sys.stdout.fileno(), "wb") as bytestream:
-            bytestream.write(dump_output)
-
-
-def conflicting_deps(tree):
-    """
-    Returns dependencies which are not present or conflict with the requirements of other packages.
-
-    e.g. will warn if pkg1 requires pkg2==2.0 and pkg2==1.0 is installed
-
-    :param tree: the requirements tree (dict)
-    :returns: dict of DistPackage -> list of unsatisfied/unknown ReqPackage
-    :rtype: dict
-    """
-    conflicting = defaultdict(list)
-    for p, rs in tree.items():
-        for req in rs:
-            if req.is_conflicting():
-                conflicting[p].append(req)
-    return conflicting
-
-
-def render_conflicts_text(conflicts):
-    if conflicts:
-        print("Warning!!! Possibly conflicting dependencies found:", file=sys.stderr)
-        # Enforce alphabetical order when listing conflicts
-        pkgs = sorted(conflicts.keys())
-        for p in pkgs:
-            pkg = p.render_as_root(False)
-            print(f"* {pkg}", file=sys.stderr)
-            for req in conflicts[p]:
-                req_str = req.render_as_branch(False)
-                print(f" - {req_str}", file=sys.stderr)
-
-
-def cyclic_deps(tree):
-    """
-    Return cyclic dependencies as list of tuples
-
-    :param PackageDAG tree: package tree/dag
-    :returns: list of tuples representing cyclic dependencies
-    :rtype: list
-    """
-    index = {p.key: {r.key for r in rs} for p, rs in tree.items()}
-    cyclic = []
-    for p, rs in tree.items():
-        for r in rs:
-            if p.key in index.get(r.key, []):
-                p_as_dep_of_r = [x for x in tree.get(tree.get_node_as_parent(r.key)) if x.key == p.key][0]
-                cyclic.append((p, r, p_as_dep_of_r))
-    return cyclic
-
-
-def render_cycles_text(cycles):
-    if cycles:
-        print("Warning!! Cyclic dependencies found:", file=sys.stderr)
-        # List in alphabetical order of the dependency that's cycling
-        # (2nd item in the tuple)
-        cycles = sorted(cycles, key=lambda xs: xs[1].key)
-        for a, b, c in cycles:
-            print(f"* {a.project_name} => {b.project_name} => {c.project_name}", file=sys.stderr)
-
-
-def get_parser():
-    parser = argparse.ArgumentParser(description="Dependency tree of the installed python packages")
-    parser.add_argument("-v", "--version", action="version", version=f"{__version__}")
-    parser.add_argument("-f", "--freeze", action="store_true", help="Print names so as to write freeze files")
-    parser.add_argument(
-        "--python",
-        default=sys.executable,
-        help="Python to use to look for packages in it (default: where" " installed)",
-    )
-    parser.add_argument("-a", "--all", action="store_true", help="list all deps at top level")
-    parser.add_argument(
-        "-l",
-        "--local-only",
-        action="store_true",
-        help="If in a virtualenv that has global access " "do not show globally installed packages",
-    )
-    parser.add_argument("-u", "--user-only", action="store_true", help="Only show installations in the user site dir")
-    parser.add_argument(
-        "-w",
-        "--warn",
-        action="store",
-        dest="warn",
-        nargs="?",
-        default="suppress",
-        choices=("silence", "suppress", "fail"),
-        help=(
-            'Warning control. "suppress" will show warnings '
-            "but return 0 whether or not they are present. "
-            '"silence" will not show warnings at all and '
-            'always return 0. "fail" will show warnings and '
-            "return 1 if any are present. The default is "
-            '"suppress".'
-        ),
-    )
-    parser.add_argument(
-        "-r",
-        "--reverse",
-        action="store_true",
-        default=False,
-        help=(
-            "Shows the dependency tree in the reverse fashion "
-            "ie. the sub-dependencies are listed with the "
-            "list of packages that need them under them."
-        ),
-    )
-    parser.add_argument(
-        "-p",
-        "--packages",
-        help=(
-            "Comma separated list of select packages to show in the output. "
-            "Wildcards are supported, like 'somepackage.*'. "
-            "If set, --all will be ignored."
-        ),
-    )
-    parser.add_argument(
-        "-e",
-        "--exclude",
-        help=(
-            "Comma separated list of select packages to exclude from the output. "
-            "Wildcards are supported, like 'somepackage.*'. "
-            "If set, --all will be ignored."
-        ),
-        metavar="PACKAGES",
-    )
-    parser.add_argument(
-        "-j",
-        "--json",
-        action="store_true",
-        default=False,
-        help=(
-            "Display dependency tree as json. This will yield "
-            '"raw" output that may be used by external tools. '
-            "This option overrides all other options."
-        ),
-    )
-    parser.add_argument(
-        "--json-tree",
-        action="store_true",
-        default=False,
-        help=(
-            "Display dependency tree as json which is nested "
-            "the same way as the plain text output printed by default. "
-            "This option overrides all other options (except --json)."
-        ),
-    )
-    parser.add_argument(
-        "--mermaid",
-        action="store_true",
-        default=False,
-        help=("Display dependency tree as a Maermaid graph. " "This option overrides all other options."),
-    )
-    parser.add_argument(
-        "--graph-output",
-        dest="output_format",
-        help=(
-            "Print a dependency graph in the specified output "
-            "format. Available are all formats supported by "
-            "GraphViz, e.g.: dot, jpeg, pdf, png, svg"
-        ),
-    )
-    return parser
-
-
-def _get_args():
-    parser = get_parser()
-    return parser.parse_args()
-
-
-def handle_non_host_target(args):
-    of_python = os.path.abspath(args.python)
-    # if target is not current python re-invoke it under the actual host
-    if of_python != os.path.abspath(sys.executable):
-        # there's no way to guarantee that graphviz is available, so refuse
-        if args.output_format:
-            print("graphviz functionality is not supported when querying" " non-host python", file=sys.stderr)
-            raise SystemExit(1)
-        argv = sys.argv[1:]  # remove current python executable
-        for py_at, value in enumerate(argv):
-            if value == "--python":
-                del argv[py_at]
-                del argv[py_at]
-            elif value.startswith("--python"):
-                del argv[py_at]
-
-        main_file = inspect.getsourcefile(sys.modules[__name__])
-        with tempfile.TemporaryDirectory() as project:
-            dest = os.path.join(project, "pipdeptree")
-            shutil.copytree(os.path.dirname(main_file), dest)
-            # invoke from an empty folder to avoid cwd altering sys.path
-            env = os.environ.copy()
-            env["PYTHONPATH"] = project
-            cmd = [of_python, "-m", "pipdeptree"]
-            cmd.extend(argv)
-            return subprocess.call(cmd, cwd=project, env=env)
-    return None
-
-
-def get_installed_distributions(local_only=False, user_only=False):
-    try:
-        from pip._internal.metadata import pkg_resources
-    except ImportError:
-        # For backward compatibility with python ver. 2.7 and pip
-        # version 20.3.4 (the latest pip version that works with python
-        # version 2.7)
-        from pip._internal.utils import misc
-
-        return misc.get_installed_distributions(local_only=local_only, user_only=user_only)
-    else:
-        dists = pkg_resources.Environment.from_paths(None).iter_installed_distributions(
-            local_only=local_only, skip=(), user_only=user_only
-        )
-        return [d._dist for d in dists]
-
-
-def main():
-    args = _get_args()
-    result = handle_non_host_target(args)
-    if result is not None:
-        return result
-
-    pkgs = get_installed_distributions(local_only=args.local_only, user_only=args.user_only)
-
-    tree = PackageDAG.from_pkgs(pkgs)
-
-    is_text_output = not any([args.json, args.json_tree, args.output_format])
-
-    return_code = 0
-
-    # Before any reversing or filtering, show warnings to console
-    # about possibly conflicting or cyclic deps if found and warnings
-    # are enabled (i.e. only if output is to be printed to console)
-    if is_text_output and args.warn != "silence":
-        conflicts = conflicting_deps(tree)
-        if conflicts:
-            render_conflicts_text(conflicts)
-            print("-" * 72, file=sys.stderr)
-
-        cycles = cyclic_deps(tree)
-        if cycles:
-            render_cycles_text(cycles)
-            print("-" * 72, file=sys.stderr)
-
-        if args.warn == "fail" and (conflicts or cycles):
-            return_code = 1
-
-    # Reverse the tree (if applicable) before filtering, thus ensuring
-    # that the filter will be applied on ReverseTree
-    if args.reverse:
-        tree = tree.reverse()
-
-    show_only = set(args.packages.split(",")) if args.packages else None
-    exclude = set(args.exclude.split(",")) if args.exclude else None
-
-    if show_only is not None or exclude is not None:
-        tree = tree.filter(show_only, exclude)
-
-    if args.json:
-        print(render_json(tree, indent=4))
-    elif args.json_tree:
-        print(render_json_tree(tree, indent=4))
-    elif args.mermaid:
-        print(render_mermaid(tree))
-    elif args.output_format:
-        output = dump_graphviz(tree, output_format=args.output_format, is_reverse=args.reverse)
-        print_graphviz(output)
-    else:
-        render_text(tree, args.all, args.freeze)
-
-    return return_code
-
-#
-# eric-ide modification: entry point to get one self-contained script
-#
-
-if __name__ == '__main__':
-    sys.exit(main())

eric ide

mercurial