Skip to content

Commit

Permalink
Added set_culprit(), add_culprit(), and get_culprit().
Browse files Browse the repository at this point in the history
  • Loading branch information
Ken Kundert authored and Ken Kundert committed Aug 9, 2018
1 parent b314c0a commit a3eac85
Show file tree
Hide file tree
Showing 6 changed files with 204 additions and 22 deletions.
6 changes: 6 additions & 0 deletions doc/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,12 @@ functionality even if you do not have local access to the informer.

.. autofunction:: inform.get_prog_name

.. autofunction:: inform.set_culprit

.. autofunction:: inform.add_culprit

.. autofunction:: inform.get_culprit

You can also request the active informer:

.. autofunction:: inform.get_informer
Expand Down
14 changes: 8 additions & 6 deletions doc/releases.rst
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ Releases
- Added *notify_if_no_tty* option.
- Informers now stack, so disconnecting from an existing informer reinstates
the previous informer.
- Generalize cull.
- Generalize :func:`inform.cull()`.
- Add support for multiple templates.
- Add *join* function.
- Added :func:`inform.join()` function.

**1.12 (2018-02-18)**:
- do not use notify override on continuations.
Expand All @@ -20,8 +20,10 @@ Releases
| Version: 1.12.7
| Released: 2018-07-15
- Added aaa() debug function
- Added exit argument to done()
- terminate() now produces an exit status of 0 if there was no errors
reported
- Added :func:`inform.aaa()` debug function.
- Added exit argument to :func:`inform.done()`.
- :func:`inform.terminate()` now produces an exit status of 0 if there was
no errors reported.
- Added :func:`inform.set_culprit()`, :func:`inform.add_culprit()`
and :func:`inform.get_culprit()`.

56 changes: 44 additions & 12 deletions doc/user.rst
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,9 @@ flush = *False*:
culprit = *None*:
A string that is added to the beginning of the message that identifies the
culprit (the object for which the problem being reported was found). May also
be a collection of strings, in which case they are joined with *culprit_sep*
(default is ', ').
number or a tuple that contains strings and numbers. If *culprit* is a tuple,
the members are converted to strings and joined with *culprit_sep* (default
is ', ').

wrap = False:
Specifies whether message should be wrapped. *wrap* may be True, in which
Expand Down Expand Up @@ -110,6 +111,37 @@ Here is an example that demonstrates the wrap and composite culprit features:
Encountered illegal value -1 when filtering. Consider regenerating
the dataset.
Occasionally the actual culprits are not available where the messages are
printed. In this case you can use culprit caching. Simply cache the culprits
in you informer using :func:`inform.set_culprit` or :func:`inform.add_culprit`
and then recall them when needed using :func:`inform.get_culprit`. For example:

