Onyx logo

Previous topic

onyx.util.codedependency – Generate a code dependency (import) diagram

Next topic

onyx.util.singleton – Support for singleton objects

This Page

onyx.util.debugprint – Conditional printing for diagnostic purposes.

This module provides support for conditional printing for diagnostic purposes.

  • Use dprint() or dcheck() in your low-level code to conditionally emit diagnostic information to an audit stream.
  • At a higher level (typically in the logic of a command-line tool, but also while developing higher-level libraries that use the low-level code), use DebugPrint() to create a context manager that sets one or more of the conditional keys that are checked in the lower-level code, thus causing the diagnostic information to be emitted from the lower-level code.

See help(DebugPrint), help(dcheck), or help(dprint) for further information.

Here’s a “low-level” function that does some work:

>>> def core_1(n):
...   s = sum(xrange(n))
...   return s

It’s just a function

>>> core_1(20)
190

Here’s a version of that function that is instrumented via dprint(); it uses 'sumx' as the conditional key for its diagnostic output:

>>> def core_1(n):
...   dprint('sumx', 'incoming core request')
...   s = sum(xrange(n))
...   dprint('sumx', 'n', n, 's', s)
...   return s

It’s still just a function

>>> core_1(20)
190

When the dprint() calls are made within the “scope” of a DebugPrint() context manager that sets the conditional key, then the diagnostic arguments get emitted

>>> with DebugPrint('sumx'): core_1(30)
sumx: incoming core request
sumx: n 30 s 435
435

Note that the key is used as a prefix for each emitted line.

Other conditional keys are ignored by core_1

>>> with DebugPrint('highx'): core_1(40)
780

Using dprint() is quick and easy for simple one-line diagnostics. But often there’s overhead involved in generating the arguments to be displayed in the diagnostic output, and also it’s tedious to put the conditional key argument in each call to dprint().

The dcheck() function addresses both these issues: it returns an object with boolean value that lets you short-circuit diagnostic-only work, and it wraps up the particular conditional key for the diagnostic output

Here’s a second function:

>>> def core_2(m, n):
...   sm = core_1(m)
...   sn = core_1(n)
...   result = sm * sn
...   return result
>>> core_2(3, 4)
18

Here’s a version of that second function that is instrumented via dcheck(); it uses 'prod' as the conditional key for its diagnostic output. It shows the compact idiom of dc and dc('blah blah blah', ...) and also shows the if dc: idiom that’s used when you need to execute additional statements as part of generating the output:

>>> def core_2(m, n):
...   dc = dcheck('prod')
...   dc and dc('core_2 entered:', 'm', m, 'n', n)
...   sm = core_1(m)
...   sn = core_1(n)
...   result = sm * sn
...   if dc:
...     import math
...     result_2 = int(math.sqrt(result) + 0.5)
...     dc('costly prediction', core_1(result), 'sqrt', result_2)
...   dc and dc('core_2 exit:', result)
...   return result
>>> core_2(3, 5)
30
>>> with DebugPrint('prod'): core_2(3, 5)
prod: core_2 entered: m 3 n 5
prod: costly prediction 435 sqrt 5
prod: core_2 exit: 30
30

The dc and dc('blah blah blah...', some_costly_function_call(), ...) idiom avoids any overhead associated with constructing the arguments for the diagnostic output unless the particular condition key is selected by an enclosing DebugPrint context.

Turn on diagnostics only from core_1

>>> with DebugPrint('sumx'): core_2(3, 5)
sumx: incoming core request
sumx: n 3 s 3
sumx: incoming core request
sumx: n 5 s 10
30

Turn on diagnostics from core_1 and core_2. Note the extra sumx output due to the if dc: conditional in core_2:

>>> with DebugPrint('sumx', 'prod'): core_2(3, 5)
prod: core_2 entered: m 3 n 5
sumx: incoming core request
sumx: n 3 s 3
sumx: incoming core request
sumx: n 5 s 10
sumx: incoming core request
sumx: n 30 s 435
prod: costly prediction 435 sqrt 5
prod: core_2 exit: 30
30

The namespace of conditionals is global and unstructured. We recommend using dotted names that include at least the module or class and function or method, but also perhaps the full package name, e.g. 'onyx.dataflow.streamprocess.CollectProcessor.process'.

