diff --git a/conftest.py b/conftest.py new file mode 100644 index 0000000000000000000000000000000000000000..00882f3766ca8f5e21bfd17763787e8acf1b6803 --- /dev/null +++ b/conftest.py @@ -0,0 +1,42 @@ +# Copyright 2011-2017 Kwant authors. +# +# This file is part of Kwant. It is subject to the license terms in the file +# LICENSE.rst found in the top-level directory of this distribution and at +# http://kwant-project.org/license. A list of Kwant authors can be found in +# the file AUTHORS.rst at the top-level directory of this distribution and at +# http://kwant-project.org/authors. +"""Pytest plugin to ignore packages that have uninstalled dependencies. + +This ignores packages on test collection, which is required when the +tests reside in a package that itself requires the dependency to be +installed. +""" + +import importlib + + +# map from subpackage to sequence of dependency module names +subpackage_dependencies = { + 'kwant/continuum': ['sympy'] +} + + +# map from subpackage to sequence of dependency modules that are not installed +dependencies_not_installed = {} +for package, dependencies in subpackage_dependencies.items(): + not_installed = [] + for dep in dependencies: + try: + importlib.import_module(dep) + except ImportError: + not_installed.append(dep) + if len(not_installed) != 0: + dependencies_not_installed[package] = not_installed + + +def pytest_ignore_collect(path, config): + for subpackage, not_installed in dependencies_not_installed.items(): + if subpackage in path.strpath: + print('ignoring {} because the following dependencies are not ' + 'installed: {}'.format(subpackage, ', '.join(not_installed))) + return True diff --git a/kwant/__init__.py b/kwant/__init__.py index b00f7f5489e31bf559f283d9b80b675c6267a120..442a6ddfbd7fe72c7033bf9b95ce363c3b730bf3 100644 --- a/kwant/__init__.py +++ b/kwant/__init__.py @@ -31,7 +31,7 @@ __all__.extend(['KwantDeprecationWarning', 'UserCodeError']) from ._common import version as __version__ for module in ['system', 'builder', 'lattice', 'solvers', 'digest', 'rmt', - 'operator', 'kpm', 'wraparound']: + 'operator', 'kpm', 'wraparound', 'continuum']: exec('from . import {0}'.format(module)) __all__.append(module) @@ -63,12 +63,3 @@ def test(verbose=True): "-s"] + (['-v'] if verbose else [])) test.__test__ = False - - -# Exposing discretizer -try: - from .continuum import discretize - __all__.extend(['discretize']) -except ImportError: - warnings.warn('Discretizer module not available. Is sympy installed?', - RuntimeWarning) diff --git a/kwant/_common.py b/kwant/_common.py index e7f716ebb9a8a71cd78f45b6cc5a1480c5ffb24b..0836c6cc94ad471d0c754b63c5aa1a377aee3978 100644 --- a/kwant/_common.py +++ b/kwant/_common.py @@ -107,6 +107,30 @@ class UserCodeError(Exception): pass +class ExtensionUnavailable: + """Class that replaces unavailable extension modules in the Kwant namespace. + + Some extensions for Kwant (e.g. 'kwant.continuum') require additional + dependencies that are not required for core functionality. When the + additional dependencies are not installed an instance of this class will + be inserted into Kwant's root namespace to simulate the presence of the + extension and inform users that they need to install additional + dependencies. + + See https://mail.python.org/pipermail/python-ideas/2012-May/014969.html + for more details. + """ + + def __init__(self, name, dependencies): + self.name = name + self.dependencies = ', '.join(dependencies) + + def __getattr__(self, _): + msg = ("'{}' is not available because one or more of the following " + "dependencies are not installed: {}") + raise RuntimeError(msg.format(self.name, self.dependencies)) + + def ensure_isinstance(obj, typ, msg=None): if isinstance(obj, typ): return diff --git a/kwant/continuum/__init__.py b/kwant/continuum/__init__.py index b90fd964cbe3bb6990b4b5a6d7cee9796e996f07..d7ef983dd5bc0160138bdd887f1a863a6ad14898 100644 --- a/kwant/continuum/__init__.py +++ b/kwant/continuum/__init__.py @@ -6,10 +6,18 @@ # the file AUTHORS.rst at the top-level directory of this distribution and at # http://kwant-project.org/authors. -from .discretizer import discretize, discretize_symbolic, build_discretized -from ._common import momentum_operators, position_operators -from ._common import sympify, lambdify, make_commutative +import sys +from .._common import ExtensionUnavailable + +try: + from .discretizer import discretize, discretize_symbolic, build_discretized + from ._common import momentum_operators, position_operators + from ._common import sympify, lambdify, make_commutative +except ImportError: + sys.modules[__name__] = ExtensionUnavailable(__name__, ('sympy',)) + +del sys, ExtensionUnavailable __all__ = ['discretize', 'discretize_symbolic', 'build_discretized', 'momentum_operators', 'position_operators', 'sympify', diff --git a/setup.py b/setup.py index d67c15fe6b5cfc5ab7ab9411317ac69f82b03c8a..74c299f831913e5fe9246c973e6ee43593055091 100755 --- a/setup.py +++ b/setup.py @@ -646,7 +646,11 @@ def main(): 'test': test}, ext_modules=exts, install_requires=['numpy > 1.6.1', 'scipy >= 0.11.0', 'tinyarray'], - extras_require={'plotting': 'matplotlib >= 1.2'}, + extras_require={ + 'plotting': 'matplotlib >= 1.2', + # Ubuntu 16.04 is the oldest supported distro with python3-sympy + 'continuum': 'sympy >= 0.7.6', + }, classifiers=[c.strip() for c in classifiers.split('\n')]) if __name__ == '__main__':