Repository: Qiskit/qiskit

Issue:
Avoid potential import cycles from Aer and IBMQ
<!-- ⚠️ If you do not respect this template, your issue will be closed -->
<!-- ⚠️ Make sure to browse the opened and closed issues to confirm this idea does not exist. -->

### What is the expected enhancement?

Right now the effort to remove namespace packaging in #5089 is blocked on the modules level imports of `Aer` and `IBMQ`. This is because this causes an import cycle when those factory objects are no longer in the qiskit namespace. Basically, `qiskit_aer` imports `qiskit` whichi imports `qiskit_aer.Aer` which can't be resolved by python. To move forward on this we need to make these objects lazily load `qiskit.providers.aer`/`qiskit_aer` and `qiskit.providers.ibmq`/`qiskit_ibmq_provider`. The easiest way to handle this is to wait for python 3.7 to be the minimum python version and then we can just use a module level `__getattr__` and import when `Aer` or `IBMQ` are accessed. But that is likely 6+ months away since 0.17.0 will start the deprecation timer on Python 3.6 support. In the meantime we should be able do something like:

```python3
class _AerWrapper:

    def __init__(self):
       self.aer = None
       
    def __getattr__(self, attr):
       if self._aer is None:
           try:
                from qiskit.providers.aer import Aer
            except ImportError:
                raise ImportError("useful message")
            self.aer = Aer
      return getattr(self.aer, attr)
```
(note this is completely untested, just my rough idea).

This should hopefully enable us to remove the import cycle at the module level. It would also have a noticeable benefit for #5100 as we're not unconditionally importing ibmq and aer all the time.


Discussion:
The other thing that might complicate this is `qiskit.__qiskit_version__` which also imports the elements version and is run at the module level. If it's an issue we should hopefully be able to come up with a similar hack for it, it's just a bit more involved because  `qiskit.__qiskit_version__` is a dict so when people use that they're actually only doing a module level getattr so we might be stuck until python 3.7 is the minimum version.

Resolving patch:
diff --git a/qiskit/__init__.py b/qiskit/__init__.py
--- 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_function import execute  # noqa
@@ -88,3 +65,62 @@
         "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:
+    """Lazy loading wrapper for Aer provider."""
+
+    def __init__(self):
+        self.aer = None
+
+    def __bool__(self):
+        if self.aer is None:
+            try:
+                from qiskit.providers import aer
+                self.aer = aer.Aer
+            except ImportError:
+                return False
+        return True
+
+    def __getattr__(self, attr):
+        if not self.aer:
+            try:
+                from qiskit.providers import aer
+                self.aer = aer.Aer
+            except ImportError as exc:
+                raise ImportError('Could not import the Aer provider from the '
+                                  'qiskit-aer package. Install qiskit-aer or '
+                                  'check your installation.') from exc
+        return getattr(self.aer, attr)
+
+
+class IBMQWrapper:
+    """Lazy loading wraooer for IBMQ provider."""
+
+    def __init__(self):
+        self.ibmq = None
+
+    def __bool__(self):
+        if self.ibmq is None:
+            try:
+                from qiskit.providers import ibmq
+                self.ibmq = ibmq.IBMQ
+            except ImportError:
+                return False
+        return True
+
+    def __getattr__(self, attr):
+        if not self.ibmq:
+            try:
+                from qiskit.providers import ibmq
+                self.ibmq = ibmq.IBMQ
+            except ImportError as exc:
+                raise ImportError('Could not import the IBMQ provider from the '
+                                  'qiskit-ibmq-provider package. Install '
+                                  'qiskit-ibmq-provider or check your  '
+                                  'installation.') from exc
+        return getattr(self.ibmq, attr)
+
+
+Aer = AerWrapper()
+IBMQ = IBMQWrapper()
diff --git a/qiskit/user_config.py b/qiskit/user_config.py
--- 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:<default location>
     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)


Test patch:
diff --git a/qiskit/test/mock/fake_backend.py b/qiskit/test/mock/fake_backend.py
--- 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/test/python/algorithms/test_vqe.py b/test/python/algorithms/test_vqe.py
--- a/test/python/algorithms/test_vqe.py
+++ b/test/python/algorithms/test_vqe.py
@@ -10,6 +10,8 @@
 # copyright notice, and modified files need to carry a notice indicating
 # that they have been altered from the originals.
 
+# pylint: disable=no-name-in-module,import-error
+
 """ Test VQE """
 
 import unittest
@@ -167,7 +169,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 +192,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 +217,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 +310,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/test_state_op_meas_evals.py b/test/python/opflow/test_state_op_meas_evals.py
--- a/test/python/opflow/test_state_op_meas_evals.py
+++ b/test/python/opflow/test_state_op_meas_evals.py
@@ -10,6 +10,8 @@
 # copyright notice, and modified files need to carry a notice indicating
 # that they have been altered from the originals.
 
+# pylint: disable=no-name-in-module,import-error
+
 """ Test Operator construction, including OpPrimitives and singletons. """
 
 import unittest
@@ -63,7 +65,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 +91,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 +105,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
--- 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)