icecream-truck¶
🍦 Flavorful Debugging - A Python library which enhances the powerful and well-known icecream package with flavored traces, configuration hierarchies, customized outputs, ready-made recipes, and more.
Key Features ⭐¶
🍒 Debugger Flavors: Numeric trace depths to control level of debugging
detail (0
to 9
) or custom named flavors for specific subsystems (e.g.,
io
, reporting
), traditional logging levels (e.g., info
, error
),
or whatever else you can imagine.
🌳 Module Hierarchy: Global and per-module configs with inheritance for precise control over output prefixes, formatters, custom flavors, etc….
🖨️ Printer Factory: Dyanamically associate output functions with debugger
objects based on module name, flavor, etc…. Swap in customized print
,
logging
, or other sinks as desired.
📚 Library-Friendly: Non-intrusive registration for libraries without stepping on application debugger/logging configuration.
🚦 Disabled by Default: Can leave in production code and explicitly activate portions as needed. (Performance and security considerations notwithstanding.)
Installation 📦¶
Method: Install Python Package¶
Install via uv pip
command:
uv pip install icecream-truck
Or, install via pip
:
pip install icecream-truck
Examples 💡¶
Please see the examples directory for greater detail.
Universal Availability¶
Install an icecream truck as a Python builtin (default alias, ictr
) and
then use anywhere in your codebase:
from ictruck import install
install( trace_levels = 3 ) # Enable TRACE0 to TRACE3
message = "Hello, debug world!"
ictr( 1 )( message ) # Prints: TRACE1| message: 'Hello, debug world!'
Library Registration¶
Libraries can register their own configurations without overriding those of the application or other libraries. By default, the name of the calling module is used to register a default configuration:
from ictruck import register_module
register_module( ) # Can pass custom configuration.
When install
is called, any module configurations that were previously
registered via register_module
are added to the installed icecream truck.
This allows an application to setup output after libraries have already
registered their flavors, giving lots of initialization-time and runtime
flexibility.
Recipes for Customization¶
Please see the package documentation for available recipes.
E.g., integrate icecream
-based introspection and formatting with the
logging
module in the Python standard library:
import logging
from ictruck.recipes.logging import produce_truck
logging.basicConfig( level = logging.INFO )
truck = produce_truck( )
admonition = "Careful now!"
answer = 42
truck( 'warning' )( admonition ) # Logs: WARNING:__main__:ic| admonition: 'Careful now!'
truck( 'info' )( answer ) # Logs: INFO:__main__:ic| answer: 42
## Note: Module name will be from whatever module calls the truck.
Motivation 🚚¶
Why icecream-truck
?
There is nothing wrong with the icecream
or logging
packages. However,
there are times that the author of icecream-truck
has wanted, for various
reasons, more than these packages inherently offer:
Coexistence: Application and libraries can coexist without configuration clashes.
Library developers are strongly advised not to create custom levels in
logging
.Library developers are advised on how to avoid polluting stderr in
logging
, when an application has not supplied a configuration.Loggers propagate upwards by default in
logging
. This means that libraries must explicitly opt-out of propagation if their authors want to be good citizens and not contribute to noise pollution / signal obfuscation.
Granularity: Control of debug output by depth threshold and subsystem.
Only one default debugging level (
DEBUG
) withlogging
. Libraries cannot safely extend this. (See point about coexistence).No concept of debugging level with
ic
builtin. Need to orchestrate multipleicecream.IceCreamDebugger
instances to support this. (In fact, this is whaticecream-truck
does.)While logger hierarchies in
logging
do support the notion of software subsystems, hierarchies are not always the most convenient or abbreviated way of representing subsystems which span parts or entireties of modules.
Signal: Prevention of undesirable library chatter.
The
logging
root logger will log all messages, at its current log level or higher, which propagate up to it. Many Python libraries have opt-out rather than opt-in logging, so you see all of theirDEBUG
andINFO
spam unless you surgically manipulate their loggers or squelch the overall log level.Use of the
ic
builtin is only recommended for temporary debugging. It cannot be left in production code without spamming. While theenabled
flag on theic
builtin can be set to false, it is easy to forget and also applies to every place whereic
is used in the code. (See point about granularity.)
Extensibility: More natural integration with packages like
rich
via robust recipes.While it is not difficult to change the
argToStringFunction
onic
to berich.pretty.pretty_repr
, there is some repetitive code involved in each project which wants to do this. And, from a safety perspective, there should be a fallback ifrich
fails to import.Similarly, one can add a
rich.logging.RichHandler
instance to a logger instance with minimal effort. However, depending on the the target output stream, one may also need to build arich.console.Console
first and pass that to the handler. This handler will also compete with whatever handler has been set on the root logger. So, some care must be taken to prevent propagation. Again, this is repetitive code across projects and there are import safety fallbacks to consider.
Contribution 🤝¶
Contribution to this project is welcome! However, it must follow the code of conduct for the project.
Please file bug reports and feature requests in the issue tracker or submit pull requests to improve the source code or documentation.
For development guidance and standards, please see the development guide.