Inform: Print & Logging Utilities

Version: 1.28
Released: 2023-03-20
Please post all bugs and suggestions at Inform Issues (or contact me directly at inform@nurdletech.com).

Inform is designed to display messages from programs that are typically run from a console. It provides a collection of ‘print’ functions that allow you to simply and cleanly print different types of messages. For example:

>>> from inform import display, error, os_error
>>> display('This is a plain message.')
This is a plain message.

>>> try:
...     with open('config') as f:
...         config = f.read()
... except OSError as e:
...     error(os_error(e))
error: config: no such file or directory.

The display and error functions are referred to as informants. They behave in a way that is very similar to the print function that is built-in to Python3, but they also provide some additional features as well. For example, they can be configured to log their messages and they can be disabled en masse.

Finally, Inform provides a generic exception and a collection of small utilities that are useful when creating messages.

Alternatives

The Python standard library provides the logging package. This package differs from Inform in that it is really intended to log events to a file. It is more intended for daemons that run in the background and the logging is not meant to communicate directly to the user in real time, but rather record enough information into a log file for an administrator to understand how well the program is performing and whether anything unusual is happening.

In contrast, Inform is meant to used to provide information from command line utilities directly to the user in real time. It is not confined to only logging events, but instead can be used anywhere the normal Python print function would be used. In effect, Inform allows you to create and use multiple print functions each of which is tailored for a specific situation or task. This of course is something you could do yourself using the built-in print function, but with Inform you will not have to embed your print functions in complex condition statements, every message is formatted in a consistent manner that follows normal Unix conventions, and you can control all of your print functions by configuring a single object.

Installation

Install the latest stable version with:

pip3 install --user inform

Requires Python2.7 or Python3.3 or better.

The source code is available from GitHub. You can download the repository and install using:

git clone https://github.com/KenKundert/inform.git
pip3 install --user inform

Issues

Please ask questions or report problems on Inform Issues.

Quick Tour

Informants

Inform defines a collection of print-like functions that have different roles. These functions are referred to as ‘informants’ and include display, warn, error, and fatal. All of them take arguments in the same manner as Python’s built-in print function and all of them write the desired message to standard output, with the last three adding a header to the message that indicates the type of message. For example:

>>> from inform import display, error, fatal, warn

>>> display('ice', 9)
ice 9

>>> warn('cannot write to file, logging suppressed.')
warning: cannot write to file, logging suppressed.

>>> filename = 'config'
>>> error('%s: file not found.' % filename)
error: config: file not found.

>>> fatal('defective input file.', culprit=filename)
error: config: defective input file.

Notice that in the error message the filename was explicitly added to the front of the message. This is an extremely common idiom and it is provided by Inform using the culprit named argument as shown in the fatal message. fatal is similar to error but additionally terminates the program. To make the error messages stand out, the header is generally rendered in a color appropriate to the message, so warnings use yellow and errors use red. However, they are not colored above because messages are only colored if they are being written to the console (a TTY).

In a manner similar to Python3’s built-in print function, unnamed arguments are converted to strings and then joined using the separator, which by default is a single space but can be specified using the sep named argument.

>>> colors = dict(red='ff5733', green='4fff33', blue='3346ff')

>>> lines = []
>>> for key in sorted(colors.keys()):
...     val = colors[key]
...     lines.append('{key:>5s} = {val}'.format(key=key, val=val))

>>> display(*lines, sep='\n')
 blue = 3346ff
green = 4fff33
  red = ff5733

Alternatively, you can specify an arbitrary collection of named and unnamed arguments, and form them into a message using the template argument:

>>> for key in sorted(colors.keys()):
...     val = colors[key]
...     display(val, k=key, template='{k:>5s} = {}')
 blue = 3346ff
green = 4fff33
  red = ff5733

You can even specify a collection of templates. The first one for which all keys are known is used. For example;

>>> colors = dict(
...     red = ('ff5733', 'failure'),
...     green = ('4fff33', 'success'),
...     blue = ('3346ff', None),
... )

>>> for name in sorted(colors.keys()):
...     code, desc = colors[name]
...     templates = ('{:>5s} = {}  -- {}', '{:>5s} = {}')
...     display(name, code, desc, template=templates)
 blue = 3346ff
