Warnings

A warning can be raised similar to an exception, but it doesn’t interrupt execution at the position where it was raised. The warning travels upwards the call-stack until it’s handled by a WarningCollector similar to a try .. except statement. A warning nobody collects is dropped; a CriticalWarning or an exception nobody collects raises an UnhandledCriticalWarningError or UnhandledExceptionError.

A warning is raised by Calling the class-method WarningCollector.Raise. This function expects a single parameter: an instance of Warning.

To handle a raised warning, a with-statement is used to collect raised warnings. Usually, a list is handed over to a WarningCollector context.

from pyTooling.Warning import WarningCollector

class ClassA:
  def methA_RaiseException(self) -> None:
    WarningCollector.Raise(Warning("Warning from ClassA.methA_RaiseException"))
from pyTooling.Warning import WarningCollector

class Caller:

  def operation(self) -> None:
    warnings = []

    a = ClassA()
    with WarningCollector(warnings) as warning:
      a.methA_RaiseException()

    print("Warnings:)
    for warning in warnings:
      print(f"  {warning}")

Competing Solutions

Python’s own warnings let execution continue too, but what happens to a warning is configured for the whole process. WarningCollector.Raise hands a warning to the innermost WarningCollector of the current thread, which keeps it in a list, and its handler decides per warning whether it is escalated - so the caller of an operation decides.

warnings

Source: warnings of Python’s standard library.

Disadvantages

  • What happens to a warning is decided by a global list of filters, matched by message, category, module and line - e.g. -W error turns every warning into an exception, for the whole process.

  • catch_warnings changes the module’s global state, which isn’t safe with several threads or coroutines - unless the flag sys.flags.context_aware_warnings is set, which Python 3.14 sets by default only in its free-threaded build.

  • A warning that can’t be ignored has no form: CriticalWarning raises UnhandledCriticalWarningError if no collector receives it.

Standoff

  • Both let execution continue after a warning, and both escalate one to an exception: the filter action error, or a collector’s handler returning True, which raises EscalatedWarningError.

  • catch_warnings(record=True) collects the warnings of a block in a list, as a collector does.

Advantages

  • Every library reports through it, the interpreter’s deprecations too, and -W or PYTHONWARNINGS configure it without changing code.

  • The default action shows a warning once per place it is issued from.

logging

Source: logging of Python’s standard library.

Disadvantages

  • A record goes to the handlers of the logger hierarchy, configured for the application. Returning the warnings of one operation to its caller needs a handler written for it.

  • Records are selected by their level, not by the class of an exception.

Standoff

Advantages

  • Destinations, formats and levels of the output are configured once for an application.