From fde0203b0e640575f04249e0bbf5aab9abb0eca7 Mon Sep 17 00:00:00 2001
From: Michael Hanselmann <hansmi@google.com>
Date: Wed, 21 Apr 2010 15:46:34 +0200
Subject: [PATCH] utils: Add function for partial application of function
 arguments
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

The function's code was mostly copied from Python's documentation
and it's equivalent to β€œfunctools.partial” in Python 2.5 and above.

Signed-off-by: Michael Hanselmann <hansmi@google.com>
Reviewed-by: Iustin Pop <iustin@google.com>
---
 lib/utils.py                  | 30 ++++++++++++++++++++++++++++++
 test/ganeti.utils_unittest.py | 27 +++++++++++++++++++++++++++
 2 files changed, 57 insertions(+)

diff --git a/lib/utils.py b/lib/utils.py
index d0d07bcdc..20a3b9b3f 100644
--- a/lib/utils.py
+++ b/lib/utils.py
@@ -55,6 +55,11 @@ except ImportError:
   import sha
   sha1 = sha.new
 
+try:
+  import functools
+except ImportError:
+  functools = None
+
 from ganeti import errors
 from ganeti import constants
 
@@ -1501,6 +1506,31 @@ def any(seq, pred=bool): # pylint: disable-msg=W0622
   return False
 
 
+# Even though we're using Python's built-in "partial" function if available,
+# this one is always defined for testing.
+def _partial(func, *args, **keywords): # pylint: disable-msg=W0622
+  """Decorator with partial application of arguments and keywords.
+
+  This function was copied from Python's documentation.
+
+  """
+  def newfunc(*fargs, **fkeywords):
+    newkeywords = keywords.copy()
+    newkeywords.update(fkeywords)
+    return func(*(args + fargs), **newkeywords) # pylint: disable-msg=W0142
+
+  newfunc.func = func
+  newfunc.args = args
+  newfunc.keywords = keywords
+  return newfunc
+
+
+if functools is None:
+  partial = _partial
+else:
+  partial = functools.partial
+
+
 def SingleWaitForFdCondition(fdobj, event, timeout):
   """Waits for a condition to occur on the socket.
 
diff --git a/test/ganeti.utils_unittest.py b/test/ganeti.utils_unittest.py
index 6adb630b8..fa900c934 100755
--- a/test/ganeti.utils_unittest.py
+++ b/test/ganeti.utils_unittest.py
@@ -1592,5 +1592,32 @@ class TestLineSplitter(unittest.TestCase):
                              "", "x"])
 
 
+class TestPartial(testutils.GanetiTestCase):
+  def test(self):
+    self._Test(utils.partial)
+    self._Test(utils._partial)
+
+  def _Test(self, fn):
+    def _TestFunc1(x, power=2):
+      return x ** power
+
+    cubic = fn(_TestFunc1, power=3)
+    self.assertEqual(cubic(1), 1)
+    self.assertEqual(cubic(3), 27)
+    self.assertEqual(cubic(4), 64)
+
+    def _TestFunc2(*args, **kwargs):
+      return (args, kwargs)
+
+    self.assertEqualValues(fn(_TestFunc2, "Hello", "World")("Foo"),
+                           (("Hello", "World", "Foo"), {}))
+
+    self.assertEqualValues(fn(_TestFunc2, "Hello", xyz=123)("Foo"),
+                           (("Hello", "Foo"), {"xyz": 123}))
+
+    self.assertEqualValues(fn(_TestFunc2, xyz=123)("Foo", xyz=999),
+                           (("Foo", ), {"xyz": 999,}))
+
+
 if __name__ == '__main__':
   testutils.GanetiTestProgram()
-- 
GitLab