From: Mike Bayer Date: Mon, 4 Jul 2011 17:56:17 +0000 (-0400) Subject: - Added an improved repr() to TypeEngine objects X-Git-Tag: rel_0_7_2~35 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=3daae3e5b6e54163452ed2aca15b300544daf455;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git - Added an improved repr() to TypeEngine objects that will only display constructor args which are positional or kwargs that deviate from the default. [ticket:2209] --- diff --git a/CHANGES b/CHANGES index 44ee35d786..5c91af432a 100644 --- a/CHANGES +++ b/CHANGES @@ -55,6 +55,11 @@ CHANGES the UUID example on the site that uses TypeEngine as the "impl". + - Added an improved repr() to TypeEngine objects + that will only display constructor args which + are positional or kwargs that deviate + from the default. [ticket:2209] + - engine - Use urllib.parse_qsl() in Python 2.6 and above, no deprecation warning about cgi.parse_qsl() diff --git a/lib/sqlalchemy/types.py b/lib/sqlalchemy/types.py index e8d0b6f22f..c7781a76f8 100644 --- a/lib/sqlalchemy/types.py +++ b/lib/sqlalchemy/types.py @@ -263,11 +263,7 @@ class TypeEngine(AbstractType): "constructor %s is deprecated" % self.__class__) def __repr__(self): - return "%s(%s)" % ( - self.__class__.__name__, - ", ".join("%s=%r" % (k, getattr(self, k, None)) - for k in inspect.getargspec(self.__init__)[0][1:])) - + return util.generic_repr(self) class UserDefinedType(TypeEngine): """Base for user defined types. diff --git a/lib/sqlalchemy/util/__init__.py b/lib/sqlalchemy/util/__init__.py index 827fce088d..4b7a43752a 100644 --- a/lib/sqlalchemy/util/__init__.py +++ b/lib/sqlalchemy/util/__init__.py @@ -26,7 +26,8 @@ from langhelpers import iterate_attributes, class_hierarchy, \ monkeypatch_proxied_specials, asbool, bool_or_str, coerce_kw_type,\ duck_type_collection, assert_arg_type, symbol, dictlike_iteritems,\ classproperty, set_creation_order, warn_exception, warn, NoneType,\ - constructor_copy, methods_equivalent, chop_traceback, asint + constructor_copy, methods_equivalent, chop_traceback, asint,\ + generic_repr from deprecations import warn_deprecated, warn_pending_deprecation, \ deprecated, pending_deprecation diff --git a/lib/sqlalchemy/util/langhelpers.py b/lib/sqlalchemy/util/langhelpers.py index e9213845ba..c3a3582200 100644 --- a/lib/sqlalchemy/util/langhelpers.py +++ b/lib/sqlalchemy/util/langhelpers.py @@ -215,6 +215,33 @@ def unbound_method_to_callable(func_or_cls): else: return func_or_cls +def generic_repr(obj): + """Produce a __repr__() based on direct association of the __init__() + specification vs. same-named attributes present. + + """ + def genargs(): + try: + (args, vargs, vkw, defaults) = inspect.getargspec(obj.__init__) + except TypeError: + return + + default_len = defaults and len(defaults) or 0 + + if not default_len: + for arg in args[1:]: + yield repr(getattr(obj, arg, None)) + if vargs is not None and hasattr(obj, vargs): + yield ', '.join(repr(val) for val in getattr(obj, vargs)) + else: + for arg in args[1:-default_len]: + yield repr(getattr(obj, arg, None)) + for (arg, defval) in zip(args[-default_len:], defaults): + val = getattr(obj, arg, None) + if val != defval: + yield '%s=%r' % (arg, val) + return "%s(%s)" % (obj.__class__.__name__, ", ".join(genargs())) + class portable_instancemethod(object): """Turn an instancemethod into a (parent, name) pair to produce a serializable callable. diff --git a/test/base/test_utils.py b/test/base/test_utils.py index ac66e78714..61c7ad9d60 100644 --- a/test/base/test_utils.py +++ b/test/base/test_utils.py @@ -937,6 +937,93 @@ class TestFormatArgspec(fixtures.TestBase): test(O.__init__, custom_spec) + +class GenericReprTest(fixtures.TestBase): + def test_all_positional(self): + class Foo(object): + def __init__(self, a, b, c): + self.a = a + self.b = b + self.c = c + eq_( + util.generic_repr(Foo(1, 2, 3)), + "Foo(1, 2, 3)" + ) + + def test_positional_plus_kw(self): + class Foo(object): + def __init__(self, a, b, c=5, d=4): + self.a = a + self.b = b + self.c = c + self.d = d + eq_( + util.generic_repr(Foo(1, 2, 3, 6)), + "Foo(1, 2, c=3, d=6)" + ) + + def test_kw_defaults(self): + class Foo(object): + def __init__(self, a=1, b=2, c=3, d=4): + self.a = a + self.b = b + self.c = c + self.d = d + eq_( + util.generic_repr(Foo(1, 5, 3, 7)), + "Foo(b=5, d=7)" + ) + + def test_discard_vargs(self): + class Foo(object): + def __init__(self, a, b, *args): + self.a = a + self.b = b + self.c, self.d = args[0:2] + eq_( + util.generic_repr(Foo(1, 2, 3, 4)), + "Foo(1, 2)" + ) + + def test_discard_vargs_kwargs(self): + class Foo(object): + def __init__(self, a, b, *args, **kw): + self.a = a + self.b = b + self.c, self.d = args[0:2] + eq_( + util.generic_repr(Foo(1, 2, 3, 4, x=7, y=4)), + "Foo(1, 2)" + ) + + def test_significant_vargs(self): + class Foo(object): + def __init__(self, a, b, *args): + self.a = a + self.b = b + self.args = args + eq_( + util.generic_repr(Foo(1, 2, 3, 4)), + "Foo(1, 2, 3, 4)" + ) + + def test_no_args(self): + class Foo(object): + def __init__(self): + pass + eq_( + util.generic_repr(Foo()), + "Foo()" + ) + + def test_no_init(self): + class Foo(object): + pass + eq_( + util.generic_repr(Foo()), + "Foo()" + ) + class AsInterfaceTest(fixtures.TestBase): class Something(object): diff --git a/test/sql/test_types.py b/test/sql/test_types.py index 4fbafe6848..106818b8d2 100644 --- a/test/sql/test_types.py +++ b/test/sql/test_types.py @@ -129,6 +129,17 @@ class AdaptTest(fixtures.TestBase): getattr(t2, k) == t1.__dict__[k] or \ t1.__dict__[k] is None + @testing.uses_deprecated() + def test_repr(self): + for typ in self._all_types(): + if typ in (types.TypeDecorator, types.TypeEngine): + continue + elif typ is dialects.postgresql.ARRAY: + t1 = typ(String) + else: + t1 = typ() + repr(t1) + def test_plain_init_deprecation_warning(self): for typ in (Integer, Date, SmallInteger): assert_raises_message(