Skip to content

Commit cf6222d

Browse files
wshankscoruscating
andauthored
Consolidate packaging functions into framework.package_deps (#1429)
Queries and tests of installed package versions were spread across a few different modules. Here they are combined into one module (`package_deps.py`) as suggested in #1427. --------- Co-authored-by: Helena Zhang <[email protected]>
1 parent 6817445 commit cf6222d

File tree

13 files changed

+83
-65
lines changed

13 files changed

+83
-65
lines changed

qiskit_experiments/data_processing/sklearn_discriminators.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
from typing import Any, List, Dict, TYPE_CHECKING
1616

1717
from qiskit_experiments.data_processing.discriminator import BaseDiscriminator
18-
from qiskit_experiments.warnings import HAS_SKLEARN
18+
from qiskit_experiments.framework.package_deps import HAS_SKLEARN
1919

2020
if TYPE_CHECKING:
2121
from sklearn.discriminant_analysis import (

qiskit_experiments/database_service/utils.py

-6
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212

1313
"""Experiment utility functions."""
1414

15-
import importlib.metadata
1615
import io
1716
import zipfile
1817
import logging
@@ -37,11 +36,6 @@
3736
LOG = logging.getLogger(__name__)
3837

3938

40-
def qiskit_version():
41-
"""Return the Qiskit version."""
42-
return {p: importlib.metadata.distribution(p).version for p in ("qiskit", "qiskit-experiments")}
43-
44-
4539
def parse_timestamp(utc_dt: Union[datetime, str]) -> datetime:
4640
"""Parse a UTC ``datetime`` object or string.
4741

qiskit_experiments/framework/__init__.py

-1
Original file line numberDiff line numberDiff line change
@@ -155,4 +155,3 @@
155155
)
156156
from .json import ExperimentEncoder, ExperimentDecoder
157157
from .restless_mixin import RestlessMixin
158-
from .package_deps import numpy_version

qiskit_experiments/framework/analysis_result.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333

3434
from qiskit_experiments.database_service.device_component import DeviceComponent, to_component
3535
from qiskit_experiments.database_service.exceptions import ExperimentDataError
36-
from qiskit_experiments.database_service.utils import qiskit_version
36+
from qiskit_experiments.framework.package_deps import qiskit_version
3737

3838
LOG = logging.getLogger(__name__)
3939

qiskit_experiments/framework/experiment_data.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,6 @@
4747
)
4848
from qiskit_experiments.framework.json import ExperimentEncoder, ExperimentDecoder
4949
from qiskit_experiments.database_service.utils import (
50-
qiskit_version,
5150
plot_to_svg_bytes,
5251
ThreadSafeOrderedDict,
5352
ThreadSafeList,
@@ -59,6 +58,7 @@
5958
from qiskit_experiments.framework import BackendData
6059
from qiskit_experiments.framework.containers import ArtifactData
6160
from qiskit_experiments.framework import ExperimentStatus, AnalysisStatus, AnalysisCallback
61+
from qiskit_experiments.framework.package_deps import qiskit_version
6262
from qiskit_experiments.database_service.exceptions import (
6363
ExperimentDataError,
6464
ExperimentEntryNotFound,

qiskit_experiments/framework/package_deps.py

+69-5
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,76 @@
1010
# copyright notice, and modified files need to carry a notice indicating
1111
# that they have been altered from the originals.
1212
"""
13-
Functions for checking dependency versions.
13+
Functions for checking and reporting installed package versions.
1414
"""
1515

16-
from importlib.metadata import version
16+
from __future__ import annotations
1717

18+
import warnings
19+
from functools import lru_cache
20+
from importlib.metadata import version as metadata_version
1821

19-
def numpy_version():
20-
"""Returns the current numpy version in (major, minor) form."""
21-
return tuple(map(int, version("numpy").split(".")[:2]))
22+
from packaging.version import InvalidVersion, Version
23+
24+
from qiskit.utils.lazy_tester import LazyImportTester
25+
26+
27+
__all__ = ["HAS_SKLEARN", "HAS_DYNAMICS", "qiskit_version", "version_is_at_least"]
28+
29+
30+
HAS_SKLEARN = LazyImportTester(
31+
{
32+
"sklearn.discriminant_analysis": (
33+
"LinearDiscriminantAnalysis",
34+
"QuadraticDiscriminantAnalysis",
35+
)
36+
},
37+
name="scikit-learn",
38+
install="pip install scikit-learn",
39+
)
40+
41+
HAS_DYNAMICS = LazyImportTester(
42+
"qiskit_dynamics",
43+
name="qiskit-dynamics",
44+
install="pip install qiskit-dynamics",
45+
)
46+
47+
48+
def qiskit_version() -> dict[str, str]:
49+
"""Return a dict with Qiskit names and versions."""
50+
return {p: metadata_version(p) for p in ("qiskit", "qiskit-experiments")}
51+
52+
53+
@lru_cache(maxsize=None)
54+
def version_is_at_least(package: str, version: str) -> bool:
55+
"""Return True if the installed version of package greater than minimum version
56+
57+
Args:
58+
package: Name of the package
59+
version: Minimum version name as a string. This should just include
60+
major, minor, and micro parts. The function will add ``.dev0`` to
61+
also catch any pre-release versions (otherwise ``0.5.0a1`` would
62+
evaluate as less than ``0.5.0``).
63+
64+
Returns:
65+
True if installed version greater than ``version``. False if it is less
66+
or if the installed version of ``package`` can not be parsed using the
67+
specifications of PEP440.
68+
69+
Raises:
70+
PackageNotFoundError:
71+
If ``package`` is not installed.
72+
"""
73+
raw_installed_version = metadata_version(package)
74+
try:
75+
installed_version = Version(raw_installed_version)
76+
except InvalidVersion:
77+
warnings.warn(
78+
(
79+
f"Version string of installed {package} does not match PyPA "
80+
f"specification. Treating as less than {version}."
81+
),
82+
RuntimeWarning,
83+
)
84+
return False
85+
return installed_version >= Version(f"{version}.dev0")

qiskit_experiments/library/characterization/analysis/multi_state_discrimination_analysis.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
from qiskit_experiments.framework import BaseAnalysis, AnalysisResultData, ExperimentData
2222
from qiskit_experiments.data_processing import SkQDA
2323
from qiskit_experiments.visualization import BasePlotter, IQPlotter, MplDrawer, PlotStyle
24-
from qiskit_experiments.warnings import HAS_SKLEARN
24+
from qiskit_experiments.framework.package_deps import HAS_SKLEARN
2525

2626
if TYPE_CHECKING:
2727
from sklearn.discriminant_analysis import QuadraticDiscriminantAnalysis

qiskit_experiments/library/tomography/tomography_analysis.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@
2525
from qiskit.quantum_info.operators.channel.quantum_channel import QuantumChannel
2626

2727
from qiskit_experiments.exceptions import AnalysisError
28-
from qiskit_experiments.framework import BaseAnalysis, AnalysisResultData, Options, numpy_version
28+
from qiskit_experiments.framework import BaseAnalysis, AnalysisResultData, Options
29+
from qiskit_experiments.framework.package_deps import version_is_at_least
2930
from .fitters import (
3031
tomography_fitter_data,
3132
postprocess_fitter,
@@ -311,7 +312,7 @@ def _fidelity_result(
311312
bs_fidelities = []
312313
for _ in range(self.options.target_bootstrap_samples):
313314
# TODO: remove conditional once numpy is pinned at 1.22 and above
314-
if numpy_version() >= (1, 22):
315+
if version_is_at_least("numpy", "1.22"):
315316
sampled_data = rng.multinomial(shot_data, prob_data)
316317
else:
317318
sampled_data = np.zeros_like(outcome_data)

qiskit_experiments/test/pulse_backend.py

+3-12
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,10 @@
1313
"""A Pulse simulation backend based on Qiskit-Dynamics"""
1414

1515
import datetime
16-
import importlib.metadata
17-
from functools import cached_property
1816
from itertools import chain
1917
from typing import Any, Dict, List, Optional, Tuple, Union
2018

2119
import numpy as np
22-
from packaging.version import Version
2320

2421
from qiskit import QuantumCircuit
2522
from qiskit.circuit import CircuitInstruction
@@ -38,9 +35,9 @@
3835
from qiskit.result import Result, Counts
3936
from qiskit.transpiler import InstructionProperties, Target
4037

41-
from qiskit_experiments.warnings import HAS_DYNAMICS
4238
from qiskit_experiments.data_processing.discriminator import BaseDiscriminator
4339
from qiskit_experiments.exceptions import QiskitError
40+
from qiskit_experiments.framework.package_deps import HAS_DYNAMICS, version_is_at_least
4441
from qiskit_experiments.test.utils import FakeJob
4542

4643

@@ -513,7 +510,7 @@ def __init__(
513510
self.rabi_rate_12 = 6.876
514511

515512
if noise is True:
516-
if self._dynamics_ge_05:
513+
if version_is_at_least("qiskit-dynamics", "0.5.0"):
517514
solver_args = {
518515
"array_library": "numpy",
519516
"vectorized": True,
@@ -524,7 +521,7 @@ def __init__(
524521
}
525522
static_dissipators = [t1_dissipator]
526523
else:
527-
if self._dynamics_ge_05:
524+
if version_is_at_least("qiskit-dynamics", "0.5.0"):
528525
solver_args = {
529526
"array_library": "numpy",
530527
}
@@ -632,9 +629,3 @@ def __init__(
632629
self._simulated_pulse_unitaries = {
633630
(schedule.name, (0,), ()): self.solve(schedule, (0,)) for schedule in default_schedules
634631
}
635-
636-
@cached_property
637-
def _dynamics_ge_05(self):
638-
"""True if installed version of qiskit-dynamics>=0.5.0.dev0"""
639-
dyn_version = Version(importlib.metadata.distribution("qiskit-dynamics").version)
640-
return dyn_version > Version("0.5.0.dev0")

qiskit_experiments/warnings.py

-31
This file was deleted.

test/data_processing/test_discriminator.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
from qiskit.exceptions import MissingOptionalLibraryError
2121

2222
from qiskit_experiments.data_processing import SkLDA, SkQDA
23-
from qiskit_experiments.warnings import HAS_SKLEARN
23+
from qiskit_experiments.framework.package_deps import HAS_SKLEARN
2424

2525

2626
def requires_sklearn(func):

test/library/characterization/test_multi_state_discrimination.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
from qiskit_experiments.library import MultiStateDiscrimination
2424
from qiskit_experiments.test.pulse_backend import SingleTransmonTestBackend
2525

26-
from qiskit_experiments.warnings import HAS_SKLEARN
26+
from qiskit_experiments.framework.package_deps import HAS_SKLEARN
2727

2828

2929
def requires_sklearn(func):

test/visualization/test_utils.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
from qiskit.exceptions import QiskitError
2323

2424
from qiskit_experiments.visualization.utils import DataExtentCalculator
25-
from qiskit_experiments.framework.package_deps import numpy_version
25+
from qiskit_experiments.framework.package_deps import version_is_at_least
2626

2727

2828
@ddt
@@ -49,7 +49,7 @@ def _dummy_data(
4949
# The result is a list of pairs representing a moving window of size 2.
5050
# TODO: remove the old code once numpy is above 1.20.
5151
dummy_data = []
52-
if numpy_version() >= (1, 20):
52+
if version_is_at_least("numpy", "1.20"):
5353
for (x_min, x_max), (y_min, y_max) in it.product(
5454
*np.lib.stride_tricks.sliding_window_view(bin_edges, 2, 1)
5555
):

0 commit comments

Comments
 (0)