From 453d17d8c94926d500fd8b91dbd3c65fb92799c0 Mon Sep 17 00:00:00 2001 From: Alex Rudy Date: Sun, 14 Jul 2019 11:13:08 -0500 Subject: [PATCH 1/4] Error type for optional executable dependencies --- lib/matplotlib/__init__.py | 31 +++++++++++++++----------- lib/matplotlib/animation.py | 2 +- lib/matplotlib/backends/backend_pgf.py | 2 +- lib/matplotlib/rcsetup.py | 4 ++-- lib/matplotlib/testing/compare.py | 4 ++-- 5 files changed, 24 insertions(+), 19 deletions(-) diff --git a/lib/matplotlib/__init__.py b/lib/matplotlib/__init__.py index 13817cef01a7..cef44d9829ea 100644 --- a/lib/matplotlib/__init__.py +++ b/lib/matplotlib/__init__.py @@ -282,6 +282,9 @@ def wrapper(): _ExecInfo = namedtuple("_ExecInfo", "executable version") +class ExecutableUnavailableError(FileNotFoundError): + """Error raised when an executable that Matplotlib optionally depends on can't be found.""" + pass @functools.lru_cache() def _get_executable_info(name): @@ -307,7 +310,7 @@ def _get_executable_info(name): Raises ------ - FileNotFoundError + ExecutableUnavailableError If the executable is not found or older than the oldest version supported by Matplotlib. ValueError @@ -319,7 +322,7 @@ def impl(args, regex, min_ver=None, ignore_exit_code=False): # Search for a regex match in the output; if the match succeeds, the # first group of the match is the version. # Return an _ExecInfo if the executable exists, and has a version of - # at least min_ver (if set); else, raise FileNotFoundError. + # at least min_ver (if set); else, raise ExecutableUnavailableError. try: output = subprocess.check_output( args, stderr=subprocess.STDOUT, universal_newlines=True) @@ -327,17 +330,19 @@ def impl(args, regex, min_ver=None, ignore_exit_code=False): if ignore_exit_code: output = _cpe.output else: - raise _cpe + raise ExecutableUnavailableError(str(_cpe)) from _cpe + except FileNotFoundError as _fnf: + raise ExecutableUnavailableError(str(_fnf)) from _fnf match = re.search(regex, output) if match: version = LooseVersion(match.group(1)) if min_ver is not None and version < min_ver: - raise FileNotFoundError( + raise ExecutableUnavailableError( f"You have {args[0]} version {version} but the minimum " f"version supported by Matplotlib is {min_ver}.") return _ExecInfo(args[0], version) else: - raise FileNotFoundError( + raise ExecutableUnavailableError( f"Failed to determine the version of {args[0]} from " f"{' '.join(args)}, which output {output}") @@ -350,9 +355,9 @@ def impl(args, regex, min_ver=None, ignore_exit_code=False): for e in execs: try: return impl([e, "--version"], "(.*)", "9") - except FileNotFoundError: + except ExecutableUnavailableError: pass - raise FileNotFoundError("Failed to find a Ghostscript installation") + raise ExecutableUnavailableError("Failed to find a Ghostscript installation") elif name == "inkscape": return impl(["inkscape", "-V"], "^Inkscape ([^ ]*)") elif name == "magick": @@ -380,7 +385,7 @@ def impl(args, regex, min_ver=None, ignore_exit_code=False): else: path = "convert" if path is None: - raise FileNotFoundError( + raise ExecutableUnavailableError( "Failed to find an ImageMagick installation") return impl([path, "--version"], r"^Version: ImageMagick (\S*)") elif name == "pdftops": @@ -389,7 +394,7 @@ def impl(args, regex, min_ver=None, ignore_exit_code=False): if info and not ("3.0" <= info.version # poppler version numbers. or "0.9" <= info.version <= "1.0"): - raise FileNotFoundError( + raise ExecutableUnavailableError( f"You have pdftops version {info.version} but the minimum " f"version supported by Matplotlib is 3.0.") return info @@ -478,14 +483,14 @@ def checkdep_ps_distiller(s): return False try: _get_executable_info("gs") - except FileNotFoundError: + except ExecutableUnavailableError: _log.warning( "Setting rcParams['ps.usedistiller'] requires ghostscript.") return False if s == "xpdf": try: _get_executable_info("pdftops") - except FileNotFoundError: + except ExecutableUnavailableError: _log.warning( "Setting rcParams['ps.usedistiller'] to 'xpdf' requires xpdf.") return False @@ -500,12 +505,12 @@ def checkdep_usetex(s): return False try: _get_executable_info("dvipng") - except FileNotFoundError: + except ExecutableUnavailableError: _log.warning("usetex mode requires dvipng.") return False try: _get_executable_info("gs") - except FileNotFoundError: + except ExecutableUnavailableError: _log.warning("usetex mode requires ghostscript.") return False return True diff --git a/lib/matplotlib/animation.py b/lib/matplotlib/animation.py index 481d8990280d..53da58643d0c 100644 --- a/lib/matplotlib/animation.py +++ b/lib/matplotlib/animation.py @@ -725,7 +725,7 @@ def bin_path(cls): def isAvailable(cls): try: return super().isAvailable() - except FileNotFoundError: # May be raised by get_executable_info. + except mpl.ExecutableUnavailableError: # May be raised by get_executable_info. return False diff --git a/lib/matplotlib/backends/backend_pgf.py b/lib/matplotlib/backends/backend_pgf.py index 78918a2cfe23..c9458b7c4827 100644 --- a/lib/matplotlib/backends/backend_pgf.py +++ b/lib/matplotlib/backends/backend_pgf.py @@ -157,7 +157,7 @@ def cairo_convert(pdffile, pngfile, dpi): return cairo_convert try: gs_info = mpl._get_executable_info("gs") - except FileNotFoundError: + except mpl.ExecutableUnavailableError: pass else: def gs_convert(pdffile, pngfile, dpi): diff --git a/lib/matplotlib/rcsetup.py b/lib/matplotlib/rcsetup.py index 343ff6c027df..316b99242179 100644 --- a/lib/matplotlib/rcsetup.py +++ b/lib/matplotlib/rcsetup.py @@ -483,14 +483,14 @@ def validate_ps_distiller(s): elif s in ('ghostscript', 'xpdf'): try: mpl._get_executable_info("gs") - except FileNotFoundError: + except mpl.ExecutableUnavailableError: _log.warning("Setting rcParams['ps.usedistiller'] requires " "ghostscript.") return None if s == "xpdf": try: mpl._get_executable_info("pdftops") - except FileNotFoundError: + except mpl.ExecutableUnavailableError: _log.warning("Setting rcParams['ps.usedistiller'] to 'xpdf' " "requires xpdf.") return None diff --git a/lib/matplotlib/testing/compare.py b/lib/matplotlib/testing/compare.py index 816210be5107..8fc27d860c06 100644 --- a/lib/matplotlib/testing/compare.py +++ b/lib/matplotlib/testing/compare.py @@ -220,13 +220,13 @@ def __call__(self, orig, dest): def _update_converter(): try: mpl._get_executable_info("gs") - except FileNotFoundError: + except mpl.ExecutableUnavailableError: pass else: converter['pdf'] = converter['eps'] = _GSConverter() try: mpl._get_executable_info("inkscape") - except FileNotFoundError: + except mpl.ExecutableUnavailableError: pass else: converter['svg'] = _SVGConverter() From b6fe9e544c4faadf070c773bca0311155a97874a Mon Sep 17 00:00:00 2001 From: Alex Rudy Date: Sun, 14 Jul 2019 11:18:45 -0500 Subject: [PATCH 2/4] Conform to flake8 --- lib/matplotlib/__init__.py | 8 ++++++-- lib/matplotlib/animation.py | 3 ++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/lib/matplotlib/__init__.py b/lib/matplotlib/__init__.py index cef44d9829ea..dc476fc7e2a8 100644 --- a/lib/matplotlib/__init__.py +++ b/lib/matplotlib/__init__.py @@ -282,10 +282,13 @@ def wrapper(): _ExecInfo = namedtuple("_ExecInfo", "executable version") + class ExecutableUnavailableError(FileNotFoundError): - """Error raised when an executable that Matplotlib optionally depends on can't be found.""" + """Error raised when an executable that Matplotlib optionally + depends on can't be found.""" pass + @functools.lru_cache() def _get_executable_info(name): """ @@ -357,7 +360,8 @@ def impl(args, regex, min_ver=None, ignore_exit_code=False): return impl([e, "--version"], "(.*)", "9") except ExecutableUnavailableError: pass - raise ExecutableUnavailableError("Failed to find a Ghostscript installation") + message = "Failed to find a Ghostscript installation" + raise ExecutableUnavailableError(message) elif name == "inkscape": return impl(["inkscape", "-V"], "^Inkscape ([^ ]*)") elif name == "magick": diff --git a/lib/matplotlib/animation.py b/lib/matplotlib/animation.py index 53da58643d0c..8945d70cf33d 100644 --- a/lib/matplotlib/animation.py +++ b/lib/matplotlib/animation.py @@ -725,7 +725,8 @@ def bin_path(cls): def isAvailable(cls): try: return super().isAvailable() - except mpl.ExecutableUnavailableError: # May be raised by get_executable_info. + except mpl.ExecutableUnavailableError: + # May be raised by get_executable_info. return False From 486df66da2ab30c90e7117a6ae58d362ba810f36 Mon Sep 17 00:00:00 2001 From: Alex Rudy Date: Sun, 14 Jul 2019 11:37:29 -0500 Subject: [PATCH 3/4] Rename new error --- lib/matplotlib/__init__.py | 30 +++++++++++++------------- lib/matplotlib/animation.py | 2 +- lib/matplotlib/backends/backend_pgf.py | 2 +- lib/matplotlib/rcsetup.py | 4 ++-- lib/matplotlib/testing/compare.py | 4 ++-- 5 files changed, 21 insertions(+), 21 deletions(-) diff --git a/lib/matplotlib/__init__.py b/lib/matplotlib/__init__.py index dc476fc7e2a8..40a5059f9368 100644 --- a/lib/matplotlib/__init__.py +++ b/lib/matplotlib/__init__.py @@ -283,7 +283,7 @@ def wrapper(): _ExecInfo = namedtuple("_ExecInfo", "executable version") -class ExecutableUnavailableError(FileNotFoundError): +class ExecutableNotFoundError(FileNotFoundError): """Error raised when an executable that Matplotlib optionally depends on can't be found.""" pass @@ -313,7 +313,7 @@ def _get_executable_info(name): Raises ------ - ExecutableUnavailableError + ExecutableNotFoundError If the executable is not found or older than the oldest version supported by Matplotlib. ValueError @@ -325,7 +325,7 @@ def impl(args, regex, min_ver=None, ignore_exit_code=False): # Search for a regex match in the output; if the match succeeds, the # first group of the match is the version. # Return an _ExecInfo if the executable exists, and has a version of - # at least min_ver (if set); else, raise ExecutableUnavailableError. + # at least min_ver (if set); else, raise ExecutableNotFoundError. try: output = subprocess.check_output( args, stderr=subprocess.STDOUT, universal_newlines=True) @@ -333,19 +333,19 @@ def impl(args, regex, min_ver=None, ignore_exit_code=False): if ignore_exit_code: output = _cpe.output else: - raise ExecutableUnavailableError(str(_cpe)) from _cpe + raise ExecutableNotFoundError(str(_cpe)) from _cpe except FileNotFoundError as _fnf: - raise ExecutableUnavailableError(str(_fnf)) from _fnf + raise ExecutableNotFoundError(str(_fnf)) from _fnf match = re.search(regex, output) if match: version = LooseVersion(match.group(1)) if min_ver is not None and version < min_ver: - raise ExecutableUnavailableError( + raise ExecutableNotFoundError( f"You have {args[0]} version {version} but the minimum " f"version supported by Matplotlib is {min_ver}.") return _ExecInfo(args[0], version) else: - raise ExecutableUnavailableError( + raise ExecutableNotFoundError( f"Failed to determine the version of {args[0]} from " f"{' '.join(args)}, which output {output}") @@ -358,10 +358,10 @@ def impl(args, regex, min_ver=None, ignore_exit_code=False): for e in execs: try: return impl([e, "--version"], "(.*)", "9") - except ExecutableUnavailableError: + except ExecutableNotFoundError: pass message = "Failed to find a Ghostscript installation" - raise ExecutableUnavailableError(message) + raise ExecutableNotFoundError(message) elif name == "inkscape": return impl(["inkscape", "-V"], "^Inkscape ([^ ]*)") elif name == "magick": @@ -389,7 +389,7 @@ def impl(args, regex, min_ver=None, ignore_exit_code=False): else: path = "convert" if path is None: - raise ExecutableUnavailableError( + raise ExecutableNotFoundError( "Failed to find an ImageMagick installation") return impl([path, "--version"], r"^Version: ImageMagick (\S*)") elif name == "pdftops": @@ -398,7 +398,7 @@ def impl(args, regex, min_ver=None, ignore_exit_code=False): if info and not ("3.0" <= info.version # poppler version numbers. or "0.9" <= info.version <= "1.0"): - raise ExecutableUnavailableError( + raise ExecutableNotFoundError( f"You have pdftops version {info.version} but the minimum " f"version supported by Matplotlib is 3.0.") return info @@ -487,14 +487,14 @@ def checkdep_ps_distiller(s): return False try: _get_executable_info("gs") - except ExecutableUnavailableError: + except ExecutableNotFoundError: _log.warning( "Setting rcParams['ps.usedistiller'] requires ghostscript.") return False if s == "xpdf": try: _get_executable_info("pdftops") - except ExecutableUnavailableError: + except ExecutableNotFoundError: _log.warning( "Setting rcParams['ps.usedistiller'] to 'xpdf' requires xpdf.") return False @@ -509,12 +509,12 @@ def checkdep_usetex(s): return False try: _get_executable_info("dvipng") - except ExecutableUnavailableError: + except ExecutableNotFoundError: _log.warning("usetex mode requires dvipng.") return False try: _get_executable_info("gs") - except ExecutableUnavailableError: + except ExecutableNotFoundError: _log.warning("usetex mode requires ghostscript.") return False return True diff --git a/lib/matplotlib/animation.py b/lib/matplotlib/animation.py index 8945d70cf33d..866efd764584 100644 --- a/lib/matplotlib/animation.py +++ b/lib/matplotlib/animation.py @@ -725,7 +725,7 @@ def bin_path(cls): def isAvailable(cls): try: return super().isAvailable() - except mpl.ExecutableUnavailableError: + except mpl.ExecutableNotFoundError: # May be raised by get_executable_info. return False diff --git a/lib/matplotlib/backends/backend_pgf.py b/lib/matplotlib/backends/backend_pgf.py index c9458b7c4827..6b95a4723323 100644 --- a/lib/matplotlib/backends/backend_pgf.py +++ b/lib/matplotlib/backends/backend_pgf.py @@ -157,7 +157,7 @@ def cairo_convert(pdffile, pngfile, dpi): return cairo_convert try: gs_info = mpl._get_executable_info("gs") - except mpl.ExecutableUnavailableError: + except mpl.ExecutableNotFoundError: pass else: def gs_convert(pdffile, pngfile, dpi): diff --git a/lib/matplotlib/rcsetup.py b/lib/matplotlib/rcsetup.py index 316b99242179..7efb61a1657c 100644 --- a/lib/matplotlib/rcsetup.py +++ b/lib/matplotlib/rcsetup.py @@ -483,14 +483,14 @@ def validate_ps_distiller(s): elif s in ('ghostscript', 'xpdf'): try: mpl._get_executable_info("gs") - except mpl.ExecutableUnavailableError: + except mpl.ExecutableNotFoundError: _log.warning("Setting rcParams['ps.usedistiller'] requires " "ghostscript.") return None if s == "xpdf": try: mpl._get_executable_info("pdftops") - except mpl.ExecutableUnavailableError: + except mpl.ExecutableNotFoundError: _log.warning("Setting rcParams['ps.usedistiller'] to 'xpdf' " "requires xpdf.") return None diff --git a/lib/matplotlib/testing/compare.py b/lib/matplotlib/testing/compare.py index 8fc27d860c06..a88be4a0ffea 100644 --- a/lib/matplotlib/testing/compare.py +++ b/lib/matplotlib/testing/compare.py @@ -220,13 +220,13 @@ def __call__(self, orig, dest): def _update_converter(): try: mpl._get_executable_info("gs") - except mpl.ExecutableUnavailableError: + except mpl.ExecutableNotFoundError: pass else: converter['pdf'] = converter['eps'] = _GSConverter() try: mpl._get_executable_info("inkscape") - except mpl.ExecutableUnavailableError: + except mpl.ExecutableNotFoundError: pass else: converter['svg'] = _SVGConverter() From 8c31af8020552dfdde97ce89caae9a3757d9bafc Mon Sep 17 00:00:00 2001 From: Alex Rudy Date: Sat, 20 Jul 2019 10:12:09 -0700 Subject: [PATCH 4/4] Fix formatting of docstring --- lib/matplotlib/__init__.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/matplotlib/__init__.py b/lib/matplotlib/__init__.py index 40a5059f9368..8103bc3d672d 100644 --- a/lib/matplotlib/__init__.py +++ b/lib/matplotlib/__init__.py @@ -284,8 +284,10 @@ def wrapper(): class ExecutableNotFoundError(FileNotFoundError): - """Error raised when an executable that Matplotlib optionally - depends on can't be found.""" + """ + Error raised when an executable that Matplotlib optionally + depends on can't be found. + """ pass