cdxcore.verbose#
This module contains the cdxcore.verbose.Context
manager class
which supports printing hierarchical verbose progress reports.
Overview#
The key point of this class is to implement an easy-to-use method to print indented progress which
can also be turned off easily without
untidy code constructs such as excessive if
blocks. In this case, we also avoid formatting
any strings.
Here is an example:
from cdxcore.verbose import Context
def f_sub( num=3, context = Context.quiet ):
context.write("Entering loop")
for i in range(num):
context.report(1, "Number %ld", i)
def f_main( context = Context.quiet ):
context.write( "First step" )
# ... do something
context.report( 1, "Intermediate step 1" )
context.report( 1, "Intermediate step 2\\n with newlines" )
# ... do something
f_sub( context=context(2) ) # call function f_sub with a sub-context
# ... do something
context.write( "Final step" )
print("Verbose=1")
context = Context(1)
f_main(context)
print("\\nVerbose=2")
context = Context(2)
f_main(context)
print("\\nVerbose='all'")
context = Context('all')
f_main(context)
print("\\nVerbose='quiet'")
context = Context('quiet')
f_main(context)
print("\\ndone")
Returns:
Verbose=1
00: First step
01: Intermediate step 1
01: Intermediate step 2
01: with newlines
00: Final step
Verbose=2
00: First step
01: Intermediate step 1
01: Intermediate step 2
01: with newlines
02: Entering loop
00: Final step
Verbose='all'
00: First step
01: Intermediate step 1
01: Intermediate step 2
01: with newlines
02: Entering loop
03: Number 0
03: Number 1
03: Number 2
00: Final step
Verbose='quiet'
done
Workflow#
The basic idea is that the root context has level 0, with increasing levels for sub-contexts. When printing information, we can limit printing up to a given level and automatically indent the output to reflect the current level of detail.
Workflow:
Create a
cdxcore.verbose.Context
model, and define its verbosity in its constructor, e.g. by specifying"all"
,"quiet"
, or a number.To write a text at current level to
stdout
usecdxcore.verbose.Context.write()
.To write a text at an indented sub-level use
cdxcore.verbose.Context.report()
.To create a sub-context (indentation), use
cdxcore.verbose.Context.__call__()
.
Lazy Formatting#
cdxcore.verbose.Context
message formattting is meant to be lazy and only executed
if a message is actually written. This means that if the Contex
is "quiet"
no string
formatting takes place.
Consider a naive example:
from cdxcore.verbose import Context
import numpy as np
def f( data : np.ndarray, verbose : Context = Context.quiet ):
verbose.write(f"'f' called; data has mean {np.mean(data)} and variance {np.var(data)}")
# ...
f( verbose = Context.quiet )
In this case f
will compute np.mean(data)
and np.var(data)
even though the use of the quiet
Context
means that the formatted
string will not be printed.
To alleviate this, cdxcore.verbose.Context.write()
supports a number of alternatives
which are leveraging cdxcore.err.fmt()
. In above example, the most efficient use case
is the use of a lambda
function:
def f( data : np.ndarray, verbose : Context ):
verbose.write(lambda : f"'f' called; data has mean {np.mean(data)} and variance {np.var(data)}")
The lambda
function is only called when the message is about to be printed.
Providing Updates#
In many applications we wish to provide progress updates in a single line, and not clutter the output. In the example from the beginng, the long lists of output are not informative.
cdxcore.verbose.Context
supports the use of “\r” and “\n for simple output formatting.
Under the hood it uses cdxcore.crman.CRMan
.
Consider the following change to f_sub
in above code example:
def f_sub( num=3, context = Context.quiet ):
context.write("Entering loop")
for i in range(num):
context.report(1, ":emphasis:`\\r`Number %ld", i, end='') # Notice use of \\r and end=''
context.write("\\rLoop done") # Notice use of \\r `
context = Context('all')
f_main(context)
During execution this prints, for example at step i==1
:
00: First step
01: Intermediate step 1
01: Intermediate step 2
01: with newlines
02: Entering loop
03: Number 1
But once the loop finished the update per i
is overwitten:
00: First step
01: Intermediate step 1
01: with newlines
02: Entering loop
02: Loop done
00: Final step
Composing Line Output and Timing#
For lengthy operations it is often considerate to provide the user with an update on
how long an operation takes. cdxcore.verbose.Context
provides some simple tooling:
from cdxcore.verbose import Context
import time as time
def takes_long( n : int, verbose : context = Context.quiet ):
with verbose.write_t("About to start... ", end='') as tme:
for t in range(n):
verbose.write(lambda : f"\\rTakes long {int(100.*(t+1)/n)}%... ", end='')
time.sleep(0.22)
verbose.write(lambda : f"done; this took {tme}.", head=False)
takes_long(5, Context.all)
During execution prints
00: Takes long 80%...
The example finishes with
00: Takes long 100%... done; this took 1.1s.
Import#
from cdxcore.verbose import Context
Documentation#
Classes
|
Class for printing indented messages, filtered by overall level of visibility. |
- class cdxcore.verbose.Context(init=None, *, indent=2, fmt_level='%02ld: ', level=None, channel=None)[source]#
Bases:
object
Class for printing indented messages, filtered by overall level of visibility.
Construction with keywords:
Context( "all" )` or Context( "quiet" )
Display everything:
Context( None )
Display only up to level 2 (top level is 0) e.g.:
Context( 2 )
Copy constructor:
Context( context )
Example:
from cdxcore.verbose import Context def f_2( verbose : Context = Context.quiet ): verbose.write( "Running 'f_2'") for i in range(5): verbose.report(1, "Sub-task {i}", i=i) # do something def f_1( verbose : Context = Context.quiet ): verbose.write( "Running 'f_1'") f_2( verbose(1) ) # do something verbose = Context("all") verbose.write("Starting:") f_1(verbose(1)) verbose.write("Done.")
prints
00: Starting: 01: Running 'f_1' 02: Running 'f_2' 03: Sub-task 0 03: Sub-task 1 03: Sub-task 2 03: Sub-task 3 03: Sub-task 4 00: Done.
If we set visibility to 2
verbose = Context(2) verbose.write("Starting:") f_1(verbose(1)) # <-- make it a level higher verbose.write("Done.")
we get the reduced
00: Starting: 01: Running 'f_1' 02: Running 'f_2' 00: Done.
Lazy Formatting
The
cdxcore.verbose.Context.write()
andcdxcore.verbose.Context.report()
functions provide string formatting capabilities. If used, then a message will only be formatted if the current level grants it visibility. This avoids unnecessary string operations when no output is required.In the second example above, the format string
verbose.report(1, "Sub-task {i}", i=i)
inf_2
will not be evaluated as that reporting level is turned off.- Parameters:
- initstr | int |
cdxcore.verbose.Context
If a string is provided: must be
"all"
or"quiet"
.If an integer is privided it represents the visibility level up to which to print. Set to 0 to print only top level messages. Any negative number will turn off any messages and is equivalent to
"quiet"
.If set to
None
display everything.A
Context
is copied.
- indentint, optional
How much to indent strings per level. Default 2.
- fmt_levelstr, optional
A format string containing
%d
for the current indentation. Default is"%02ld: "
.- levelint, optional
Current level. If
init
is another context, andlevel
is specified, it overwrites thelevel
from the other context.If
level
isNone
:If
init
is anotherContext
object, use that object’s level.If
init
is an integer or one of the keywords above, use the default, 0.
- channelCallable, optional
Advanced parameter.
A callable which is called to print text. The call signature is:
channel( msg : str, flush : bool )`
which is meant to mirror
print( msg, end='', flush )
for the providedchannel
. In particular do not terminatemsg
automatically with a new line.Illustration:
class Collector: def __init__(self): self.messages = [] def __call__(self, msg, flush ): self.messages.append( msg ) collect = Collector() verbose = Context( channel = collect ) verbose.write("Write at 0") verbose.report(1,"Report at 1") print(collect.messages)
prints
['00: Write at 0\\n', '01: Report at 1\\n']
- initstr | int |
- Attributes:
as_quiet
Return a Context at the same current reporting level as
self
with zero visibilityas_verbose
Return a Context at the same current reporting level as
self
with full visibilityis_quiet
Whether the current context is
"quiet"
Methods
__call__
([add_level, message, end, head])Create and return a sub
Context
at current level plusadd_level
.apply_channel
(channel)Advanced Use
fmt
(level, message, *args[, head])Formats message with the formattting arguments at curent context level plus
level
.report
(level, message, *args[, end, head])Report message at current level plus
level
.shall_report
([add_level])Returns whether to print something at current level plus
add_level
.str_indent
([add_level])Returns the string identation for the current level plus
add_level
timer
()Returns a new
cdxcore.util.Timer
object to measure time spent in a block of code.write
(message, *args[, end, head])Report message at current level.
write_t
(message, *args[, end, head])Reports
message
subject to string formatting at current level if visible and returns acdxcore.util.Timer
object which can be used to measure time elapsed sincewrite_t()
was called.all
- ALL = 'all'#
Constant for the keyword
"all"
- QUIET = 'quiet'#
Constant for the keyword
"quiet"
- __call__(add_level=1, message=None, end='\\n', head=True, *args, **kwargs)[source]#
Create and return a sub
Context
at current level plusadd_level
.If a
message
is provided,cdxcore.verbose.Context.write()
is called before the newContext
is created.Example:
from cdxcore.verbose import Context def f( verbose : Context = Context.quiet ): # ... verbose.write("'f'' usuing a sub-context.") verbose = Context.all verbose.write("Main") f( verbose=verbose(1) ) # create sub-context
prints
00: Main 01: 'f'' usuing a sub-context.
- Parameters:
- add_levelint
Level to add to the current level. Set to 0 for the same level.
- messagestr|Callable, optional.
Text containing format characters, or
None
to not print a message.The following alternatives are suppoted:
Python 3
`{parameter:d}`
, in which casemessage.fmt(kwargs)
forstr.format()
is used to obtain the output message.Python 2
`%(parameter)d`
in which casemessage % kwargs
is used to obtain the output message.Classic C-stype
`%d, %s, %f`
in which casemessage % args
is used to obtain the output message.If
message
is aCallable
such as alambda
function, thenmessage( *args, **kwargs )
is called to obtain the output message.Note that a common use case is using an f-string wrapped in a
lambda
function. In this case you do not needargs
orkwargs
:x = 1 verbose.write(lambda : f"Delayed f-string formatting {x}")
- headbool, optional;
Whether this message needs a header (i.e. the
01
and spacing). TypicallyFalse
if the previous call towrite()
used end=’’. See examples above.- *args, **kwargs:
See above
- Returns:
- verbose
Context
Sub context with new level equal to current level plus
add_level
.
- verbose
- all = <cdxcore.verbose.Context object>#
- apply_channel(channel)[source]#
Advanced Use
Returns a new
`Context
object with the same currrent state asself
, but pointing tochannel
.
- property as_quiet#
Return a Context at the same current reporting level as
self
with zero visibility
- property as_verbose#
Return a Context at the same current reporting level as
self
with full visibility
- fmt(level, message, *args, head=True, **kwargs)[source]#
Formats message with the formattting arguments at curent context level plus
level
.This function returns ```` if current level plus
level
is not visible. In that case no string formatting takes place.- Parameters:
- levelint
Level to add to current level.
- messagestr|Callable
Text containing format characters.
The following alternatives are suppoted:
Python 3
`{parameter:d}`
, in which casemessage.fmt(kwargs)
forstr.format()
is used to obtain the output message.Python 2
`%(parameter)d`
in which casemessage % kwargs
is used to obtain the output message.Classic C-stype
`%d, %s, %f`
in which casemessage % args
is used to obtain the output message.If
message
is aCallable
such as alambda
function, thenmessage( *args, **kwargs )
is called to obtain the output message.Note that a common use case is using an f-string wrapped in a
lambda
function. In this case you do not needargs
orkwargs
:x = 1 verbose.write(lambda : f"Delayed f-string formatting {x}")
- headbool, optional;
Whether this message needs a header (i.e. the
01
and spacing). TypicallyFalse
if the previous call towrite()
used end=’’. See examples above.- *args, **kwargs:
See above
- Returns:
- Stringstr
Formatted string, or
None` `if the current level plus ``level
is not visible.
- quiet = <cdxcore.verbose.Context object>#
- report(level, message, *args, end='\\n', head=True, **kwargs)[source]#
Report message at current level plus
level
.The message will be formatted using
cdxcore.err.fmt()
is the current level plus level is visible.The parameter
end
matchesend
inprint()
e.g.end=''
avoids a newline at the end of the message.If
head
isTrue
, then the first line of the text will be preceeded by proper indentation.If
head
isFalse
, the first line will be printed without preamble.
This means the following is a valid pattern:
from cdxcore.verbose import Context verbose = Context() verbose.report(1, "Doing something... ", end='') # ... do something verbose.report(1, "done.", head=False)
which prints
01: Doing something... done.
Another use case is updates per line, for example::
from cdxcore.verbose import Context verbose = Context() N = 1000 for i in range(N): verbose.report(1,f"\\rStatus {int(float(i+1)/float(N)*100)}%... ", end='') # do something verbose.report(1,"done.", head=False)
will provide progress information in the current line as the loop is processed.
Implementation notice: The use of
\\r
is managed usingcdxcore.crman.CRMan
.- Parameters:
- levelint
Level to add to current level.
- messagestr|Callable
Text containing format characters.
The following alternatives are suppoted:
Python 3
`{parameter:d}`
, in which casemessage.fmt(kwargs)
forstr.format()
is used to obtain the output message.Python 2
`%(parameter)d`
in which casemessage % kwargs
is used to obtain the output message.Classic C-stype
`%d, %s, %f`
in which casemessage % args
is used to obtain the output message.If
message
is aCallable
such as alambda
function, thenmessage( *args, **kwargs )
is called to obtain the output message.Note that a common use case is using an f-string wrapped in a
lambda
function. In this case you do not needargs
orkwargs
:x = 1 verbose.write(lambda : f"Delayed f-string formatting {x}")
- endstr, optional
Terminating string akin to
end
inprint()
. Use''
to not print a newline. See example above for a use case.- headbool, optional;
Whether this message needs a header (i.e. the
01
and spacing). TypicallyFalse
if the previous call towrite()
used end=’’. See examples above.- *args, **kwargs:
See above
- shall_report(add_level=0)[source]#
Returns whether to print something at current level plus
add_level
.
- timer()[source]#
Returns a new
cdxcore.util.Timer
object to measure time spent in a block of code.Example:
import time as time from cdxcore.verbose import Context verbose = Context("all") with verbose.Timer() as tme: verbose.write("Starting job... ", end='') time.sleep(1) verbose.write(f"done; this took {tme}.", head=False)
produces
00: Starting job... done; this took 1s.
- write(message, *args, end='\\n', head=True, **kwargs)[source]#
Report message at current level.
The message will be formatted using
cdxcore.err.fmt()
if the current level is visible. If the current level is not visible no message formatting will take place.The parameter
end
matchesend
inprint()
e.g.end=''
avoids a newline at the end of the message.If
head
isTrue
, then the first line of the text will be preceeded by proper indentation.If
head
isFalse
, the first line will be printed without preamble.
This means the following is a valid pattern:
from cdxcore.verbose import Context verbose = Context() verbose.write("Doing something... ", end='') # ... do something verbose.write("done.", head=False)
which prints
00: Doing something... done.
Another use case is updates per line, for example:
from cdxcore.verbose import Context verbose = Context() N = 1000 for i in range(N): verbose.write(f"\\rDoing something {int(float(i+1)/float(N)*100)}%... ", end='') # do something verbose.write("done.", head=False)
which will provide progress information in a given line.
Implementation notice: the use of
\r
is managed usingcdxcore.crman.CRMan
.- Parameters:
- messagestr|Callable
Text containing format characters.
The following alternatives are suppoted:
Python 3
`{parameter:d}`
, in which casemessage.fmt(kwargs)
forstr.format()
is used to obtain the output message.Python 2
`%(parameter)d`
in which casemessage % kwargs
is used to obtain the output message.Classic C-stype
`%d, %s, %f`
in which casemessage % args
is used to obtain the output message.If
message
is aCallable
such as alambda
function, thenmessage( *args, **kwargs )
is called to obtain the output message.Note that a common use case is using an f-string wrapped in a
lambda
function. In this case you do not needargs
orkwargs
:x = 1 verbose.write(lambda : f"Delayed f-string formatting {x}")
- endstr, optional
Terminating string akin to
end
inprint()
. Use''
to not print a newline. See example above for a use case.- headbool, optional;
Whether this message needs a header (i.e. the
01
and spacing). TypicallyFalse
if the previous call towrite()
used end=’’. See examples above.- *args, **kwargs:
See above
- write_t(message, *args, end='\\n', head=True, **kwargs)[source]#
Reports
message
subject to string formatting at current level if visible and returns acdxcore.util.Timer
object which can be used to measure time elapsed sincewrite_t()
was called:from cdxcore.verbose import Context verbose = Context() with verbose.write_t("Doing something... ", end='') as tme: # do something verbose.write("done; this took {tme}.", head=False)
produces
00: Doing something... done; this took 1s.
Equivalent to using
cdxcore.verbose.Context.write()
first followed bycdxcore.verbose.Context.timer()
.