Skip to main content

A python error handler. Catch exceptions and handle them gracefully.

Project description

Catchery: A Robust Python Error Handling Library

PyPI - Version PyPI - Python Version License

Catchery is a comprehensive Python library designed to streamline error handling in your applications. It provides a centralized, structured, and flexible approach to managing errors, logging, and exceptions, making your code more robust and maintainable.

Features

  • Structured Error Representation: Define and manage application errors with severity levels, contextual data, and associated exceptions using the AppError dataclass.
  • Simplified Setup: Quickly configure your error handling system with the setup_catchery_logging() convenience function, enabling easy setup of logging levels, file outputs (plain text and structured JSON), and more.
  • Flexible Logging: Integrate seamlessly with Python's standard logging module. Output logs to console, plain text files, or structured JSON Lines files for easy parsing and analysis.
  • Thread-Safe Context Management: Attach thread-local contextual data to all errors within a specific scope using ErrorHandler.Context, ensuring rich and relevant error information.
  • Robust Exception Chaining: Utilize the re_raise_chained decorator to catch exceptions, log them, and re-raise new exceptions while preserving the original exception chain, providing clear error propagation paths.
  • Safe Operation Execution: Execute potentially risky operations with built-in fallback mechanisms using safe_operation, preventing application crashes and ensuring graceful degradation.
  • Integrated Validation: Leverage validation utilities that seamlessly log warnings and errors through the centralized error handling system.
  • In-Memory Error History: Maintain a configurable history of recent AppError instances for debugging and analysis.

Installation

You can install catchery using pip:

pip install catchery

Quick Start

Get your error handling system up and running quickly with these examples.

Basic Console Logging

Log messages directly to the console.

import logging
from catchery.error_handler import setup_catchery_logging, log_error, log_info

# Set up logging to console (default behavior)
handler = setup_catchery_logging(level=logging.INFO)

log_info("Application started successfully.")

try:
    result = 10 / 0
except ZeroDivisionError as e:
    log_error("An unexpected error occurred during calculation.", context={"operation": "division"}, exception=e)

log_info("Application finished.")

Logging to a Plain Text File

Direct your logs to a plain text file for persistent storage.

import logging
import os
from catchery.error_handler import setup_catchery_logging, log_error, log_info

LOG_FILE = "app_errors.log"
if os.path.exists(LOG_FILE): os.remove(LOG_FILE) # Clean up from previous runs

# Set up logging to a plain text file
handler = setup_catchery_logging(
    level=logging.INFO,
    text_log_path=LOG_FILE
)

log_info("Application started, logging to file.")
try:
    value = int("not-a-number")
except ValueError as e:
    log_error("Failed to parse configuration value.", context={"config_key": "timeout"}, exception=e)

log_info("Application finished, check app_errors.log.")

# In a real application, you might read the log file here to verify
# with open(LOG_FILE, "r") as f:
#     print(f.read())
# os.remove(LOG_FILE) # Clean up

Logging Structured JSON Errors

Store detailed error information in a JSON Lines file for easy programmatic parsing and analysis.

import logging
import os
from catchery.error_handler import setup_catchery_logging, log_error, log_info

JSON_LOG_FILE = "app_errors.jsonl"
if os.path.exists(JSON_LOG_FILE): os.remove(JSON_LOG_FILE) # Clean up from previous runs

# Set up logging to a structured JSON Lines file
handler = setup_catchery_logging(
    level=logging.DEBUG,
    json_log_path=JSON_LOG_FILE
)

log_info("Application started, logging structured errors.")
try:
    data = {"user_id": "abc-123", "input_data": [1, 2, {"complex": "object"}]}
    raise ValueError("Invalid data received")
except ValueError as e:
    log_error(
        "Data processing failed.",
        context={"data_payload": data, "processor_id": "P-789"},
        exception=e
    )
log_info("Application finished, check app_errors.jsonl.")

# In a real application, you might read the log file here to verify
# import json
# with open(JSON_LOG_FILE, "r") as f:
#     for line in f:
#         print(json.loads(line))
# os.remove(JSON_LOG_FILE) # Clean up

Combined Logging Setup

Combine console, plain text file, and structured JSON logging for comprehensive error management.

import logging
import os
from catchery.error_handler import setup_catchery_logging, log_error, log_info

CONSOLE_LOG_FILE = "app_console.log" # For demonstration, console output can also go to a file
TEXT_LOG_FILE = "app_text.log"
JSON_LOG_FILE = "app_json.jsonl"

# Clean up from previous runs
if os.path.exists(CONSOLE_LOG_FILE): os.remove(CONSOLE_LOG_FILE)
if os.path.exists(TEXT_LOG_FILE): os.remove(TEXT_LOG_FILE)
if os.path.exists(JSON_LOG_FILE): os.remove(JSON_LOG_FILE)

# Set up combined logging
handler = setup_catchery_logging(
    level=logging.DEBUG,
    text_log_path=TEXT_LOG_FILE,
    json_log_path=JSON_LOG_FILE,
    use_json_logging=True # Main logger will output JSON to console/text_log_path
)

