monasca-analytics/monasca_analytics/banana/bytecode/decorators.py

105 lines
3.8 KiB
Python

#!/usr/bin/env python
# Copyright (c) 2016 Hewlett Packard Enterprise Development Company, L.P.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
# This file is an adaptation of BytecodeAssembler:
#
# http://peak.telecommunity.com/DevCenter/BytecodeAssembler#toc
#
# It has been adapted to match the requirements of monasca_analytics
import sys
def enclosing_frame(frame=None, level=3):
"""Get an enclosing frame that skips DecoratorTools callback code"""
frame = frame or sys._getframe(level)
while frame.f_globals.get('__name__') == __name__:
frame = frame.f_back
return frame
def decorate_assignment(callback, depth=2, frame=None):
"""Invoke 'callback(frame,name,value,old_locals)' on next assign in 'frame'
The frame monitored is determined by the 'depth' argument, which gets
passed to 'sys._getframe()'. When 'callback' is invoked, 'old_locals'
contains a copy of the frame's local variables as they were before the
assignment took place, allowing the callback to access the previous value
of the assigned variable, if any. The callback's return value will become
the new value of the variable. 'name' is the name of the variable being
created or modified, and 'value' is its value (the same as
'frame.f_locals[name]').
This function also returns a decorator function for forward-compatibility
with Python 2.4 '@' syntax. Note, however, that if the returned decorator
is used with Python 2.4 '@' syntax, the callback 'name' argument may be
'None' or incorrect, if the 'value' is not the original function (e.g.
when multiple decorators are used).
"""
frame = enclosing_frame(frame, depth + 1)
oldtrace = [frame.f_trace]
old_locals = frame.f_locals.copy()
def tracer(frm, event, arg):
if event == 'call':
# We don't want to trace into any calls
if oldtrace[0]:
# ...but give the previous tracer a chance to, if it wants
return oldtrace[0](frm, event, arg)
else:
return None
try:
if frm is frame and event != 'exception':
# Aha, time to check for an assignment...
for k, v in frm.f_locals.items():
if k not in old_locals or old_locals[k] is not v:
break
else:
# No luck, keep tracing
return tracer
# Got it, fire the callback, then get the heck outta here...
frm.f_locals[k] = callback(frm, k, v, old_locals)
finally:
# Give the previous tracer a chance to run before we return
if oldtrace[0]:
# And allow it to replace our idea of the "previous" tracer
oldtrace[0] = oldtrace[0](frm, event, arg)
uninstall()
return oldtrace[0]
def uninstall():
# Unlink ourselves from the trace chain.
frame.f_trace = oldtrace[0]
sys.settrace(oldtrace[0])
# Install the trace function
frame.f_trace = tracer
sys.settrace(tracer)
def do_decorate(f):
# Python 2.4 '@' compatibility; call the callback
uninstall()
frame = sys._getframe(1)
return callback(
frame, getattr(f, '__name__', None), f, frame.f_locals
)
return do_decorate