Commits

Jonathan Eunice committed c2d0bbf

improved show.props; added show.watch

Comments (0)

Files changed (4)

-Simple, effective debug printing. 
+Simple, effective debug printing.
+
+Logging, assertions, unit tests, and
+interactive debuggers are all great development tools. But sometimes you
+just need to print values as a program runs to see what's going on. Every
+language has features to print text, but they're not really customized for
+printing debugging information. ``show`` is. It provides a simple, DRY
+mechanism to "show what's going on."
 
 Usage
 =====
 Or if you prefer the keyword syntax, this is equivalent to::
 
     show(x, props='name,age')
+
+Or if you'd like all properties except a few::
+
+    show.props(x, omit='description,blurb')
+
+Showing What's Changed
+======================
+
+::
+
+    show.watch()
+    
+will display the value of local variables. When invoked again, only those
+variables that have changed (since the last ``show.watch()`` in the same context)
+will be displayed. 
+
+You may ``omit`` some local variables if you like.
+By default, those starting with underscores (``_``) will be omitted, as
+will those containing functions, methods, builtins, and other parts Python
+program infrastructure. If you'd like to add those, or global variables into
+the mix, that's easily done:
+
+    show.watch(_private, MY_GLOBAL_VAR)
+    
+Will start watching those.
     
 Interactive Limitations
 =======================
 
 ``show`` has  draft support for both interactive Python and iPython.
