diff --git a/johnnydep/__init__.py b/johnnydep/__init__.py index c3d1e5d..f170fa6 100644 --- a/johnnydep/__init__.py +++ b/johnnydep/__init__.py @@ -1,5 +1,5 @@ """Display dependency tree of Python distribution""" -__version__ = "1.18.0" +__version__ = "1.19.0" from johnnydep.lib import * diff --git a/johnnydep/cli.py b/johnnydep/cli.py index 0a393d6..f0b0661 100644 --- a/johnnydep/cli.py +++ b/johnnydep/cli.py @@ -22,6 +22,7 @@ ("requires", "Immediate dependencies"), ("required_by", "Parent(s) in the tree"), ("import_names", "Python imports provided (top-level names only)"), + ("console_scripts", "Entry points in the console_scripts group"), ("homepage", "Project URL"), ("extras_available", "Optional extensions available for the distribution"), ("extras_requested", "Optional extensions parsed from requirement specifier"), diff --git a/johnnydep/compat.py b/johnnydep/compat.py index 9181b06..acb03b3 100644 --- a/johnnydep/compat.py +++ b/johnnydep/compat.py @@ -62,6 +62,12 @@ def urlretrieve(url, filename, data=None, auth=None): try: - from importlib.metadata import distribution, PackageNotFoundError + from importlib.metadata import distribution, PackageNotFoundError, PathDistribution except ImportError: - from importlib_metadata import distribution, PackageNotFoundError + from importlib_metadata import distribution, PackageNotFoundError, PathDistribution + + +try: + from zipfile import Path as zipfile_path +except ImportError: + from zipfile39 import Path as zipfile_path diff --git a/johnnydep/lib.py b/johnnydep/lib.py index 57c8c92..9c199e3 100644 --- a/johnnydep/lib.py +++ b/johnnydep/lib.py @@ -29,6 +29,8 @@ from johnnydep.compat import distribution from johnnydep.compat import oyaml from johnnydep.compat import PackageNotFoundError +from johnnydep.compat import PathDistribution +from johnnydep.compat import zipfile_path from johnnydep.dot import jd2dot from johnnydep.util import CircularMarker @@ -70,6 +72,7 @@ def __init__(self, req_string, parent=None, index_url=None, env=None, extra_inde self.req = requirements.Requirement(self.name + sep + extras + self.specifier) self.import_names = _discover_import_names(fname) self.metadata = _extract_metadata(fname) + self.entry_points = _discover_entry_points(fname) self._from_fname = os.path.abspath(fname) else: self._from_fname = None @@ -78,7 +81,7 @@ def __init__(self, req_string, parent=None, index_url=None, env=None, extra_inde self.specifier = str(self.req.specifier) log.debug("fetching best wheel") try: - self.import_names, self.metadata = _get_info( + self.import_names, self.metadata, self.entry_points = _get_info( dist_name=req_string, index_url=index_url, env=env, @@ -89,6 +92,7 @@ def __init__(self, req_string, parent=None, index_url=None, env=None, extra_inde raise self.import_names = None self.metadata = {} + self.entry_points = None self.error = err self.extras_requested = sorted(self.req.extras) @@ -249,6 +253,11 @@ def extras_available(self): def project_name(self): return self.metadata.get("name", self.name) + @property + def console_scripts(self): + eps = [ep for ep in self.entry_points or [] if ep.group == "console_scripts"] + return ", ".join(["{} = {}".format(ep.name, ep.value) for ep in eps]) + @property def pinned(self): if self.extras_requested: @@ -420,9 +429,9 @@ def flatten_deps(johnnydist): def _discover_import_names(whl_file): log = logger.bind(whl_file=whl_file) - logger.debug("finding import names") - zipfile = ZipFile(file=whl_file) - namelist = zipfile.namelist() + log.debug("finding import names") + zf = ZipFile(file=whl_file) + namelist = zf.namelist() try: [top_level_fname] = [x for x in namelist if x.endswith("top_level.txt")] except ValueError: @@ -447,13 +456,25 @@ def _discover_import_names(whl_file): # found a top level module public_names.append(name) else: - all_names = zipfile.read(top_level_fname).decode("utf-8").strip().splitlines() + all_names = zf.read(top_level_fname).decode("utf-8").strip().splitlines() public_names = [n for n in all_names if not n.startswith("_")] - return public_names + result = [n.replace("/", ".") for n in public_names] + return result + + +def _discover_entry_points(whl_file): + log = logger.bind(whl_file=whl_file) + log.debug("finding entry points") + parts = os.path.basename(whl_file).split("-") + metadata_path = "-".join(parts[:2]) + ".dist-info/" + zf_path = zipfile_path(whl_file, metadata_path) + path_dist = PathDistribution(zf_path) + return path_dist.entry_points def _extract_metadata(whl_file): - logger.debug("searching metadata", whl_file=whl_file) + log = logger.bind(whl_file=whl_file) + log.debug("searching metadata", whl_file=whl_file) info = pkginfo.get_metadata(whl_file) if info is None: raise JohnnyError("failed to get metadata") @@ -486,10 +507,11 @@ def _get_info(dist_name, index_url=None, env=None, extra_index_url=None): # downloaded file can be cleaned up immediately import_names = _discover_import_names(dist_path) metadata = _extract_metadata(dist_path) + entry_points = _discover_entry_points(dist_path) finally: log.debug("removing scratch", tmpdir=tmpdir) rmtree(tmpdir, ignore_errors=True) - return import_names, metadata + return import_names, metadata, entry_points # TODO: multi-line progress bar? diff --git a/requirements-dev.txt b/requirements-dev.txt index e94af38..ad3f68c 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -6,5 +6,5 @@ pytest-raisin pytest-socket coverage wheel -whl >= 0.0.3 +whl >= 0.0.4 wimpy == 0.3 # just something we can pin diff --git a/setup.py b/setup.py index 3131e76..bcb6e4f 100644 --- a/setup.py +++ b/setup.py @@ -31,6 +31,7 @@ "wheel >= 0.32.0", "pkginfo >= 1.4.2", "importlib_metadata ; python_version < '3.7'", + "zipfile39 ; python_version < '3.9'", ], entry_points={ "console_scripts": [ diff --git a/tests/test_cli.py b/tests/test_cli.py index 4320b29..1d75eda 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -148,6 +148,7 @@ def test_all_fields_toml_out(mocker, capsys, make_dist): requires = [] required_by = [] import_names = [ "that",] + console_scripts = "" homepage = "https://www.example.org/default" extras_available = [] extras_requested = [] diff --git a/tests/test_lib.py b/tests/test_lib.py index 4c102eb..98997bd 100644 --- a/tests/test_lib.py +++ b/tests/test_lib.py @@ -537,3 +537,15 @@ def test_local_whl_json(make_dist): assert link.startswith("file://") assert link.endswith("loc-0.1.2-py2.py3-none-any.whl") assert result["versions_available"] == ["0.1.1", "0.1.2", "0.1.3"] + + +def test_entry_points(make_dist): + # https://packaging.python.org/en/latest/specifications/entry-points/ + entry_points = {"console_scripts": ["my-script = mypkg.mymod:foo"]} + make_dist(name="example", entry_points=entry_points) + dist = JohnnyDist("example") + [ep] = dist.entry_points + assert ep.name == "my-script" + assert ep.group == "console_scripts" + assert ep.value == "mypkg.mymod:foo" + assert dist.console_scripts == "my-script = mypkg.mymod:foo"