From 4f9acda851826eb5e3438e25f18c7e48d69ccb35 Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Tue, 12 Jan 2021 18:32:33 -0500 Subject: [PATCH] Wrap qiskit.Aer and qiskit.IBMQ with lazy loading object This commit migrates the qiskit.Aer and qiskit.IBMQ module attributes to be lazy loading instances of lazy loading wrapper classes. The intent here is to avoid importing from qiskit-aer or qiskit-ibmq-provider from qiskit-terra, while this is safe while the packages share a shared namespace we've been actively working to remove the use of namespace packaging (see #5089, #4767, and Qiskit/qiskit#559) and the circular dependency caused by re-exporting these attributes is blocking progress on this. By using a lazy loading wrapper class we avoid an import type circular dependency and opportunistically use qiskit-aer and/or qiskit-ibmq-provider at runtime only if they're present after everything is imported. This also may have some benefit for the overall import performance of qiskit (being tracked in #5100) as it removes qiskit-aer and qiskit-ibmq-provider from the import path unless they're being used. Although the presence of qiskit.__qiskit_version__ might prevent any performance improvements as it still imports all the elements to get version information (and will be tackled in a separate PR). Fixes #5532 --- qiskit/__init__.py | 81 +++++++++++++------ qiskit/test/mock/fake_backend.py | 2 +- qiskit/user_config.py | 7 -- ...oad-provider-factory-bdfb3925a2514ef7.yaml | 22 +++++ test/python/algorithms/test_vqe.py | 8 +- .../legacy/test_weighted_pauli_operator.py | 2 +- .../python/opflow/test_state_op_meas_evals.py | 6 +- test/python/test_user_config.py | 46 +---------- 8 files changed, 90 insertions(+), 84 deletions(-) create mode 100644 releasenotes/notes/lazy-load-provider-factory-bdfb3925a2514ef7.yaml diff --git a/qiskit/__init__.py b/qiskit/__init__.py index a32d77ef91b4..5824a5c61913 100644 --- a/qiskit/__init__.py +++ b/qiskit/__init__.py @@ -15,7 +15,6 @@ """Main Qiskit public functionality.""" - import pkgutil import sys import warnings @@ -49,28 +48,6 @@ _config = _user_config.get_config() -# Try to import the Aer provider if installed. -try: - from qiskit.providers.aer import Aer -except ImportError: - suppress_warnings = os.environ.get('QISKIT_SUPPRESS_PACKAGING_WARNINGS', '') - if suppress_warnings.upper() != 'Y': - if not _config.get('suppress_packaging_warnings') or suppress_warnings.upper() == 'N': - warnings.warn('Could not import the Aer provider from the qiskit-aer ' - 'package. Install qiskit-aer or check your installation.', - RuntimeWarning) -# Try to import the IBMQ provider if installed. -try: - from qiskit.providers.ibmq import IBMQ -except ImportError: - suppress_warnings = os.environ.get('QISKIT_SUPPRESS_PACKAGING_WARNINGS', '') - if suppress_warnings.upper() != 'Y': - if not _config.get('suppress_packaging_warnings') or suppress_warnings.upper() == 'N': - warnings.warn('Could not import the IBMQ provider from the ' - 'qiskit-ibmq-provider package. Install ' - 'qiskit-ibmq-provider or check your installation.', - RuntimeWarning) - # Moved to after IBMQ and Aer imports due to import issues # with other modules that check for IBMQ (tools) from qiskit.execute import execute # noqa @@ -88,3 +65,61 @@ "Using Qiskit with Python 3.6 is deprecated as of the 0.17.0 release. " "Support for running Qiskit with Python 3.6 will be removed in a " "future release.", DeprecationWarning) + + +class AerWrapper: + + def __init__(self): + self.aer = None + + def __bool__(self): + if self.aer is None: + try: + from qiskit.providers.aer import Aer + self.aer = Aer + except ImportError: + return False + return True + + def __getattr__(self, attr): + if not self.aer: + try: + from qiskit.providers.aer import Aer + self.aer = Aer + except ImportError: + raise ImportError('Could not import the Aer provider from the ' + 'qiskit-aer package. Install qiskit-aer or ' + 'check your installation.') + return getattr(self.aer, attr) + + + +class IBMQWrapper: + + def __init__(self): + self.ibmq = None + + def __bool__(self): + if self.ibmq is None: + try: + from qiskit.providers.ibmq import IBMQ + self.ibmq = IBMQ + except ImportError: + return False + return True + + def __getattr__(self, attr): + if not self.ibmq: + try: + from qiskit.providers.ibmq import IBMQ + self.ibmq = IBMQ + except ImportError: + raise ImportError('Could not import the IBMQ provider from the ' + 'qiskit-ibmq-provider package. Install ' + 'qiskit-ibmq-provider or check your ' + 'installation.') + return getattr(self.ibmq, attr) + + +Aer = AerWrapper() +IBMQ = IBMQWrapper() diff --git a/qiskit/test/mock/fake_backend.py b/qiskit/test/mock/fake_backend.py index 1c96936cfc72..0fa2491a65ea 100644 --- a/qiskit/test/mock/fake_backend.py +++ b/qiskit/test/mock/fake_backend.py @@ -23,7 +23,7 @@ from qiskit.exceptions import QiskitError try: - from qiskit import Aer + from qiskit.providers.aer import Aer HAS_AER = True except ImportError: HAS_AER = False diff --git a/qiskit/user_config.py b/qiskit/user_config.py index d609a41547cf..b9c0e6faab3c 100644 --- a/qiskit/user_config.py +++ b/qiskit/user_config.py @@ -41,7 +41,6 @@ class UserConfig: circuit_mpl_style = default circuit_mpl_style_path = ~/.qiskit: transpile_optimization_level = 1 - suppress_packaging_warnings = False parallel = False num_processes = 4 @@ -116,12 +115,6 @@ def read_config_file(self): self.settings['transpile_optimization_level'] = ( transpile_optimization_level) - # Parse package warnings - package_warnings = self.config_parser.getboolean( - 'default', 'suppress_packaging_warnings', fallback=False) - if package_warnings: - self.settings['suppress_packaging_warnings'] = package_warnings - # Parse parallel parallel_enabled = self.config_parser.getboolean( 'default', 'parallel', fallback=PARALLEL_DEFAULT) diff --git a/releasenotes/notes/lazy-load-provider-factory-bdfb3925a2514ef7.yaml b/releasenotes/notes/lazy-load-provider-factory-bdfb3925a2514ef7.yaml new file mode 100644 index 000000000000..7d8425558dc9 --- /dev/null +++ b/releasenotes/notes/lazy-load-provider-factory-bdfb3925a2514ef7.yaml @@ -0,0 +1,22 @@ +--- +upgrade: + - | + The ``qiskit.Aer`` and ``qiskit.IBMQ`` top level attributes are now lazy + loaded. This means that the objects will always now exist and warnings will + no longer be raised on import if ``qiskit-aer`` or ``qiskit-ibmq-provider`` + are not installed (or can't be found by Python). If you were checking for + the presence of ``qiskit-aer`` or ``qiskit-ibmq-provider`` using these + module attributes and explicitly comparing to ``None`` or looking for the + absence of the attribute this no longer will work because they are always + defined as an object now. You can either explicitly use + ``qiskit.providers.aer.Aer`` and ``qiskit.providers.ibmq.IBMQ`` or + check ``bool(qiskit.Aer)`` and ``bool(qiskit.IBMQ)`` instead to determine + if those providers are present. This change was necessary to avoid potential + import cycle issues between the qiskit packages and also to improve the + import time when Aer or IBMQ is not being used. + - | + The user config file option ``suppress_packaging_warnings`` option in the + user config file and the ``QISKIT_SUPPRESS_PACKAGING_WARNINGS`` environment + variable no longer has any effect and will be silently ignored. These + warnings have been removed and will no longer be emitted at import time + from the qiskit module. diff --git a/test/python/algorithms/test_vqe.py b/test/python/algorithms/test_vqe.py index 438b2f02a7b0..b62a2b30bc57 100644 --- a/test/python/algorithms/test_vqe.py +++ b/test/python/algorithms/test_vqe.py @@ -167,7 +167,7 @@ def test_with_aer_statevector(self): """Test VQE with Aer's statevector_simulator.""" try: # pylint: disable=import-outside-toplevel - from qiskit import Aer + from qiskit.providers.aer import Aer except Exception as ex: # pylint: disable=broad-except self.skipTest("Aer doesn't appear to be installed. Error: '{}'".format(str(ex))) return @@ -190,7 +190,7 @@ def test_with_aer_qasm(self): """Test VQE with Aer's qasm_simulator.""" try: # pylint: disable=import-outside-toplevel - from qiskit import Aer + from qiskit.providers.aer import Aer except Exception as ex: # pylint: disable=broad-except self.skipTest("Aer doesn't appear to be installed. Error: '{}'".format(str(ex))) return @@ -215,7 +215,7 @@ def test_with_aer_qasm_snapshot_mode(self): """Test the VQE using Aer's qasm_simulator snapshot mode.""" try: # pylint: disable=import-outside-toplevel - from qiskit import Aer + from qiskit.providers.aer import Aer except Exception as ex: # pylint: disable=broad-except self.skipTest("Aer doesn't appear to be installed. Error: '{}'".format(str(ex))) return @@ -308,7 +308,7 @@ def test_vqe_expectation_select(self): """Test expectation selection with Aer's qasm_simulator.""" try: # pylint: disable=import-outside-toplevel - from qiskit import Aer + from qiskit.providers.aer import Aer except Exception as ex: # pylint: disable=broad-except self.skipTest("Aer doesn't appear to be installed. Error: '{}'".format(str(ex))) return diff --git a/test/python/opflow/legacy/test_weighted_pauli_operator.py b/test/python/opflow/legacy/test_weighted_pauli_operator.py index 283dacd2a4dc..530b9bd75dd7 100644 --- a/test/python/opflow/legacy/test_weighted_pauli_operator.py +++ b/test/python/opflow/legacy/test_weighted_pauli_operator.py @@ -498,7 +498,7 @@ def test_evaluate_with_aer_mode(self): """ evaluate with aer mode test """ try: # pylint: disable=import-outside-toplevel - from qiskit import Aer + from qiskit.providers.aer import Aer except Exception as ex: # pylint: disable=broad-except self.skipTest("Aer doesn't appear to be installed. Error: '{}'".format(str(ex))) return diff --git a/test/python/opflow/test_state_op_meas_evals.py b/test/python/opflow/test_state_op_meas_evals.py index 7268bc5e768e..8edf2c401827 100644 --- a/test/python/opflow/test_state_op_meas_evals.py +++ b/test/python/opflow/test_state_op_meas_evals.py @@ -63,7 +63,7 @@ def test_coefficients_correctly_propagated(self): """Test that the coefficients in SummedOp and states are correctly used.""" try: # pylint: disable=import-outside-toplevel - from qiskit import Aer + from qiskit.providers.aer import Aer except Exception as ex: # pylint: disable=broad-except self.skipTest("Aer doesn't appear to be installed. Error: '{}'".format(str(ex))) return @@ -89,7 +89,7 @@ def test_is_measurement_correctly_propagated(self): """Test if is_measurement property of StateFn is propagated to converted StateFn.""" try: # pylint: disable=import-outside-toplevel - from qiskit import Aer + from qiskit.providers.aer import Aer except Exception as ex: # pylint: disable=broad-except self.skipTest("Aer doesn't appear to be installed. Error: '{}'".format(str(ex))) return @@ -103,7 +103,7 @@ def test_parameter_binding_on_listop(self): """Test passing a ListOp with differing parameters works with the circuit sampler.""" try: # pylint: disable=import-outside-toplevel - from qiskit import Aer + from qiskit.providers.aer import Aer except Exception as ex: # pylint: disable=broad-except self.skipTest("Aer doesn't appear to be installed. Error: '{}'".format(str(ex))) return diff --git a/test/python/test_user_config.py b/test/python/test_user_config.py index 9f70a9918b81..36dc3ee5cc3e 100644 --- a/test/python/test_user_config.py +++ b/test/python/test_user_config.py @@ -31,18 +31,6 @@ def test_empty_file_read(self): self.assertEqual({}, config.settings) - def test_invalid_suppress_packaging_warnings(self): - test_config = """ - [default] - suppress_packaging_warnings = 76 - """ - self.addCleanup(os.remove, self.file_path) - with open(self.file_path, 'w') as file: - file.write(test_config) - file.flush() - config = user_config.UserConfig(self.file_path) - self.assertRaises(ValueError, config.read_config_file) - def test_invalid_optimization_level(self): test_config = """ [default] @@ -100,37 +88,6 @@ def test_optimization_level_valid(self): 'parallel_enabled': user_config.PARALLEL_DEFAULT}, config.settings) - def test_valid_suppress_packaging_warnings_false(self): - test_config = """ - [default] - suppress_packaging_warnings = false - """ - self.addCleanup(os.remove, self.file_path) - with open(self.file_path, 'w') as file: - file.write(test_config) - file.flush() - config = user_config.UserConfig(self.file_path) - config.read_config_file() - self.assertEqual( - {'parallel_enabled': user_config.PARALLEL_DEFAULT}, - config.settings) - - def test_valid_suppress_packaging_warnings_true(self): - test_config = """ - [default] - suppress_packaging_warnings = true - """ - self.addCleanup(os.remove, self.file_path) - with open(self.file_path, 'w') as file: - file.write(test_config) - file.flush() - config = user_config.UserConfig(self.file_path) - config.read_config_file() - self.assertEqual( - {'parallel_enabled': user_config.PARALLEL_DEFAULT, - 'suppress_packaging_warnings': True}, - config.settings) - def test_invalid_num_processes(self): test_config = """ [default] @@ -197,6 +154,5 @@ def test_all_options_valid(self): 'circuit_mpl_style_path': ['~', '~/.qiskit'], 'transpile_optimization_level': 3, 'num_processes': 15, - 'parallel_enabled': False, - 'suppress_packaging_warnings': True}, + 'parallel_enabled': False}, config.settings)