[pypy-svn] r56133 - in pypy/dist/pypy/lib: . app_test

fijal at codespeak.net fijal at codespeak.net
Fri Jun 27 19:23:35 CEST 2008


Author: fijal
Date: Fri Jun 27 19:23:34 2008
New Revision: 56133

Added:
   pypy/dist/pypy/lib/app_test/test_functools.py   (contents, props changed)
   pypy/dist/pypy/lib/functools.py   (contents, props changed)
Log:
Lousy implementation of functools with some tests. Not as compliant, but working


Added: pypy/dist/pypy/lib/app_test/test_functools.py
==============================================================================
--- (empty file)
+++ pypy/dist/pypy/lib/app_test/test_functools.py	Fri Jun 27 19:23:34 2008
@@ -0,0 +1,271 @@
+import functools
+import unittest
+from test import test_support
+from weakref import proxy
+import py
+
+ at staticmethod
+def PythonPartial(func, *args, **keywords):
+    'Pure Python approximation of partial()'
+    def newfunc(*fargs, **fkeywords):
+        newkeywords = keywords.copy()
+        newkeywords.update(fkeywords)
+        return func(*(args + fargs), **newkeywords)
+    newfunc.func = func
+    newfunc.args = args
+    newfunc.keywords = keywords
+    return newfunc
+
+def capture(*args, **kw):
+    """capture all positional and keyword arguments"""
+    return args, kw
+
+class TestPartial:
+
+    thetype = functools.partial
+
+    def test_basic_examples(self):
+        p = self.thetype(capture, 1, 2, a=10, b=20)
+        assert p(3, 4, b=30, c=40) == (
+                         ((1, 2, 3, 4), dict(a=10, b=30, c=40)))
+        p = self.thetype(map, lambda x: x*10)
+        assert p([1,2,3,4]) == [10, 20, 30, 40]
+
+    def test_attributes(self):
+        p = self.thetype(capture, 1, 2, a=10, b=20)
+        # attributes should be readable
+        assert p.func == capture
+        assert p.args == (1, 2)
+        assert p.keywords == dict(a=10, b=20)
+        # attributes should not be writable
+        if not isinstance(self.thetype, type):
+            return
+        py.test.raises(TypeError, setattr, p, 'func', map)
+        py.test.raises(TypeError, setattr, p, 'args', (1, 2))
+        py.test.raises(TypeError, setattr, p, 'keywords', dict(a=1, b=2))
+
+    def test_argument_checking(self):
+        py.test.raises(TypeError, self.thetype)     # need at least a func arg
+        try:
+            self.thetype(2)()
+        except TypeError:
+            pass
+        else:
+            raise AssertionError, 'First arg not checked for callability'
+
+    def test_protection_of_callers_dict_argument(self):
+        # a caller's dictionary should not be altered by partial
+        def func(a=10, b=20):
+            return a
+        d = {'a':3}
+        p = self.thetype(func, a=5)
+        assert p(**d) == 3
+        assert d == {'a':3}
+        p(b=7)
+        assert d == {'a':3}
+
+    def test_arg_combinations(self):
+        # exercise special code paths for zero args in either partial
+        # object or the caller
+        p = self.thetype(capture)
+        assert p() == ((), {})
+        assert p(1,2) == ((1,2), {})
+        p = self.thetype(capture, 1, 2)
+        assert p() == ((1,2), {})
+        assert p(3,4) == ((1,2,3,4), {})
+
+    def test_kw_combinations(self):
+        # exercise special code paths for no keyword args in
+        # either the partial object or the caller
+        p = self.thetype(capture)
+        assert p() == ((), {})
+        assert p(a=1) == ((), {'a':1})
+        p = self.thetype(capture, a=1)
+        assert p() == ((), {'a':1})
+        assert p(b=2) == ((), {'a':1, 'b':2})
+        # keyword args in the call override those in the partial object
+        assert p(a=3, b=2) == ((), {'a':3, 'b':2})
+
+    def test_positional(self):
+        # make sure positional arguments are captured correctly
+        for args in [(), (0,), (0,1), (0,1,2), (0,1,2,3)]:
+            p = self.thetype(capture, *args)
+            expected = args + ('x',)
+            got, empty = p('x')
+            assert expected == got and empty == {}
+
+    def test_keyword(self):
+        # make sure keyword arguments are captured correctly
+        for a in ['a', 0, None, 3.5]:
+            p = self.thetype(capture, a=a)
+            expected = {'a':a,'x':None}
+            empty, got = p(x=None)
+            assert expected == got and empty == ()
+
+    def test_no_side_effects(self):
+        # make sure there are no side effects that affect subsequent calls
+        p = self.thetype(capture, 0, a=1)
+        args1, kw1 = p(1, b=2)
+        assert args1 == (0,1) and kw1 == {'a':1,'b':2}
+        args2, kw2 = p()
+        assert args2 == (0,) and kw2 == {'a':1}
+
+    def test_error_propagation(self):
+        def f(x, y):
+            x / y
+        py.test.raises(ZeroDivisionError, self.thetype(f, 1, 0))
+        py.test.raises(ZeroDivisionError, self.thetype(f, 1), 0)
+        py.test.raises(ZeroDivisionError, self.thetype(f), 1, 0)
+        py.test.raises(ZeroDivisionError, self.thetype(f, y=0), 1)
+
+    def test_attributes(self):
+        py.test.skip("Unsupported")
+        p = self.thetype(hex)
+        try:
+            del p.__dict__
+        except TypeError:
+            pass
+        else:
+            raise AssertionError, 'partial object allowed __dict__ to be deleted'
+
+    def test_weakref(self):
+        py.test.skip("unsupported")
+        f = self.thetype(int, base=16)
+        p = proxy(f)
+        assert f.func == p.func
+        f = None
+        py.test.raises(ReferenceError, getattr, p, 'func')
+
+    def test_with_bound_and_unbound_methods(self):
+        data = map(str, range(10))
+        join = self.thetype(str.join, '')
+        assert join(data) == '0123456789'
+        join = self.thetype(''.join)
+        assert join(data) == '0123456789'
+
+#class PartialSubclass(functools.partial):
+#    pass
+
+#class TestPartialSubclass(TestPartial):
+
+#    thetype = PartialSubclass
+
+
+class TestPythonPartial(TestPartial):
+
+    thetype = PythonPartial
+
+class TestUpdateWrapper:
+
+    def check_wrapper(self, wrapper, wrapped,
+                      assigned=functools.WRAPPER_ASSIGNMENTS,
+                      updated=functools.WRAPPER_UPDATES):
+        # Check attributes were assigned
+        for name in assigned:
+            assert getattr(wrapper, name) == getattr(wrapped, name)
+        # Check attributes were updated
+        for name in updated:
+            wrapper_attr = getattr(wrapper, name)
+            wrapped_attr = getattr(wrapped, name)
+            for key in wrapped_attr:
+                assert wrapped_attr[key] == wrapper_attr[key]
+
+    def test_default_update(self):
+        def f():
+            """This is a test"""
+            pass
+        f.attr = 'This is also a test'
+        def wrapper():
+            pass
+        functools.update_wrapper(wrapper, f)
+        self.check_wrapper(wrapper, f)
+        assert wrapper.__name__ == 'f'
+        assert wrapper.__doc__ == 'This is a test'
+        assert wrapper.attr == 'This is also a test'
+
+    def test_no_update(self):
+        def f():
+            """This is a test"""
+            pass
+        f.attr = 'This is also a test'
+        def wrapper():
+            pass
+        functools.update_wrapper(wrapper, f, (), ())
+        self.check_wrapper(wrapper, f, (), ())
+        assert wrapper.__name__ == 'wrapper'
+        assert wrapper.__doc__ == None
+        assert not hasattr(wrapper, 'attr')
+
+    def test_selective_update(self):
+        def f():
+            pass
+        f.attr = 'This is a different test'
+        f.dict_attr = dict(a=1, b=2, c=3)
+        def wrapper():
+            pass
+        wrapper.dict_attr = {}
+        assign = ('attr',)
+        update = ('dict_attr',)
+        functools.update_wrapper(wrapper, f, assign, update)
+        self.check_wrapper(wrapper, f, assign, update)
+        assert wrapper.__name__ == 'wrapper'
+        assert wrapper.__doc__ == None
+        assert wrapper.attr == 'This is a different test'
+        assert wrapper.dict_attr == f.dict_attr
+
+    def test_builtin_update(self):
+        py.test.skip("Unsupported")
+        # Test for bug #1576241
+        def wrapper():
+            pass
+        functools.update_wrapper(wrapper, max)
+        assert wrapper.__name__ == 'max'
+        assert wrapper.__doc__.startswith('max(')
+
+class TestWraps(TestUpdateWrapper):
+
+    def test_default_update(self):
+        def f():
+            """This is a test"""
+            pass
+        f.attr = 'This is also a test'
+        @functools.wraps(f)
+        def wrapper():
+            pass
+        self.check_wrapper(wrapper, f)
+        assert wrapper.__name__ == 'f'
+        assert wrapper.__doc__ == 'This is a test'
+        assert wrapper.attr == 'This is also a test'
+
+    def test_no_update(self):
+        def f():
+            """This is a test"""
+            pass
+        f.attr = 'This is also a test'
+        @functools.wraps(f, (), ())
+        def wrapper():
+            pass
+        self.check_wrapper(wrapper, f, (), ())
+        assert wrapper.__name__ == 'wrapper'
+        assert wrapper.__doc__ == None
+        assert not hasattr(wrapper, 'attr')
+
+    def test_selective_update(self):
+        def f():
+            pass
+        f.attr = 'This is a different test'
+        f.dict_attr = dict(a=1, b=2, c=3)
+        def add_dict_attr(f):
+            f.dict_attr = {}
+            return f
+        assign = ('attr',)
+        update = ('dict_attr',)
+        @functools.wraps(f, assign, update)
+        @add_dict_attr
+        def wrapper():
+            pass
+        self.check_wrapper(wrapper, f, assign, update)
+        assert wrapper.__name__ == 'wrapper'
+        assert wrapper.__doc__ == None
+        assert wrapper.attr == 'This is a different test'
+        assert wrapper.dict_attr == f.dict_attr

