Skip to main content

Drop-in replacement for Python's built-in exceptions and warnings. Structured output, automatic logging, zero boilerplate.

Project description

Ladon - clear exceptions 'n warnings

Drop-in replacement for Python's built-in exceptions and warnings. Structured output, automatic logging, zero boilerplate.

Python License Version Community


In Greek mythology, Ladon is the eternal guardian of the Golden Apples of the Hesperides. Many-headed, ever-watchful,
standing at the boundary between the mortal and the divine.
That boundary is exactly what Python's core exception system is:
something every developer depends on, something almost nobody touches.

This package watches over it. Better output, more context, no silent failures,
and the ability to hook into anything the runtime raises before it propagates.


The problem

Python's default exception output looks like this:

Traceback (most recent call last):
  File "main.py", line 12, in <module>
    process(user_input)
TypeError: unsupported operand type(s) for +: 'int' and 'str'

Everything is red. No structure. No context about what value caused the error or what was expected.
Warnings are silently swallowed by default. Logging requires manual setup everywhere.

If you have worked on any Python project longer than a weekend, you have probably written your own exception wrappers.
This package does it once, properly, for everyone.


What this does

Structured, color-coded output.
Exceptions and warnings are visually distinct and readable.
The final stack frame is highlighted so you see immediately where it broke, not just the traceback wall.

Colors and styles are fully customizable per element.

Structured error data.
Every exception carries a .data dict with the values that caused the error,
an .error_code for programmatic handling, and a .message for human-readable output.

Automatic logging.
Logging happens inside __init__, before the exception propagates.
Even caught-and-silenced exceptions leave a log trail when auto-logging is enabled.

.check() on supported classes. Replaces the if not isinstance(...): raise pattern with a single inline call.

Warnings that cannot be dropped. Every warning is either printed to STDERR or written to a logger.
Nothing is ever silently swallowed.

Hook system. Register your own function to be called before any exception is raised or any warning is shown.
Useful for health monitoring, alerting, or metrics.

One-time warnings. Mark any warning subclass as _one_time = True and it will only fire once per runtime.

No dependencies. Standard library only.


How it looks

This is the default config, you can change it as you like!

example_exceptions_n_warnings.png


Installation

pip install ladon_clear_exceptions_n_warnings

Or via the community page: soss.page


Usage

More examples here: example_usage.py example_warn.log

Add one line at the top of your entry module, before any other import:

import ladon_clear_exceptions_n_warnings

That is it. All overwritten exception and warning classes are registered directly into Python's builtins on import.
There is no need to import individual classes. Everything you already use continues to work exactly as before.

# All of these work unchanged
raise ValueError("something went wrong")

try:
    do_something()
except TypeError as e:
    handle(e)

class MyError(ValueError):
    pass

Two things surface as warnings rather than silently continuing:

warnings.warn(...) called with positional string arguments triggers an OldWarningWarning pointing to the exact call site.

Raising exceptions with positional arguments, for example raise TypeError("message")
instead of raise TypeError(message="message"), triggers an OldExceptionWarning
showing the type and the values that were passed. Execution still continues normally,
the warning is just a signal that the call site should be updated.

If you want to suppress these during a migration before fixing all call sites, use ladon.supress_warning() temporarily.


Structured exceptions

Any keyword argument passed to an exception constructor is stored in .data and available after the fact.

raise TypeError(
    message="Invalid input for user_id",
    expected=int,
    got=type(user_id),
    var="user_id"
)

When caught:

except TypeError as e:
    print(e.message)       # "Invalid input for user_id"
    print(e.error_code)    # numeric code, e.g. 412
    print(e.data)          # {"expected": <class 'int'>, "got": <class 'str'>, "var": "user_id"}

Error codes

Every exception class has a numeric error code. When a subclass inherits from another,
the parent's code is prepended to the child's. This means the code itself carries the inheritance chain:
you can see at a glance whether two errors are related, without inspecting the class hierarchy.

For CheckableError subclasses, the code is additionally prefixed with 4 to mark it as a checkable type.


.check() - inline validation

The following classes expose a .check() classmethod. It performs the validation and
raises the exception with full context already populated. No if/raise block needed.

# Type check
TypeError.check(x, int)

# None check
NoneValueError.check(user_id, "user_id")

# Zero division guard
ZeroDivisionError.check(divisor)

# Assertion
AssertionError.check((25 >=18), "condition name")
# if you use ifs or other complex things, try to use () around, to make sure it does not break

