Skip to content

Commit

Permalink
Deprecate ObjectWrapper API (#996)
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

* Unify ObjectWrapper with FunctionWrapper

* Remove ObjectWrapper from httplib

* Remove ObjectWrapper from tastypie

* Replace ObjectWrapper use in console

* Remove ObjectWrapper from celery

* Remove extra import

* Update agent APIs

* Deprecate ObjectWrapper

* Fix object wrapper imports

* More import issues

* Fix taskwrapper in celery

* Pin last supported flask restx version for 3.7

* Undo tox changes

* Change all api.object_wrapper references to use new locations

* Fixup: callable_name import

* Fixup: callable_name import

---------

Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
Co-authored-by: Hannah Stepanek <hstepanek@newrelic.com>
  • Loading branch information
3 people authored Jan 5, 2024
1 parent 27c874c commit 32c66d8
Show file tree
Hide file tree
Showing 16 changed files with 65 additions and 94 deletions.
5 changes: 4 additions & 1 deletion newrelic/agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
from newrelic.api.application import application_instance as __application
from newrelic.api.application import application_settings as __application_settings
from newrelic.api.application import register_application as __register_application
from newrelic.api.log import NewRelicContextFormatter # noqa
from newrelic.api.log import NewRelicContextFormatter as __NewRelicContextFormatter
from newrelic.api.time_trace import (
add_custom_span_attribute as __add_custom_span_attribute,
)
Expand Down Expand Up @@ -177,6 +177,7 @@ def __asgi_application(*args, **kwargs):
from newrelic.common.object_wrapper import FunctionWrapper as __FunctionWrapper
from newrelic.common.object_wrapper import InFunctionWrapper as __InFunctionWrapper
from newrelic.common.object_wrapper import ObjectProxy as __ObjectProxy
from newrelic.common.object_wrapper import CallableObjectProxy as __CallableObjectProxy
from newrelic.common.object_wrapper import ObjectWrapper as __ObjectWrapper
from newrelic.common.object_wrapper import OutFunctionWrapper as __OutFunctionWrapper
from newrelic.common.object_wrapper import PostFunctionWrapper as __PostFunctionWrapper
Expand Down Expand Up @@ -276,6 +277,7 @@ def __asgi_application(*args, **kwargs):
wrap_background_task = __wrap_api_call(__wrap_background_task, "wrap_background_task")
LambdaHandlerWrapper = __wrap_api_call(__LambdaHandlerWrapper, "LambdaHandlerWrapper")
lambda_handler = __wrap_api_call(__lambda_handler, "lambda_handler")
NewRelicContextFormatter = __wrap_api_call(__NewRelicContextFormatter, "NewRelicContextFormatter")
transaction_name = __wrap_api_call(__transaction_name, "transaction_name")
TransactionNameWrapper = __wrap_api_call(__TransactionNameWrapper, "TransactionNameWrapper")
wrap_transaction_name = __wrap_api_call(__wrap_transaction_name, "wrap_transaction_name")
Expand Down Expand Up @@ -316,6 +318,7 @@ def __asgi_application(*args, **kwargs):
wrap_message_transaction = __wrap_api_call(__wrap_message_transaction, "wrap_message_transaction")
callable_name = __wrap_api_call(__callable_name, "callable_name")
ObjectProxy = __wrap_api_call(__ObjectProxy, "ObjectProxy")
CallableObjectProxy = __wrap_api_call(__CallableObjectProxy, "CallableObjectProxy")
wrap_object = __wrap_api_call(__wrap_object, "wrap_object")
wrap_object_attribute = __wrap_api_call(__wrap_object_attribute, "wrap_object_attribute")
resolve_path = __wrap_api_call(__resolve_path, "resolve_path")
Expand Down
3 changes: 2 additions & 1 deletion newrelic/api/solr_trace.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

import newrelic.api.object_wrapper
import newrelic.api.time_trace
import newrelic.common.object_wrapper
import newrelic.core.solr_node


Expand Down Expand Up @@ -111,4 +112,4 @@ def decorator(wrapped):


def wrap_solr_trace(module, object_path, library, command):
newrelic.api.object_wrapper.wrap_object(module, object_path, SolrTraceWrapper, (library, command))
newrelic.common.object_wrapper.wrap_object(module, object_path, SolrTraceWrapper, (library, command))
20 changes: 7 additions & 13 deletions newrelic/common/object_wrapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
"""

import inspect
import warnings

from newrelic.packages.wrapt import BoundFunctionWrapper as _BoundFunctionWrapper
from newrelic.packages.wrapt import CallableObjectProxy as _CallableObjectProxy
Expand All @@ -31,7 +32,6 @@
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 Down Expand Up @@ -122,19 +122,13 @@ class CallableObjectProxy(ObjectProxy, _CallableObjectProxy):
# own code no longer uses it. It reaches down into what are wrapt internals
# at present which shouldn't be doing.


class ObjectWrapper(ObjectProxy, _FunctionWrapperBase):
__bound_function_wrapper__ = _NRBoundFunctionWrapper

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

super(ObjectWrapper, self).__init__(wrapped, instance, wrapper, binding=binding)
warnings.warn(
("The ObjectWrapper API is deprecated. Please use one of ObjectProxy, FunctionWrapper, or CallableObjectProxy instead."),
DeprecationWarning,
)
super(ObjectWrapper, self).__init__(wrapped, wrapper)


# Function for creating a decorator for applying to functions, as well as
Expand Down
20 changes: 10 additions & 10 deletions newrelic/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
import newrelic.api.generator_trace
import newrelic.api.import_hook
import newrelic.api.memcache_trace
import newrelic.api.object_wrapper
from newrelic.common.object_names import callable_name
import newrelic.api.profile_trace
import newrelic.api.settings
import newrelic.api.transaction_name
Expand Down Expand Up @@ -1345,7 +1345,7 @@ def _process_background_task_configuration():
group = _config_object.get(section, "group")

if name and name.startswith("lambda "):
callable_vars = {"callable_name": newrelic.api.object_wrapper.callable_name}
callable_vars = {"callable_name": callable_name}
name = eval(name, callable_vars) # nosec, pylint: disable=W0123

_logger.debug("register background-task %s", ((module, object_path, application, name, group),))
Expand Down Expand Up @@ -1395,7 +1395,7 @@ def _process_database_trace_configuration():
sql = _config_object.get(section, "sql")

if sql.startswith("lambda "):
callable_vars = {"callable_name": newrelic.api.object_wrapper.callable_name}
callable_vars = {"callable_name": callable_name}
sql = eval(sql, callable_vars) # nosec, pylint: disable=W0123

_logger.debug("register database-trace %s", ((module, object_path, sql),))
Expand Down Expand Up @@ -1450,11 +1450,11 @@ def _process_external_trace_configuration():
method = _config_object.get(section, "method")

if url.startswith("lambda "):
callable_vars = {"callable_name": newrelic.api.object_wrapper.callable_name}
callable_vars = {"callable_name": callable_name}
url = eval(url, callable_vars) # nosec, pylint: disable=W0123

if method and method.startswith("lambda "):
callable_vars = {"callable_name": newrelic.api.object_wrapper.callable_name}
callable_vars = {"callable_name": callable_name}
method = eval(method, callable_vars) # nosec, pylint: disable=W0123

_logger.debug("register external-trace %s", ((module, object_path, library, url, method),))
Expand Down Expand Up @@ -1522,7 +1522,7 @@ def _process_function_trace_configuration():
rollup = _config_object.get(section, "rollup")

if name and name.startswith("lambda "):
callable_vars = {"callable_name": newrelic.api.object_wrapper.callable_name}
callable_vars = {"callable_name": callable_name}
name = eval(name, callable_vars) # nosec, pylint: disable=W0123

_logger.debug(
Expand Down Expand Up @@ -1580,7 +1580,7 @@ def _process_generator_trace_configuration():
group = _config_object.get(section, "group")

if name and name.startswith("lambda "):
callable_vars = {"callable_name": newrelic.api.object_wrapper.callable_name}
callable_vars = {"callable_name": callable_name}
name = eval(name, callable_vars) # nosec, pylint: disable=W0123

_logger.debug("register generator-trace %s", ((module, object_path, name, group),))
Expand Down Expand Up @@ -1639,7 +1639,7 @@ def _process_profile_trace_configuration():
depth = _config_object.get(section, "depth")

if name and name.startswith("lambda "):
callable_vars = {"callable_name": newrelic.api.object_wrapper.callable_name}
callable_vars = {"callable_name": callable_name}
name = eval(name, callable_vars) # nosec, pylint: disable=W0123

_logger.debug("register profile-trace %s", ((module, object_path, name, group, depth),))
Expand Down Expand Up @@ -1689,7 +1689,7 @@ def _process_memcache_trace_configuration():
command = _config_object.get(section, "command")

if command.startswith("lambda "):
callable_vars = {"callable_name": newrelic.api.object_wrapper.callable_name}
callable_vars = {"callable_name": callable_name}
command = eval(command, callable_vars) # nosec, pylint: disable=W0123

_logger.debug("register memcache-trace %s", (module, object_path, command))
Expand Down Expand Up @@ -1749,7 +1749,7 @@ def _process_transaction_name_configuration():
priority = _config_object.getint(section, "priority")

if name and name.startswith("lambda "):
callable_vars = {"callable_name": newrelic.api.object_wrapper.callable_name}
callable_vars = {"callable_name": callable_name}
name = eval(name, callable_vars) # nosec, pylint: disable=W0123

_logger.debug("register transaction-name %s", ((module, object_path, name, group, priority),))
Expand Down
9 changes: 4 additions & 5 deletions newrelic/console.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,7 @@ def doc_signature(func):
return formatargspec(args[1:], varargs, keywords, defaults)


from newrelic.api.object_wrapper import ObjectWrapper
from newrelic.api.transaction import Transaction
from newrelic.common.object_wrapper import ObjectProxy
from newrelic.core.agent import agent_instance
from newrelic.core.config import flatten_settings, global_settings
from newrelic.core.trace_cache import trace_cache
Expand Down Expand Up @@ -161,7 +160,7 @@ def __call__(self, code=None):
__builtin__.exit = Quitter("exit")


class OutputWrapper(ObjectWrapper):
class OutputWrapper(ObjectProxy):
def flush(self):
try:
shell = _consoles.active
Expand All @@ -187,8 +186,8 @@ def writelines(self, data):
def intercept_console():
setquit()

sys.stdout = OutputWrapper(sys.stdout, None, None)
sys.stderr = OutputWrapper(sys.stderr, None, None)
sys.stdout = OutputWrapper(sys.stdout)
sys.stderr = OutputWrapper(sys.stderr)


class EmbeddedConsole(code.InteractiveConsole):
Expand Down
3 changes: 2 additions & 1 deletion newrelic/core/internal_metrics.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import types
import time
import threading
import newrelic.common.object_wrapper

_context = threading.local()

Expand Down Expand Up @@ -88,7 +89,7 @@ def decorator(wrapped):
return decorator

def wrap_internal_trace(module, object_path, name=None):
newrelic.api.object_wrapper.wrap_object(module, object_path,
newrelic.common.object_wrapper.wrap_object(module, object_path,
InternalTraceWrapper, (name,))

def internal_metric(name, value):
Expand Down
21 changes: 6 additions & 15 deletions newrelic/hooks/application_celery.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@
from newrelic.api.background_task import BackgroundTask
from newrelic.api.function_trace import FunctionTrace
from newrelic.api.pre_function import wrap_pre_function
from newrelic.api.object_wrapper import callable_name, ObjectWrapper
from newrelic.common.object_names import callable_name
from newrelic.common.object_wrapper import FunctionWrapper
from newrelic.api.transaction import current_transaction
from newrelic.core.agent import shutdown_agent

Expand Down Expand Up @@ -98,10 +99,6 @@ def _application():
with BackgroundTask(_application(), _name, 'Celery', source=instance):
return wrapped(*args, **kwargs)

# Start Hotfix v2.2.1.
# obj = ObjectWrapper(wrapped, None, wrapper)
# End Hotfix v2.2.1.

# Celery tasks that inherit from celery.app.task must implement a run()
# method.
# ref: (http://docs.celeryproject.org/en/2.5/reference/
Expand All @@ -110,29 +107,23 @@ def _application():
# task. But celery does a micro-optimization where if the __call__ method
# was not overridden by an inherited task, then it will directly execute
# the run() method without going through the __call__ method. Our
# instrumentation via ObjectWrapper() relies on __call__ being called which
# instrumentation via FunctionWrapper() relies on __call__ being called which
# in turn executes the wrapper() function defined above. Since the micro
# optimization bypasses __call__ method it breaks our instrumentation of
# celery. To circumvent this problem, we added a run() attribute to our
# ObjectWrapper which points to our __call__ method. This causes Celery
# FunctionWrapper which points to our __call__ method. This causes Celery
# to execute our __call__ method which in turn applies the wrapper
# correctly before executing the task.
#
# This is only a problem in Celery versions 2.5.3 to 2.5.5. The later
# versions included a monkey-patching provision which did not perform this
# optimization on functions that were monkey-patched.

# Start Hotfix v2.2.1.
# obj.__dict__['run'] = obj.__call__

class _ObjectWrapper(ObjectWrapper):
class TaskWrapper(FunctionWrapper):
def run(self, *args, **kwargs):
return self.__call__(*args, **kwargs)

obj = _ObjectWrapper(wrapped, None, wrapper)
# End Hotfix v2.2.1.

return obj
return TaskWrapper(wrapped, wrapper)


def instrument_celery_app_task(module):
Expand Down
7 changes: 4 additions & 3 deletions newrelic/hooks/component_piston.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,15 @@

import newrelic.api.transaction
import newrelic.api.function_trace
import newrelic.api.object_wrapper
import newrelic.common.object_wrapper
from newrelic.common.object_names import callable_name
import newrelic.api.in_function


class MethodWrapper(object):

def __init__(self, wrapped, priority=None):
self._nr_name = newrelic.api.object_wrapper.callable_name(wrapped)
self._nr_name = callable_name(wrapped)
self._nr_wrapped = wrapped
self._nr_priority = priority

Expand Down Expand Up @@ -76,7 +77,7 @@ def __call__(self, *args, **kwargs):

def instrument_piston_resource(module):

newrelic.api.object_wrapper.wrap_object(module,
newrelic.common.object_wrapper.wrap_object(module,
'Resource.__init__', ResourceInitWrapper)


Expand Down
19 changes: 7 additions & 12 deletions newrelic/hooks/component_tastypie.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,11 @@
# See the License for the specific language governing permissions and
# limitations under the License.

import sys

from newrelic.api.function_trace import FunctionTraceWrapper
from newrelic.api.object_wrapper import ObjectWrapper, callable_name
from newrelic.common.object_names import callable_name
from newrelic.common.object_wrapper import wrap_function_wrapper, function_wrapper
from newrelic.api.transaction import current_transaction
from newrelic.api.time_trace import notice_error
from newrelic.common.object_wrapper import wrap_function_wrapper


def _nr_wrap_handle_exception(wrapped, instance, args, kwargs):
Expand Down Expand Up @@ -56,6 +54,7 @@ def outer_fn_wrapper(outer_fn, instance, args, kwargs):
name = callable_name(callback)
group = None

@function_wrapper
def inner_fn_wrapper(inner_fn, instance, args, kwargs):
transaction = current_transaction()

Expand All @@ -69,18 +68,14 @@ def inner_fn_wrapper(inner_fn, instance, args, kwargs):

result = outer_fn(*args, **kwargs)

return ObjectWrapper(result, None, inner_fn_wrapper)
return inner_fn_wrapper(result)


def instrument_tastypie_resources(module):
_wrap_view = module.Resource.wrap_view
module.Resource.wrap_view = ObjectWrapper(
_wrap_view, None, outer_fn_wrapper)
wrap_function_wrapper(module, "Resource.wrap_view", outer_fn_wrapper)

wrap_function_wrapper(module, 'Resource._handle_500',
_nr_wrap_handle_exception)
wrap_function_wrapper(module, 'Resource._handle_500', _nr_wrap_handle_exception)


def instrument_tastypie_api(module):
_wrap_view = module.Api.wrap_view
module.Api.wrap_view = ObjectWrapper(_wrap_view, None, outer_fn_wrapper)
wrap_function_wrapper(module, "Api.wrap_view", outer_fn_wrapper)
3 changes: 2 additions & 1 deletion newrelic/hooks/external_feedparser.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

import newrelic.api.transaction
import newrelic.api.object_wrapper
import newrelic.common.object_wrapper
import newrelic.api.external_trace

class capture_external_trace(object):
Expand Down Expand Up @@ -70,5 +71,5 @@ def __getattr__(self, name):
return getattr(self._nr_next_object, name)

def instrument(module):
newrelic.api.object_wrapper.wrap_object(
newrelic.common.object_wrapper.wrap_object(
module, 'parse', capture_external_trace)
Loading

0 comments on commit 32c66d8

Please sign in to comment.