green = 4fff33  -- success
  red = ff5733  -- failure

>>> for name in sorted(colors.keys()):
...     code, desc = colors[name]
...     templates = ('{k:>5s} = {v}  -- {d}', '{k:>5s} = {v}')
...     display(k=name, v=code, d=desc, template=templates)
 blue = 3346ff
green = 4fff33  -- success
  red = ff5733  -- failure

All informants support the culprit named argument, which is used to identify the object of the message. The culprit can be a scalar, as above, or a collection, in which case the members of the collection are joined together:

>>> line = 5
>>> display('syntax error.', culprit=(filename, line))
config, 5: syntax error.

Besides the four informants already described, Inform provides several others, including log, codicil, comment, narrate, output, notify, debug and panic. Informants in general can write to the log file, to the standard output, or to a notifier. They can add headers and specify the color of the header and the message. They can also continue the previous message or they can terminate the program. Each informant embodies a predefined set of these choices. In addition, they are affected by options passed to the active informer (described next), which is often used to enable or disable informants based on various verbosity options.

Controlling Informants

For more control of the informants, you can import and instantiate the Inform class yourself along with the desired informants. This gives you the ability to specify options:

>>> from inform import Inform, display, error
>>> Inform(logfile=True, prog_name="teneya", quiet=True)
<...>
>>> display('Initializing ...')

>>> error('file not found.', culprit='data.in')
teneya error: data.in: file not found.

Notice that in this case the call to display did not print anything. That is because the quiet argument was passed to Inform, which acts to suppress all but error messages. However, a logfile was specified, so the message would be logged. In addition, the program name was specified, with the result in it being added to the header of the error message.

An object of the Inform class is referred to as an informer (not to be confused with the print functions, which are referred to as informants). Once instantiated, you can use the informer to change various settings, terminate the program, or return a count of the number of errors that have occurred.

>>> from inform import Inform, error
>>> informer = Inform(prog_name=False)
>>> error('file not found.', culprit='data.in')
error: data.in: file not found.
>>> informer.errors_accrued()
1

Utility Functions

Inform provides a collection of utility functions that are often useful when constructing messages.

aaa Pretty prints, then returns, its argument; used when debugging code.
Color Class Used to color messages sent to the console.
columns Distribute an array over enough columns to fill the screen.
conjoin Like join, but adds a conjunction like ‘and’ or ‘or’ between the last two items.
cull Strips uninteresting value from collections.
ddd Pretty prints its arguments, used when debugging code.
fmt Similar to format(), but can pull arguments from the local scope.
full_stop Add a period to end of string if it has no other punctuation.
indent Adds indentation.
Info Class A base class that can be used to create helper classes.
is_collection Is object a collection (i.e., is it iterable and not a string)?
is_iterable Is object iterable (includes strings).
is_mapping Is object a mapping (i.e., is it a dictionary or is it dictionary like)?
is_str Is object a string?
join Combines arguments into a string in the same way as an informant.
os_error Generates clean messages for operating system errors
plural Pluralizes a word if needed.
ppp Print function, used when debugging code.
ProgressBar Class Used to generate progress bars.
render Converts many of the built-in Python data types into attractive, compact, and easy to read strings.
sss Prints stack trace, used when debugging code.
vvv Print all variables that have given value, used when debugging code.

One of the most used is os_error. It converts OSError exceptions into a simple well formatted string that can be used to describe the exception to the user.

>>> from inform import os_error, error
>>> try:
...     with open(filename) as f:
...         config = f.read()
... except OSError as e:
...     error(os_error(e))
error: config: no such file or directory.

Generic Exception

Inform also provides a generic exception, Error, that can be used directly or can be subclassed to create your own exceptions. It takes arguments in the same manner as informants, and provides some useful methods used when reporting errors:

>>> from inform import Error

>>> def read_config(filename):
...     try:
...         with open(filename) as f:
...             config = f.read()
...     except OSError as e:
...         raise Error(os_error(e))

>>> try:
...     read_config('config')
... except Error as e:
...     e.report()
error: config: no such file or directory.