Skip to content

Commit

Permalink
Update wrapt (#993)
Browse files Browse the repository at this point in the history
* Update wrapt to 1.16.0

* Import duplicate functions directly from wrapt

* Update object wrappers for wrapt 1.16.0

* Add warning to wrapt duplicate code

* Linting

* Use super rather than hard coded Object proxy

* Formatting

* Add test file for wrapper attributes

* Linting

* Add descriptions to assertions

* Overhaul test suite for clarity

* Move functions into fixtures

* [Mega-Linter] Apply linters fixes

* Bump tests

* Fix typo

* Larger timeout for protobuf

---------

Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
Co-authored-by: TimPansino <TimPansino@users.noreply.github.com>
  • Loading branch information
3 people authored Dec 6, 2023
1 parent cd74bc4 commit 030cfc9
Show file tree
Hide file tree
Showing 11 changed files with 552 additions and 490 deletions.
212 changes: 55 additions & 157 deletions newrelic/common/object_wrapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,19 @@
"""

import sys
import inspect

from newrelic.packages import six

from newrelic.packages.wrapt import (ObjectProxy as _ObjectProxy,
FunctionWrapper as _FunctionWrapper,
BoundFunctionWrapper as _BoundFunctionWrapper)

from newrelic.packages.wrapt.wrappers import _FunctionWrapperBase
from newrelic.packages.wrapt import BoundFunctionWrapper as _BoundFunctionWrapper
from newrelic.packages.wrapt import CallableObjectProxy as _CallableObjectProxy
from newrelic.packages.wrapt import FunctionWrapper as _FunctionWrapper
from newrelic.packages.wrapt import ObjectProxy as _ObjectProxy
from newrelic.packages.wrapt import ( # noqa: F401; pylint: disable=W0611
apply_patch,
resolve_path,
wrap_object,
wrap_object_attribute,
)
from newrelic.packages.wrapt.__wrapt__ import _FunctionWrapperBase

# We previously had our own pure Python implementation of the generic
# object wrapper but we now defer to using the wrapt module as its C
Expand All @@ -47,28 +50,36 @@
# ObjectProxy or FunctionWrapper should be used going forward.


class _ObjectWrapperBase(object):
class ObjectProxy(_ObjectProxy):
"""
This class provides method overrides for all object wrappers used by the
agent. These methods allow attributes to be defined with the special prefix
_nr_ to be interpretted as attributes on the wrapper, rather than the
wrapped object. Inheriting from the base class wrapt.ObjectProxy preserves
method resolution order (MRO) through multiple inheritance.
(See https://www.python.org/download/releases/2.3/mro/).
"""

def __setattr__(self, name, value):
if name.startswith('_nr_'):
name = name.replace('_nr_', '_self_', 1)
if name.startswith("_nr_"):
name = name.replace("_nr_", "_self_", 1)
setattr(self, name, value)
else:
_ObjectProxy.__setattr__(self, name, value)
super(ObjectProxy, self).__setattr__(name, value)

def __getattr__(self, name):
if name.startswith('_nr_'):
name = name.replace('_nr_', '_self_', 1)
if name.startswith("_nr_"):
name = name.replace("_nr_", "_self_", 1)
return getattr(self, name)
else:
return _ObjectProxy.__getattr__(self, name)
return super(ObjectProxy, self).__getattr__(name)

def __delattr__(self, name):
if name.startswith('_nr_'):
name = name.replace('_nr_', '_self_', 1)
if name.startswith("_nr_"):
name = name.replace("_nr_", "_self_", 1)
delattr(self, name)
else:
_ObjectProxy.__delattr__(self, name)
super(ObjectProxy, self).__delattr__(name)

@property
def _nr_next_object(self):
Expand All @@ -79,8 +90,7 @@ def _nr_last_object(self):
try:
return self._self_last_object
except AttributeError:
self._self_last_object = getattr(self.__wrapped__,
'_nr_last_object', self.__wrapped__)
self._self_last_object = getattr(self.__wrapped__, "_nr_last_object", self.__wrapped__)
return self._self_last_object

@property
Expand All @@ -96,166 +106,45 @@ def _nr_parent(self):
return self._self_parent


class _NRBoundFunctionWrapper(_ObjectWrapperBase, _BoundFunctionWrapper):
class _NRBoundFunctionWrapper(ObjectProxy, _BoundFunctionWrapper):
pass


class FunctionWrapper(_ObjectWrapperBase, _FunctionWrapper):
class FunctionWrapper(ObjectProxy, _FunctionWrapper):
__bound_function_wrapper__ = _NRBoundFunctionWrapper


class ObjectProxy(_ObjectProxy):

def __setattr__(self, name, value):
if name.startswith('_nr_'):
name = name.replace('_nr_', '_self_', 1)
setattr(self, name, value)
else:
_ObjectProxy.__setattr__(self, name, value)

def __getattr__(self, name):
if name.startswith('_nr_'):
name = name.replace('_nr_', '_self_', 1)
return getattr(self, name)
else:
return _ObjectProxy.__getattr__(self, name)

def __delattr__(self, name):
if name.startswith('_nr_'):
name = name.replace('_nr_', '_self_', 1)
delattr(self, name)
else:
_ObjectProxy.__delattr__(self, name)

@property
def _nr_next_object(self):
return self.__wrapped__

@property
def _nr_last_object(self):
try:
return self._self_last_object
except AttributeError:
self._self_last_object = getattr(self.__wrapped__,
'_nr_last_object', self.__wrapped__)
return self._self_last_object


class CallableObjectProxy(ObjectProxy):
class CallableObjectProxy(ObjectProxy, _CallableObjectProxy):
pass

def __call__(self, *args, **kwargs):
return self.__wrapped__(*args, **kwargs)

# The ObjectWrapper class needs to be deprecated and removed once all our
# own code no longer uses it. It reaches down into what are wrapt internals
# at present which shouldn't be doing.


class ObjectWrapper(_ObjectWrapperBase, _FunctionWrapperBase):
class ObjectWrapper(ObjectProxy, _FunctionWrapperBase):
__bound_function_wrapper__ = _NRBoundFunctionWrapper

def __init__(self, wrapped, instance, wrapper):
if isinstance(wrapped, classmethod):
binding = 'classmethod'
binding = "classmethod"
elif isinstance(wrapped, staticmethod):
binding = 'staticmethod'
binding = "staticmethod"
else:
binding = 'function'

super(ObjectWrapper, self).__init__(wrapped, instance, wrapper,
binding=binding)


# Helper functions for performing monkey patching.
binding = "function"

super(ObjectWrapper, self).__init__(wrapped, instance, wrapper, binding=binding)

def resolve_path(module, name):
if isinstance(module, six.string_types):
__import__(module)
module = sys.modules[module]

parent = module

path = name.split('.')
attribute = path[0]

original = getattr(parent, attribute)
for attribute in path[1:]:
parent = original

# We can't just always use getattr() because in doing
# that on a class it will cause binding to occur which
# will complicate things later and cause some things not
# to work. For the case of a class we therefore access
# the __dict__ directly. To cope though with the wrong
# class being given to us, or a method being moved into
# a base class, we need to walk the class hierarchy to
# work out exactly which __dict__ the method was defined
# in, as accessing it from __dict__ will fail if it was
# not actually on the class given. Fallback to using
# getattr() if we can't find it. If it truly doesn't
# exist, then that will fail.

if inspect.isclass(original):
for cls in inspect.getmro(original):
if attribute in vars(cls):
original = vars(cls)[attribute]
break
else:
original = getattr(original, attribute)

else:
original = getattr(original, attribute)

return (parent, attribute, original)


def apply_patch(parent, attribute, replacement):
setattr(parent, attribute, replacement)


def wrap_object(module, name, factory, args=(), kwargs={}):
(parent, attribute, original) = resolve_path(module, name)
wrapper = factory(original, *args, **kwargs)
apply_patch(parent, attribute, wrapper)
return wrapper

# Function for apply a proxy object to an attribute of a class instance.
# The wrapper works by defining an attribute of the same name on the
# class which is a descriptor and which intercepts access to the
# instance attribute. Note that this cannot be used on attributes which
# are themselves defined by a property object.


class AttributeWrapper(object):

def __init__(self, attribute, factory, args, kwargs):
self.attribute = attribute
self.factory = factory
self.args = args
self.kwargs = kwargs

def __get__(self, instance, owner):
value = instance.__dict__[self.attribute]
return self.factory(value, *self.args, **self.kwargs)

def __set__(self, instance, value):
instance.__dict__[self.attribute] = value

def __delete__(self, instance):
del instance.__dict__[self.attribute]


def wrap_object_attribute(module, name, factory, args=(), kwargs={}):
path, attribute = name.rsplit('.', 1)
parent = resolve_path(module, path)[2]
wrapper = AttributeWrapper(attribute, factory, args, kwargs)
apply_patch(parent, attribute, wrapper)
return wrapper

# Function for creating a decorator for applying to functions, as well as
# short cut functions for applying wrapper functions via monkey patching.

# WARNING: These functions are reproduced directly from wrapt, but using
# our FunctionWrapper class which includes the _nr_ attriubte overrides
# that are inherited from our subclass of wrapt.ObjectProxy.These MUST be
# kept in sync with wrapt when upgrading, or drift may introduce bugs.


def function_wrapper(wrapper):
def _wrapper(wrapped, instance, args, kwargs):
Expand All @@ -267,16 +156,18 @@ def _wrapper(wrapped, instance, args, kwargs):
else:
target_wrapper = wrapper.__get__(instance, type(instance))
return FunctionWrapper(target_wrapped, target_wrapper)

return FunctionWrapper(wrapper, _wrapper)


def wrap_function_wrapper(module, name, wrapper):
return wrap_object(module, name, FunctionWrapper, (wrapper,))


def patch_function_wrapper(module, name):
def patch_function_wrapper(module, name, enabled=None):
def _wrapper(wrapper):
return wrap_object(module, name, FunctionWrapper, (wrapper,))
return wrap_object(module, name, FunctionWrapper, (wrapper, enabled))

return _wrapper


Expand All @@ -299,10 +190,14 @@ def _execute(wrapped, instance, args, kwargs):
return wrapped(*args, **kwargs)
finally:
setattr(parent, attribute, original)

return FunctionWrapper(target_wrapped, _execute)

return FunctionWrapper(wrapper, _wrapper)

return _decorator


# Generic decorators for performing actions before and after a wrapped
# function is called, or modifying the inbound arguments or return value.

Expand All @@ -315,6 +210,7 @@ def _wrapper(wrapped, instance, args, kwargs):
else:
function(*args, **kwargs)
return wrapped(*args, **kwargs)

return _wrapper


Expand All @@ -335,6 +231,7 @@ def _wrapper(wrapped, instance, args, kwargs):
else:
function(*args, **kwargs)
return result

return _wrapper


Expand Down Expand Up @@ -382,6 +279,7 @@ def out_function(function):
@function_wrapper
def _wrapper(wrapped, instance, args, kwargs):
return function(wrapped(*args, **kwargs))

return _wrapper


Expand Down
11 changes: 7 additions & 4 deletions newrelic/packages/wrapt/__init__.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
__version_info__ = ('1', '14', '1')
__version_info__ = ('1', '16', '0')
__version__ = '.'.join(__version_info__)

from .wrappers import (ObjectProxy, CallableObjectProxy, FunctionWrapper,
BoundFunctionWrapper, WeakFunctionProxy, PartialCallableObjectProxy,
resolve_path, apply_patch, wrap_object, wrap_object_attribute,
from .__wrapt__ import (ObjectProxy, CallableObjectProxy, FunctionWrapper,
BoundFunctionWrapper, PartialCallableObjectProxy)

from .patches import (resolve_path, apply_patch, wrap_object, wrap_object_attribute,
function_wrapper, wrap_function_wrapper, patch_function_wrapper,
transient_function_wrapper)

from .weakrefs import WeakFunctionProxy

from .decorators import (adapter_factory, AdapterFactory, decorator,
synchronized)

Expand Down
14 changes: 14 additions & 0 deletions newrelic/packages/wrapt/__wrapt__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import os

from .wrappers import (ObjectProxy, CallableObjectProxy,
PartialCallableObjectProxy, FunctionWrapper,
BoundFunctionWrapper, _FunctionWrapperBase)

try:
if not os.environ.get('WRAPT_DISABLE_EXTENSIONS'):
from ._wrappers import (ObjectProxy, CallableObjectProxy,
PartialCallableObjectProxy, FunctionWrapper,
BoundFunctionWrapper, _FunctionWrapperBase)

except ImportError:
pass
Loading

0 comments on commit 030cfc9

Please sign in to comment.