# Overflow
OverflowError.check(value, max_allowed)

# Value in allowed set
ValueError.check(status, {"active", "inactive"})

# Configuration validity
ConfigurationError.check(("boot" in config_list), "port")

Note: TypeError.check() skips None silently. Use NoneValueError.check() when None itself is the error.


Built-in extra exception classes

Beyond the standard Python exceptions, the package ships several additional classes that cover common patterns.
You can use them to save time writing common usage exceptions yourself.

Feel free to reach out to us for adding nex exceptions and check logics.

NoneValueError raises when a value is None but None is not acceptable.
It accepts an optional parameter_name for a readable message.

ConfigurationError raises when a configuration condition is not met. Pass any boolean expression directly:
ConfigurationError.check(api_key is not None).

AttributeReadOnlyError is intended for use inside __setattr__
to protect attributes that must not be changed after assignment.

StateError raises when a method is called on an object that is not in a valid state for that operation.
It accepts input_object and expected_object_state for context.


Warnings

Warnings are instantiated as objects.
Instantiating them triggers output immediately.
extra_message is available on all legacy warning classes to append
additional context to the message, for example when forwarding a message from a legacy warning call.

DeprecationWarning(
    message="use new_method() instead",
    old="old_method",
    replacement="new_method",
    since="v1.4"
)

warnings.warn() is still accepted but will surface an OldWarningWarning pointing to the exact call site.
The *args positional parameter exists on all warning classes for this compatibility reason only.
Do not pass values through it intentionally, it will trigger that same warning.

One-time warnings

class MyWarning(UserWarning):
    _one_time = True

This warning fires only once per runtime regardless of how many times it is triggered.

Built-in extra warning classes

PerformanceWarning is for operations that are valid but known to be slow or suboptimal,
such as fallback paths or missing optimizations.

SecurityWarning is for non-fatal security-relevant conditions, such as missing encryption or insecure defaults.

ConfigurationWarning is for configurations that are accepted but suboptimal, where execution continues
but a developer should review the value.

FixedButWrongValueWarning is for cases where a function received a wrong input but applied a fallback internally.
It signals to the caller that their value was incorrect even though nothing crashed.


Logging

Set a logger before enabling log output.
The logger must have a FileHandler with UTF-8 encoding and no StreamHandler,
to prevent duplicate or wrong console output.

import logging
import ladon_clear_exceptions_n_warnings as ladon

logger = logging.getLogger("my_app")
handler = logging.FileHandler("app.log", encoding="utf-8")
logger.addHandler(handler)

ladon.configure_log_warning_and_exceptions(
    logger_instance=logger,
    write_log_warning=True,
    auto_log_exceptions=True,
    suppress_warnings=False,
    code_lines_before_warning=3
)

Log output is plain text without ANSI codes. Terminal output is color-coded. Both run independently.

The individual setters are also available:

import ladon_clear_exceptions_n_warnings as ladon
ladon.set_logger(logger)
ladon.enable_log_warning()
ladon.enable_auto_log_exceptions()
ladon.supress_warning()
ladon.set_number_of_prior_warning_code_lines(5)

Hook system

Register a function that fires before any exception is raised or any warning is shown.
Useful for monitoring, alerting, or metrics collection.

def on_exception(exc, is_oop_exception):
    # exc is the exception instance
    # is_oop_exception is True for OOPException subclasses, False for non-overwritten builtins
    send_to_monitoring(exc)

def on_warning(warning):
    log_warning_metric(warning)

ladon.set_exceptions_hook_function(on_exception)
ladon.set_warnings_hook_function(on_warning)

The exception hook fires on every OOPException instantiation, before the exception propagates.
The warning hook fires on every warning instantiation, before output is written.


Custom exceptions and warnings

Use OOPException as the base for simple custom exceptions.
Use CheckableError if you want to add a .check() classmethod.
Since error codes are passed up the inheritance chain and concatenated at the final parent,
your custom code is automatically embedded in the code structure.

# No import needed, OOPException and CheckableError are in builtins after the package import

class MyAppError(OOPException):
    def __init__(self, *, resource=None, message=None, **kwargs):
        self.message = message or f"Resource failed: {resource}"
        self.error_code = 999
        super().__init__(message=self.message, error_code=self.error_code, resource=resource, **kwargs)


