Repository: Qiskit/qiskit

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_


Discussion:
I'm having trouble reproducing this using only `wrap_method`. For example, the following works as expected:
```python
import qiskit.utils

class Foo: 
    pass

def print_blerg(*_): 
    print("blerg")

qiskit.utils.classtools.wrap_method(Foo, "__init_subclass__", after=print_blerg)

class Goo(Foo):
    pass
```
If it works on 3.11.0 maybe https://github.com/python/cpython/pull/99646 is related which touches `TestCase.__init_subclass__`? Though it's weird that the failure isn't happening consistently, in some of the CI runs only Mac and in some only Linux failed..
please, revert https://github.com/Qiskit/qiskit-terra/pull/9296 when fixed.

Resolving patch:
diff --git a/qiskit/utils/classtools.py b/qiskit/utils/classtools.py
--- 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))
-
 
 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))


Test patch:
diff --git a/test/python/utils/test_classtools.py b/test/python/utils/test_classtools.py
--- 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)