Diagnostic output is only generated when there’s a match between the condition keys that are set up in an enclosing context and the condition key that’s used in the dprint() or dcheck() call.

No output here

>>> dprint('onyx.foo.bar.sumx', "World")

Nor here

>>> dprint('never_seen', "World")

You can use file or cStringIO objects in the list of keys to control where the diagnostic output associated with subsequent keys is sent by dprint() or dcheck(). The default is to print to sys.stdout. In the following: 'foo' output goes to sys.stdout, 'prod' output goes to out1, and 'sumx' output goes to out2:

>>> out1, out2 = cStringIO.StringIO(), cStringIO.StringIO()
>>> with DebugPrint('foo', out1, 'prod', out2, 'sumx'):
...   core_1(20)
...   dprint('foo', 'some bar joke')
...   core_2(3, 5)
...   dc = dcheck('foo')
...   dc and dc('some choke')
...   core_1(30)
190
foo: some bar joke
30
foo: some choke
435
>>> print(out1.getvalue().strip())
prod: core_2 entered: m 3 n 5
prod: costly prediction 435 sqrt 5
prod: core_2 exit: 30
>>> print(out2.getvalue().strip())
sumx: incoming core request
sumx: n 20 s 190
sumx: incoming core request
sumx: n 3 s 3
sumx: incoming core request
sumx: n 5 s 10
sumx: incoming core request
sumx: n 30 s 435
sumx: incoming core request
sumx: n 30 s 435
>>> out1.close()
>>> out2.close()
class onyx.util.debugprint.DebugPrint(*keys)

Bases: object

A context manager for conditional printing:

with DebugPrint(*keys):
  calls_with_keys_based_diagnostics(...)

Instances of this class support conditional printing for diagnostic purposes. Creation of an instance in the context of a with statement will activate the keys used for creation, each of which which must be an immutable object. The end of the with statement will deactivate the keys (except those that were already activated in some outer context). Key activation controls the behavior of the functions dcheck() and dprint(); see help(dcheck) and help(dprint) for documentation on how to do conditional printing. Each key may be one of four kinds: string, two-tuple, file object, and special value. A string key activates that key for printing by dprint(). A two-tuple must have a string as its first element, which is the key to be activated. The second element of the two-tuple can be any immutable object and will be present as the control_data attribute on the callable returned by dcheck(). Note that this attribute will otherwise be set to None. A key which is a file object or cStringIO object will be used as the output target for conditional printing for all subsequent keys; keys without an associated output target will have sys.stdout as their output target. Adding the special value DebugPrint.TIMESTAMP_ON to the list of keys will turn on time stamping for output for subsequent keys. Use DebugPrint.TIMESTAMP_OFF to turn it back off for subsequent keys.

Passing an empty keys means ‘do not activate any key’.

The usual idiom is to use dcheck(key) to get a value which is False if key isn’t active, or a non-False callable if key is active. This allows the dc and dc(mesg,...) construction, which avoids evaluation of the mesg argument(s) if no printing will be done. See help(dcheck) for details on the callable returned for an active key.

Note

In typical usage scenarios the code that actually calls dcheck() or dprint() appears within methods and functions that are reached on the call stack in the scope of the with context. For simplicity, the examples below call dcheck() and dprint() from the top-level of the with context rather than from deeper in a function call stack.

>>> with DebugPrint('HI'):
...    dc = dcheck('HI')
...    dc and dc("Hello")
HI: Hello

Illustrating the use of control_data. Ordinary string keys leave the control_data attribute as None.

>>> with DebugPrint(('HI')):
...    dc = dcheck('HI')
...    print dc.control_data
None

A two-tuple key sets the control_data to the second element

>>> with DebugPrint(('HI', (3, 4, 5))):
...    dc = dcheck('HI')
...    cd = dc.control_data
...    print('control_data is %s' % (cd,))
...    for i in xrange(10):
...        if i in cd:
...            dc and dc("Hello (i=%d)" % (i,))
control_data is (3, 4, 5)
HI: Hello (i=3)
HI: Hello (i=4)
HI: Hello (i=5)

Illustrating the use of special values to control time-stamping.

