GrahamDumpleton / wrapt

A Python module for decorators, wrappers and monkey patching.
BSD 2-Clause "Simplified" License
2.04k stars 230 forks source link

Python 3.11: ImportError: cannot import name 'formatargspec' from 'inspect' #196

Closed jwilk closed 2 years ago

jwilk commented 2 years ago

wrapt cannot be imported in Python 3.11:

$ python3.11 --version
Python 3.11.0a2

$ python3.11 -c 'import wrapt'
Traceback (most recent call last):
  File "<string>", line 1, in <module>
  File "/home/jwilk/.local/lib/python3.11/site-packages/wrapt/__init__.py", line 10, in <module>
    from .decorators import (adapter_factory, AdapterFactory, decorator,
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/jwilk/.local/lib/python3.11/site-packages/wrapt/decorators.py", line 34, in <module>
    from inspect import ismethod, isclass, formatargspec
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
ImportError: cannot import name 'formatargspec' from 'inspect' (/usr/local/lib/python3.11/inspect.py)

This is most likely caused by https://github.com/python/cpython/pull/28618 ("Remove deprecated inspect methods").

GrahamDumpleton commented 2 years ago

Can anyone suggest how I can replace what wrapt does when using Python 3.11.

The code is:

    # The 'adapter' argument is used to optionally denote a
    # separate function which is notionally used by an adapter
    # decorator. In that case parts of the function '__code__' and
    # '__defaults__' attributes are used from the adapter function
    # rather than those of the wrapped function. This allows for the
    # argument specification from inspect.getfullargspec() and similar
    # functions to be overridden with a prototype for a different
    # function than what was wrapped.

    ...

            if adapter:
                if isinstance(adapter, AdapterFactory):
                    adapter = adapter(wrapped)

                if not callable(adapter):
                    ns = {}

                    # Check if the signature argument specification has
                    # annotations. If it does then we need to remember
                    # it but also drop it when attempting to manufacture
                    # a standin adapter function. This is necessary else
                    # it will try and look up any types referenced in
                    # the annotations in the empty namespace we use,
                    # which will fail.

                    annotations = {}

                    if not isinstance(adapter, string_types):
                        if len(adapter) == 7:
                            annotations = adapter[-1]
                            adapter = adapter[:-1]
                        adapter = formatargspec(*adapter)

The comment doesn't really explain it too well, but docs also have an example in:

What the code is specifically looking for is where adapter is not a callable nor string, and thus should be an ArgSpec or FullArgSpec tuple. In the latter case annotations is ignored, and a string prototype for the function created without any annotations information.

Python docs say that instead of using formatargspec():

Use signature() and Signature Object, which provide a better introspecting API for callables.

How can they be used though? The signature() function is of no use:

and looks what I need to do is instead convert an ArgSpec or FullArgSpec into a Signature object instead, without annotation information, and then use str() on it.

If someone understands Signature objects can you supply an example of what is required to do that?

Also, wrapt probably needs to me modified so it can be supplied a Signature object for adapter as well, with a copy being made with annotations set, to generate the prototype string.

jwilk commented 2 years ago

looks what I need to do is instead convert an ArgSpec or FullArgSpec into a Signature object instead, without annotation information, and then use str() on it.

I suspect the FullArgSpecSignature converter would be as complex as formatargspec(), but the latter is already implemented, so… How about just copy&pasting it from Python 3.10 stdlib?

Also, wrapt probably needs to me modified so it can be supplied a Signature object for adapter as well

Indeed.

GrahamDumpleton commented 2 years ago

It need not be that hard since getfullargspec() in recent Python versions is implemented using Signature. So the code gives pointers to how the process can be reversed.

Looks like just need to reconstruct the Parameter list and give it to Signature.

GrahamDumpleton commented 2 years ago

Should be fixed in 1.14.0.