Added: pypy/dist/pypy/lib/functools.py
==============================================================================
--- (empty file)
+++ pypy/dist/pypy/lib/functools.py	Fri Jun 27 19:23:34 2008
@@ -0,0 +1,56 @@
+
+def partial_func(func, *args, **keywords):
+    def newfunc(*fargs, **fkeywords):
+        newkeywords = keywords.copy()
+        newkeywords.update(fkeywords)
+        return func(*(args + fargs), **newkeywords)
+    newfunc.func = func
+    newfunc.args = args
+    newfunc.keywords = keywords
+    newfunc.__doc__ = getattr(func, '__doc__', '')
+    return newfunc
+
+partial = staticmethod(partial_func)
+
+from __builtin__ import reduce
+
+# update_wrapper() and wraps() are tools to help write
+# wrapper functions that can handle naive introspection
+
+WRAPPER_ASSIGNMENTS = ('__module__', '__name__', '__doc__')
+WRAPPER_UPDATES = ('__dict__',)
+def update_wrapper(wrapper,
+                   wrapped,
+                   assigned = WRAPPER_ASSIGNMENTS,
+                   updated = WRAPPER_UPDATES):
+    """Update a wrapper function to look like the wrapped function
+
+       wrapper is the function to be updated
+       wrapped is the original function
+       assigned is a tuple naming the attributes assigned directly
+       from the wrapped function to the wrapper function (defaults to
+       functools.WRAPPER_ASSIGNMENTS)
+       updated is a tuple naming the attributes of the wrapper that
+       are updated with the corresponding attribute from the wrapped
+       function (defaults to functools.WRAPPER_UPDATES)
+    """
+    for attr in assigned:
+        setattr(wrapper, attr, getattr(wrapped, attr))
+    for attr in updated:
+        getattr(wrapper, attr).update(getattr(wrapped, attr, {}))
+    # Return the wrapper so this can be used as a decorator via partial()
+    return wrapper
+
+def wraps(wrapped,
+          assigned = WRAPPER_ASSIGNMENTS,
+          updated = WRAPPER_UPDATES):
+    """Decorator factory to apply update_wrapper() to a wrapper function
+
+       Returns a decorator that invokes update_wrapper() with the decorated
+       function as the wrapper argument and the arguments to wraps() as the
+       remaining arguments. Default arguments are as for update_wrapper().
+       This is a convenience function to simplify applying partial() to
+       update_wrapper().
+    """
+    return partial_func(update_wrapper, wrapped=wrapped,
+                        assigned=assigned, updated=updated)



More information about the Pypy-commit mailing list