Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a lazy loading wrapper class for __qiskit_version__ #5620

Merged
merged 14 commits into from
Feb 2, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/apidocs/execute.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
.. _qiskit-execute:

.. automodule:: qiskit.execute
.. automodule:: qiskit.execute_function
:no-members:
:no-inherited-members:
:no-special-members:
6 changes: 3 additions & 3 deletions qiskit/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,14 +73,14 @@

# 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
from qiskit.execute_function import execute # noqa
from qiskit.compiler import transpile, assemble, schedule, sequence # noqa

from .version import __version__ # noqa
from .version import _get_qiskit_versions # noqa
from .version import QiskitVersion # noqa


__qiskit_version__ = _get_qiskit_versions()
__qiskit_version__ = QiskitVersion()


if sys.version_info[0] == 3 and sys.version_info[1] == 6:
Expand Down
8 changes: 4 additions & 4 deletions qiskit/compiler/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@

"""

from .assemble import assemble
from .transpile import transpile
from .schedule import schedule
from .sequence import sequence
from .assembler import assemble
from .transpiler import transpile
from .scheduler import schedule
from .sequencer import sequence
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
8 changes: 4 additions & 4 deletions qiskit/execute.py → qiskit/execute_function.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,11 @@
# that they have been altered from the originals.

"""
=============================================
Executing Experiments (:mod:`qiskit.execute`)
=============================================
======================================================
Executing Experiments (:mod:`qiskit.execute_function`)
======================================================

.. currentmodule:: qiskit.execute
.. currentmodule:: qiskit.execute_function

