From e31211c578854d63128a30c036e40eee5c43edc7 Mon Sep 17 00:00:00 2001 From: Mike Bayer Date: Thu, 29 Oct 2015 14:25:58 -0400 Subject: [PATCH] - convert wrap_callable() to a general purpose update_wrapper-like function; the caller still passes in the "wrapper" - move tests for wrap_callable() to be generic util tests - changelog for pullreq github:204 --- doc/build/changelog/changelog_11.rst | 10 ++++ lib/sqlalchemy/sql/schema.py | 4 +- lib/sqlalchemy/util/langhelpers.py | 18 +++----- test/base/test_utils.py | 69 ++++++++++++++++++++++++++++ test/sql/test_defaults.py | 60 ------------------------ 5 files changed, 88 insertions(+), 73 deletions(-) diff --git a/doc/build/changelog/changelog_11.rst b/doc/build/changelog/changelog_11.rst index e37fd1a692..e296be0e2e 100644 --- a/doc/build/changelog/changelog_11.rst +++ b/doc/build/changelog/changelog_11.rst @@ -21,6 +21,16 @@ .. changelog:: :version: 1.1.0b1 + .. change:: + :tags: enhancement, schema + :pullreq: github:204 + + The default generation functions passed to :class:`.Column` objects + are now run through "update_wrapper", or an equivalent function + if a callable non-function is passed, so that introspection tools + preserve the name and docstring of the wrapped function. Pull + request courtesy hsum. + .. change:: :tags: change, mssql :tickets: 3434 diff --git a/lib/sqlalchemy/sql/schema.py b/lib/sqlalchemy/sql/schema.py index 0c433d16e4..a88203e848 100644 --- a/lib/sqlalchemy/sql/schema.py +++ b/lib/sqlalchemy/sql/schema.py @@ -1981,13 +1981,13 @@ class ColumnDefault(DefaultGenerator): try: argspec = util.get_callable_argspec(fn, no_self=True) except TypeError: - return util.wrap_callable(fn) + return util.wrap_callable(lambda ctx: fn(), fn) defaulted = argspec[3] is not None and len(argspec[3]) or 0 positionals = len(argspec[0]) - defaulted if positionals == 0: - return util.wrap_callable(fn) + return util.wrap_callable(lambda ctx: fn(), fn) elif positionals == 1: return fn diff --git a/lib/sqlalchemy/util/langhelpers.py b/lib/sqlalchemy/util/langhelpers.py index 9f259aea3a..e9d4e09bc2 100644 --- a/lib/sqlalchemy/util/langhelpers.py +++ b/lib/sqlalchemy/util/langhelpers.py @@ -1378,28 +1378,24 @@ class EnsureKWArgType(type): return update_wrapper(wrap, fn) -def wrap_callable(fn): - """Wrap callable and set __name__, __doc__, and __module__. +def wrap_callable(wrapper, fn): + """Augment functools.update_wrapper() to work with objects with + a ``__call__()`` method. :param fn: object with __call__ method + """ if hasattr(fn, '__name__'): - _f = update_wrapper(lambda ctx: fn(), fn) - _f.__doc__ = _f.__doc__ or fn.__name__ - return _f + return update_wrapper(wrapper, fn) else: - _f = lambda ctx: fn() + _f = wrapper _f.__name__ = fn.__class__.__name__ _f.__module__ = fn.__module__ if hasattr(fn.__call__, '__doc__') and fn.__call__.__doc__: - _f.__doc__ = fn.__call__.__doc__ - elif hasattr(fn.__class__, '__doc__') and fn.__class__.__doc__: - _f.__doc__ = fn.__class__.__doc__ + _f.__doc__ = fn.__call__.__doc__ elif fn.__doc__: _f.__doc__ = fn.__doc__ - else: - _f.__doc__ = fn.__class__.__name__ return _f diff --git a/test/base/test_utils.py b/test/base/test_utils.py index 8074de53ed..c1027ec8ea 100644 --- a/test/base/test_utils.py +++ b/test/base/test_utils.py @@ -313,6 +313,75 @@ class MemoizedAttrTest(fixtures.TestBase): eq_(canary.mock_calls, [mock.call.attr(), mock.call.method()]) +class WrapCallableTest(fixtures.TestBase): + def test_wrapping_update_wrapper_fn(self): + def my_fancy_default(): + """run the fancy default""" + return 10 + + c = util.wrap_callable(lambda: my_fancy_default, my_fancy_default) + + eq_(c.__name__, "my_fancy_default") + eq_(c.__doc__, "run the fancy default") + + def test_wrapping_update_wrapper_fn_nodocstring(self): + def my_fancy_default(): + return 10 + + c = util.wrap_callable(lambda: my_fancy_default, my_fancy_default) + eq_(c.__name__, "my_fancy_default") + eq_(c.__doc__, None) + + def test_wrapping_update_wrapper_cls(self): + class MyFancyDefault(object): + """a fancy default""" + + def __call__(self): + """run the fancy default""" + return 10 + + def_ = MyFancyDefault() + c = util.wrap_callable(lambda: def_(), def_) + + eq_(c.__name__, "MyFancyDefault") + eq_(c.__doc__, "run the fancy default") + + def test_wrapping_update_wrapper_cls_noclsdocstring(self): + class MyFancyDefault(object): + + def __call__(self): + """run the fancy default""" + return 10 + + def_ = MyFancyDefault() + c = util.wrap_callable(lambda: def_(), def_) + eq_(c.__name__, "MyFancyDefault") + eq_(c.__doc__, "run the fancy default") + + def test_wrapping_update_wrapper_cls_nomethdocstring(self): + class MyFancyDefault(object): + """a fancy default""" + + def __call__(self): + return 10 + + def_ = MyFancyDefault() + c = util.wrap_callable(lambda: def_(), def_) + eq_(c.__name__, "MyFancyDefault") + eq_(c.__doc__, "a fancy default") + + def test_wrapping_update_wrapper_cls_noclsdocstring_nomethdocstring(self): + class MyFancyDefault(object): + + def __call__(self): + return 10 + + def_ = MyFancyDefault() + c = util.wrap_callable(lambda: def_(), def_) + eq_(c.__name__, "MyFancyDefault") + eq_(c.__doc__, None) + + class ToListTest(fixtures.TestBase): def test_from_string(self): eq_( diff --git a/test/sql/test_defaults.py b/test/sql/test_defaults.py index e2250e8346..88679e2082 100644 --- a/test/sql/test_defaults.py +++ b/test/sql/test_defaults.py @@ -301,66 +301,6 @@ class DefaultTest(fixtures.TestBase): c = sa.ColumnDefault(fn) c.arg("context") - def test_wrapping_update_wrapper_fn(self): - def my_fancy_default(): - """run the fancy default""" - return 10 - - c = sa.ColumnDefault(my_fancy_default) - eq_(c.arg.__name__, "my_fancy_default") - eq_(c.arg.__doc__, "run the fancy default") - - def test_wrapping_update_wrapper_fn_nodocstring(self): - def my_fancy_default(): - return 10 - - c = sa.ColumnDefault(my_fancy_default) - eq_(c.arg.__name__, "my_fancy_default") - eq_(c.arg.__doc__, "my_fancy_default") - - def test_wrapping_update_wrapper_cls(self): - class MyFancyDefault(object): - """a fancy default""" - - def __call__(self): - """run the fancy default""" - return 10 - - c = sa.ColumnDefault(MyFancyDefault()) - eq_(c.arg.__name__, "MyFancyDefault") - eq_(c.arg.__doc__, "run the fancy default") - - def test_wrapping_update_wrapper_cls_noclassdocstring(self): - class MyFancyDefault(object): - - def __call__(self): - """run the fancy default""" - return 10 - - c = sa.ColumnDefault(MyFancyDefault()) - eq_(c.arg.__name__, "MyFancyDefault") - eq_(c.arg.__doc__, "run the fancy default") - - def test_wrapping_update_wrapper_cls_nomethoddocstring(self): - class MyFancyDefault(object): - """a fancy default""" - - def __call__(self): - return 10 - - c = sa.ColumnDefault(MyFancyDefault()) - eq_(c.arg.__name__, "MyFancyDefault") - eq_(c.arg.__doc__, "a fancy default") - - def test_wrapping_update_wrapper_cls_noclassdocstring_nomethoddocstring(self): - class MyFancyDefault(object): - - def __call__(self): - return 10 - - c = sa.ColumnDefault(MyFancyDefault()) - eq_(c.arg.__name__, "MyFancyDefault") - eq_(c.arg.__doc__, "MyFancyDefault") @testing.fails_on('firebird', 'Data type unknown') def test_standalone(self): -- 2.47.2