From a1c377a6433c4b135aa89b58a912c64cffcff62a Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Mon, 27 Jun 2022 14:07:02 -0400 Subject: [PATCH] fix: update wrapt to 1.14.1 to avoid issues with ImportFinder on Python 3.10 (backport #3789) (#3813) * fix: update wrapt to 1.14.1 to avoid issues with ImportFinder on Python 3.10 (#3789) Changelog: https://github.com/GrahamDumpleton/wrapt/blob/f2f1a680113d500f525de78da91ae19235efef16/docs/changes.rst Updating to address issue with new Python requiring find_spec for import hooks ## Reproduction Running the Starlette test suite with Python 3.10.2 as we do for it's framework test suite: ```shell pytest --ddtrace-patch-all tests -k 'not test_request_headers and not test_subdomain_route and not test_websocket_headers and not test_staticfiles_with_invalid_dir_permissions_returns_401' ``` Produces the following error: ``` _____________________________________________________________________________________________________________ test_wsgi_exc_info[asyncio] _____________________________________________________________________________________________________________ name = 'netrc', path = None, target = None > ??? E AttributeError: 'ImportHookFinder' object has no attribute 'find_spec' :939: AttributeError During handling of the above exception, another exception occurred: test_client_factory = functools.partial(, backend='asyncio', backend_options={}) > ??? /src/starlette/tests/middleware/test_wsgi.py:79: ``` This is because `wrapt`s `ImportHookFinder` did not define a `find_spec` method which was not required up until newer versions of Python. ## Checklist - [ ] Library documentation is updated. - [ ] [Corp site](https://github.com/DataDog/documentation/) documentation is updated (link to the PR). ## Reviewer Checklist - [x] Title is accurate. - [ ] Description motivates each change. - [x] No unnecessary changes were introduced in this PR. - [x] PR cannot be broken up into smaller PRs. - [x] Avoid breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes unless absolutely necessary. - [ ] Tests provided or description of manual testing performed is included in the code or PR. - [x] Release note has been added for fixes and features, or else `changelog/no-changelog` label added. - [ ] All relevant GitHub issues are correctly linked. - [ ] Backports are identified and tagged with Mergifyio. - [ ] Add to milestone. (cherry picked from commit 5bd3f792c133b94cc851d8151746950cb881dd54) * add release note Co-authored-by: Brett Langdon Co-authored-by: Kyle Verhoog --- ddtrace/vendor/__init__.py | 2 +- ddtrace/vendor/wrapt/LICENSE | 4 +- ddtrace/vendor/wrapt/__init__.py | 13 +- ddtrace/vendor/wrapt/_wrappers.c | 124 +++++++++++++++++- ddtrace/vendor/wrapt/arguments.py | 38 ++++++ ddtrace/vendor/wrapt/decorators.py | 43 ++++-- ddtrace/vendor/wrapt/importer.py | 76 +++++++++-- ddtrace/vendor/wrapt/wrappers.py | 57 ++++++-- ...-update-wrapt-1.14.1-cacf8d945f0c6fa4.yaml | 4 + tests/integration/test_integration.py | 15 +-- 10 files changed, 324 insertions(+), 52 deletions(-) create mode 100644 ddtrace/vendor/wrapt/arguments.py create mode 100644 releasenotes/notes/fix-update-wrapt-1.14.1-cacf8d945f0c6fa4.yaml diff --git a/ddtrace/vendor/__init__.py b/ddtrace/vendor/__init__.py index 99afe3b78fb..20e2868546c 100644 --- a/ddtrace/vendor/__init__.py +++ b/ddtrace/vendor/__init__.py @@ -13,7 +13,7 @@ Website: https://wrapt.readthedocs.io/en/latest/ Source: https://github.com/GrahamDumpleton/wrapt/ -Version: 1.12.1 +Version: 1.14.1 License: BSD 2-Clause "Simplified" License Notes: diff --git a/ddtrace/vendor/wrapt/LICENSE b/ddtrace/vendor/wrapt/LICENSE index d49cae8439d..fa5e1558c14 100644 --- a/ddtrace/vendor/wrapt/LICENSE +++ b/ddtrace/vendor/wrapt/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2013-2019, Graham Dumpleton +Copyright (c) 2013-2022, Graham Dumpleton All rights reserved. Redistribution and use in source and binary forms, with or without @@ -21,4 +21,4 @@ SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -POSSIBILITY OF SUCH DAMAGE. +POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/ddtrace/vendor/wrapt/__init__.py b/ddtrace/vendor/wrapt/__init__.py index 7be739bf659..ee6539b774a 100644 --- a/ddtrace/vendor/wrapt/__init__.py +++ b/ddtrace/vendor/wrapt/__init__.py @@ -1,4 +1,4 @@ -__version_info__ = ('1', '12', '1') +__version_info__ = ('1', '14', '1') __version__ = '.'.join(__version_info__) from .wrappers import (ObjectProxy, CallableObjectProxy, FunctionWrapper, @@ -13,4 +13,15 @@ from .importer import (register_post_import_hook, when_imported, notify_module_loaded, discover_post_import_hooks) +# Import of inspect.getcallargs() included for backward compatibility. An +# implementation of this was previously bundled and made available here for +# Python <2.7. Avoid using this in future. + from inspect import getcallargs + +# Variant of inspect.formatargspec() included here for forward compatibility. +# This is being done because Python 3.11 dropped inspect.formatargspec() but +# code for handling signature changing decorators relied on it. Exposing the +# bundled implementation here in case any user of wrapt was also needing it. + +from .arguments import formatargspec diff --git a/ddtrace/vendor/wrapt/_wrappers.c b/ddtrace/vendor/wrapt/_wrappers.c index 660ad6b3bcc..67c5d5e1afd 100644 --- a/ddtrace/vendor/wrapt/_wrappers.c +++ b/ddtrace/vendor/wrapt/_wrappers.c @@ -1961,13 +1961,13 @@ static int WraptPartialCallableObjectProxy_init( if (!PyObject_Length(args)) { PyErr_SetString(PyExc_TypeError, - "__init__ of partial needs an argument"); + "__init__ of partial needs an argument"); return -1; } if (PyObject_Length(args) < 1) { PyErr_SetString(PyExc_TypeError, - "partial type takes at least one argument"); + "partial type takes at least one argument"); return -1; } @@ -1975,7 +1975,7 @@ static int WraptPartialCallableObjectProxy_init( if (!PyCallable_Check(wrapped)) { PyErr_SetString(PyExc_TypeError, - "the first argument must be callable"); + "the first argument must be callable"); return -1; } @@ -1985,7 +1985,7 @@ static int WraptPartialCallableObjectProxy_init( return -1; result = WraptPartialCallableObjectProxy_raw_init(self, wrapped, - fnargs, kwds); + fnargs, kwds); Py_DECREF(fnargs); @@ -2299,12 +2299,15 @@ static PyObject *WraptFunctionWrapperBase_call( PyObject *result = NULL; static PyObject *function_str = NULL; + static PyObject *classmethod_str = NULL; if (!function_str) { #if PY_MAJOR_VERSION >= 3 function_str = PyUnicode_InternFromString("function"); + classmethod_str = PyUnicode_InternFromString("classmethod"); #else function_str = PyString_InternFromString("function"); + classmethod_str = PyString_InternFromString("classmethod"); #endif } @@ -2334,8 +2337,10 @@ static PyObject *WraptFunctionWrapperBase_call( kwds = param_kwds; } - if (self->instance == Py_None && (self->binding == function_str || + if ((self->instance == Py_None) && (self->binding == function_str || PyObject_RichCompareBool(self->binding, function_str, + Py_EQ) == 1 || self->binding == classmethod_str || + PyObject_RichCompareBool(self->binding, classmethod_str, Py_EQ) == 1)) { PyObject *instance = NULL; @@ -2510,6 +2515,101 @@ static PyObject *WraptFunctionWrapperBase_descr_get( /* ------------------------------------------------------------------------- */ +static PyObject *WraptFunctionWrapperBase_set_name( + WraptFunctionWrapperObject *self, PyObject *args, PyObject *kwds) +{ + PyObject *method = NULL; + PyObject *result = NULL; + + if (!self->object_proxy.wrapped) { + PyErr_SetString(PyExc_ValueError, "wrapper has not been initialized"); + return NULL; + } + + method = PyObject_GetAttrString(self->object_proxy.wrapped, + "__set_name__"); + + if (!method) { + PyErr_Clear(); + Py_INCREF(Py_None); + return Py_None; + } + + result = PyObject_Call(method, args, kwds); + + Py_DECREF(method); + + return result; +} + +/* ------------------------------------------------------------------------- */ + +static PyObject *WraptFunctionWrapperBase_instancecheck( + WraptFunctionWrapperObject *self, PyObject *instance) +{ + PyObject *object = NULL; + PyObject *result = NULL; + + int check = 0; + + if (!self->object_proxy.wrapped) { + PyErr_SetString(PyExc_ValueError, "wrapper has not been initialized"); + return NULL; + } + + check = PyObject_IsInstance(instance, self->object_proxy.wrapped); + + if (check < 0) { + return NULL; + } + + result = check ? Py_True : Py_False; + + Py_INCREF(result); + return result; +} + +/* ------------------------------------------------------------------------- */ + +static PyObject *WraptFunctionWrapperBase_subclasscheck( + WraptFunctionWrapperObject *self, PyObject *args) +{ + PyObject *subclass = NULL; + PyObject *object = NULL; + PyObject *result = NULL; + + int check = 0; + + if (!self->object_proxy.wrapped) { + PyErr_SetString(PyExc_ValueError, "wrapper has not been initialized"); + return NULL; + } + + if (!PyArg_ParseTuple(args, "O", &subclass)) + return NULL; + + object = PyObject_GetAttrString(subclass, "__wrapped__"); + + if (!object) + PyErr_Clear(); + + check = PyObject_IsSubclass(object ? object: subclass, + self->object_proxy.wrapped); + + Py_XDECREF(object); + + if (check == -1) + return NULL; + + result = check ? Py_True : Py_False; + + Py_INCREF(result); + + return result; +} + +/* ------------------------------------------------------------------------- */ + static PyObject *WraptFunctionWrapperBase_get_self_instance( WraptFunctionWrapperObject *self, void *closure) { @@ -2580,6 +2680,18 @@ static PyObject *WraptFunctionWrapperBase_get_self_parent( /* ------------------------------------------------------------------------- */; +static PyMethodDef WraptFunctionWrapperBase_methods[] = { + { "__set_name__", (PyCFunction)WraptFunctionWrapperBase_set_name, + METH_VARARGS | METH_KEYWORDS, 0 }, + { "__instancecheck__", (PyCFunction)WraptFunctionWrapperBase_instancecheck, + METH_O, 0}, + { "__subclasscheck__", (PyCFunction)WraptFunctionWrapperBase_subclasscheck, + METH_VARARGS, 0 }, + { NULL, NULL }, +}; + +/* ------------------------------------------------------------------------- */; + static PyGetSetDef WraptFunctionWrapperBase_getset[] = { { "__module__", (getter)WraptObjectProxy_get_module, (setter)WraptObjectProxy_set_module, 0 }, @@ -2633,7 +2745,7 @@ PyTypeObject WraptFunctionWrapperBase_Type = { offsetof(WraptObjectProxyObject, weakreflist), /*tp_weaklistoffset*/ 0, /*tp_iter*/ 0, /*tp_iternext*/ - 0, /*tp_methods*/ + WraptFunctionWrapperBase_methods, /*tp_methods*/ 0, /*tp_members*/ WraptFunctionWrapperBase_getset, /*tp_getset*/ 0, /*tp_base*/ diff --git a/ddtrace/vendor/wrapt/arguments.py b/ddtrace/vendor/wrapt/arguments.py new file mode 100644 index 00000000000..032bc059e00 --- /dev/null +++ b/ddtrace/vendor/wrapt/arguments.py @@ -0,0 +1,38 @@ +# The inspect.formatargspec() function was dropped in Python 3.11 but we need +# need it for when constructing signature changing decorators based on result of +# inspect.getargspec() or inspect.getfullargspec(). The code here implements +# inspect.formatargspec() base on Parameter and Signature from inspect module, +# which were added in Python 3.6. Thanks to Cyril Jouve for the implementation. + +try: + from inspect import Parameter, Signature +except ImportError: + from inspect import formatargspec +else: + def formatargspec(args, varargs=None, varkw=None, defaults=None, + kwonlyargs=(), kwonlydefaults={}, annotations={}): + if kwonlydefaults is None: + kwonlydefaults = {} + ndefaults = len(defaults) if defaults else 0 + parameters = [ + Parameter( + arg, + Parameter.POSITIONAL_OR_KEYWORD, + default=defaults[i] if i >= 0 else Parameter.empty, + annotation=annotations.get(arg, Parameter.empty), + ) for i, arg in enumerate(args, ndefaults - len(args)) + ] + if varargs: + parameters.append(Parameter(varargs, Parameter.VAR_POSITIONAL)) + parameters.extend( + Parameter( + kwonlyarg, + Parameter.KEYWORD_ONLY, + default=kwonlydefaults.get(kwonlyarg, Parameter.empty), + annotation=annotations.get(kwonlyarg, Parameter.empty), + ) for kwonlyarg in kwonlyargs + ) + if varkw: + parameters.append(Parameter(varkw, Parameter.VAR_KEYWORD)) + return_annotation = annotations.get('return', Signature.empty) + return str(Signature(parameters, return_annotation=return_annotation)) \ No newline at end of file diff --git a/ddtrace/vendor/wrapt/decorators.py b/ddtrace/vendor/wrapt/decorators.py index 506303d7a31..c3f2547295a 100644 --- a/ddtrace/vendor/wrapt/decorators.py +++ b/ddtrace/vendor/wrapt/decorators.py @@ -31,10 +31,11 @@ def exec_(_code_, _globs_=None, _locs_=None): del builtins from functools import partial -from inspect import ismethod, isclass, formatargspec -from collections import namedtuple +from inspect import isclass from threading import Lock, RLock +from .arguments import formatargspec + try: from inspect import signature except ImportError: @@ -173,7 +174,7 @@ def __call__(self, wrapped): # function so the wrapper is effectively indistinguishable from the # original wrapped function. -def decorator(wrapper=None, enabled=None, adapter=None): +def decorator(wrapper=None, enabled=None, adapter=None, proxy=FunctionWrapper): # The decorator should be supplied with a single positional argument # which is the wrapper function to be used to implement the # decorator. This may be preceded by a step whereby the keyword @@ -183,7 +184,7 @@ def decorator(wrapper=None, enabled=None, adapter=None): # 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.getargspec() and similar + # argument specification from inspect.getfullargspec() and similar # functions to be overridden with a prototype for a different # function than what was wrapped. The 'enabled' argument provides a # way to enable/disable the use of the decorator. If the type of @@ -194,6 +195,8 @@ def decorator(wrapper=None, enabled=None, adapter=None): # if 'enabled' is callable it will be called to obtain the value to # be checked. If False, the wrapper will not be called and instead # the original wrapped function will be called directly instead. + # The 'proxy' argument provides a way of passing a custom version of + # the FunctionWrapper class used in decorating the function. if wrapper is not None: # Helper function for creating wrapper of the appropriate @@ -206,16 +209,37 @@ def _build(wrapped, wrapper, enabled=None, adapter=None): 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) + exec_('def adapter{}: pass'.format(adapter), ns, ns) adapter = ns['adapter'] + # Override the annotations for the manufactured + # adapter function so they match the original + # adapter signature argument specification. + + if annotations: + adapter.__annotations__ = annotations + return AdapterWrapper(wrapped=wrapped, wrapper=wrapper, enabled=enabled, adapter=adapter) - return FunctionWrapper(wrapped=wrapped, wrapper=wrapper, - enabled=enabled) + return proxy(wrapped=wrapped, wrapper=wrapper, enabled=enabled) # The wrapper has been provided so return the final decorator. # The decorator is itself one of our function wrappers so we @@ -360,7 +384,7 @@ def _capture(target_wrapped): # This one is a bit strange because binding was actually # performed on the wrapper created by our decorator # factory. We need to apply that binding to the decorator - # wrapper function which which the decorator factory + # wrapper function that the decorator factory # was applied to. target_wrapper = wrapper.__get__(None, instance) @@ -384,7 +408,7 @@ def _capture(target_wrapped): # This one is a bit strange because binding was actually # performed on the wrapper created by our decorator # factory. We need to apply that binding to the decorator - # wrapper function which which the decorator factory + # wrapper function that the decorator factory # was applied to. target_wrapper = wrapper.__get__(instance, type(instance)) @@ -408,7 +432,8 @@ def _capture(target_wrapped): # decorator again wrapped in a partial using the collected # arguments. - return partial(decorator, enabled=enabled, adapter=adapter) + return partial(decorator, enabled=enabled, adapter=adapter, + proxy=proxy) # Decorator for implementing thread synchronization. It can be used as a # decorator, in which case the synchronization context is determined by diff --git a/ddtrace/vendor/wrapt/importer.py b/ddtrace/vendor/wrapt/importer.py index 4665f38650c..5c4d4cc6630 100644 --- a/ddtrace/vendor/wrapt/importer.py +++ b/ddtrace/vendor/wrapt/importer.py @@ -10,16 +10,17 @@ if PY2: string_types = basestring, + find_spec = None else: - import importlib string_types = str, + from importlib.util import find_spec from .decorators import synchronized # The dictionary registering any post import hooks to be triggered once # the target module has been imported. Once a module has been imported # and the hooks fired, the list of hooks recorded against the target -# module will be truncacted but the list left in the dictionary. This +# module will be truncated but the list left in the dictionary. This # acts as a flag to indicate that the module had already been imported. _post_import_hooks = {} @@ -152,12 +153,29 @@ class _ImportHookChainedLoader: def __init__(self, loader): self.loader = loader - def load_module(self, fullname): + if hasattr(loader, "load_module"): + self.load_module = self._load_module + if hasattr(loader, "create_module"): + self.create_module = self._create_module + if hasattr(loader, "exec_module"): + self.exec_module = self._exec_module + + def _load_module(self, fullname): module = self.loader.load_module(fullname) notify_module_loaded(module) return module + # Python 3.4 introduced create_module() and exec_module() instead of + # load_module() alone. Splitting the two steps. + + def _create_module(self, spec): + return self.loader.create_module(spec) + + def _exec_module(self, module): + self.loader.exec_module(module) + notify_module_loaded(module) + class ImportHookFinder: def __init__(self): @@ -187,7 +205,7 @@ def find_module(self, fullname, path=None): # Now call back into the import system again. try: - if PY2: + if not find_spec: # For Python 2 we don't have much choice but to # call back in to __import__(). This will # actually cause the module to be imported. If no @@ -208,14 +226,52 @@ def find_module(self, fullname, path=None): # our own loader which will then in turn call the # real loader to import the module and invoke the # post import hooks. - try: - import importlib.util - loader = importlib.util.find_spec(fullname).loader - except (ImportError, AttributeError): - loader = importlib.find_loader(fullname, path) - if loader: + + loader = getattr(find_spec(fullname), "loader", None) + + if loader and not isinstance(loader, _ImportHookChainedLoader): return _ImportHookChainedLoader(loader) + finally: + del self.in_progress[fullname] + + def find_spec(self, fullname, path=None, target=None): + # Since Python 3.4, you are meant to implement find_spec() method + # instead of find_module() and since Python 3.10 you get deprecation + # warnings if you don't define find_spec(). + + # If the module being imported is not one we have registered + # post import hooks for, we can return immediately. We will + # take no further part in the importing of this module. + + if not fullname in _post_import_hooks: + return None + + # When we are interested in a specific module, we will call back + # into the import system a second time to defer to the import + # finder that is supposed to handle the importing of the module. + # We set an in progress flag for the target module so that on + # the second time through we don't trigger another call back + # into the import system and cause a infinite loop. + + if fullname in self.in_progress: + return None + + self.in_progress[fullname] = True + + # Now call back into the import system again. + + try: + # This should only be Python 3 so find_spec() should always + # exist so don't need to check. + + spec = find_spec(fullname) + loader = getattr(spec, "loader", None) + + if loader and not isinstance(loader, _ImportHookChainedLoader): + spec.loader = _ImportHookChainedLoader(loader) + + return spec finally: del self.in_progress[fullname] diff --git a/ddtrace/vendor/wrapt/wrappers.py b/ddtrace/vendor/wrapt/wrappers.py index 18cf5e053eb..2716cd1da1a 100644 --- a/ddtrace/vendor/wrapt/wrappers.py +++ b/ddtrace/vendor/wrapt/wrappers.py @@ -86,6 +86,14 @@ def __init__(self, wrapped): except AttributeError: pass + # Python 3.10 onwards also does not allow itself to be overridden + # using a property and it must instead be set explicitly. + + try: + object.__setattr__(self, '__annotations__', wrapped.__annotations__) + except AttributeError: + pass + @property def __name__(self): return self.__wrapped__.__name__ @@ -102,14 +110,6 @@ def __class__(self): def __class__(self, value): self.__wrapped__.__class__ = value - @property - def __annotations__(self): - return self.__wrapped__.__annotations__ - - @__annotations__.setter - def __annotations__(self, value): - self.__wrapped__.__annotations__ = value - def __dir__(self): return dir(self.__wrapped__) @@ -178,11 +178,23 @@ def __setattr__(self, name, value): object.__setattr__(self, '__qualname__', value.__qualname__) except AttributeError: pass + try: + object.__delattr__(self, '__annotations__') + except AttributeError: + pass + try: + object.__setattr__(self, '__annotations__', value.__annotations__) + except AttributeError: + pass elif name == '__qualname__': setattr(self.__wrapped__, name, value) object.__setattr__(self, name, value) + elif name == '__annotations__': + setattr(self.__wrapped__, name, value) + object.__setattr__(self, name, value) + elif hasattr(type(self), name): object.__setattr__(self, name, value) @@ -550,7 +562,7 @@ def __call__(self, *args, **kwargs): # a function that was already bound to an instance. In that case # we want to extract the instance from the function and use it. - if self._self_binding == 'function': + if self._self_binding in ('function', 'classmethod'): if self._self_instance is None: instance = getattr(self.__wrapped__, '__self__', None) if instance is not None: @@ -566,6 +578,33 @@ def __call__(self, *args, **kwargs): return self._self_wrapper(self.__wrapped__, self._self_instance, args, kwargs) + def __set_name__(self, owner, name): + # This is a special method use to supply information to + # descriptors about what the name of variable in a class + # definition is. Not wanting to add this to ObjectProxy as not + # sure of broader implications of doing that. Thus restrict to + # FunctionWrapper used by decorators. + + if hasattr(self.__wrapped__, "__set_name__"): + self.__wrapped__.__set_name__(owner, name) + + def __instancecheck__(self, instance): + # This is a special method used by isinstance() to make checks + # instance of the `__wrapped__`. + return isinstance(instance, self.__wrapped__) + + def __subclasscheck__(self, subclass): + # This is a special method used by issubclass() to make checks + # about inheritance of classes. We need to upwrap any object + # proxy. Not wanting to add this to ObjectProxy as not sure of + # broader implications of doing that. Thus restrict to + # FunctionWrapper used by decorators. + + if hasattr(subclass, "__wrapped__"): + return issubclass(subclass.__wrapped__, self.__wrapped__) + else: + return issubclass(subclass, self.__wrapped__) + class BoundFunctionWrapper(_FunctionWrapperBase): def __call__(self, *args, **kwargs): diff --git a/releasenotes/notes/fix-update-wrapt-1.14.1-cacf8d945f0c6fa4.yaml b/releasenotes/notes/fix-update-wrapt-1.14.1-cacf8d945f0c6fa4.yaml new file mode 100644 index 00000000000..e62a26ee78a --- /dev/null +++ b/releasenotes/notes/fix-update-wrapt-1.14.1-cacf8d945f0c6fa4.yaml @@ -0,0 +1,4 @@ +--- +fixes: + - | + Update ``wrapt`` to ``1.14.1`` to fix issues with ``ImportFinder`` usage on Python 3.10. diff --git a/tests/integration/test_integration.py b/tests/integration/test_integration.py index 24435addda2..b17581a8350 100644 --- a/tests/integration/test_integration.py +++ b/tests/integration/test_integration.py @@ -798,17 +798,4 @@ def test_no_warnings(): env["DD_TRACE_SQLITE3_ENABLED"] = "false" out, err, _, _ = call_program("ddtrace-run", sys.executable, "-Wall", "-c", "'import ddtrace'", env=env) assert out == b"", out - - # Wrapt is using features deprecated in Python 3.10 - # See https://github.com/GrahamDumpleton/wrapt/issues/200 - if sys.version_info < (3, 10, 0): - assert err == b"", err - else: - # Assert that the only log lines in the output are import warnings - lines = err.splitlines() - assert len(lines) > 0, err - for line in lines: - assert line == ( - b":914: ImportWarning: " - b"ImportHookFinder.find_spec() not found; falling back to find_module()" - ), err + assert err == b"", err