>>> with DebugPrint(DebugPrint.TIMESTAMP_ON, 'HI', DebugPrint.TIMESTAMP_OFF, 'HI2'):  
...    dc0 = dcheck('HI')
...    dc2 = dcheck('HI2')
...    dc0 and dc0("Hello")
...    dc2 and dc2("Hello2")
[20...] HI: Hello
HI2: Hello2

Note: remaining examples use dprint directly for illustrative purposes; you probably don’t want to do this yourself.

>>> with DebugPrint('HI', 'HI2'):
...    dprint('HI', "Hello")
...    dprint('HI2', "World")
...    dprint("not_set", "You should not see this")
HI: Hello
HI2: World

Here’s a simple way to have a secondary local condition for printing:

>>> blah = False
>>> with DebugPrint('foo') if blah else DebugPrint():
...    dprint('foo', "World")
>>> blah = True
>>> with DebugPrint('foo') if blah else DebugPrint():
...    dprint('foo', "World")
foo: World

You can also create DebugPrint objects without using ‘with’ and turn them on and off with the functions ‘on’ and ‘off’.

>>> dp = DebugPrint('HI')
>>> dp.on()
>>> dc = dcheck('HI')
>>> dc and dc("Hello")
HI: Hello
>>> dp.off()
>>> dc = dcheck('HI')
>>> dc and dc("Hello")
False
static active(key)
static get_stream(key)
off(dummy1=None, dummy2=None, dummy3=None)
on()
onyx.util.debugprint.dcheck(*keys)

Check to see if any of the debug printing keys are active; if so, return a debug print function.

Call this function to see if any of several keys is active in DebugPrint. If no active key is found, return False. If an active key is found, return a callable with signature (*things) which behaves just like dprint(). Note that such a callable is considered True when evaluated in a boolean context; this supports the and idiom used below which avoids evaluation of the arguments to the callable. See help(DebugPrint) for documentation on how to activate keys.

Note

For simplicity, the examples below call dcheck() from the top-level of the with context rather than from deeper in a function call stack.

>>> with DebugPrint('HI'):
...    print('HI %s active' % ('is' if dcheck('HI') else 'is not'))
...    print('HI2 %s active' % ('is' if dcheck('HI2') else 'is not'))
...    print('HI or HI2 %s active' % ('is' if dcheck('HI', 'HI2') else 'is not'))
HI is active
HI2 is not active
HI or HI2 is active
>>> with DebugPrint('HI'):
...    dc = dcheck('HI')
...    dc and dc("Hello")
HI: Hello
onyx.util.debugprint.dprint(key, *things)

If key is active in a DebugPrint context manager, format and print key and things to the output stream associated with key (default sys.stdout).

Setting the initial argument, things[0], to DebugPrint.NO_PREFIX suppresses the boilerplate prefix in the formatted output. If things[0] is DebugPrint.NEWLINE_PREFIX, include a newline before the boilerplate output; this is useful for setting off a block of debug printing.

Setting the final argument, things[-1], to DebugPrint.NO_NEWLINE_SUFFIX suppresses the usual newline at the end of the formatted output; this is useful if the preceding argument already includes a final newline.

See help(DebugPrint) for documentation on how to activate keys and how to change the stream associated with a key.

Note

For simplicity, the examples below call dprint() from the top-level of the with context rather than from deeper in a function call stack.

>>> with DebugPrint('HI'):
...    dprint('HI', "Hello,", "World!")
HI: Hello, World!
>>> with DebugPrint('HI'):
...    dprint('HI', DebugPrint.NO_PREFIX, "Hello")
Hello
>>> with DebugPrint('HI'):
...    dprint('HI', "Hello\n", DebugPrint.NO_NEWLINE_SUFFIX)
HI: Hello

Turning off the prefix includes turning off timestamps if they were turned on:

>>> with DebugPrint(DebugPrint.TIMESTAMP_ON, 'HI'):
...    dprint('HI', DebugPrint.NO_PREFIX, "Hello")
Hello
>>> with DebugPrint('HI'):
...    dprint('HI', DebugPrint.NEWLINE_PREFIX, "World!")
<BLANKLINE>
HI: World!

A newline is printed before the prefix, including the timestamp if there is one:

>>> with DebugPrint(DebugPrint.TIMESTAMP_ON, 'HI'):  
...    dprint('HI', DebugPrint.NEWLINE_PREFIX, "World!")
<BLANKLINE>
[20...] HI: World!