Repository: Qiskit/qiskit-terra (python)

Issue:
`qiskit.test.decorators.enforce_subclasses_call` machinery is broken in 3.11.1
### Environment

- **Qiskit Terra version**: 

HEAD of main, currently `dd7f9390cf07`

- **Python version**: 

3.11.1

- **Operating system**: 

Both macOS and ubuntu, so far.


### What is happening?

`import test.python` raises an error in 3.11.1, therefore tests do not run. @wshanks has indicated that it is working fine for him in 3.11.0 but not 3.11.1 (I don't have 3.11.0 installed).

```
Python 3.11.1 (v3.11.1:a7a450f84a, Dec  6 2022, 15:24:06) [Clang 13.0.0 (clang-1300.0.29.30)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import test.python
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/Users/ihincks/ibm/qiskit-terra/test/__init__.py", line 16, in <module>
    from qiskit.test.utils import generate_cases
  File "/Users/ihincks/ibm/qiskit-terra/qiskit/test/__init__.py", line 15, in <module>
    from .base import QiskitTestCase
  File "/Users/ihincks/ibm/qiskit-terra/qiskit/test/base.py", line 167, in <module>
    class QiskitTestCase(BaseQiskitTestCase):
  File "/Users/ihincks/ibm/qiskit-terra/qiskit/utils/classtools.py", line 117, in out
    retval = method(*args, **kwargs)
             ^^^^^^^^^^^^^^^^^^^^^^^
TypeError: TestCase.__init_subclass__() missing 1 required positional argument: 'cls'
```

Or, see an example of this happening in CI: https://github.com/Qiskit/qiskit-experiments/actions/runs/3704009805/jobs/6281449337

### How can we reproduce the issue?

Run the tests or simply `import test.python` in Python 3.11.1.

### What should happen?

This should not error.

### Any suggestions?

_No response_

Pull request:
Update `qiskit.utils.wrap_method` for Python 3.11
<!--
⚠️ If you do not respect this template, your pull request will be closed.
⚠️ Your pull request title should be short detailed and understandable for all.
⚠️ Also, please add a release note file using reno if the change needs to be
  documented in the release notes.
⚠️ If your pull request fixes an open issue, please link to the issue.

- [ ] I have added the tests to cover my changes.
- [ ] I have updated the documentation accordingly.
- [ ] I have read the CONTRIBUTING document.
-->

### Summary

Fixed the behavior of `qiskit.utils.wrap_method` when applied to a `classmethod` that is on `type`. Code changes in Python 3.11 and 3.11.1 revealed that `wrap_method` did not avoid calling a `classmethod` desscriptor's `__get__` method. `wrap_method` was changed to use `inspect.getattr_static` to avoid calling `__get__`.

### Details and comments

The poor handling of `classmethod` by `wrap_method` was first noticed with Python 3.11.1 as described in #9291. `qiskit.test.decorators.enforce_subclasses_call` wraps `QiskitBaseTestCase.__init_subclass__` using `wrap_method`. Prior to 3.11.1, this meant wrapping `object.__init_subclass__` (which is a builtin method that behaves differently), but in 3.11.1 `unittest.TestCase` added an `__init_subclass__` method. This change meant that now `wrap_method` was wrapping a "real" `classmethod` (`__init_subclass__` is a special case but it behaves enough like a `classmethod` for this issue).

In Python 3.11.0, the behavior of the bound method descriptor changed (specifically, it was [this PR](https://github.com/python/cpython/pull/31367) and the change to `funcobject.c` there). The following code illustrates the difference in behavior:

```
class A:
    def m(self):
        pass


print(A().m.__get__(None, A))
```

In Python 3.10, this gives

    <bound method A.m of <__main__.A object at 0x7f942bc8ace0>>

while in Python 3.11 it gives

    <function A.m at 0x7f432b0daa20>

So in Python 3.10 and earlier, the bound method's descriptor returns itself. With Python 3.11 though, the descriptor returns the unbound function.

Prior to the change here, `wrap_method` used `object.__getattribute__` and `type.__getattribute__` to retrieve the descriptor for the method being wrapped so that its `__get__` could be  called by the wrapping descriptor. For methods that are not in `dir(type)`, it used `object.__getattribute__(cls, name)` to retrieve the descriptor in a loop over every `cls` in the wrapped class's MRO passing on `AttributeError`. `object.__getattribute__(cls, name)` checks `type(cls)` (so `type`) for a descriptor and otherwise checks `cls.__dict__` for `name`, raising an `AttributeError` if `name` is not found (the code is [here](https://github.com/python/cpython/blob/432117cd1f59c76d97da2eaff55a7d758301dbc7/Objects/object.c#L1231)). By doing this loop over the MRO, `name` is eventually found. Note, however, that `object.__getattribute__` follows a different path if `name` is a descriptor in `type(cls)` (so in `type`). To avoid this different path, `wrap_method` checked `name` against `dir(type)` and used `type.__getattribute__` instead. `type.__getattribute__(cls, name)` loops over the MRO of `cls` looking for `name` and when it finds it  calls `.__get__(None, cls)` on it (the code is [here](https://github.com/python/cpython/blob/73d2b15bd278e84e89de83dbd2ee64eac2e63cda/Objects/typeobject.c#L3902)). This call returns a bound method version of the classmethod with `cls` bound and this is where the Python 3.11 change in behavior comes in. In `qiskit.utils.classtools._WrappedMethod`, `__get__` tries to delegate to the wrapped method by calling `.__get__` on it  but for this `type` case that means calling `.__get__` on a bound method instead of the `classmethod` and so for 3.11 getting back an unbound function that expects an extra first argument containing the `cls`.

So in Python 3.11.1 with the addition of `__init_subclass__` to `unittest.TestCase`, the `wrap_method` call around `__init_subclass__` on `QiskitBaseTestCase` meant that the definition of `QiskitTestCase(QiskitBaseTestCase)` now tried to  call the wrapped `__init_subclass__` as an unbound function without passing it the required `cls` argument and this led to the error. In 3.11.0, the handling of the descriptor did not matter because `object.__init_subclass__` was the method being wrapped and it has a `*args, **kwargs` signature and does nothing, so it did not matter that it was invoked incorrectly (see [here](https://github.com/python/cpython/blob/48e352a2410b6e962d40359939a0d43aaba5ece9/Objects/typeobject.c#L5863-L5866)).

Closes #9291 

Patch:
diff --git a/azure-pipelines.yml b/azure-pipelines.yml
index d04351c27..2c3539229 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:
diff --git a/qiskit/utils/classtools.py b/qiskit/utils/classtools.py
index 71c17754b..1e58b1ad2 100644
--- a/qiskit/utils/classtools.py
+++ b/qiskit/utils/classtools.py
@@ -25,11 +25,6 @@ from typing import Type, Callable
 _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))
-
 
 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))
diff --git a/releasenotes/notes/wrap-method-311-147d254d4b40e805.yaml b/releasenotes/notes/wrap-method-311-147d254d4b40e805.yaml
new file mode 100644
index 000000000..146e01b90
--- /dev/null
+++ b/releasenotes/notes/wrap-method-311-147d254d4b40e805.yaml
@@ -0,0 +1,11 @@
+---
+fixes:
+  - |
+    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
+    <https://github.com/Qiskit/qiskit-terra/issues/9291>`__.


Test patch:
diff --git a/test/python/utils/test_classtools.py b/test/python/utils/test_classtools.py
index 272c3e0a6..b373675ea 100644
--- a/test/python/utils/test_classtools.py
+++ b/test/python/utils/test_classtools.py
@@ -529,5 +529,5 @@ class TestWrapMethod(QiskitTestCase):
         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)