User messaging: result records vs exceptions vs logging
This specification delineates the applicable contexts for using result records, exceptions, progress reporting, specific log levels, or other types of user messaging processes.
Result records are the only return value format for all DataLad interfaces.
Contrasting with classic Python interfaces that return specific non-annotated values,
DataLad interfaces (i.e. subclasses of
implement message passing by yielding result records
that are associated with individual operations. Result records are routinely inspected throughout
the code base and their annotations are used to inform general program flow and error handling.
DataLad interface calls can include an
on_failure parameterization to specify how to
proceed with a particular operation if a returned result record is
classified as a failure result. DataLad interface calls can
also include a
result_renderer parameterization to explicitly enable or
disable the rendering of result records.
Developers should be aware that external callers will use DataLad interface call parameterizations that can selectively ignore or act on result records, and that the process should therefore yield meaningful result records. If, in turn, the process itself receives a set of result records from a sub-process, these should be inspected individually in order to identify result values that could require re-annotation or status re-classification.
For user messaging purposes, result records can also be enriched with additional human-readable
information on the nature of the result, via the
message key, and human-readable hints to
the user, via the
hints key. Both of these are rendered via the UI Module.
In general, exceptions should be raised when there is no way to ignore or recover from the offending action.
More specifically, raise an exception when:
A DataLad interface’s parameter specifications are violated
An additional requirement (beyond parameters) for the meaningful continuation of a DataLad interface, function, or process is not met
It must be made clear to the user/caller what the exact cause of the exception is, given the context within which the user/caller triggered the action. This is achieved directly via a (re)raised exception, as opposed to logging messages or results records which could be ignored or unseen by the user.
In the case of a complex set of dependent actions it could be expensive to
confirm parameter violations. In such cases, initial sub-routines might already generate
result records that have to be inspected by the caller, and it could be practically better
to yield a result record (with
status=[error|impossible]) to communicate the failure.
It would then be up to the upstream caller to decide whether to specify
on_failure='ignore' or whether to inspect individual result records and turn them
into exceptions or not.
Logging provides developers with additional means to describe steps in a process, so as to allow insight into the program flow during debugging or analysis of e.g. usage patterns. Logging can be turned off externally, filtered, and redirected. Apart from the log-level and message, it is not inspectable and cannot be used to control the logic or flow of a program.
Importantly, logging should not be the primary user messaging method for command outcomes, Therefore:
No interface should rely solely on logging for user communication
Use logging for in-progress user communication via the mechanism for progress reporting
Use logging to inform debugging processes
ui module provides the means to communicate information
to the user in a user-interface-specific manner, e.g. via a console, dialog, or an iPython interface.
Internally, all DataLad results processed by the result renderer are passed through the UI module.
Therefore: unless the criteria for logging apply, and unless the message to be delivered to the user
is specified via the
message key of a result record, developers should let explicit user communication
happen through the UI module as it provides the flexibility to adjust to the present UI.
datalad.ui.message() allows passing a simple message via the UI module.
The following links point to actual code implementations of the respective user messaging methods: