Coverage for /usr/lib/python3/dist-packages/sympy/external/importtools.py: 36%
61 statements
« prev ^ index » next coverage.py v7.9.1, created at 2025-06-14 15:55 +0200
« prev ^ index » next coverage.py v7.9.1, created at 2025-06-14 15:55 +0200
1"""Tools to assist importing optional external modules."""
3import sys
4import re
6# Override these in the module to change the default warning behavior.
7# For example, you might set both to False before running the tests so that
8# warnings are not printed to the console, or set both to True for debugging.
10WARN_NOT_INSTALLED = None # Default is False
11WARN_OLD_VERSION = None # Default is True
14def __sympy_debug():
15 # helper function from sympy/__init__.py
16 # We don't just import SYMPY_DEBUG from that file because we don't want to
17 # import all of SymPy just to use this module.
18 import os
19 debug_str = os.getenv('SYMPY_DEBUG', 'False')
20 if debug_str in ('True', 'False'):
21 return eval(debug_str)
22 else:
23 raise RuntimeError("unrecognized value for SYMPY_DEBUG: %s" %
24 debug_str)
26if __sympy_debug():
27 WARN_OLD_VERSION = True
28 WARN_NOT_INSTALLED = True
31_component_re = re.compile(r'(\d+ | [a-z]+ | \.)', re.VERBOSE)
33def version_tuple(vstring):
34 # Parse a version string to a tuple e.g. '1.2' -> (1, 2)
35 # Simplified from distutils.version.LooseVersion which was deprecated in
36 # Python 3.10.
37 components = []
38 for x in _component_re.split(vstring):
39 if x and x != '.':
40 try:
41 x = int(x)
42 except ValueError:
43 pass
44 components.append(x)
45 return tuple(components)
48def import_module(module, min_module_version=None, min_python_version=None,
49 warn_not_installed=None, warn_old_version=None,
50 module_version_attr='__version__', module_version_attr_call_args=None,
51 import_kwargs={}, catch=()):
52 """
53 Import and return a module if it is installed.
55 If the module is not installed, it returns None.
57 A minimum version for the module can be given as the keyword argument
58 min_module_version. This should be comparable against the module version.
59 By default, module.__version__ is used to get the module version. To
60 override this, set the module_version_attr keyword argument. If the
61 attribute of the module to get the version should be called (e.g.,
62 module.version()), then set module_version_attr_call_args to the args such
63 that module.module_version_attr(*module_version_attr_call_args) returns the
64 module's version.
66 If the module version is less than min_module_version using the Python <
67 comparison, None will be returned, even if the module is installed. You can
68 use this to keep from importing an incompatible older version of a module.
70 You can also specify a minimum Python version by using the
71 min_python_version keyword argument. This should be comparable against
72 sys.version_info.
74 If the keyword argument warn_not_installed is set to True, the function will
75 emit a UserWarning when the module is not installed.
77 If the keyword argument warn_old_version is set to True, the function will
78 emit a UserWarning when the library is installed, but cannot be imported
79 because of the min_module_version or min_python_version options.
81 Note that because of the way warnings are handled, a warning will be
82 emitted for each module only once. You can change the default warning
83 behavior by overriding the values of WARN_NOT_INSTALLED and WARN_OLD_VERSION
84 in sympy.external.importtools. By default, WARN_NOT_INSTALLED is False and
85 WARN_OLD_VERSION is True.
87 This function uses __import__() to import the module. To pass additional
88 options to __import__(), use the import_kwargs keyword argument. For
89 example, to import a submodule A.B, you must pass a nonempty fromlist option
90 to __import__. See the docstring of __import__().
92 This catches ImportError to determine if the module is not installed. To
93 catch additional errors, pass them as a tuple to the catch keyword
94 argument.
96 Examples
97 ========
99 >>> from sympy.external import import_module
101 >>> numpy = import_module('numpy')
103 >>> numpy = import_module('numpy', min_python_version=(2, 7),
104 ... warn_old_version=False)
106 >>> numpy = import_module('numpy', min_module_version='1.5',
107 ... warn_old_version=False) # numpy.__version__ is a string
109 >>> # gmpy does not have __version__, but it does have gmpy.version()
111 >>> gmpy = import_module('gmpy', min_module_version='1.14',
112 ... module_version_attr='version', module_version_attr_call_args=(),
113 ... warn_old_version=False)
115 >>> # To import a submodule, you must pass a nonempty fromlist to
116 >>> # __import__(). The values do not matter.
117 >>> p3 = import_module('mpl_toolkits.mplot3d',
118 ... import_kwargs={'fromlist':['something']})
120 >>> # matplotlib.pyplot can raise RuntimeError when the display cannot be opened
121 >>> matplotlib = import_module('matplotlib',
122 ... import_kwargs={'fromlist':['pyplot']}, catch=(RuntimeError,))
124 """
125 # keyword argument overrides default, and global variable overrides
126 # keyword argument.
127 warn_old_version = (WARN_OLD_VERSION if WARN_OLD_VERSION is not None
128 else warn_old_version or True)
129 warn_not_installed = (WARN_NOT_INSTALLED if WARN_NOT_INSTALLED is not None
130 else warn_not_installed or False)
132 import warnings
134 # Check Python first so we don't waste time importing a module we can't use
135 if min_python_version:
136 if sys.version_info < min_python_version:
137 if warn_old_version:
138 warnings.warn("Python version is too old to use %s "
139 "(%s or newer required)" % (
140 module, '.'.join(map(str, min_python_version))),
141 UserWarning, stacklevel=2)
142 return
144 try:
145 mod = __import__(module, **import_kwargs)
147 ## there's something funny about imports with matplotlib and py3k. doing
148 ## from matplotlib import collections
149 ## gives python's stdlib collections module. explicitly re-importing
150 ## the module fixes this.
151 from_list = import_kwargs.get('fromlist', ())
152 for submod in from_list:
153 if submod == 'collections' and mod.__name__ == 'matplotlib':
154 __import__(module + '.' + submod)
155 except ImportError:
156 if warn_not_installed:
157 warnings.warn("%s module is not installed" % module, UserWarning,
158 stacklevel=2)
159 return
160 except catch as e:
161 if warn_not_installed:
162 warnings.warn(
163 "%s module could not be used (%s)" % (module, repr(e)),
164 stacklevel=2)
165 return
167 if min_module_version:
168 modversion = getattr(mod, module_version_attr)
169 if module_version_attr_call_args is not None:
170 modversion = modversion(*module_version_attr_call_args)
171 if version_tuple(modversion) < version_tuple(min_module_version):
172 if warn_old_version:
173 # Attempt to create a pretty string version of the version
174 if isinstance(min_module_version, str):
175 verstr = min_module_version
176 elif isinstance(min_module_version, (tuple, list)):
177 verstr = '.'.join(map(str, min_module_version))
178 else:
179 # Either don't know what this is. Hopefully
180 # it's something that has a nice str version, like an int.
181 verstr = str(min_module_version)
182 warnings.warn("%s version is too old to use "
183 "(%s or newer required)" % (module, verstr),
184 UserWarning, stacklevel=2)
185 return
187 return mod