Skip to content

Commit

Permalink
Wrap qiskit.Aer and qiskit.IBMQ with lazy loading object
Browse files Browse the repository at this point in the history
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 Qiskit#5089, Qiskit#4767, and 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 Qiskit#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 Qiskit#5532
  • Loading branch information
mtreinish committed Jan 13, 2021
1 parent 5515717 commit 4f9acda
Show file tree
Hide file tree
Showing 8 changed files with 90 additions and 84 deletions.
81 changes: 58 additions & 23 deletions qiskit/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@

"""Main Qiskit public functionality."""


import pkgutil
import sys
import warnings
Expand Down Expand Up @@ -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
Expand All @@ -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()
2 changes: 1 addition & 1 deletion qiskit/test/mock/fake_backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
7 changes: 0 additions & 7 deletions qiskit/user_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@ class UserConfig:
circuit_mpl_style = default
circuit_mpl_style_path = ~/.qiskit:<default location>
transpile_optimization_level = 1
suppress_packaging_warnings = False
parallel = False
num_processes = 4
Expand Down Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
8 changes: 4 additions & 4 deletions test/python/algorithms/test_vqe.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion test/python/opflow/legacy/test_weighted_pauli_operator.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 3 additions & 3 deletions test/python/opflow/test_state_op_meas_evals.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
Expand Down
46 changes: 1 addition & 45 deletions test/python/test_user_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down Expand Up @@ -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]
Expand Down Expand Up @@ -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)

0 comments on commit 4f9acda

Please sign in to comment.