.. autofunction:: execute
"""
Expand Down
100 changes: 68 additions & 32 deletions qiskit/version.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

"""Contains the terra version."""

from collections.abc import Mapping
import os
import subprocess
import pkg_resources
Expand Down Expand Up @@ -81,35 +82,70 @@ def get_version_info():
__version__ = get_version_info()


def _get_qiskit_versions():
out_dict = {}
out_dict['qiskit-terra'] = __version__
try:
from qiskit.providers import aer
out_dict['qiskit-aer'] = aer.__version__
except Exception:
out_dict['qiskit-aer'] = None
try:
from qiskit import ignis
out_dict['qiskit-ignis'] = ignis.__version__
except Exception:
out_dict['qiskit-ignis'] = None
try:
from qiskit.providers import ibmq
out_dict['qiskit-ibmq-provider'] = ibmq.__version__
except Exception:
out_dict['qiskit-ibmq-provider'] = None
try:
from qiskit import aqua
out_dict['qiskit-aqua'] = aqua.__version__
except Exception:
out_dict['qiskit-aqua'] = None
try:
out_dict['qiskit'] = pkg_resources.get_distribution('qiskit').version
except Exception:
out_dict['qiskit'] = None

return out_dict


__qiskit_version__ = _get_qiskit_versions()
class QiskitVersion(Mapping):
"""A lazy loading wrapper to get qiskit versions."""

__slots__ = ['_version_dict', '_loaded']

def __init__(self):
self._version_dict = {
'qiskit-terra': __version__,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should the get_version_info() call that populates __version__ also be moved to runtime (or maybe this needs python 3.7)?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ideally yes it would be good to do this only at runtime, although for the release case (or anything outside of the dev case) this only adds an extra stat() call which is limited overhead. But I think we'll have to wait until python 3.7 with module getattr to do this.

We could create a custom class with a __str__ and __repr__ that runs get_version_info() internally but that would cause issues for people who use string methods on version (the use case I'm thinking of is qiskit.__version__.split('.') so I think it would be best to wait

'qiskit-aer': None,
'qiskit-ignis': None,
'qiskit-ibmq-provider': None,
'qiskit-aqua': None,
'qiskit': None}
self._loaded = False

def _load_versions(self):
try:
from qiskit.providers import aer
self._version_dict['qiskit-aer'] = aer.__version__
except Exception:
self._version_dict['qiskit-aer'] = None
try:
from qiskit import ignis
self._version_dict['qiskit-ignis'] = ignis.__version__
except Exception:
self._version_dict['qiskit-ignis'] = None
try:
from qiskit.providers import ibmq
self._version_dict['qiskit-ibmq-provider'] = ibmq.__version__
except Exception:
self._version_dict['qiskit-ibmq-provider'] = None
try:
from qiskit import aqua
self._version_dict['qiskit-aqua'] = aqua.__version__
except Exception:
self._version_dict['qiskit-aqua'] = None
try:
self._version_dict['qiskit'] = pkg_resources.get_distribution('qiskit').version
except Exception:
self._version_dict['qiskit'] = None
self._loaded = True

def __repr__(self):
if not self._loaded:
self._load_versions()
return repr(self._version_dict)

def __str__(self):
if not self._loaded:
self._load_versions()
return str(self._version_dict)

def __getitem__(self, key):
if not self._loaded:
self._load_versions()
return self._version_dict[key]

def __iter__(self):
if not self._loaded:
self._load_versions()
return iter(self._version_dict)

def __len__(self):
return len(self._version_dict)


__qiskit_version__ = QiskitVersion()
45 changes: 45 additions & 0 deletions releasenotes/notes/qiskit-version-wrapper-90cb7fcffeaafd6a.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
---
upgrade:
- |
The ``qiskit.__qiskit_version__`` module attribute was previously a ``dict``
will now return a custom read-only ``Mapping`` object that checks the
version of qiskit elements at runtime instead of at import time. This was
done to speed up the import path of qiskit and eliminate a possible import
cycle by only importing the element packages at runtime if the version
is needed from the package. This should be fully compatible with the
``dict`` previously return and for most normal use cases there will be no
difference. However, if some applications were relying on either mutating
the contents or explicitly type checking it may require updates to adapt to
this change.
- |
The ``qiskit.execute`` module has been renamed to
:mod:`qiskit.execute_function`. This was necessary to avoid a potentical
name conflict between the :func:`~qiskit.execute_function.execute` function
which is re-exported as ``qiskit.execute``. ``qiskit.execute`` the function
in some situations could conflict with ``qiskit.execute`` the module which
would lead to a cryptic error because Python was treating ``qiskit.execute``
as the module when the intent was to the function or vice versa. The module
rename was necessary to avoid this conflict. If you're importing
``qiskit.execute`` to get the module (typical usage was
``from qiskit.execute import execute``) you will need to update this to
use ``qiskit.execute_function`` instead. ``qiskit.execute`` will now always
resolve to the function.
- |
The ``qiskit.compiler.transpile``, ``qiskit.compiler.assemble``,
``qiskit.compiler.schedule``, and ``qiskit.compiler.sequence`` modules have
been renamed to ``qiskit.compiler.transpiler``,
``qiskit.compiler.assembler``, ``qiskit.compiler.scheduler``, and
``qiskit.compiler.sequence`` respectively. This was necessary to avoid a
potentical name conflict between the modules and the re-exported function
paths :func:`qiskit.compiler.transpile`, :func:`qiskit.compiler.assemble`,
:func:`qiskit.compiler.schedule`, and :func:`qiskit.compiler.sequence`.
In some situations this name conflict between the module path and
re-exported function path would lead to a cryptic error because Python was
treating an import as the module when the intent was to use the function or
vice versa. The module rename was necessary to avoid this conflict. If
you were using the imports to get the modules before (typical usage would
be like``from qiskit.compiler.transpile import transpile``) you will need
to update this to use the new module paths.
:func:`qiskit.compiler.transpile`, :func:`qiskit.compiler.assemble`,
:func:`qiskit.compiler.schedule`, and :func:`qiskit.compiler.sequence`
will now always resolve to the functions.
2 changes: 1 addition & 1 deletion test/python/circuit/test_parameters.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
ParameterVector)
from qiskit.circuit.exceptions import CircuitError
from qiskit.compiler import assemble, transpile
from qiskit.execute import execute
from qiskit.execute_function import execute
from qiskit import pulse
from qiskit.quantum_info import Operator
from qiskit.test import QiskitTestCase
Expand Down
2 changes: 1 addition & 1 deletion test/python/compiler/test_assembler.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
import qiskit.pulse as pulse
from qiskit.circuit import Instruction, Gate, Parameter, ParameterVector, ParameterExpression
from qiskit.circuit import QuantumRegister, ClassicalRegister, QuantumCircuit
from qiskit.compiler.assemble import assemble
from qiskit.compiler.assembler import assemble
from qiskit.exceptions import QiskitError
from qiskit.pulse import Schedule, Acquire, Play
from qiskit.pulse.channels import MemorySlot, AcquireChannel, DriveChannel, MeasureChannel
Expand Down
2 changes: 1 addition & 1 deletion test/python/compiler/test_disassembler.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
from qiskit.assembler.run_config import RunConfig
from qiskit.circuit import QuantumRegister, ClassicalRegister, QuantumCircuit
from qiskit.circuit import Instruction
from qiskit.compiler.assemble import assemble
from qiskit.compiler.assembler import assemble
from qiskit.test import QiskitTestCase
from qiskit.test.mock import FakeOpenPulse2Q
import qiskit.quantum_info as qi
Expand Down
2 changes: 1 addition & 1 deletion test/python/providers/test_fake_backends.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
from ddt import ddt, data

from qiskit.circuit import QuantumCircuit
from qiskit.execute import execute
from qiskit.execute_function import execute
from qiskit.test.base import QiskitTestCase
from qiskit.test.mock import FakeProvider
from qiskit.test.mock.fake_backend import HAS_AER
Expand Down
2 changes: 1 addition & 1 deletion test/python/quantum_info/test_local_invariance.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
import unittest

from numpy.testing import assert_allclose
from qiskit.execute import execute
from qiskit.execute_function import execute
from qiskit.circuit import QuantumCircuit, QuantumRegister
from qiskit.test import QiskitTestCase
from qiskit.providers.basicaer import UnitarySimulatorPy
Expand Down
25 changes: 25 additions & 0 deletions test/python/test_version.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# This code is part of Qiskit.
#
# (C) Copyright IBM 2021
#
# This code is licensed under the Apache License, Version 2.0. You may
# obtain a copy of this license in the LICENSE.txt file in the root directory
# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
#
# Any modifications or derivative works of this code must retain this
# copyright notice, and modified files need to carry a notice indicating
# that they have been altered from the originals.

"""Tests for qiskit/version.py"""

from qiskit import __qiskit_version__
from qiskit import __version__
from qiskit.test import QiskitTestCase


class TestVersion(QiskitTestCase):
"""Tests for qiskit/version.py"""

def test_qiskit_version(self):
"""Test qiskit-version sets the correct version for terra."""
self.assertEqual(__version__, __qiskit_version__['qiskit-terra'])