Package: behold
Behold:  A debugging tool for large Python projects===Behold is a package that makes it easier to debug large Python projects. Itenables you to perform [contextual debugging](#contextual-debugging-explained)over your entire code base.  This means that you can use the state inside onemodule to control either printing or step-debugging in a completely differentmodule.  Given the stateful nature of many large, multi-file applications (I'mlooking at you, Django), this capability provides valuable control over yourdebugging work flow.Behold is written in pure Python with no dependencies.  It is compatible withboth Python2 and Python3.This page shows several examples to get you started. The<a href="http://behold.readthedocs.io/en/latest/ref/behold.html">API documentation can be found here.</a>Installation---```bashpip install behold```Table of Contents---* [API Documentation](http://behold.readthedocs.io/en/latest/ref/behold.html)* [Simple print-style debugging](#simple-print-style-debugging)* [Conditional printing](#conditional-printing)* [Tagged printing](#tagged-printing)* [Contextual debugging](#contextual-debugging-explained)* [Printing object attributes](#printing-object-attributes)* [Printing global variables and nested attributes](#printing-global-variables-and-nested-attributes)* [Stashing results](#stashing-results)* [Custom attribute extraction](#custom-attribute-extraction)Simple Print-Style Debugging---Behold provides a uniform look to your print-style debugging statements.```pythonfrom behold import Beholdletters  = ['a', 'b', 'c', 'd', 'A', 'B', 'C', 'D']for index, letter in enumerate(letters):# The following line is equivalent to# print('index: {}, letter: {}'.format(index, letter))Behold().show('index', 'letter')```Output:```index: 0, letter: aindex: 1, letter: bindex: 2, letter: cindex: 3, letter: dindex: 4, letter: Aindex: 5, letter: Bindex: 6, letter: Cindex: 7, letter: D```Conditional Printing---You can filter your debugging statements based on scope variables.```pythonfrom behold import Beholdletters  = ['a', 'b', 'c', 'd', 'A', 'B', 'C', 'D']for index, letter in enumerate(letters):# The following line is equivalent to# if letter.upper() == letter and index % 2 == 0:#     print('index: {}'.format(index))Behold().when(letter.upper() == letter and index % 2 == 0).show('index')# If you don't like typing, the Behold class is aliased to B# from behold import B  # this also works```Output:```index: 4index: 6```Tagged Printing---Each instance of a behold object can be tagged to produce distinguishableoutput.  This makes it easy to grep for specific output you want to see.```pythonfrom behold import Beholdletters  = ['a', 'b', 'c', 'd', 'A', 'B', 'C', 'D']for index, letter in enumerate(letters):# The following two lines of code are equivalent to# if letter.upper() == letter and index % 2 == 0:#     print('index: {}, letter:, {}, even_uppercase'.format(index, letter))# if letter.upper() != letter and index % 2 != 0:#     print('index: {}, letter: {} odd_lowercase'.format(index, letter))Behold(tag='even_uppercase').when(letter.upper() == letter and index % 2 == 0).show('index', 'letter')Behold(tag='odd_lowercase').when(letter.lower() == letter and index % 2 != 0).show('index', 'letter')```Output:```index: 1, letter: b, odd_lowercaseindex: 3, letter: d, odd_lowercaseindex: 4, letter: A, even_uppercaseindex: 6, letter: C, even_uppercase```Contextual Debugging Explained---Let's say you have a complicated code base consisting of many files spread overmany directories. In the course of chasing down bugs, you may want to print outwhat is going on inside a particular function. But you only want the printingto happen when that function is called from some other function defined in acompletely different file. Situations like this frequently arise in Django webprojects where the code can be spread across multiple apps. This is the usecase where Behold really shines. Here is a simple example.Say you want to debug a reusable function somewhere in one of your modules.```pythonfrom behold import Behold# Some function that is used everywhere in your code basedef my_function():x = 'hello'  # your complicated logic goes here# This will print the value of x, but only when in 'testing' contextBehold().when_context(what='testing').show('x')# This will drop into a step debugger only when in 'debugging' contextif Behold().when_context(what='debugging').is_true():import pdb; pdb.set_trace()```Now, from a completely different module somewhere else in your project, you cancontrol how your function gets debugged.```pythonfrom behold import in_context# Decorate your testing function to execute in a 'testing' context@in_context(what='testing')def test_x():my_function()test_x()  # This will print 'x: hello' to your console# Use a context manager to set a debugging contextwith in_context(what='debugging'):my_function()  # This will drop you into the pdb debugger.```Printing Object Attributes---Up to this point, we have only called the `.show()` method with string argumentsholding names of local variables.  What if we wanted to show attributes of someobject in our code?  The example below uses an instance of the<a href="http://behold.readthedocs.io/en/latest/ref/behold.html#items">Item class</a>```pythonfrom behold import Behold, Item# Define an item with three attributes.item = Item(a=1, b=2, c=3)# The show() method will accept up to one non-string argument.  If it detects that# that a non-string argument has been passed, it will call getattr() on the# non-string variable to display the str representation of the attributes listed# in the string arguments.Behold(tag='with_args').show(item, 'a', 'b')# Calling show with an object and no string arguments defaults to printing all# attributes in the object's __dict__.Behold(tag='no_args').show(item)```Output:```a: 1, b: 2, with_argsa: 1, b: 2, c: 3, no_args```Printing Global Variables and Nested Attributes---When providing string arguments to the `.show()` method, the default behavior isto examine the local variables for names matching the strings.  Global variablescan not be accessed in this way.  Furthermore, if you have classes with nestedattributes, those will also not be accessible with simple string arguments.This example illustrates how to use `.show()` to access these types ofvariables.```pythonfrom __future__ import print_functionfrom behold import Behold, Item# define a global variableg = 'global_content'# Now set up nested a nested function to create a closure variabledef example_func():employee = Item(name='Toby')boss = Item(employee=employee, name='Michael')print('# Can\'t see global variable')Behold().show('boss', 'employee', 'g')print('\n# I can see the the boss\'s name, but not employee name')Behold('no_employee_name').show(boss)print('\n# Here is how to show global variables')Behold().show(global_g=g, boss=boss)# Or if you don't like the ordering the dict keys give you,# you can enforce it with the order of some string argumentsprint('\n# You can force variable ordering by supplying string arguments')Behold().show('global_g', 'boss', global_g=g, boss=boss)print('\n# And a similar strategy for nested attributes')Behold().show(employee_name=boss.employee.name)example_func()```Output:```bash# Can't see global variableboss: Item('employee', 'name'), employee: Item('name'), g: None# I can see the the boss's name, but not employee nameemployee: Item('name'), name: Michael, no_employee_name# Here is how to show global variablesboss: Item('employee', 'name'), global_g: global_content# You can force variable ordering by supplying string argumentsglobal_g: global_content, boss: Item('employee', 'name')# And a similar strategy for nested attributesemployee_name: Toby```Stashing Results---Behold provides a global stash space where you can store observed values forlater use in a top-level summary.  The stash space is global, so you need tocarefully manage it in order not to confuse yourself.  Here is an example ofusing the stash feature to print summary info. The list of dicts returned by the`.get_stash()` function was specifically designed to be passed directly to a <ahref="http://pandas.pydata.org/">Pandas</a> Dataframe constructor to helpsimplify further analysis.```pythonfrom __future__ import print_functionfrom pprint import pprintfrom behold import Behold, in_context, get_stash, clear_stashdef my_function():out = []for nn in range(5):x, y, z = nn, 2 * nn, 3 * nnout.append((x, y, z))# You must define tags if you want to stash variables.  The tag# names become the keys in the global stash space# this will only populate when testing xBehold(tag='test_x').when_context(what='test_x').stash('y', 'z')# this will only populate when testing yBehold(tag='test_y').when_context(what='test_y').stash('x', 'z')# this will only populate when testing zBehold(tag='test_z').when_context(what='test_z').stash('x', 'y')return out@in_context(what='test_x')def test_x():assert(sum([t[0] for t in my_function()]) == 10)@in_context(what='test_y')def test_y():assert(sum([t[1] for t in my_function()]) == 20)@in_context(what='test_z')def test_z():assert(sum([t[2] for t in my_function()]) == 30)test_x()test_y()test_z()print('\n# contents of test_x stash.  Notice only y and z as expected')pprint(get_stash('test_x'))print('\n# contents of test_y stash.  Notice only x and z as expected')pprint(get_stash('test_y'))print('\n# contents of test_z stash.  Notice only x and y as expected')pprint(get_stash('test_z'))# With no arguments, clear_stash will delete all stashes.  You can# select a specific set of stashes to clear by supplying their names.clear_stash()```Output:```# contents of test_x stash.  Notice only y and z as expected[{'y': 0, 'z': 0},{'y': 2, 'z': 3},{'y': 4, 'z': 6},{'y': 6, 'z': 9},{'y': 8, 'z': 12}]# contents of test_y stash.  Notice only x and z as expected[{'x': 0, 'z': 0},{'x': 1, 'z': 3},{'x': 2, 'z': 6},{'x': 3, 'z': 9},{'x': 4, 'z': 12}]# contents of test_z stash.  Notice only x and y as expected[{'x': 0, 'y': 0},{'x': 1, 'y': 2},{'x': 2, 'y': 4},{'x': 3, 'y': 6},{'x': 4, 'y': 8}]```Custom Attribute Extraction---When working with database applications, you frequently encounter objects thatare referenced by id numbers.  These ids serve as record keys from which you canextract human-readable information.  When you are debugging, it can often getconfusing if your screen dump involves just a bunch of id numbers.  What youwould actually like to see is some meaningful name corresponding to that id.  Bysimply overriding one method of the Behold class, this behavior is quite easy toimplement.  This example shows how.```pythonfrom __future__ import print_functionfrom behold import Behold, Item# Subclass Behold to enable custom attribute extractionclass CustomBehold(Behold):@classmethoddef load_state(cls):# Notice this is a class method so that the loaded state will be# available to all instances of CustomBehold.  A common use case would# be to load state like this once from a database and then be able to# reuse it at will without invoking continual database activity.  In# this example, imagine the numbers are database ids and you have# constructed a mapping from id to some human-readable description.cls.name_lookup = {1: 'John',2: 'Paul',3: 'George',4: 'Ringo'}cls.instrument_lookup = {1: 'Rhythm Guitar',2: 'Bass Guitar',3: 'Lead Guitar',4: 'Drums'}def extract(self, item, name):"""I am overriding the extract() method of the behold class.  This methodis responsible for taking an object and turning it into a string.  Thedefault behavior is to simply call str() on the object."""# if the lookup state hasn't been loaded, do so now.if not hasattr(self.__class__, 'name_lookup'):self.__class__.load_state()# extract the value from the behold itemval = getattr(item, name)# If this is a Item object, enable name translationif isinstance(item, Item) and name == 'name':return self.__class__.name_lookup.get(val, None)# If this is a Item object, enable instrument translationelif isinstance(item, Item) and name == 'instrument':return self.__class__.instrument_lookup.get(val, None)# otherwise, just call the default extractorelse:return super(CustomBehold, self).extract(item, name)# define a list of items where names and instruments are given by id numbersitems = [Item(name=nn, instrument=nn) for nn in range(1, 5)]print('\n# Show items using standard Behold class')for item in items:Behold().show(item)print('\n# Show items using CustomBehold class with specialized extractor')for item in items:CustomBehold().show(item, 'name', 'instrument')```Output:```bash# Show items using standard Behold classinstrument: 1, name: 1instrument: 2, name: 2instrument: 3, name: 3instrument: 4, name: 4# Show items using CustomBehold class with specialized extractorname: John, instrument: Rhythm Guitarname: Paul, instrument: Bass Guitarname: George, instrument: Lead Guitarname: Ringo, instrument: Drums```