-It orks well at the
+It works well at the
 interactive prompt, and within imported modules. It cannot, however, be used
 within functions and classes defined within the interactive session. This is
 due to how Python supprots--or fails to support--introspection in this instance.
 
 setup(
     name='show',
-    version=verno("0.519"),
+    version=verno("0.561"),
     author='Jonathan Eunice',
     author_email='jonathan.eunice@gmail.com',
     description='Debug print statements, done right. E.g. show(x)',
 import textwrap
 
 from options.nulltype import NullType
-Private = NullType('Private')
+Private    = NullType('Private')
 Impossible = NullType('Impossible')
+Ignore     = NullType('Ignore')
 
 def wrapped_if(value, prefix="", suffix="", transform=None):
     """
     delivered via cache lookup.
     """
 
-    TARGET_FUNCS = set(['show', 'show.items', 'show.props'])  # functions we care about
+    TARGET_FUNCS = set(['show', 'show.items', 'show.props', 'show.watch'])  # functions we care about
 
     def __init__(self, filepath, lineno):
         ast.NodeVisitor.__init__(self)
         return data.strip().split(',')
     else:
         return data.strip().split()
+    
+def ellipsis(s, maxlen=232):
+    s = str(s)
+    if len(s) > maxlen:
+        return s[:maxlen-3] + '...'
+    else:
+        return s
 
+def _afunction(f): pass
+function = type(_afunction)
+module   = type(sys)
+class _XYZ(object):
+    def method(self): pass
+
+FUNKY = (function, module, type, type(_XYZ.method), type(len)) # funky => functional infrastructure
 
 class Show(object):
     """Show objects print debug output in a 'name: value' format that
             else:
                 return "{0}: {1!r}".format(name, value)
 
-    def arg_format_props(self, name, value, caller):
+    def arg_format_props(self, name, value, caller, ignore_double=True, ignore_funky=True):
         """
         Format a single argument to show properties.
         """
                     try:
                         propkeys = list(value.__dict__.keys())
                     except AttributeError:  # no __dict__ => __slots__ object
-                        propkeys = list(value.__slots__)
-                    cdict = value.__class__.__dict__
-                    realproperties = [ k for k in cdict.keys() if isinstance(cdict[k], property) ]
-                    propkeys.extend(realproperties)
+                        try:
+                            propkeys = list(value.__slots__)
+                        except AttributeError:
+                            propkeys = list(dir(value))
+                    try:
+                        cdict = value.__class__.__dict__
+                        realproperties = [ k for k in cdict.keys() if isinstance(cdict[k], property) ]
+                        propkeys.extend(realproperties)
+                    except AttributeError:
+                        pass
+            
+                    if ignore_double:
+                        propkeys = [ p for p in propkeys if not p.startswith('__') ]
+                    if ignore_funky:
+                        propkeys = [ p for p in propkeys if not isinstance(getattr(value, p), FUNKY) ]
+                    if self.opts.omit:
+                        propkeys = [ p for p in propkeys if not p in cwsv_or_list(self.opts.omit) ]
+
                     proplist = sorted(propkeys, key=lambda x: x.replace('_','~'))
-                propvals = [ "{0}={1}".format(p, self.value_repr(getattr(value, p))) for p in proplist ]
-                return "{0}: {1}".format(name, ' '.join(propvals))
+                #propvals = [ "{0}={1}".format(p, self.value_repr(getattr(value, p))) for p in proplist ]
+                #return "{0}: {1}".format(name, ' '.join(propvals))
+                propvals = [ "    {0}={1}".format(p, ellipsis(self.value_repr(getattr(value, p)))) for p in proplist ]
+                return "{0}:\n{1}".format(name, '\n'.join(propvals))
             except Exception:
                 return "{0}: {1}".format(name, self.value_repr(value))
        
         """
         Show properties of objects.
         """
-        opts = self.options.push(kwargs)
+        opts = self.opts = self.options.push(kwargs)
         if len(args) > 1 and isinstance(args[-1], str):
             used = opts.addflat([ args[-1] ], ['props'])
-            args = args[:-1]        
+            args = args[:-1]
+        if opts.sep == Show.options.sep:
+            opts.sep = '\n\n'
         caller = inspect.currentframe().f_back
         return self._showcore(args, kwargs, caller, self.arg_format_props, opts)
     
+        # should this check for and show (perhaps with ^ annotation), properties
+        # of object inherited from class?
+    
     def locals(self, *args, **kwargs):
         """
         Show all local vars, plus any other values mentioned.
         """
         opts = self.options.push(kwargs)
         caller = inspect.currentframe().f_back
-        assert not args # for now
         
-        locdict = dict([ (k, v) for (k, v) in caller.f_locals.items() if not k.startswith('@py_assert') ])
-        watching = self._watching.get(id(caller), None)
+        f_locals = caller.f_locals
+        _id = id(f_locals)
+
+        valitems  = [ (k, v) for (k, v) in f_locals.items() if \
+                                not k.startswith('@py_assert') and \
+                                not k.startswith('_') and \
+                                not isinstance(v, FUNKY) and \
+                                not getattr(v, '__module__', '').startswith( ('IPython', 'site', 'show')) and \
+                                not (isInteractive and (k == 'In' or k == 'Out'))
+                            ]        
+        if args:
+            self.say("args = {args!r}")
+            argtuples = self.get_arg_tuples(caller, args)
+            valitems.extend(argtuples)
+        
+        valdict = dict(valitems)
+        _id = id(f_locals)
+        watching = self._watching.get(_id, None)
         if watching is None:
-            to_show = {}
-            to_show.update(locdict)
-            self._watching[id(caller)] = watching = to_show
+            self._watching[_id] = watching = to_show = valdict
         else:
             to_show = {}
-            for k, v in locdict.items():
+            for k, v in valdict.items():
                 if k not in watching or v != watching[k]:
                     to_show[k] = v
                     watching[k] = v
+            
                 
         omit = cwsv_or_list(opts.omit)
             
         if names:
             valstr = opts.sep.join([ self.arg_format(name, to_show[name], caller) for name in names ])
         else:
-            valstr = '<unchanged>'
+            valstr = six.u('\u2205')
         locval = [ self.call_location(caller) + ":  ", valstr ] if opts.where else [ valstr ]
 
         # Emit the result string, and optionally return it

test/test_show.py

 
 import pytest
 from show import Show
-import sys
+import sys, six
 
-PY3 = sys.version_info[0] == 3
+_PY3 = sys.version_info[0] == 3
 
 show = Show(where=False, retvalue=True)
 
     s = set([1,2,99])
     
     # native literals for sets different in py2 and py3
-    if PY3:
+    if _PY3:
         assert show(s) == "s: {1, 2, 99}"
     else:
         assert show(s) == "s: set([1, 2, 99])"
     o.name = 'An Object'
     o.age  = 2
     o.address = 'RAM'
-    assert show.props(o) == "o: address='RAM' age=2 name='An Object'"
-    assert show.props(o, 'name,age,address') == "o: name='An Object' age=2 address='RAM'"
-    assert show.props(o, 'name,age') == "o: name='An Object' age=2"
+    #assert show.props(o) == "o: address='RAM' age=2 name='An Object'"
+    #assert show.props(o, 'name,age,address') == "o: name='An Object' age=2 address='RAM'"
+    #assert show.props(o, 'name,age') == "o: name='An Object' age=2"
+    assert show.props(o) == "o:\n    address='RAM'\n    age=2\n    name='An Object'"
+    assert show.props(o, 'name,age,address') == "o:\n    name='An Object'\n    age=2\n    address='RAM'"
+    assert show.props(o, 'name,age') == "o:\n    name='An Object'\n    age=2"
     
     assert show.props(o, 'name,age') == show(o, props='name,age')
         
     
     oo = OO('Joe', 36, 'Waverly Place')
 
-    assert show.props(oo) == "oo: address='Waverly Place' age=36 name='Joe' _name='JOE'"
+    # assert show.props(oo) == "oo: address='Waverly Place' age=36 name='Joe' _name='JOE'"
+    assert show.props(oo) == "oo:\n    address='Waverly Place'\n    age=36\n    name='Joe'\n    _name='JOE'"
     
 def test_show_real_properties():
         
             return self.name
     
     p = P('Joe')
-    assert show.props(p) == "p: name='Joe' namer='joe' _name='JOE'"
+    assert show.props(p) == "p:\n    name='Joe'\n    namer='joe'\n    _name='JOE'"
     
 def test_show_locals():
     x = 1
     assert show.watch() == 'x: 4'
     x = 5
     y = 3
+    _z = 99
     assert show.watch() == 'x: 5  y: 3'
-    assert show.watch() == '<unchanged>'
-
-    
+    assert show.watch() == six.u('\u2205')
+    assert show.watch(_z) == '_z: 99'
+    assert show.watch() == six.u('\u2205')