From 464400a43fdf8ba9b7cc44d5b3c7c14c0f592ba2 Mon Sep 17 00:00:00 2001 From: Bane Sullivan Date: Fri, 20 Oct 2023 00:27:14 -0700 Subject: [PATCH 01/14] Autogenerate reports CLI --- README.md | 17 ++++++++++++---- scooby/__main__.py | 50 +++++++++++++++++++++++++++++++--------------- 2 files changed, 47 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index 25cdb96..64c6958 100644 --- a/README.md +++ b/README.md @@ -364,15 +364,24 @@ scooby ``` in a terminal will display the default report. You can also use it to show the -scooby-report of another package, if that package has scooby implemented as -suggested above, using `packagename.Report()`. As an example, to print the -report of pyvista you can run +scooby-report of another package: if that package has scooby implemented as +suggested above, using `packagename.Report()`, otherwise it will get that +packages dependences from its distribution to generate a report. + +As an example, to print the report of pyvista you can run ```bash scooby --report pyvista ``` -which will show the report of PyVista. +which will show the report implemented in PyVista. + +Or you could generate a report of a package and its dependencies that hasn't +implemented a report class: + +```bash +scooby --report matplotlib +``` Simply type diff --git a/scooby/__main__.py b/scooby/__main__.py index ec56dea..4f78b1e 100644 --- a/scooby/__main__.py +++ b/scooby/__main__.py @@ -4,6 +4,8 @@ import sys from typing import Any, Dict, List, Optional +import pkg_resources + import scooby from scooby.report import Report @@ -31,8 +33,8 @@ def main(args: Optional[List[str]] = None): parser.add_argument( "--no-opt", action="store_true", - default=False, - help="do not show the default optional packages", + default=None, + help="do not show the default optional packages. Defaults to True if using --report and defaults to False otherwise.", ) # arg: Sort @@ -45,7 +47,7 @@ def main(args: Optional[List[str]] = None): # arg: Version parser.add_argument( - "--version", action="store_true", default=False, help="only display scooby version" + "--version", "-v", action="store_true", default=False, help="only display scooby version" ) # Call act with command line arguments as dict. @@ -59,32 +61,48 @@ def act(args_dict: Dict[str, Any]) -> None: print(f"scooby v{scooby.__version__}") return - # Report of another package. report = args_dict.pop('report') + no_opt = args_dict.pop('no_opt') + packages = args_dict.pop('packages') + + if no_opt is None and report is None: + no_opt = False + elif no_opt is None: + no_opt = True + + # Report of another package. if report: try: module = importlib.import_module(report) except ImportError: print(f"Package `{report}` could not be imported.", file=sys.stderr) - return + sys.exit(1) try: print(module.Report()) + return except AttributeError: - print(f"Package `{report}` has no attribute `Report()`.", file=sys.stderr) + pass + + try: + # Generate our own report based on package requirements + dist = pkg_resources.get_distribution(report) + dist.requires() + packages = [report] + [pkg.name for pkg in dist.requires()] + packages + except pkg_resources.DistributionNotFound: + print(f"Package `{report}` has no distribution or Report class.", file=sys.stderr) + sys.exit(1) - # Scooby report with additional options. - else: - # Collect input. - inp = {'additional': args_dict['packages'], 'sort': args_dict['sort']} + # Collect input. + inp = {'additional': packages, 'sort': args_dict['sort']} - # Define optional as empty list if no-opt. - if args_dict['no_opt']: - inp['optional'] = [] + # Define optional as empty list if no-opt. + if no_opt: + inp['optional'] = [] - # Print the report. - print(Report(**inp)) + # Print the report. + print(Report(**inp)) if __name__ == "__main__": - sys.exit(main()) + main() From 29968f65df578b0880517031c45311f88dcc496b Mon Sep 17 00:00:00 2001 From: Bane Sullivan Date: Fri, 20 Oct 2023 00:41:35 -0700 Subject: [PATCH 02/14] pkg_resources is optional fallback --- scooby/__main__.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/scooby/__main__.py b/scooby/__main__.py index 4f78b1e..e5d0ac9 100644 --- a/scooby/__main__.py +++ b/scooby/__main__.py @@ -4,8 +4,6 @@ import sys from typing import Any, Dict, List, Optional -import pkg_resources - import scooby from scooby.report import Report @@ -85,10 +83,18 @@ def act(args_dict: Dict[str, Any]) -> None: pass try: + import pkg_resources + # Generate our own report based on package requirements dist = pkg_resources.get_distribution(report) dist.requires() packages = [report] + [pkg.name for pkg in dist.requires()] + packages + except ImportError: + print( + f"Package `{report}` has no report and `pkg_resources` could not be used to autogenerate one.", + file=sys.stderr, + ) + sys.exit(1) except pkg_resources.DistributionNotFound: print(f"Package `{report}` has no distribution or Report class.", file=sys.stderr) sys.exit(1) From 375e340091aadaac89a2a360e5b98f96fbbb62f8 Mon Sep 17 00:00:00 2001 From: Bane Sullivan Date: Fri, 20 Oct 2023 00:47:32 -0700 Subject: [PATCH 03/14] More tests --- tests/test_scooby.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/tests/test_scooby.py b/tests/test_scooby.py index dfb7805..b55c014 100644 --- a/tests/test_scooby.py +++ b/tests/test_scooby.py @@ -252,3 +252,19 @@ def rep_comp(inp): ret = script_runner.run(['python', os.path.join('scooby', '__main__.py'), '--version']) assert ret.success assert "scooby v" in ret.stdout + + # default: scooby-Report for matplotlibe + ret = script_runner.run(['scooby', '--report', 'matplotlib']) + assert ret.success + assert "matplotlib" in ret.stdout + assert "cycler" in ret.stdout + + # handle error -- no distribution + ret = script_runner.run(['scooby', '--report', 'pathlib']) + assert not ret.success + assert "no distribution" in ret.stderr + + # handle error -- not found + ret = script_runner.run(['scooby', '--report', 'foobar']) + assert not ret.success + assert "could not be imported" in ret.stderr From 122fa2e5e3c1f92387f2f226016480838db0db42 Mon Sep 17 00:00:00 2001 From: Bane Sullivan Date: Fri, 20 Oct 2023 00:51:10 -0700 Subject: [PATCH 04/14] -r alias --- scooby/__main__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scooby/__main__.py b/scooby/__main__.py index e5d0ac9..b042cbe 100644 --- a/scooby/__main__.py +++ b/scooby/__main__.py @@ -24,7 +24,7 @@ def main(args: Optional[List[str]] = None): # arg: Report of a package parser.add_argument( - "--report", default=None, type=str, help=("print `Report()` of this package") + "--report", "-r", default=None, type=str, help=("print `Report()` of this package") ) # arg: Sort From 6739fc763c21cedacaa20df4a66e8cb6b6773efb Mon Sep 17 00:00:00 2001 From: Bane Sullivan Date: Fri, 20 Oct 2023 00:53:06 -0700 Subject: [PATCH 05/14] Fix test --- tests/test_scooby.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/test_scooby.py b/tests/test_scooby.py index b55c014..3aaac20 100644 --- a/tests/test_scooby.py +++ b/tests/test_scooby.py @@ -254,10 +254,10 @@ def rep_comp(inp): assert "scooby v" in ret.stdout # default: scooby-Report for matplotlibe - ret = script_runner.run(['scooby', '--report', 'matplotlib']) + ret = script_runner.run(['scooby', '--report', 'pytest']) assert ret.success - assert "matplotlib" in ret.stdout - assert "cycler" in ret.stdout + assert "pytest" in ret.stdout + assert "iniconfig" in ret.stdout # handle error -- no distribution ret = script_runner.run(['scooby', '--report', 'pathlib']) From 441634bafcef219edd57d9d2b91cde5fc9dd604e Mon Sep 17 00:00:00 2001 From: Bane Sullivan Date: Fri, 20 Oct 2023 16:25:41 -0700 Subject: [PATCH 06/14] Improvements --- scooby/__main__.py | 24 +++++++++--------------- scooby/report.py | 24 ++++++++++++++++++++++++ tests/test_scooby.py | 2 +- 3 files changed, 34 insertions(+), 16 deletions(-) diff --git a/scooby/__main__.py b/scooby/__main__.py index b042cbe..fa26adf 100644 --- a/scooby/__main__.py +++ b/scooby/__main__.py @@ -5,7 +5,7 @@ from typing import Any, Dict, List, Optional import scooby -from scooby.report import Report +from scooby.report import Report, get_distribution_dependencies def main(args: Optional[List[str]] = None): @@ -63,10 +63,11 @@ def act(args_dict: Dict[str, Any]) -> None: no_opt = args_dict.pop('no_opt') packages = args_dict.pop('packages') - if no_opt is None and report is None: - no_opt = False - elif no_opt is None: - no_opt = True + if no_opt is None: + if report is None: + no_opt = False + else: + no_opt = True # Report of another package. if report: @@ -83,21 +84,14 @@ def act(args_dict: Dict[str, Any]) -> None: pass try: - import pkg_resources - - # Generate our own report based on package requirements - dist = pkg_resources.get_distribution(report) - dist.requires() - packages = [report] + [pkg.name for pkg in dist.requires()] + packages + dist_deps = get_distribution_dependencies(report) + packages = [report, *dist_deps, *packages] except ImportError: print( - f"Package `{report}` has no report and `pkg_resources` could not be used to autogenerate one.", + f"Package `{report}` has no Report class and `pkg_resources` could not be used to autogenerate one.", file=sys.stderr, ) sys.exit(1) - except pkg_resources.DistributionNotFound: - print(f"Package `{report}` has no distribution or Report class.", file=sys.stderr) - sys.exit(1) # Collect input. inp = {'additional': packages, 'sort': args_dict['sort']} diff --git a/scooby/report.py b/scooby/report.py index b01a4e1..2c4ba65 100644 --- a/scooby/report.py +++ b/scooby/report.py @@ -512,3 +512,27 @@ def platform() -> ModuleType: import platform return platform + + +def get_distribution_dependencies(dist_name: str): + """Get the dependencies of a specified package distribution. + + Parameters + ---------- + dist_name : str + Name of the package distribution. + + Returns + ------- + dependencies : list + List of dependency names. + """ + try: + import pkg_resources + except ImportError: + raise ImportError('Package `pkg_resources` could not be imported.') + try: + dist = pkg_resources.get_distribution(dist_name) + except pkg_resources.DistributionNotFound: + raise ImportError(f"Package `{dist_name}` has no distribution.") + return [pkg.name for pkg in dist.requires()] diff --git a/tests/test_scooby.py b/tests/test_scooby.py index 3aaac20..46c4e15 100644 --- a/tests/test_scooby.py +++ b/tests/test_scooby.py @@ -262,7 +262,7 @@ def rep_comp(inp): # handle error -- no distribution ret = script_runner.run(['scooby', '--report', 'pathlib']) assert not ret.success - assert "no distribution" in ret.stderr + assert "pkg_resources" in ret.stderr # handle error -- not found ret = script_runner.run(['scooby', '--report', 'foobar']) From d8243f9f86c0100f9a3d4ee2ac2e382db2a668b1 Mon Sep 17 00:00:00 2001 From: Bane Sullivan Date: Fri, 20 Oct 2023 16:29:14 -0700 Subject: [PATCH 07/14] Improve README --- README.md | 42 +++++++++++++++++++++++++++++++++--------- 1 file changed, 33 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 64c6958..0f65231 100644 --- a/README.md +++ b/README.md @@ -363,24 +363,49 @@ Scooby comes with a command-line interface. Simply typing scooby ``` -in a terminal will display the default report. You can also use it to show the -scooby-report of another package: if that package has scooby implemented as -suggested above, using `packagename.Report()`, otherwise it will get that -packages dependences from its distribution to generate a report. +in a terminal will display the default report. You can also use the CLI to show +the scooby Report of another package if that package has scooby implemented as +suggested above, using `packagename.Report()`. As an example, to print the report of pyvista you can run ```bash -scooby --report pyvista +scooby -r pyvista ``` which will show the report implemented in PyVista. -Or you could generate a report of a package and its dependencies that hasn't -implemented a report class: +The CLI can also generate a report based on the dependencies of a package's +distribution where that package hasn't implemented a report class. For example, +we can generate a Report on `matplotlib` and its dependencies: ```bash -scooby --report matplotlib +$ scooby -r matplotlib +-------------------------------------------------------------------------------- + Date: Fri Oct 20 16:28:13 2023 PDT + + OS : Darwin + CPU(s) : 8 + Machine : arm64 + Architecture : 64bit + RAM : 16.0 GiB + Environment : Python + File system : apfs + + Python 3.11.3 | packaged by conda-forge | (main, Apr 6 2023, 08:58:31) + [Clang 14.0.6 ] + + matplotlib : 3.7.1 + contourpy : 1.0.7 + cycler : 0.11.0 + fonttools : 4.39.4 + kiwisolver : 1.4.4 + numpy : 1.24.3 + packaging : 23.1 + pillow : 9.5.0 + pyparsing : 3.0.9 + python-dateutil : 2.8.2 +-------------------------------------------------------------------------------- ``` Simply type @@ -391,7 +416,6 @@ scooby --help to see all the possibilities. - ## Optional Requirements The following is a list of optional requirements and their purpose: From 15ba95f36839dc9b78e4c4b4abd24b560896f338 Mon Sep 17 00:00:00 2001 From: Bane Sullivan Date: Fri, 20 Oct 2023 16:31:06 -0700 Subject: [PATCH 08/14] Typos --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 0f65231..98b456b 100644 --- a/README.md +++ b/README.md @@ -364,8 +364,8 @@ scooby ``` in a terminal will display the default report. You can also use the CLI to show -the scooby Report of another package if that package has scooby implemented as -suggested above, using `packagename.Report()`. +the scooby Report of another package if that package has implemented a Report +class as suggested above, using `packagename.Report()`. As an example, to print the report of pyvista you can run @@ -373,11 +373,11 @@ As an example, to print the report of pyvista you can run scooby -r pyvista ``` -which will show the report implemented in PyVista. +which will show the Report implemented in PyVista. The CLI can also generate a report based on the dependencies of a package's -distribution where that package hasn't implemented a report class. For example, -we can generate a Report on `matplotlib` and its dependencies: +distribution where that package hasn't implemented a Report class. For example, +we can generate a Report for `matplotlib` and its dependencies: ```bash $ scooby -r matplotlib From 93098d31a79cc5edfad1040c99ba132a093e3299 Mon Sep 17 00:00:00 2001 From: Bane Sullivan Date: Fri, 20 Oct 2023 16:46:55 -0700 Subject: [PATCH 09/14] Create AutoReport class --- scooby/__init__.py | 3 ++- scooby/report.py | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/scooby/__init__.py b/scooby/__init__.py index 4120216..52bf6a0 100644 --- a/scooby/__init__.py +++ b/scooby/__init__.py @@ -22,12 +22,13 @@ meets_version, version_tuple, ) -from scooby.report import Report, get_version +from scooby.report import AutoReport, Report, get_version from scooby.tracker import TrackedReport, track_imports, untrack_imports doo = Report __all__ = [ + 'AutoReport', 'Report', 'TrackedReport', 'doo', diff --git a/scooby/report.py b/scooby/report.py index 2c4ba65..fb08c19 100644 --- a/scooby/report.py +++ b/scooby/report.py @@ -434,6 +434,41 @@ def to_dict(self) -> Dict[str, str]: return out +class AutoReport(Report): + """Auto-generate a scooby.Report for a package. + + This will check if the specified package has a ``Report`` class and use that or + fallback to generating a report based on the distribution requirements of the package. + """ + + def __init__(self, module, additional=None, ncol=3, text_width=80, sort=False): + """Initialize.""" + if not isinstance(module, (str, ModuleType)): + raise TypeError("Cannot generate report for type " "({})".format(type(module))) + + if isinstance(module, ModuleType): + module = module.__name__ + + try: + package = importlib.import_module(module) + if issubclass(package.Report, Report): + package.Report.__init__( + self, additional=additional, ncol=ncol, text_width=text_width, sort=sort + ) + except (AttributeError, TypeError): + # Autogenerate from distribution requirements + core = [module, *get_distribution_dependencies(module)] + Report.__init__( + self, + additional=additional, + core=core, + optional=[], + ncol=ncol, + text_width=text_width, + sort=sort, + ) + + # This functionaliy might also be of interest on its own. def get_version(module: Union[str, ModuleType]) -> Tuple[str, Optional[str]]: """Get the version of ``module`` by passing the package or it's name. From 74c809cbfa4df8300a2e296bdd2e4147d096d4f7 Mon Sep 17 00:00:00 2001 From: Bane Sullivan Date: Fri, 20 Oct 2023 17:02:27 -0700 Subject: [PATCH 10/14] Use importlib --- scooby/__main__.py | 2 +- scooby/report.py | 12 ++++-------- tests/test_scooby.py | 8 +++++++- 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/scooby/__main__.py b/scooby/__main__.py index fa26adf..6335f9b 100644 --- a/scooby/__main__.py +++ b/scooby/__main__.py @@ -88,7 +88,7 @@ def act(args_dict: Dict[str, Any]) -> None: packages = [report, *dist_deps, *packages] except ImportError: print( - f"Package `{report}` has no Report class and `pkg_resources` could not be used to autogenerate one.", + f"Package `{report}` has no Report class and `importlib` could not be used to autogenerate one.", file=sys.stderr, ) sys.exit(1) diff --git a/scooby/report.py b/scooby/report.py index fb08c19..87fbfdd 100644 --- a/scooby/report.py +++ b/scooby/report.py @@ -1,7 +1,7 @@ """The main module containing the `Report` class.""" import importlib -from importlib.metadata import PackageNotFoundError, version as importlib_version +from importlib.metadata import PackageNotFoundError, distribution, version as importlib_version import sys import time from types import ModuleType @@ -563,11 +563,7 @@ def get_distribution_dependencies(dist_name: str): List of dependency names. """ try: - import pkg_resources - except ImportError: - raise ImportError('Package `pkg_resources` could not be imported.') - try: - dist = pkg_resources.get_distribution(dist_name) - except pkg_resources.DistributionNotFound: + dist = distribution(dist_name) + except PackageNotFoundError: raise ImportError(f"Package `{dist_name}` has no distribution.") - return [pkg.name for pkg in dist.requires()] + return [pkg.split()[0] for pkg in dist.requires] diff --git a/tests/test_scooby.py b/tests/test_scooby.py index 46c4e15..46d9840 100644 --- a/tests/test_scooby.py +++ b/tests/test_scooby.py @@ -262,9 +262,15 @@ def rep_comp(inp): # handle error -- no distribution ret = script_runner.run(['scooby', '--report', 'pathlib']) assert not ret.success - assert "pkg_resources" in ret.stderr + assert "importlib" in ret.stderr # handle error -- not found ret = script_runner.run(['scooby', '--report', 'foobar']) assert not ret.success assert "could not be imported" in ret.stderr + + +def test_auto_report(): + report = scooby.AutoReport('pytest') + assert 'pytest' in report.packages + assert 'iniconfig' in report.packages From cbf2001944c582acb3e5eeaccdea5d14fe591637 Mon Sep 17 00:00:00 2001 From: Bane Sullivan Date: Fri, 20 Oct 2023 17:04:40 -0700 Subject: [PATCH 11/14] Fix output --- README.md | 74 +++++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 56 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index 98b456b..40f9729 100644 --- a/README.md +++ b/README.md @@ -257,6 +257,43 @@ installed, it will raise the following exception: `conda install -c conda-forge scooby`. ``` +### Autogenerate Reports for any Packages + +Scooby can automatically generate a Report for any package and its +distribution requirements with the `AutoReport` class: + +```py +>>> import scooby +>>> scooby.AutoReport('matplotlib') + +-------------------------------------------------------------------------------- + Date: Fri Oct 20 16:49:34 2023 PDT + + OS : Darwin + CPU(s) : 8 + Machine : arm64 + Architecture : 64bit + RAM : 16.0 GiB + Environment : Python + File system : apfs + + Python 3.11.3 | packaged by conda-forge | (main, Apr 6 2023, 08:58:31) + [Clang 14.0.6 ] + + matplotlib : 3.7.1 + contourpy : 1.0.7 + cycler : 0.11.0 + fonttools : 4.39.4 + kiwisolver : 1.4.4 + numpy : 1.24.3 + packaging : 23.1 + pillow : 9.5.0 + pyparsing : 3.0.9 + python-dateutil : 2.8.2 +-------------------------------------------------------------------------------- +>>> +``` + ### Solving Mysteries Are you struggling with the mystery of whether or not code is being executed in @@ -382,29 +419,30 @@ we can generate a Report for `matplotlib` and its dependencies: ```bash $ scooby -r matplotlib -------------------------------------------------------------------------------- - Date: Fri Oct 20 16:28:13 2023 PDT + Date: Fri Oct 20 17:03:45 2023 PDT - OS : Darwin - CPU(s) : 8 - Machine : arm64 - Architecture : 64bit - RAM : 16.0 GiB - Environment : Python - File system : apfs + OS : Darwin + CPU(s) : 8 + Machine : arm64 + Architecture : 64bit + RAM : 16.0 GiB + Environment : Python + File system : apfs Python 3.11.3 | packaged by conda-forge | (main, Apr 6 2023, 08:58:31) [Clang 14.0.6 ] - matplotlib : 3.7.1 - contourpy : 1.0.7 - cycler : 0.11.0 - fonttools : 4.39.4 - kiwisolver : 1.4.4 - numpy : 1.24.3 - packaging : 23.1 - pillow : 9.5.0 - pyparsing : 3.0.9 - python-dateutil : 2.8.2 + matplotlib : 3.7.1 + contourpy : 1.0.7 + cycler : 0.11.0 + fonttools : 4.39.4 + kiwisolver : 1.4.4 + numpy : 1.24.3 + packaging : 23.1 + pillow : 9.5.0 + pyparsing : 3.0.9 + python-dateutil : 2.8.2 +importlib-resources : 5.12.0 -------------------------------------------------------------------------------- ``` From af3b0f7934eea995c7d60db57bb1337bf1d46eb2 Mon Sep 17 00:00:00 2001 From: Bane Sullivan Date: Fri, 20 Oct 2023 17:05:40 -0700 Subject: [PATCH 12/14] Fix formatting --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 40f9729..5eac877 100644 --- a/README.md +++ b/README.md @@ -265,7 +265,8 @@ distribution requirements with the `AutoReport` class: ```py >>> import scooby >>> scooby.AutoReport('matplotlib') - +``` +``` -------------------------------------------------------------------------------- Date: Fri Oct 20 16:49:34 2023 PDT From 78c9176aabd70899030d71d7833dc0639fe3776f Mon Sep 17 00:00:00 2001 From: Bane Sullivan Date: Fri, 20 Oct 2023 17:06:11 -0700 Subject: [PATCH 13/14] Typo --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 5eac877..a86f282 100644 --- a/README.md +++ b/README.md @@ -292,7 +292,6 @@ distribution requirements with the `AutoReport` class: pyparsing : 3.0.9 python-dateutil : 2.8.2 -------------------------------------------------------------------------------- ->>> ``` ### Solving Mysteries From c9e67c0b2fbc0c9e8ff7ecfe6b162ff9abacf91c Mon Sep 17 00:00:00 2001 From: Bane Sullivan Date: Fri, 20 Oct 2023 17:09:45 -0700 Subject: [PATCH 14/14] Improve errors --- scooby/__main__.py | 3 ++- scooby/report.py | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/scooby/__main__.py b/scooby/__main__.py index 6335f9b..62c33aa 100644 --- a/scooby/__main__.py +++ b/scooby/__main__.py @@ -1,6 +1,7 @@ """Create entry point for the command-line interface (CLI).""" import argparse import importlib +from importlib.metadata import PackageNotFoundError import sys from typing import Any, Dict, List, Optional @@ -86,7 +87,7 @@ def act(args_dict: Dict[str, Any]) -> None: try: dist_deps = get_distribution_dependencies(report) packages = [report, *dist_deps, *packages] - except ImportError: + except PackageNotFoundError: print( f"Package `{report}` has no Report class and `importlib` could not be used to autogenerate one.", file=sys.stderr, diff --git a/scooby/report.py b/scooby/report.py index 87fbfdd..a78f119 100644 --- a/scooby/report.py +++ b/scooby/report.py @@ -455,7 +455,7 @@ def __init__(self, module, additional=None, ncol=3, text_width=80, sort=False): package.Report.__init__( self, additional=additional, ncol=ncol, text_width=text_width, sort=sort ) - except (AttributeError, TypeError): + except (AttributeError, ImportError): # Autogenerate from distribution requirements core = [module, *get_distribution_dependencies(module)] Report.__init__( @@ -565,5 +565,5 @@ def get_distribution_dependencies(dist_name: str): try: dist = distribution(dist_name) except PackageNotFoundError: - raise ImportError(f"Package `{dist_name}` has no distribution.") + raise PackageNotFoundError(f"Package `{dist_name}` has no distribution.") return [pkg.split()[0] for pkg in dist.requires]