From f3ac4ead7a31b25bd2b29f13918146b0ba135d8d Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Mon, 23 Oct 2023 12:45:04 -0400 Subject: [PATCH 1/3] Emit a descriptive error when the QPY version is too new This commit updates the qpy load() function to ensure we are raising a descriptive error message when a user attempts to load a qpy version that's not supported by this version of qiskit. Previously it would fail but with a hard to understand internal error message which was just confusing and not helpful. For example, when trying to load a QPY version 10 payload (the version used by Qiskit 0.45.x) with qiskit-terra 0.25.2 (which only supported up to version 9) it would raise an error message like: ``` Invalid payload format data kind 'b'p''." ``` which doesn't tell you anything meaningful unless you are intimately familiar with the QPY file format and how the load() function works. With this commit it now checks if the version number is supported immediately and if it's too new it will raise an error message that says exactly that. So in the above scenario instead of that error message it will say: ``` The QPY format version being read, 10, isn't supported by this Qiskit version. Please upgrade your version of Qiskit to load this QPY payload. ``` In addition to fix CI on the stable/0.25 branch this commit puts a hard cap on the versions we test backwards compatibility with. Previously the script ran with all tagged releases of Qiskit, but with the recent release of the 0.45.0rc1 tag we no longer can do that. The QPY format emitted by 0.45.0rc1 is not something that the 0.25.x release series can read. --- qiskit/qpy/interface.py | 5 +++++ ...-message-qpy-version-cf0763da22ce2224.yaml | 9 +++++++++ test/python/qpy/test_circuit_load_from_qpy.py | 19 ++++++++++++++++++- test/qpy_compat/process_version.sh | 2 ++ 4 files changed, 34 insertions(+), 1 deletion(-) create mode 100644 releasenotes/notes/fix-error-message-qpy-version-cf0763da22ce2224.yaml diff --git a/qiskit/qpy/interface.py b/qiskit/qpy/interface.py index a22ce5f20db3..6b4e3218ed95 100644 --- a/qiskit/qpy/interface.py +++ b/qiskit/qpy/interface.py @@ -225,6 +225,11 @@ def load( file_obj.read(formats.FILE_HEADER_SIZE), ) ) + if data.version > common.QPY_VERSION: + raise QiskitError( + f"The QPY format version being read, {data.version}, isn't supported by " + "this Qiskit version. Please upgrade your version of Qiskit to load this QPY payload" + ) if data.preface.decode(common.ENCODE) != "QISKIT": raise QiskitError("Input file is not a valid QPY file") version_match = VERSION_PATTERN_REGEX.search(__version__) diff --git a/releasenotes/notes/fix-error-message-qpy-version-cf0763da22ce2224.yaml b/releasenotes/notes/fix-error-message-qpy-version-cf0763da22ce2224.yaml new file mode 100644 index 000000000000..67690c011126 --- /dev/null +++ b/releasenotes/notes/fix-error-message-qpy-version-cf0763da22ce2224.yaml @@ -0,0 +1,9 @@ +--- +fixes: + - | + Fixed an issue with :func:`.qpy.load` when attempting to load a QPY format + version that is not supported by this version of Qiskit it will now display + a descriptive error message. Previously, it would raise an internal error + because of the incompatibility between the formats which was difficult to + debug. If the QPY format verison is not supported that indicates the Qiskit + version will need to be upgraded to read the QPY payload. diff --git a/test/python/qpy/test_circuit_load_from_qpy.py b/test/python/qpy/test_circuit_load_from_qpy.py index 3684e8aacfe6..ba6aeb81f290 100644 --- a/test/python/qpy/test_circuit_load_from_qpy.py +++ b/test/python/qpy/test_circuit_load_from_qpy.py @@ -13,12 +13,15 @@ """Test cases for the schedule block qpy loading and saving.""" import io +import struct from ddt import ddt, data from qiskit.circuit import QuantumCircuit, QuantumRegister, Qubit from qiskit.providers.fake_provider import FakeHanoi, FakeSherbrooke -from qiskit.qpy import dump, load +from qiskit.exceptions import QiskitError +from qiskit.qpy import dump, load, formats +from qiskit.qpy.common import QPY_VERSION from qiskit.test import QiskitTestCase from qiskit.transpiler import PassManager, TranspileLayout from qiskit.transpiler import passes @@ -71,6 +74,20 @@ def test_rzx_calibration_echo(self, angle): self.assert_roundtrip_equal(rzx_qc) +class TestVersions(QpyCircuitTestCase): + """Test version handling in qpy.""" + + def test_invalid_qpy_version(self): + """Test a descriptive exception is raised if QPY version is too new.""" + with io.BytesIO() as buf: + buf.write( + struct.pack(formats.FILE_HEADER_PACK, b"QISKIT", QPY_VERSION + 4, 42, 42, 1, 2) + ) + buf.seek(0) + with self.assertRaisesRegex(QiskitError, str(QPY_VERSION + 4)): + load(buf) + + @ddt class TestLayout(QpyCircuitTestCase): """Test circuit serialization for layout preservation.""" diff --git a/test/qpy_compat/process_version.sh b/test/qpy_compat/process_version.sh index 59c5610eb95c..22bddc1e8916 100755 --- a/test/qpy_compat/process_version.sh +++ b/test/qpy_compat/process_version.sh @@ -18,6 +18,8 @@ version=$1 parts=( ${version//./ } ) if [[ ${parts[1]} -lt 18 ]] ; then exit 0 +elif [[[[ ${parts[1]} -gt 25 ]] ; then + exit 0 fi if [[ ! -d qpy_$version ]] ; then From 763f8a76ac4b3b1471c15e9f0be1c845dde24474 Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Mon, 23 Oct 2023 13:10:29 -0400 Subject: [PATCH 2/3] Fix copy paste error --- test/qpy_compat/process_version.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/qpy_compat/process_version.sh b/test/qpy_compat/process_version.sh index 22bddc1e8916..c575317fd0b1 100755 --- a/test/qpy_compat/process_version.sh +++ b/test/qpy_compat/process_version.sh @@ -18,7 +18,7 @@ version=$1 parts=( ${version//./ } ) if [[ ${parts[1]} -lt 18 ]] ; then exit 0 -elif [[[[ ${parts[1]} -gt 25 ]] ; then +elif [[ ${parts[1]} -gt 25 ]] ; then exit 0 fi From 079ebcd481c60d5728d937d47dc3ad84e648cbac Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Mon, 23 Oct 2023 13:43:38 -0400 Subject: [PATCH 3/3] Fix attribute access name from named tuple --- qiskit/qpy/interface.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/qiskit/qpy/interface.py b/qiskit/qpy/interface.py index 6b4e3218ed95..aaeb3417b287 100644 --- a/qiskit/qpy/interface.py +++ b/qiskit/qpy/interface.py @@ -225,9 +225,9 @@ def load( file_obj.read(formats.FILE_HEADER_SIZE), ) ) - if data.version > common.QPY_VERSION: + if data.qpy_version > common.QPY_VERSION: raise QiskitError( - f"The QPY format version being read, {data.version}, isn't supported by " + f"The QPY format version being read, {data.qpy_version}, isn't supported by " "this Qiskit version. Please upgrade your version of Qiskit to load this QPY payload" ) if data.preface.decode(common.ENCODE) != "QISKIT":