Commits

Jonathan Eunice  committed 4ead8a1

improved attribute collection; first attempts at Windows support for readline

  • Participants
  • Parent commits c2d0bbf

Comments (0)

Files changed (6)

-See README.rst
+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
+=====
+
+::
+
+    from show import show
+    
+    x = 12
+    nums = list(range(4))
+    
+    show(x, nums)
+    
+yields::
+
+    x: 12  nums: [0, 1, 2, 3]
+
+Debug Printing
+==============
+
+Sometimes programs print so that users can see things, and sometimes they print
+so that develpopers can. ``show()`` is for developers, helping
+rapidly print the current state of variables. It replaces require the craptastic
+repetitiveness of::
+
+    print "x: {0}".format(x)
+    
+with::
+
+    show(x)
+
+If you'd like to see where the data is being produced,::
+
+    show.set(where=True)
+    
+will turn on location reporting. This can also be set on call-by-call basis.
+``show`` is built atop the `options <http://pypi.python.org/pypi/options>`_ module
+for configuration management, and also the output management of
+`say <http://pypi.python.org/pypi/say>`_. All ``say`` options work in show. If you
+``show()`` a literal string, it will be iterpolated as it would be in ``say``::
+
+    show("{n} iterations, still running")
+    
+yields something like::
+
+    14312 iterations, still running
+    
+While::
+
+    s = '{n} iterations'
+    show(s)
+    
+yields::
+
+    s: '{n} iterations'
+    
+See ``say`` `say <http://pypi.python.org/pypi/say>`_ for additional detail on its
+operation.
+
+Showing Collections
+===================
+
+The goal of ``show`` is to provide the most useful information possible,
+in the quickest and simplest way. Not requiring programmers to explicitly
+restate values and names in print statements is the start, but the module also
+provides some additional functions that provide a bit more semantic value.
+For example, ``say.items()`` is designed to make printing collections easy.
+It shows not just the values, but also the cardinality (i.e., length) of the
+collection::
+
+    nums = list(range(4))
+    show.items(nums)
+    
+yields::
+
+    nums (4 items): [0, 1, 2, 3]
+
+Showing Object Properties
+=========================
+
+::
+
+    show.props(x)
+    
+shows the properties of object ``x``. ("Properties" here
+is generic language for "values" or "attributes" associated with
+an object, and isn't used in the technical sense of Python properties.)
+Properties will be listed alphabetically, but with those starting with underscores
+(``_``), usually indicating "private" data, sorted after those that are
+conventionally considered public. 
+
+If ``x`` has real ``@property`` members, those too displayed. However, other class
+attributes that ``x`` rightfully inherits, but that are not directly present in the
+``x`` instance, will not be displayed.
+
+An optional second
+parameter can determine which properties are shown. E.g.::
+
+    show.props(x, 'name,age')
+    
+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 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.
+Whether this is a hard limit, or something
+that can be worked around over time, remains to be seen.
+
+See e.g. `this <http://stackoverflow.com/questions/13204161/how-to-access-the-calling-source-line-from-interactive-shell>`_.
+
+Python under Windows does not support readline the same way it is supported
+on Unix, Linux, and Mac OS X. As of version 0.60, experimental 
+support is provided for the use of ``pyreadline`` under Windows to correct
+this variance. This feature is yet untested. Works/doesn't work reports welcome!
+
+Notes
+=====
+
+ *  ``show`` is in its early days. Over time, it will provide additional
+    context-specific output helpers. For example, the "diff" views of ``py.test``
+    seem a high-value enhancement.
+ 
+ *  Automated multi-version testing managed with the wonderful
+    `pytest <http://pypi.python.org/pypi/pytest>`_
+    and `tox <http://pypi.python.org/pypi/tox>`_. ``show`` is 
+    successfully packaged for, and tested against, all late-model verions of
+    Python: 2.6, 2.7, 3.2, and 3.3, as well as PyPy 1.9 (based on 2.7.2).
+ 
+ *  The author, `Jonathan Eunice <mailto:jonathan.eunice@gmail.com>`_ or
+    `@jeunice on Twitter <http://twitter.com/jeunice>`_
+    welcomes your comments and suggestions.
+
+Installation
+============
+
+To install the latest version::
+
+    pip install -U show
+
+To ``easy_install`` under a specific Python version (3.3 in this example)::
+
+    python3.3 -m easy_install --upgrade show
+    
+(You may need to prefix these with "sudo " to authorize installation.)
 
 See e.g. `this <http://stackoverflow.com/questions/13204161/how-to-access-the-calling-source-line-from-interactive-shell>`_.
 
+Python under Windows does not support readline the same way it is supported
+on Unix, Linux, and Mac OS X. As of version 0.60, experimental 
+support is provided for the use of ``pyreadline`` under Windows to correct
+this variance. This feature is yet untested. Works/doesn't work reports welcome!
+
 Notes
 =====
 
 #! /usr/bin/env python
 from setuptools import setup
-import sys
-
-def verno(s):
-    """
-    Update the version number passed in by extending it to the 
-    thousands place and adding 1/1000, then returning that result
-    and as a side-effect updating setup.py
-
-    Dangerous, self-modifying, and also, helps keep version numbers
-    ascending without human intervention.
-    """
-    
-    from decimal import Decimal
-    import re
-    d = Decimal(s)
-    increment = Decimal('0.001')
-    d = d.quantize(increment) + increment
-    dstr = str(d)
-    setup = open('setup.py', 'r').read()
-    setup = re.sub('verno\(\w*[\'"]([\d\.]+)[\'"]', 'verno("' + dstr + '"', setup)
-    open('setup.py', 'w').write(setup)
-    return dstr
+import sys, platform
 
 def linelist(text):
     """
     Returns each non-blank line in text enclosed in a list.
     """
-    return [ l.strip() for l in text.strip().splitlines() if l.split() ]
+    return [ l.strip() for l in text.strip().splitlines() if l.strip() ]
     
     # The double-mention of l.strip() is yet another fine example of why
     # Python needs en passant aliasing.
 
+
+system = str(sys.platform).lower()
+impl = platform.python_implementation()
+
+install_requires = ['six', 'options>=0.426', 'say>=0.833', 'stuf>=0.9.10',
+                    'mementos>=0.5', 'codegen' ]
+
+if 'darwin' in system:
+    if impl != 'PyPy':
+        install_requires += ['readline']
+        
+        # if iPython ran under PyPy, it'd require readline too
+        # but on my system, readline fails to install under PyPy
+        # thus this spot omission
+        
+elif 'win32' in system:
+    install_requires += ['pyreadline']
+
+
+
 setup(
     name='show',
-    version=verno("0.561"),
+    version='0.60',
     author='Jonathan Eunice',
     author_email='jonathan.eunice@gmail.com',
     description='Debug print statements, done right. E.g. show(x)',
     long_description=open('README').read(),
     url='https://bitbucket.org/jeunice/show',
     packages=['show'],
-    install_requires=['six', 'options>=0.426', 'say>=0.833', 'stuf>=0.9.10', 'mementos>=0.5', 'codegen'],
+    install_requires=install_requires,
     tests_require = ['tox', 'pytest', 'six'],
     # zip_safe = True,
     keywords='debug print display show',

File show/core.py

 Impossible = NullType('Impossible')
 Ignore     = NullType('Ignore')
 
+_PY3 = sys.version_info[0] > 2
+
 def wrapped_if(value, prefix="", suffix="", transform=None):
     """
     If a string has a value, then transform it (optinally) and add the prefix and
                     try:
                         propkeys = list(value.__dict__.keys())
                     except AttributeError:  # no __dict__ => __slots__ object
+                        t = type(value)
                         try:
-                            propkeys = list(value.__slots__)
+                            propkeys = list(t.__dict__.keys())
                         except AttributeError:
-                            propkeys = list(dir(value))
+                            try:
+                                propkeys = list(t.__slots__)
+                            except AttributeError:
+                                propkeys = list(dir(value))
+                    
+                    # Might add option to show attributes not in self.__dict__ but
+                    # inherited from the class (as ``dir`` does always)
+                    
                     try:
                         cdict = value.__class__.__dict__
                         realproperties = [ k for k in cdict.keys() if isinstance(cdict[k], property) ]
                     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) ]
+                        omitlist = cwsv_or_list(self.opts.omit) 
+                        propkeys = [ p for p in propkeys if not p in omitlist ]
 
                     proplist = sorted(propkeys, key=lambda x: x.replace('_','~'))
                 #propvals = [ "{0}={1}".format(p, self.value_repr(getattr(value, p))) for p in proplist ]
         retval = self.say(*locval, **kwargs)
         if opts.retvalue:
             return retval
+        
+    def inout(self, func):
+        """
+        Decorator that shows arguments to a function.
+        """
+        func_code = func.__code__ if _PY3 else func.func_code
+        argnames = func_code.co_varnames[:func_code.co_argcount]
+        fname = func.__name__ if _PY3 else func.func_name
+
+        def echo_func(*args, **kwargs):
+            argcore = ', '.join('%s=%r' % entry for entry in zip(argnames,args) + kwargs.items())
+            callstr = ''.join([fname, '(', argcore, ')'])
+            self.say(callstr)
+            try:
+                retval = func(*args, **kwargs)
+                retstr = ''.join([' -> ', repr(retval)])
+                self.say(retstr)
+                return retval
+            except Exception as e:
+                raise e
+
+        return echo_func
+    
+    def retval(self, func):
+        """
+        Decorator that shows arguments to a function, and return value, once
+        the function is complete.
+        """ 
+        func_code = func.__code__ if _PY3 else func.func_code
+        argnames = func_code.co_varnames[:func_code.co_argcount]
+        fname = func.__name__ if _PY3 else func.func_name
+
+        def echo_func(*args, **kwargs):
+            argcore = ', '.join('%s=%r' % entry for entry in list(zip(argnames,args)) + list(kwargs.items()))
+            callstr = ''.join([fname, '(', argcore, ')'])
+            try:
+                retval = func(*args, **kwargs)
+                retstr = ''.join([callstr, ' -> ', repr(retval)])
+                self.say(retstr)
+                return retval
+            except Exception as e:
+                retstr = ''.join([callstr, ' -> '])
+                self.say(retstr)
+                raise e
+            
+        return echo_func
+    
+    # TODO: Give option for showing return value differently
+    # TODO: Give this decorator standard show kwargs
+    # TODO: Unifiy inout and retval function argument/return value decorators
+    # TODO: Figure out why test not working (correct answer, but output caputure fails)
     
 class ShowContext(OptionsContext):
     """

File show/linecacher.py

 # http://stackoverflow.com/questions/967369/python-find-out-if-running-in-shell-or-not-e-g-sun-grid-engine-queue
 
 if isInteractive:
-    import readline as rl
+    
+    system = str(sys.platform).lower()
+
+    if 'win32' in system:
+        import pyreadline as rl
+    else:
+        import readline as rl
     
     class History(object):
         """
             cur_hist_len = rl.get_current_history_length()
             if cur_hist_len > self._lastseen:
                 for i in range(self._lastseen+1, cur_hist_len+1):
-                    self._lines.extend(rl.get_history_item(i).splitlines())
+                    self._lines.extend(                                                    rl.get_history_item(i).splitlines())
                 self._lastseen = cur_hist_len
                 
             # Fancier splitlines() thing required because iPython stores history

File test/test_show.py

     assert show.watch() == six.u('\u2205')
     assert show.watch(_z) == '_z: 99'
     assert show.watch() == six.u('\u2205')
+    
+def test_show_retval(capsys):
+
+    # this tweak needed to make pytest's capsys work right
+    show.say.options.files = [sys.stdout]
+    
+    @show.retval
+    def f(a):
+        return a + 1
+    
+    assert f(12) == 13
+    out, err = capsys.readouterr()
+    assert out == "f(a=12) -> 13\n"
+    assert err == ""