From 00131ab3f857dd62344717e11d61e9fb702767df Mon Sep 17 00:00:00 2001 From: Will Shanks Date: Mon, 19 Dec 2022 16:54:07 -0500 Subject: [PATCH 1/8] Revert "[Test] Pin maximum python version in CI to <3.11.1 (#9296)" This reverts commit 07e0a2fc79bada7c1fbf0594f4ad33934f70b7e2. --- azure-pipelines.yml | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index d04351c270c4..2c3539229277 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -33,9 +33,7 @@ parameters: - name: "supportedPythonVersions" displayName: "All supported versions of Python" type: object - # TODO remove the explicit 3.11.0 pin and set it to 3.11. The pin is currently added - # since 3.11.1 breaks CI, see Qiskit/qiskit-terra#9291. - default: ["3.7", "3.8", "3.9", "3.10", "3.11.0"] + default: ["3.7", "3.8", "3.9", "3.10", "3.11"] - name: "minimumPythonVersion" displayName: "Minimum supported version of Python" @@ -45,7 +43,7 @@ parameters: - name: "maximumPythonVersion" displayName: "Maximum supported version of Python" type: string - default: "3.11.0" + default: "3.11" - name: "minimumRustVersion" displayName: "Minimum supported version of Rust" @@ -111,9 +109,7 @@ stages: - template: ".azure/test-macos.yml" parameters: - # TODO Manually setting this to exclude 3.11 completely, since 3.11.1 breaks - # (see Qiskit/qiskit-terra#9291) and 3.11.0 is not available on azure for MacOS. - pythonVersion: ["3.7", "3.8", "3.9", "3.10"] + pythonVersion: ${{ version }} - template: ".azure/test-windows.yml" parameters: @@ -185,8 +181,7 @@ stages: - template: ".azure/test-macos.yml" parameters: - # TODO remove pin to 3.10 and reset to ${{ parameters.maximumPythonVersion }} - pythonVersion: "3.10" + pythonVersion: ${{ parameters.maximumPythonVersion }} - template: ".azure/test-windows.yml" parameters: From e6e80e8db3b1d502a4b521498d50c30e955fc0c4 Mon Sep 17 00:00:00 2001 From: Will Shanks Date: Mon, 19 Dec 2022 16:54:27 -0500 Subject: [PATCH 2/8] Do not treat __init_subclass__ as a special type method --- qiskit/utils/classtools.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit/utils/classtools.py b/qiskit/utils/classtools.py index 71c17754bdad..4a23e3b97300 100644 --- a/qiskit/utils/classtools.py +++ b/qiskit/utils/classtools.py @@ -28,7 +28,7 @@ # `type` itself has several methods (mostly dunders). When we are wrapping those names, we need to # make sure that we don't interfere with `type.__getattribute__`'s handling that circumvents the # normal inheritance rules when appropriate. -_TYPE_METHODS = set(dir(type)) +_TYPE_METHODS = set(dir(type)) - {"__init_subclass__"} class _lift_to_method: # pylint: disable=invalid-name From e0501e38ddb416052fb95d6e5db0115b9a85f879 Mon Sep 17 00:00:00 2001 From: Will Shanks Date: Tue, 20 Dec 2022 14:34:48 -0500 Subject: [PATCH 3/8] Release note --- .../notes/wrap-method-311-147d254d4b40e805.yaml | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 releasenotes/notes/wrap-method-311-147d254d4b40e805.yaml diff --git a/releasenotes/notes/wrap-method-311-147d254d4b40e805.yaml b/releasenotes/notes/wrap-method-311-147d254d4b40e805.yaml new file mode 100644 index 000000000000..2c1427d94980 --- /dev/null +++ b/releasenotes/notes/wrap-method-311-147d254d4b40e805.yaml @@ -0,0 +1,10 @@ +--- +fixes: + - | + Fixed bad handling of ``__init_subclass__`` by `~qiskit.utils.wrap_method` + in Python 3.11. Previously, in Python 3.11, ``wrap_method`` would wrap the + unbounded function of ``__init_subclass__`` and then fail when invoked + because the bound class object was not passed to the function. Starting in + Python 3.11.1, this issue affected `~qiskit.test.QiskitTestCase`, + preventing it from being imported by other test code. Fixed `#9291 + `__. From 6e31c02eb4bf1287d3b257c119f711c1b20c2d6a Mon Sep 17 00:00:00 2001 From: Will Shanks Date: Tue, 20 Dec 2022 15:51:37 -0500 Subject: [PATCH 4/8] Apply suggestions from code review Co-authored-by: Julien Gacon --- releasenotes/notes/wrap-method-311-147d254d4b40e805.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/releasenotes/notes/wrap-method-311-147d254d4b40e805.yaml b/releasenotes/notes/wrap-method-311-147d254d4b40e805.yaml index 2c1427d94980..1a77f32aca3e 100644 --- a/releasenotes/notes/wrap-method-311-147d254d4b40e805.yaml +++ b/releasenotes/notes/wrap-method-311-147d254d4b40e805.yaml @@ -1,10 +1,10 @@ --- fixes: - | - Fixed bad handling of ``__init_subclass__`` by `~qiskit.utils.wrap_method` + Fixed bad handling of ``__init_subclass__`` by :func:`~qiskit.utils.wrap_method` in Python 3.11. Previously, in Python 3.11, ``wrap_method`` would wrap the unbounded function of ``__init_subclass__`` and then fail when invoked because the bound class object was not passed to the function. Starting in - Python 3.11.1, this issue affected `~qiskit.test.QiskitTestCase`, + Python 3.11.1, this issue affected :class:`~qiskit.test.QiskitTestCase`, preventing it from being imported by other test code. Fixed `#9291 `__. From ce9ade57e2fd1a1f0da7e91dc7ef458418a91fc6 Mon Sep 17 00:00:00 2001 From: Will Shanks Date: Wed, 21 Dec 2022 16:13:52 -0500 Subject: [PATCH 5/8] Use inspect.getattr_static to bypass descriptor call --- qiskit/utils/classtools.py | 19 ++----------------- 1 file changed, 2 insertions(+), 17 deletions(-) diff --git a/qiskit/utils/classtools.py b/qiskit/utils/classtools.py index 4a23e3b97300..1e58b1ad2b91 100644 --- a/qiskit/utils/classtools.py +++ b/qiskit/utils/classtools.py @@ -25,11 +25,6 @@ _MAGIC_STATICMETHODS = {"__new__"} _MAGIC_CLASSMETHODS = {"__init_subclass__", "__prepare__"} -# `type` itself has several methods (mostly dunders). When we are wrapping those names, we need to -# make sure that we don't interfere with `type.__getattribute__`'s handling that circumvents the -# normal inheritance rules when appropriate. -_TYPE_METHODS = set(dir(type)) - {"__init_subclass__"} - class _lift_to_method: # pylint: disable=invalid-name """A decorator that ensures that an input callable object implements ``__get__``. It is @@ -146,16 +141,6 @@ def wrap_method(cls: Type, name: str, *, before: Callable = None, after: Callabl # The best time to apply decorators to methods is before they are bound (e.g. by using function # decorators during the class definition), but if we're making a class decorator, we can't do # that. We need the actual definition of the method, so we have to dodge the normal output of - # `type.__getattribute__`, which evalutes descriptors if it finds them, unless the name we're - # looking for is defined on `type` itself. In that case, we need the attribute getter to - # correctly return the underlying object, not the one that `type` defines for its own purposes. - attribute_getter = type.__getattribute__ if name in _TYPE_METHODS else object.__getattribute__ - for cls_ in inspect.getmro(cls): - try: - method = attribute_getter(cls_, name) - break - except AttributeError: - pass - else: - raise ValueError(f"Method '{name}' is not defined for class '{cls.__name__}'") + # `type.__getattribute__`, which evalutes descriptors if it finds them. + method = inspect.getattr_static(cls, name) setattr(cls, name, _WrappedMethod(method, before, after)) From b10f587e1e70ba8f0cb14c119ef6977a6644de5b Mon Sep 17 00:00:00 2001 From: Will Shanks Date: Wed, 21 Dec 2022 16:33:28 -0500 Subject: [PATCH 6/8] Update release note --- releasenotes/notes/wrap-method-311-147d254d4b40e805.yaml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/releasenotes/notes/wrap-method-311-147d254d4b40e805.yaml b/releasenotes/notes/wrap-method-311-147d254d4b40e805.yaml index 1a77f32aca3e..c18000bdccd8 100644 --- a/releasenotes/notes/wrap-method-311-147d254d4b40e805.yaml +++ b/releasenotes/notes/wrap-method-311-147d254d4b40e805.yaml @@ -1,10 +1,10 @@ --- fixes: - | - Fixed bad handling of ``__init_subclass__`` by :func:`~qiskit.utils.wrap_method` + Fixed bad handling of ``classmethod``s by :func:`~qiskit.utils.wrap_method` in Python 3.11. Previously, in Python 3.11, ``wrap_method`` would wrap the - unbounded function of ``__init_subclass__`` and then fail when invoked - because the bound class object was not passed to the function. Starting in - Python 3.11.1, this issue affected :class:`~qiskit.test.QiskitTestCase`, + unbounded function of ``classmethod`` and then fail when invoked because + the bound class object was not passed to the function. Starting in Python + 3.11.1, this issue affected :class:`~qiskit.test.QiskitTestCase`, preventing it from being imported by other test code. Fixed `#9291 `__. From 123ab5dc77dcd9a23ba11588a813bd7255452df3 Mon Sep 17 00:00:00 2001 From: Will Shanks Date: Wed, 21 Dec 2022 17:03:15 -0500 Subject: [PATCH 7/8] Update wrap_method test --- test/python/utils/test_classtools.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/python/utils/test_classtools.py b/test/python/utils/test_classtools.py index 272c3e0a6146..b373675ea569 100644 --- a/test/python/utils/test_classtools.py +++ b/test/python/utils/test_classtools.py @@ -529,5 +529,5 @@ def test_raises_on_invalid_name(self): class Dummy: pass - with self.assertRaisesRegex(ValueError, "Method 'bad' is not defined for class 'Dummy'"): + with self.assertRaisesRegex(AttributeError, "bad"): wrap_method(Dummy, "bad", before=lambda self: None) From b4f39abbf4fbc1c4864f2678bc860001c90189c0 Mon Sep 17 00:00:00 2001 From: Will Shanks Date: Thu, 22 Dec 2022 14:57:36 -0500 Subject: [PATCH 8/8] Adjust wording on release note --- .../notes/wrap-method-311-147d254d4b40e805.yaml | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/releasenotes/notes/wrap-method-311-147d254d4b40e805.yaml b/releasenotes/notes/wrap-method-311-147d254d4b40e805.yaml index c18000bdccd8..146e01b903c5 100644 --- a/releasenotes/notes/wrap-method-311-147d254d4b40e805.yaml +++ b/releasenotes/notes/wrap-method-311-147d254d4b40e805.yaml @@ -1,10 +1,11 @@ --- fixes: - | - Fixed bad handling of ``classmethod``s by :func:`~qiskit.utils.wrap_method` - in Python 3.11. Previously, in Python 3.11, ``wrap_method`` would wrap the - unbounded function of ``classmethod`` and then fail when invoked because - the bound class object was not passed to the function. Starting in Python - 3.11.1, this issue affected :class:`~qiskit.test.QiskitTestCase`, + Fixed handling of some ``classmethod``s by + :func:`~qiskit.utils.wrap_method` in Python 3.11. Previously, in Python + 3.11, ``wrap_method`` would wrap the unbounded function associated with the + ``classmethod`` and then fail when invoked because the class object usually + bound to the ``classmethod`` was not passed to the function. Starting in + Python 3.11.1, this issue affected :class:`~qiskit.test.QiskitTestCase`, preventing it from being imported by other test code. Fixed `#9291 `__.