log_info("Combined setup: Application started.", context={"env": "development"})
try:
    # Simulate an error with complex context
    data = {"transaction_id": "tx-456", "amount": 100.50}
    raise RuntimeError("Payment gateway timeout")
except RuntimeError as e:
    log_error(
        "Payment processing failed.",
        context={"transaction_details": data, "gateway": "stripe"},
        exception=e
    )
log_info("Combined setup: Application finished.")

# Don't forget to call shutdown if your script is short-lived
# atexit automatically registers shutdown for the default handler
# handler.shutdown() # Not strictly necessary here due to atexit, but good practice for clarity

# In a real application, you would check the contents of the log files
# os.remove(TEXT_LOG_FILE)
# os.remove(JSON_LOG_FILE)

Advanced Usage

Context Management

Use ErrorHandler.Context to add temporary, thread-local context to your error logs:

from catchery.error_handler import get_default_handler, log_error

handler = get_default_handler() # Assumes setup_catchery_logging has been called

with handler.Context(request_id="req-001", user_session="sess-xyz"):
    log_error("Failed to process user request.")
    # All errors logged within this 'with' block will automatically include request_id and user_session

Safe Operations

Prevent crashes and provide fallback values for risky operations:

from catchery.error_handler import safe_operation

@safe_operation(default_value=None, error_message="Failed to fetch data from API")
def fetch_data_from_api(url: str):
    # Simulate an API call that might fail
    if "error" in url:
        raise ConnectionError("API connection failed")
    return {"status": "success", "data": "some_data"}

# Successful call
data = fetch_data_from_api("http://api.example.com/data")
print(f"Fetched data: {data}")

# Failed call, returns default_value (None)
error_data = fetch_data_from_api("http://api.example.com/error")
print(f"Failed to fetch data: {error_data}")

Exception Chaining

Chain exceptions to provide a clear audit trail of error propagation:

from catchery.error_handler import re_raise_chained, ChainedReRaiseError

class DatabaseError(Exception):
    pass

class ServiceError(Exception):
    pass

@re_raise_chained(message="Failed to connect to database.", new_exception_type=DatabaseError)
def connect_to_db():
    raise ConnectionRefusedError("DB server not responding")

@re_raise_chained(message="Service operation failed.", new_exception_type=ServiceError)
def perform_service_operation():
    try:
        connect_to_db()
    except DatabaseError as e:
        # Re-raise the DatabaseError as a ServiceError, chaining it
        raise ServiceError("Underlying database issue.") from e

try:
    perform_service_operation()
except ServiceError as e:
    print(f"Caught ServiceError: {e}")
    if e.__cause__:
        print(f"Caused by: {e.__cause__} ({type(e.__cause__).__name__})")

Development

Running Tests

To run the unit and integration tests, ensure you have pytest installed and run:

PYTHONPATH=./src pytest

Linting with Ruff

This project uses Ruff for linting and code formatting. Ruff is configured via the pyproject.toml file in the project root.

To run the linter, execute the following command from the project root:

ruff check src/catchery/

Type Checking with MyPy

This project uses MyPy for static type checking. MyPy is configured via the pyproject.toml file.

To run type checks, execute the following command from the project root:

mypy src/catchery/

Contributing

We welcome contributions! Please see our CONTRIBUTING.md (Coming soon) for guidelines.

License

This project is licensed under the MIT License - see the LICENSE.md file for details.

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

catchery-0.6.5.tar.gz (26.2 kB view details)

Uploaded Source

Built Distribution

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

catchery-0.6.5-py3-none-any.whl (18.8 kB view details)

Uploaded Python 3

File details

Details for the file catchery-0.6.5.tar.gz.

File metadata

  • Download URL: catchery-0.6.5.tar.gz
  • Upload date:
  • Size: 26.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.3

File hashes

Hashes for catchery-0.6.5.tar.gz
Algorithm Hash digest
SHA256 fc93a9946a37769fddc21202149c25a2d375604e08cf981eb4e78e380d60e2da
MD5 0e9b27a5bceb2d350b4bb6e5c54d8a7f
BLAKE2b-256 9486f256a1fc70c1cecb1c2c145261d1274855baf1a2a91237b9c93ee2548aba

See more details on using hashes here.

File details

Details for the file catchery-0.6.5-py3-none-any.whl.

File metadata

  • Download URL: catchery-0.6.5-py3-none-any.whl
  • Upload date:
  • Size: 18.8 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.3

File hashes

Hashes for catchery-0.6.5-py3-none-any.whl
Algorithm Hash digest
SHA256 3fb36528a6b374c8b42e9b640da080f163ce107c337925faf504ef9feadec7a3
MD5 92d67434c0d1e497c6d36bd6acc96a7e
BLAKE2b-256 115de59dd3087fa9b251889fba65c27647e36ece13c7cb35f823c929a4756aa9

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