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.
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.
Part of the Sora Open Source Software community.
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.
What it could look like
Here you can see the updated output with data & error_code in version 1.1.1
Installation
pip install ladon_clear_exceptions_n_warnings
Or via the community page: soss.page
Usage
Add one line at the top of your entry module, before any other import:
import ladon_clear_exceptions_n_warnings
That is it.
All exception and warning classes register themselves directly into Python's builtins via __init_subclass__ on import.
There is no need to import individual classes anywhere else in your project.
Your IDE gets full type information through the included .pyi stub file.
# All of these work unchanged after the import
raise LadonValueError(message="something went wrong")
try:
do_something()
except LadonTypeError as e:
handle(e)
class MyError(OOPException):
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 LadonTypeError("message")
instead of raise LadonTypeError(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.
For deep understanding how to use it check these files:
examples_no_logger.py
examples_with_logger.py
Exception class names
All overwritten builtin exceptions follow the Ladon prefix convention.
The Ladon class is also registered under the original builtin name so existing except TypeError
blocks continue to work, but the preferred style for raising is the prefixed name.
| Builtin | Ladon class |
|---|---|
AssertionError |
LadonAssertionError |
TypeError |
LadonTypeError |
ValueError |
LadonValueError |
ArithmeticError |
LadonArithmeticError |
FloatingPointError |
LadonFloatingPointError |
OverflowError |
LadonOverflowError |
ZeroDivisionError |
LadonZeroDivisionError |
LookupError |
LadonLookupError |
KeyError |
LadonKeyError |
NameError |
LadonNameError |
RuntimeError |
LadonRuntimeError |
NotImplementedError |
LadonNotImplementedError |
BufferError |
LadonBufferError |
EOFError |
LadonEOFError |
MemoryError |
LadonMemoryError |
ReferenceError |
LadonReferenceError |
Structured exceptions
Any keyword argument passed to an exception constructor is stored in .data and available after the fact.
raise LadonTypeError(
message="Invalid input for user_id",
expected_type=int,
received_type=type(user_id)
)
When caught:
except LadonTypeError as e:
print(e.message) # "Invalid input for user_id"
print(e.error_code) # numeric code, e.g. 412
print(e.data) # {"expected_type": <class 'int'>, "received_type": <class 'str'>}
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 that raises the exception with full context
already populated if the check fails.
# Type check - skips silently if value is None
LadonTypeError.check(user_id, int)
# Also works with callable
LadonTypeError.check(handler, callable)
# None check - raises if value IS None
NoneValueError.check(user_id, "user_id")
# Zero division guard
LadonZeroDivisionError.check(divisor)
# Overflow guard
LadonOverflowError.check(value, 1_000_000)
# Value in allowed set
LadonValueError.check(status, {"active", "inactive"})
# Assertion
LadonAssertionError.check(age >= 18, "age >= 18")
# Key exists in dict
LadonKeyError.check("host", config)
# Name exists in scope
LadonNameError.check("my_var", dir())
# Configuration validity - pass any boolean expression
ConfigurationError.check(port > 0, "port")
Note: LadonTypeError.check() skips None silently. Use NoneValueError.check() when None itself is the error.
Built-in extra exception classes
Beyond the overwritten builtins, the package ships several additional classes that cover common patterns.
These are also registered in builtins on import.
NoneValueError raises when a value is None but None is not acceptable.
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. 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 warning classes
to append additional context to the message.
DeprecationWarning(
message="use new_method() instead",
old="old_method",
replacement="new_method",
since="v1.4"
)
warnings.warn() is still accepted but surfaces 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.
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.
Accepts parameter_name and received_value for context.
Logging
Set a logger before enabling log output.
The logger must have a FileHandler with UTF-8 encoding and no StreamHandler, to prevent duplicate 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:
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.
Both are registered in builtins after import, so no additional import is needed in other files.
Since error codes are concatenated up the inheritance chain, your custom code is automatically embedded in the structure.
# No import needed anywhere in your project after the initial package import
class DatabaseConnectionError(OOPException):
def __init__(self, *, host=None, port=None, message=None, **kwargs):
self.message = message or (
f"Could not connect to database at {host}:{port}" if host and port
else "Could not connect to database"
)
self.error_code = 90
super().__init__(message=self.message, error_code=self.error_code, host=host, port=port, **kwargs)
class PayloadTooLargeError(CheckableError):
def __init__(self, *, size=None, max_size=None, message=None, **kwargs):
self.message = message or (
f"Payload size {size} exceeds maximum of {max_size}" if size and max_size
else "Payload is too large"
)
self.error_code = 91
super().__init__(message=self.message, error_code=self.error_code, size=size, max_size=max_size, **kwargs)
@classmethod
def check(cls, size: int, max_size: int) -> None:
if size > max_size:
raise cls(size=size, max_size=max_size, drop_last_stacktrace=True)
Use OOPWarning as the base for custom warnings. It is also registered in builtins after 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 warning by instantiating it directly, or conditionally via .show():
SlowQueryWarning(duration_ms=4200)
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 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 |
The following cannot be replaced because they are loaded and used before this package is imported:
UnicodeDecodeError, UnicodeEncodeError, UnicodeTranslateError, ImportError,
ModuleNotFoundError, SyntaxError, IndentationError, TabError, StopIteration,
StopAsyncIteration, UnicodeError, RecursionError, IndexError, OSError and all its subclasses.
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.
Pydantic
Pydantic triggers a specific warning pattern when used together with FastAPI.
This package detects and corrects that automatically, no configuration needed.
This is even documented inline of pydantic & fastapi, that it should not trigger.
FastAPI / Uvicorn
FastAPI does not work out of the box with this package. Uvicorn spawns worker processes with its own loading mechanics,
which means the package import in your main module does not carry over into the workers.
A patch method is included in this package to handle this:
import ladon_clear_exceptions_n_warnings as ladon
fastapi_instance: FastAPI = ...
fastapi_instance = ladon.patch_uvicorn(fastapi_instance)
Call this before starting the application server.
License
SOSS 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
Part of the Sora Open Source Software 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
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file ladon_clear_exceptions_n_warnings-1.1.1.tar.gz.
File metadata
- Download URL: ladon_clear_exceptions_n_warnings-1.1.1.tar.gz
- Upload date:
- Size: 292.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.4
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a9710d05ae945ca6c624ada1d3cd8e486d2cd408e41eb9adf1ed316fd9a53e52
|
|
| MD5 |
f85cf068a1e32060842d02dcefb5815c
|
|
| BLAKE2b-256 |
ea285868afb234c9efda3377d3298d833068cc083fc22b2c9bc310be7bbbc5c2
|
File details
Details for the file ladon_clear_exceptions_n_warnings-1.1.1-py3-none-any.whl.
File metadata
- Download URL: ladon_clear_exceptions_n_warnings-1.1.1-py3-none-any.whl
- Upload date:
- Size: 36.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.4
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b2080cb757f7518203ae9b23f454992bb360f5cd3eae86ccde02a838b192bd8c
|
|
| MD5 |
f0064fd99edc2ac836d5a13468169c55
|
|
| BLAKE2b-256 |
02321b85b5f5488e064a4eee7194ef322619cb768b91430abb8c0a374ccceaf5
|