class StrictIntError(CheckableError):
    def __init__(self, *, message=None, **kwargs):
        self.message = message or "Expected a strict integer"
        self.error_code = 77
        super().__init__(message=self.message, error_code=self.error_code, **kwargs)

    @classmethod
    def check(cls, value):
        if not isinstance(value, int) or isinstance(value, bool):
            raise cls(got=type(value))

Use OOPWarning as the base for custom warnings.

# OOPWarning is also in builtins after the package import

class SlowQueryWarning(OOPWarning):
    _one_time = False

    def __init__(self, *, duration_ms=None, message=None, extra_message=None, **kwargs):
        self.message = message or f"Query took {duration_ms}ms"
        self.message += (f"\n{extra_message}" if extra_message else "")
        super().__init__(message=self.message, **kwargs)


class MigrationWarning(OOPWarning):
    _one_time = True  # only fires once per runtime

    def __init__(self, *, table=None, message=None, extra_message=None, **kwargs):
        self.message = message or f"Pending migration on table '{table}'" if table else "Pending migration"
        self.message += (f"\n{extra_message}" if extra_message else "")
        super().__init__(message=self.message, **kwargs)

Trigger a custom warning by instantiating it directly:

SlowQueryWarning(duration_ms=4200)

# or conditionally
MigrationWarning.show(pending_migrations > 0)

Customizing output

All visual elements are managed through FormatElements. Each element has a name, a foreground color,
a background color, and one or more text styles.
You can update any element at runtime before exceptions or warnings fire.

from ladon_clear_exceptions_n_warnings import FormatElements

# Change the color of the file path in exception output
FormatElements.get("file").update_fg("bright_green")

# Change the error type badge to a blue background
FormatElements.get("error_type").update_bg("blue")

# Inspect what is available
print(FormatElements.get_all_elements())
print(FormatElements.get_all_fg())
print(FormatElements.get_all_bg())
print(FormatElements.get_all_styles())

Valid styles are bold, dim, italic, underline, blink, inverse, and strike.
Colors follow the standard 16-color ANSI set with bright variants.

FormatElements instances can also be created independently for use in your own terminal output,
building on the same system the package uses internally.


What is NOT replaced

Class Reason
BaseException Root of the entire exception hierarchy
KeyboardInterrupt System-level signal
SystemExit Used by sys.exit()
GeneratorExit Generator and coroutine lifecycle
KeyError Used incorrectly in too many builtin import mechanisms

The following cannot be replaced because they are loaded before this package is imported:

UnicodeDecodeError, UnicodeEncodeError, UnicodeTranslateError, ImportError, ModuleNotFoundError,
SyntaxError, IndentationError, TabError, StopIteration, StopAsyncIteration, UnicodeError,
RecursionError, IndexError, NameError, OSError and all its subclasses.

We will probably look into this later again, because to solve this we need to change some core packages from python.

C-level exceptions raised by the Python interpreter itself also remain native.
This package covers exceptions raised by your own code and any libraries loaded after the import.


Compatibility

Python 3.12 or higher. No dependencies outside the standard library.


License

OSS Community License.
If you use this package in a project, include a credit in your README, credits section, or anywhere visible,
with the package name and a link to soss.page.


Community and contributing

Hosted and maintained through the OSS Community.
Visit github.com/soss-community for contribution guidelines,
issue tracking, and discussion.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

ladon_clear_exceptions_n_warnings-1.0.1.tar.gz (256.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

File details

Details for the file ladon_clear_exceptions_n_warnings-1.0.1.tar.gz.

File metadata

File hashes

Hashes for ladon_clear_exceptions_n_warnings-1.0.1.tar.gz
Algorithm Hash digest
SHA256 7bfa2b2ff4f0160545ca4edd196ae661a9189bc98a92edc076f30e9e4ab0d2f6
MD5 62970d11cc79f1dbcf08a1a36a486e42
BLAKE2b-256 a81fb05df0673b61bc609936274623a1326f16bf25a74e358e4bec47892841d9

See more details on using hashes here.

File details

Details for the file ladon_clear_exceptions_n_warnings-1.0.1-py3-none-any.whl.

File metadata

File hashes

Hashes for ladon_clear_exceptions_n_warnings-1.0.1-py3-none-any.whl
Algorithm Hash digest
SHA256 6275e11e5c5e0147bc28a16a01ad479c8c085255ec75b34516392ff6978ef1da
MD5 3683611a2281f397593a7d7e1ca32501
BLAKE2b-256 56bc27f6a458426be29a9abc0dfc5b6de49de42177e708d8c8a5df5fe43738ab

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page