summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJenkins <jenkins@review.openstack.org>2013-06-12 18:30:29 +0000
committerGerrit Code Review <review@openstack.org>2013-06-12 18:30:29 +0000
commit151d027b2002c5d9b188006ef05515c615d72419 (patch)
tree20ebf76071dfc70d5a848a7855f885af7623ca55
parent706fa4b31d1e13ab8774bcd10a917849d14033d9 (diff)
parentd7970343cfd880817ac7985e328c9a8fe5cf6b73 (diff)
downloadoslo-151d027b2002c5d9b188006ef05515c615d72419.tar.gz
oslo-151d027b2002c5d9b188006ef05515c615d72419.tar.xz
oslo-151d027b2002c5d9b188006ef05515c615d72419.zip
Merge "Add a funcutils file for working with functions."
-rw-r--r--MAINTAINERS6
-rw-r--r--openstack/common/funcutils.py76
-rw-r--r--tests/unit/test_funcutils.py139
3 files changed, 221 insertions, 0 deletions
diff --git a/MAINTAINERS b/MAINTAINERS
index 11d0670..128cb22 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -121,6 +121,12 @@ M: Monty Taylor <mordred@inaugust.com>
S: Maintained
F: fixture/
+== funcutils ==
+
+M: Joshua Harlow <harlowja@yahoo-inc.com>
+S: Maintained
+F: funcutils.py
+
== gettextutils ==
M: Mark McLoughlin <markmc@redhat.com>
diff --git a/openstack/common/funcutils.py b/openstack/common/funcutils.py
new file mode 100644
index 0000000..b04d58f
--- /dev/null
+++ b/openstack/common/funcutils.py
@@ -0,0 +1,76 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2011 OpenStack Foundation.
+# All Rights Reserved.
+#
+# 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.
+
+"""Utility methods for working with functions/decorators."""
+
+import inspect
+
+
+def get_wrapped_function(function):
+ """Get the method at the bottom of a stack of decorators."""
+
+ if not hasattr(function, 'func_closure') or not function.func_closure:
+ return function
+
+ def _get_wrapped_function(function):
+ if not hasattr(function, 'func_closure') or not function.func_closure:
+ return None
+
+ for closure in function.func_closure:
+ func = closure.cell_contents
+
+ deeper_func = _get_wrapped_function(func)
+ if deeper_func:
+ return deeper_func
+ elif hasattr(closure.cell_contents, '__call__'):
+ return closure.cell_contents
+
+ return _get_wrapped_function(function)
+
+
+def getcallargs(function, *args, **kwargs):
+ """This is a simplified inspect.getcallargs (2.7+).
+
+ It should be replaced when python >= 2.7 is standard.
+ """
+
+ keyed_args = {}
+ argnames, varargs, keywords, defaults = inspect.getargspec(function)
+
+ keyed_args.update(kwargs)
+
+ # NOTE(alaski) the implicit 'self' or 'cls' argument shows up in
+ # argnames but not in args or kwargs. Uses 'in' rather than '==' because
+ # some tests use 'self2'.
+ if 'self' in argnames[0] or 'cls' == argnames[0]:
+ # The function may not actually be a method or have im_self.
+ # Typically seen when it's stubbed with mox.
+ if inspect.ismethod(function) and hasattr(function, 'im_self'):
+ keyed_args[argnames[0]] = function.im_self
+ else:
+ keyed_args[argnames[0]] = None
+
+ remaining_argnames = filter(lambda x: x not in keyed_args, argnames)
+ keyed_args.update(dict(zip(remaining_argnames, args)))
+
+ if defaults:
+ num_defaults = len(defaults)
+ for argname, value in zip(argnames[-num_defaults:], defaults):
+ if argname not in keyed_args:
+ keyed_args[argname] = value
+
+ return keyed_args
diff --git a/tests/unit/test_funcutils.py b/tests/unit/test_funcutils.py
new file mode 100644
index 0000000..439d825
--- /dev/null
+++ b/tests/unit/test_funcutils.py
@@ -0,0 +1,139 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2011 OpenStack Foundation.
+# Copyright 2011 Justin Santa Barbara
+#
+# All Rights Reserved.
+#
+# 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.
+
+import functools
+
+from openstack.common import funcutils
+
+from tests import utils
+
+
+class FuncutilsTestCase(utils.BaseTestCase):
+ def _test_func(self, instance, red=None, blue=None):
+ pass
+
+ def test_all_kwargs(self):
+ args = ()
+ kwargs = {'instance': {'uuid': 1}, 'red': 3, 'blue': 4}
+ callargs = funcutils.getcallargs(self._test_func, *args, **kwargs)
+
+ #implicit self counts as an arg
+ self.assertEqual(4, len(callargs))
+ self.assertTrue('instance' in callargs)
+ self.assertEqual({'uuid': 1}, callargs['instance'])
+ self.assertTrue('red' in callargs)
+ self.assertEqual(3, callargs['red'])
+ self.assertTrue('blue' in callargs)
+ self.assertEqual(4, callargs['blue'])
+
+ def test_all_args(self):
+ args = ({'uuid': 1}, 3, 4)
+ kwargs = {}
+ callargs = funcutils.getcallargs(self._test_func, *args, **kwargs)
+
+ #implicit self counts as an arg
+ self.assertEqual(4, len(callargs))
+ self.assertTrue('instance' in callargs)
+ self.assertEqual({'uuid': 1}, callargs['instance'])
+ self.assertTrue('red' in callargs)
+ self.assertEqual(3, callargs['red'])
+ self.assertTrue('blue' in callargs)
+ self.assertEqual(4, callargs['blue'])
+
+ def test_mixed_args(self):
+ args = ({'uuid': 1}, 3)
+ kwargs = {'blue': 4}
+ callargs = funcutils.getcallargs(self._test_func, *args, **kwargs)
+
+ #implicit self counts as an arg
+ self.assertEqual(4, len(callargs))
+ self.assertTrue('instance' in callargs)
+ self.assertEqual({'uuid': 1}, callargs['instance'])
+ self.assertTrue('red' in callargs)
+ self.assertEqual(3, callargs['red'])
+ self.assertTrue('blue' in callargs)
+ self.assertEqual(4, callargs['blue'])
+
+ def test_partial_kwargs(self):
+ args = ()
+ kwargs = {'instance': {'uuid': 1}, 'red': 3}
+ callargs = funcutils.getcallargs(self._test_func, *args, **kwargs)
+
+ #implicit self counts as an arg
+ self.assertEqual(4, len(callargs))
+ self.assertTrue('instance' in callargs)
+ self.assertEqual({'uuid': 1}, callargs['instance'])
+ self.assertTrue('red' in callargs)
+ self.assertEqual(3, callargs['red'])
+ self.assertTrue('blue' in callargs)
+ self.assertEqual(None, callargs['blue'])
+
+ def test_partial_args(self):
+ args = ({'uuid': 1}, 3)
+ kwargs = {}
+ callargs = funcutils.getcallargs(self._test_func, *args, **kwargs)
+
+ #implicit self counts as an arg
+ self.assertEqual(4, len(callargs))
+ self.assertTrue('instance' in callargs)
+ self.assertEqual({'uuid': 1}, callargs['instance'])
+ self.assertTrue('red' in callargs)
+ self.assertEqual(3, callargs['red'])
+ self.assertTrue('blue' in callargs)
+ self.assertEqual(None, callargs['blue'])
+
+ def test_partial_mixed_args(self):
+ args = (3,)
+ kwargs = {'instance': {'uuid': 1}}
+ callargs = funcutils.getcallargs(self._test_func, *args, **kwargs)
+
+ self.assertEqual(4, len(callargs))
+ self.assertTrue('instance' in callargs)
+ self.assertEqual({'uuid': 1}, callargs['instance'])
+ self.assertTrue('red' in callargs)
+ self.assertEqual(3, callargs['red'])
+ self.assertTrue('blue' in callargs)
+ self.assertEqual(None, callargs['blue'])
+
+ def _wrapper(self, function):
+
+ @functools.wraps(function)
+ def decorated_function(self, *args, **kwargs):
+ function(self, *args, **kwargs)
+
+ return decorated_function
+
+ def test_wrapped_X(self):
+
+ def wrapped(self, instance, red=None, blue=None):
+ pass
+
+ old_wrapped = wrapped
+
+ # Wrap it many times and ensure that its still the right one.
+ for _i in range(0, 10):
+ wrapped = self._wrapper(wrapped)
+ func = funcutils.get_wrapped_function(wrapped)
+ func_code = func.func_code
+ self.assertEqual(4, len(func_code.co_varnames))
+ self.assertTrue('self' in func_code.co_varnames)
+ self.assertTrue('instance' in func_code.co_varnames)
+ self.assertTrue('red' in func_code.co_varnames)
+ self.assertTrue('blue' in func_code.co_varnames)
+ self.assertEqual(old_wrapped, func)