]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
- Added an improved repr() to TypeEngine objects
authorMike Bayer <mike_mp@zzzcomputing.com>
Mon, 4 Jul 2011 17:56:17 +0000 (13:56 -0400)
committerMike Bayer <mike_mp@zzzcomputing.com>
Mon, 4 Jul 2011 17:56:17 +0000 (13:56 -0400)
that will only display constructor args which
are positional or kwargs that deviate
from the default.  [ticket:2209]

CHANGES
lib/sqlalchemy/types.py
lib/sqlalchemy/util/__init__.py
lib/sqlalchemy/util/langhelpers.py
test/base/test_utils.py
test/sql/test_types.py

diff --git a/CHANGES b/CHANGES
index 44ee35d7867313af9c18c8a6cf556c1bcda72a66..5c91af432a9f5682e273c8bf740393275a3c566e 100644 (file)
--- 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()
index e8d0b6f22f7f84154370385104dbc29fb63f4ddc..c7781a76f8bc486d49034a5967373250b56669e6 100644 (file)
@@ -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.
index 827fce088d0d8517632caf5c68204765450968cb..4b7a43752adaa99ee1b621aa1ffbf60b4baa2c5d 100644 (file)
@@ -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
index e9213845baa00c4fbde2d04e663198639ed4a0d6..c3a358220041d4e8b6382bb678ce390b7fd2fa19 100644 (file)
@@ -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.
index ac66e787149f22a66f8191490ac043a0cc68adf9..61c7ad9d606183bca0b6d8f4384d337a9849bf06 100644 (file)
@@ -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):
index 4fbafe6848cdd751ba0659018f6d1459fed80222..106818b8d2d2e3652d5b6db0561d23147a3988dd 100644 (file)
@@ -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(