.. code-block:: python
>>> from inform import add_culprit, get_culprit, set_culprit, error
>>> def read_param(line, parameters):
... name, value = line.split(' = ')
... try:
... parameters[name] = float(value)
... except ValueError:
... error(
... 'expected a number, found:', value,
... culprit=get_culprit(name)
... )
>>> def read_params(lines):
... parameters = {}
... for lineno, line in enumerate(lines):
... with add_culprit(lineno+1):
... read_param(line, parameters)
>>> filename = 'parameters'
>>> with open(filename) as f, set_culprit(filename):
... lines = f.read().splitlines()
... parameters = read_params(lines)
error: parameters, 3, c: expected a number, found: ack
The *template* strings are the same as one would use with Python's built-in
format function and string method (as described in `Format String Syntax
<https://docs.python.org/3/library/string.html#format-string-syntax>`_. The
Expand Down Expand Up @@ -1199,11 +1231,11 @@ method has the side effect of updating the state of the integrator.
>>> for t in range(1, 3):
... vout = 0.7*aaa(int2=int2.update(aaa(int1=int1.update(vin-vout))))
... display('vout = {}'.format(vout))
myprog DEBUG: <doctest user.rst[130]>, 2, __main__: int1: 2
myprog DEBUG: <doctest user.rst[130]>, 2, __main__: int2: 2
myprog DEBUG: <doctest user.rst[135]>, 2, __main__: int1: 2
myprog DEBUG: <doctest user.rst[135]>, 2, __main__: int2: 2
vout = 1.4
myprog DEBUG: <doctest user.rst[130]>, 2, __main__: int1: 1.6
myprog DEBUG: <doctest user.rst[130]>, 2, __main__: int2: 3.6
myprog DEBUG: <doctest user.rst[135]>, 2, __main__: int1: 1.6
myprog DEBUG: <doctest user.rst[135]>, 2, __main__: int2: 3.6
vout = 2.52
Expand All @@ -1224,7 +1256,7 @@ ddd
>>> c = (2, 3)
>>> d = {'a': a, 'b': b, 'c': c}
>>> ddd(a, b, c, d)
myprog DEBUG: <doctest user.rst[136]>, 1, __main__:
myprog DEBUG: <doctest user.rst[141]>, 1, __main__:
1
'this is a test'
(2, 3)
Expand All @@ -1240,7 +1272,7 @@ If you give named arguments, the name is prepended to its value:
>>> from inform import ddd
>>> ddd(a=a, b=b, c=c, d=d, s='hey now!')
myprog DEBUG: <doctest user.rst[138]>, 1, __main__:
myprog DEBUG: <doctest user.rst[143]>, 1, __main__:
a = 1
b = 'this is a test'
c = (2, 3)
Expand All @@ -1264,7 +1296,7 @@ argument itself.
... ddd(self=self)
>>> contact = Info(email='ted@ledbelly.com', name='Ted Ledbelly')
myprog DEBUG: <doctest user.rst[140]>, 4, __main__.Info.__init__():
myprog DEBUG: <doctest user.rst[145]>, 4, __main__.Info.__init__():
self = Info object containing {
'email': 'ted@ledbelly.com',
'name': 'Ted Ledbelly',
Expand Down Expand Up @@ -1294,7 +1326,7 @@ good way of confirming that a line of code has been reached.
>>> c = (2, 3)
>>> d = {'a': a, 'b': b, 'c': c}
>>> ppp(a, b, c)
myprog DEBUG: <doctest user.rst[147]>, 1, __main__: 1 this is a test (2, 3)
myprog DEBUG: <doctest user.rst[152]>, 1, __main__: 1 this is a test (2, 3)
.. _sss desc:
Expand Down Expand Up @@ -1338,7 +1370,7 @@ variables on the argument list and only those variables are printed.
>>> from inform import vvv
>>> vvv(b, d)
myprog DEBUG: <doctest user.rst[149]>, 1, __main__:
myprog DEBUG: <doctest user.rst[154]>, 1, __main__:
b = 'this is a test'
d = {
'a': 1,
Expand All @@ -1356,7 +1388,7 @@ shown.
>>> aa = 1
>>> vvv(a)
myprog DEBUG: <doctest user.rst[152]>, 1, __main__:
myprog DEBUG: <doctest user.rst[157]>, 1, __main__:
a = 1
aa = 1
vin = 1
Expand Down
5 changes: 4 additions & 1 deletion inform/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,8 @@
notify, debug, warn, error, fatal, panic,

# the currently active informer
get_informer
get_informer,

# culprit functions
set_culprit, add_culprit, get_culprit
)
141 changes: 140 additions & 1 deletion inform/inform.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,13 @@
# stderr is used on all messages that include headers
}

"""
These are used to configure inform for doctests:
>>> from inform import Inform
>>> inform = Inform(prog_name=False, logfile=False)
"""

# Inform Utilities {{{1
# indent {{{2
Expand Down Expand Up @@ -900,7 +907,7 @@ class InformantFactory:
messages in green, and *fail*, which prints its messages in red. Output
to the standard output for both is suppressed if *quiet* is *True*::
>>> from inform import InformantFactory, Inform
>>> from inform import InformantFactory
>>> passes = InformantFactory(
... output=lambda inform: not inform.quiet,
Expand Down Expand Up @@ -1239,6 +1246,7 @@ def __init__(
self.stream_policy = stream_policy
self.notifier = notifier
self.notify_if_no_tty = notify_if_no_tty
self.culprit = ()

# make verbosity flags consistent
self.mute = mute
Expand Down Expand Up @@ -1584,6 +1592,112 @@ def __enter__(self):
def __exit__(self, type, value, traceback):
self.disconnect()

# culprit {{{2
# first create a context manager
class CulpritContextManager:
def __init__(self, informer, culprit, append=True):
self.informer = informer
self.culprit = culprit if is_collection(culprit) else (culprit,)
self.append = append

def __enter__(self):
self.saved_culprit = self.informer.culprit
if self.append:
self.informer.culprit += self.culprit
else:
self.informer.culprit = self.culprit

def __exit__(self, *args):
self.informer.culprit = self.saved_culprit

# set/replace the culprit
def set_culprit(self, culprit):
"""Set the culprit while displacing current culprit.
Squirrels away a culprit for later use. Any existing culprit is moved
out of the way.
Args:
culprit (string, number or tuple of strings and numbers):
A culprit or collection of culprits that are cached with the
intent that they are available to be included in a message. They
generally are used to indicate what a message refers to.
This function is designed to work as a context manager, meaning that it
meant to be used with Python's *with* statement. It temporarily replaces
any existing culprit, but that culprit in reinstated upon exiting the
*with* statement. This is used with :meth:`inform.Inform.get_culprit`
when the message that needs the culprit is printed from a remote
location where the culprit would not normally be known. For example::
>>> from inform import get_culprit, set_culprit, warn
>>> def count_lines(lines):
... empty = 0
... for lineno, line in enumerate(lines):
... if not line:
... warn('empty line.', culprit=get_culprit(lineno))
>>> filename = 'setup.py'
>>> with open(filename) as f, set_culprit(filename):
... lines = f.read().splitlines()
... num_lines = count_lines(lines)
warning: setup.py, 5: empty line.
warning: setup.py, 8: empty line.
warning: setup.py, 13: empty line.
"""
return self.CulpritContextManager(self, culprit, append=False)

# add to the culprit
def add_culprit(self, culprit):
"""Add to the current culprit.
Similar to :meth:`Inform.set_culprit` except that this method appends
the given culprit to the cached culprit rather than simply replacing it.
Args:
culprit (string, number or tuple of strings and numbers):
A culprit or collection of culprits that are cached with the
intent that they are available to be included in a message. They
generally are used to indicate what a message refers to.
This function is designed to work as a context manager, meaning that it
meant to be used with Python's *with* statement. It temporarily replaces
any existing culprit, but that culprit in reinstated upon exiting the
*with* statement. This is used with :meth:`inform.Inform.get_culprit`
when the message that needs the culprit is printed from a remote
location where the culprit would not normally be known.
See :meth:`Inform.set_culprit` for an example of a closely related
method.
"""
return self.CulpritContextManager(self, culprit, append=True)

# get the culprit
def get_culprit(self, culprit=None):
"""Get the current culprit.
Return the currently cached culprit as a tuple. If a culprit is specified as an
argument, it is appended to the cached culprit without modifying it.
Args:
culprit (string, number or tuple of strings and numbers):
A culprit or collection of culprits that is appended to the
return value without modifying the cached culprit.
Returns:
The culprit argument is appended to the cached culprit and the
combination is returned. The return value is always in the form of a
tuple even if there is only one component.
See :meth:`Inform.set_culprit` for an example use of this method.
"""
if culprit:
culprit = culprit if is_collection(culprit) else (culprit,)
return self.culprit + culprit
return self.culprit


# Direct access to class methods {{{1
# done {{{2
Expand Down Expand Up @@ -1637,6 +1751,31 @@ def get_informer():
return INFORMER


# set/replace the culprit {{{2
def set_culprit(culprit):
"""Set the culprit while displacing current culprit.
Calls :meth:`inform.Inform.set_culprit` for the active informer.
"""
return INFORMER.set_culprit(culprit)

# add to the culprit {{{2
def add_culprit(culprit):
"""Append to the end of the current culprit.
Calls :meth:`inform.Inform.add_culprit` for the active informer.
"""
return INFORMER.add_culprit(culprit)

# get the culprit {{{2
def get_culprit(culprit=None):
"""Get the current culprit.
Calls :meth:`inform.Inform.get_culprit` for the active informer.
"""
return INFORMER.get_culprit(culprit)


# Instantiate default informer {{{1
DEFAULT_INFORMER = Inform()
INFORMER = DEFAULT_INFORMER
Expand Down
4 changes: 2 additions & 2 deletions tests/test_inform.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,12 +112,12 @@ def test_fabricate():
with messenger(hanging_indent=False) as (msg, stdout, stderr, logfile):
error('hey now!')
codicil('baby', 'bird', sep='\n')
error('uh-huh\nuh-huh', culprit='yep yep yep yep yep yep yep yep yep yep yep')
error('uh-huh\nuh-huh', culprit='yep yep yep yep yep yep yep yep yep yep yep'.split())
expected = dedent('''
error: hey now!
baby
bird
error: yep yep yep yep yep yep yep yep yep yep yep:
error: yep, yep, yep, yep, yep, yep, yep, yep, yep, yep, yep:
uh-huh
uh-huh
''').strip()
Expand Down

0 comments on commit a3eac85

Please sign in to comment.