From a8541e10e5424a8e6855a45c8907a61f413d63c2 Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Mon, 30 Oct 2017 19:28:55 -0700 Subject: [PATCH] Check dependencies at runtime as declared in setup.py. The goal is to list the requirements exactly once in code (in setupext.py) and once in the docs (in INSTALL.rst). Instead of manually importing and checking that (python) dependencies are installed with the correct versions, rely on pkg_resources, which allows automatically keeping things in sync with setupext, as well as correctly check e.g. the complex version requirements on pyparsing. Remove the overly complex way to specify the minimum numpy version. Add comments reminding to update INSTALL.rst when the requirements are changed in setupext.py; reorder dependencies in INSTALL.rst in a more logical order. Remove unneeded reference to MATLAB being a registered trademark. Note that if you are running Matplotlib from source by manipulating PYTHONPATH, then it will not appear to pkg_resources. In this case, there is no check for other dependencies (you are assumed to be responsible enough for that). Conversely, Matplotlib will fail to import if it is installed into site-packages but some dependency (six, etc.) is only available in PYTHONPATH instead of being correctly installed. I think this is acceptable (or we could just completely drop the import checks and let bad installs fail with normal ImportErrors). --- INSTALL.rst | 19 ++++---- doc/conf.py | 4 -- lib/matplotlib/__init__.py | 88 +++++++++++++++++++------------------- setupext.py | 16 ++----- 4 files changed, 58 insertions(+), 69 deletions(-) diff --git a/INSTALL.rst b/INSTALL.rst index 5bd7bec62c30..f87ed368c136 100644 --- a/INSTALL.rst +++ b/INSTALL.rst @@ -137,19 +137,20 @@ e.g., if the header of some required library is in Dependencies ------------ -Matplotlib requires a large number of dependencies: +Matplotlib requires the following dependencies: * `Python `_ (>= 3.5) - * `NumPy `_ (>= |minimum_numpy_version|) - * `setuptools `__ + * `setuptools `_ + * `NumPy `_ (>= 1.10.0) + * `cycler `_ (>= 0.10.0) * `dateutil `_ (>= 2.1) - * `pyparsing `__ - * `libpng `__ (>= 1.2) - * `pytz `__ - * FreeType (>= 2.3) - * `cycler `__ (>= 0.10.0) - * `six `_ * `kiwisolver `__ (>= 1.0.0) + * `pyparsing `_ (>= 2.1.7; some older + versions may work) + * `pytz `_ + * `six `_ (>= 1.10) + * `FreeType `_ (>= 2.3) + * `libpng `_ (>= 1.2) Optionally, you can also install a number of packages to enable better user interface toolkits. See :ref:`what-is-a-backend` for more details on the diff --git a/doc/conf.py b/doc/conf.py index 9342fa58c646..f02b1fc94565 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -323,10 +323,6 @@ def _check_deps(): # documentation autoclass_content = 'both' -rst_epilog = """ -.. |minimum_numpy_version| replace:: %s -""" % matplotlib.__version__numpy__ - texinfo_documents = [ ("contents", 'matplotlib', 'Matplotlib Documentation', 'John Hunter@*Darren Dale@*Eric Firing@*Michael Droettboom@*' diff --git a/lib/matplotlib/__init__.py b/lib/matplotlib/__init__.py index 16cce6f04584..642064bfc87c 100644 --- a/lib/matplotlib/__init__.py +++ b/lib/matplotlib/__init__.py @@ -94,15 +94,25 @@ matplotlib was initially written by John D. Hunter (1968-2012) and is now developed and maintained by a host of others. - -Occasionally the internal documentation (python docstrings) will refer -to MATLAB®, a registered trademark of The MathWorks, Inc. - """ # NOTE: This file must remain Python 2 compatible for the foreseeable future, # to ensure that we error out properly for existing editable installs. from __future__ import absolute_import, division, print_function +import pkg_resources + +try: + pkg_resources.get_distribution("matplotlib") +except pkg_resources.DistributionNotFound: + # Running from source -- you're on your own. + pass +else: + try: + # Check that dependencies are correctly installed. + pkg_resources.require("matplotlib") + except pkg_resources.ResolutionError as e: + raise ImportError("Matplotlib requires {}".format(e.req)) + import six import sys @@ -120,8 +130,7 @@ import atexit from collections import MutableMapping import contextlib -import distutils.version -import distutils.sysconfig +from distutils.version import LooseVersion import functools import io import inspect @@ -137,16 +146,39 @@ import tempfile import warnings +if sys.version_info < (3, 5): # noqa: E402 + raise ImportError(""" +Matplotlib 3.0+ does not support Python 2.x, 3.0, 3.1, 3.2, 3.3, or 3.4. +Beginning with Matplotlib 3.0, Python 3.5 and above is required. + +See Matplotlib `INSTALL.rst` file for more information: + + https://github.com/matplotlib/matplotlib/blob/master/INSTALL.rst + +""") + +if sys.version_info < (3, 5): # noqa: E402 + raise ImportError(""" +Matplotlib 3.0+ does not support Python 2.x, 3.0, 3.1, 3.2, 3.3, or 3.4. +Beginning with Matplotlib 3.0, Python 3.5 and above is required. + +See Matplotlib `INSTALL.rst` file for more information: + + https://github.com/matplotlib/matplotlib/blob/master/INSTALL.rst + +""") + +from six.moves.urllib.request import urlopen +from six.moves import reload_module as reload + +import numpy + # cbook must import matplotlib only within function # definitions, so it is safe to import from it here. from . import cbook -from matplotlib.cbook import ( +from .cbook import ( _backports, mplDeprecation, dedent, get_label, sanitize_sequence) -from matplotlib.rcsetup import defaultParams, validate_backend, cycler - -import numpy -from six.moves.urllib.request import urlopen -from six.moves import reload_module as reload +from .rcsetup import defaultParams, validate_backend, cycler # Get the version from the _version.py versioneer file. For a git checkout, # this is computed based on the number of commits since the last tag. @@ -191,41 +223,11 @@ def compare_versions(a, b): a = a.decode('ascii') if isinstance(b, bytes): b = b.decode('ascii') - a = distutils.version.LooseVersion(a) - b = distutils.version.LooseVersion(b) - return a >= b + return LooseVersion(a) >= LooseVersion(b) else: return False -try: - import dateutil -except ImportError: - raise ImportError("Matplotlib requires dateutil") - - -if not compare_versions(six.__version__, '1.10'): - raise ImportError( - "Matplotlib requires six>=1.10; you have %s" % six.__version__) - - -try: - import pyparsing -except ImportError: - raise ImportError("Matplotlib requires pyparsing") -else: - if not compare_versions(pyparsing.__version__, '2.0.1'): - raise ImportError( - "Matplotlib requires pyparsing>=2.0.1; you have %s" - % pyparsing.__version__) - - -if not compare_versions(numpy.__version__, __version__numpy__): - raise ImportError( - "Matplotlib requires numpy>=%s; you have %s" % ( - __version__numpy__, numpy.__version__)) - - if not hasattr(sys, 'argv'): # for modpython sys.argv = [str('modpython')] diff --git a/setupext.py b/setupext.py index 646794812fff..ac6c5fe7530c 100644 --- a/setupext.py +++ b/setupext.py @@ -107,18 +107,6 @@ def get_win32_compiler(): win32_compiler = get_win32_compiler() -def extract_versions(): - """ - Extracts version values from the main matplotlib __init__.py and - returns them as a dictionary. - """ - with open('lib/matplotlib/__init__.py') as fd: - for line in fd.readlines(): - if (line.startswith('__version__numpy__')): - exec(line.strip()) - return locals() - - def has_include_file(include_dirs, filename): """ Returns `True` if `filename` can be found in one of the @@ -925,7 +913,8 @@ def include_dirs_hook(): return [numpy.get_include()] def check(self): - min_version = extract_versions()['__version__numpy__'] + # Remember to keep the version in INSTALL.rst in sync. + min_version = "1.7.1" try: import numpy except ImportError: @@ -1391,6 +1380,7 @@ def check(self): return "handled by setuptools" def get_install_requires(self): + # Remember to keep the list in INSTALL.rst in sync. install_requires = [ "cycler>=0.10", "pyparsing>=2.0.1,!=2.0.4,!=2.1.2,!=2.1.6",