catchery


Namecatchery JSON
Version 0.6.1 PyPI version JSON
download
home_pageNone
SummaryA python error handler. Catch exceptions and handle them gracefully.
upload_time2025-08-19 19:05:40
maintainerNone
docs_urlNone
authorNone
requires_python>=3.11
licenseNone
keywords error-handling exceptions python
VCS
bugtrack_url
requirements ruff mypy pytest pylint
Travis-CI No Travis.
coveralls test coverage No coveralls.
            # Catchery: A Robust Python Error Handling Library

[![PyPI - Version](https://img.shields.io/pypi/v/catchery.svg)](https://pypi.org/project/catchery/)
[![PyPI - Python Version](https://img.shields.io/pypi/pyversions/catchery.svg)](https://pypi.org/project/catchery/)
[![License](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)

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:

```bash
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.

```python
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.

```python
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.

```python
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.

```python
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:

```python
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:

```python
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:

```python
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:

```bash
PYTHONPATH=./src pytest
```

### Linting with Ruff

This project uses [Ruff](https://beta.ruff.rs/docs/) 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:

```bash
ruff check src/catchery/
```

### Type Checking with MyPy

This project uses [MyPy](https://mypy.readthedocs.io/en/stable/) for static type checking. MyPy is configured via the `pyproject.toml` file.

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

```bash
mypy src/catchery/
```

## Contributing

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

## License

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

            

Raw data

            {
    "_id": null,
    "home_page": null,
    "name": "catchery",
    "maintainer": null,
    "docs_url": null,
    "requires_python": ">=3.11",
    "maintainer_email": "Enrico Fraccaroli <enrico.fraccaroli@univr.it>",
    "keywords": "error-handling, exceptions, python",
    "author": null,
    "author_email": "Enrico Fraccaroli <enrico.fraccaroli@univr.it>",
    "download_url": "https://files.pythonhosted.org/packages/16/e9/56a609fc9b65898aba05e71585752f695701a82b6aa8cc52b307f2d30b33/catchery-0.6.1.tar.gz",
    "platform": null,
    "description": "# Catchery: A Robust Python Error Handling Library\n\n[![PyPI - Version](https://img.shields.io/pypi/v/catchery.svg)](https://pypi.org/project/catchery/)\n[![PyPI - Python Version](https://img.shields.io/pypi/pyversions/catchery.svg)](https://pypi.org/project/catchery/)\n[![License](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)\n\nCatchery 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.\n\n## Features\n\n- **Structured Error Representation:** Define and manage application errors with severity levels, contextual data, and associated exceptions using the `AppError` dataclass.\n- **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.\n- **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.\n- **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.\n- **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.\n- **Safe Operation Execution:** Execute potentially risky operations with built-in fallback mechanisms using `safe_operation`, preventing application crashes and ensuring graceful degradation.\n- **Integrated Validation:** Leverage validation utilities that seamlessly log warnings and errors through the centralized error handling system.\n- **In-Memory Error History:** Maintain a configurable history of recent `AppError` instances for debugging and analysis.\n\n## Installation\n\nYou can install `catchery` using pip:\n\n```bash\npip install catchery\n```\n\n## Quick Start\n\nGet your error handling system up and running quickly with these examples.\n\n### Basic Console Logging\n\nLog messages directly to the console.\n\n```python\nimport logging\nfrom catchery.error_handler import setup_catchery_logging, log_error, log_info\n\n# Set up logging to console (default behavior)\nhandler = setup_catchery_logging(level=logging.INFO)\n\nlog_info(\"Application started successfully.\")\n\ntry:\n    result = 10 / 0\nexcept ZeroDivisionError as e:\n    log_error(\"An unexpected error occurred during calculation.\", context={\"operation\": \"division\"}, exception=e)\n\nlog_info(\"Application finished.\")\n```\n\n### Logging to a Plain Text File\n\nDirect your logs to a plain text file for persistent storage.\n\n```python\nimport logging\nimport os\nfrom catchery.error_handler import setup_catchery_logging, log_error, log_info\n\nLOG_FILE = \"app_errors.log\"\nif os.path.exists(LOG_FILE): os.remove(LOG_FILE) # Clean up from previous runs\n\n# Set up logging to a plain text file\nhandler = setup_catchery_logging(\n    level=logging.INFO,\n    text_log_path=LOG_FILE\n)\n\nlog_info(\"Application started, logging to file.\")\ntry:\n    value = int(\"not-a-number\")\nexcept ValueError as e:\n    log_error(\"Failed to parse configuration value.\", context={\"config_key\": \"timeout\"}, exception=e)\n\nlog_info(\"Application finished, check app_errors.log.\")\n\n# In a real application, you might read the log file here to verify\n# with open(LOG_FILE, \"r\") as f:\n#     print(f.read())\n# os.remove(LOG_FILE) # Clean up\n```\n\n### Logging Structured JSON Errors\n\nStore detailed error information in a JSON Lines file for easy programmatic parsing and analysis.\n\n```python\nimport logging\nimport os\nfrom catchery.error_handler import setup_catchery_logging, log_error, log_info\n\nJSON_LOG_FILE = \"app_errors.jsonl\"\nif os.path.exists(JSON_LOG_FILE): os.remove(JSON_LOG_FILE) # Clean up from previous runs\n\n# Set up logging to a structured JSON Lines file\nhandler = setup_catchery_logging(\n    level=logging.DEBUG,\n    json_log_path=JSON_LOG_FILE\n)\n\nlog_info(\"Application started, logging structured errors.\")\ntry:\n    data = {\"user_id\": \"abc-123\", \"input_data\": [1, 2, {\"complex\": \"object\"}]}\n    raise ValueError(\"Invalid data received\")\nexcept ValueError as e:\n    log_error(\n        \"Data processing failed.\",\n        context={\"data_payload\": data, \"processor_id\": \"P-789\"},\n        exception=e\n    )\nlog_info(\"Application finished, check app_errors.jsonl.\")\n\n# In a real application, you might read the log file here to verify\n# import json\n# with open(JSON_LOG_FILE, \"r\") as f:\n#     for line in f:\n#         print(json.loads(line))\n# os.remove(JSON_LOG_FILE) # Clean up\n```\n\n### Combined Logging Setup\n\nCombine console, plain text file, and structured JSON logging for comprehensive error management.\n\n```python\nimport logging\nimport os\nfrom catchery.error_handler import setup_catchery_logging, log_error, log_info\n\nCONSOLE_LOG_FILE = \"app_console.log\" # For demonstration, console output can also go to a file\nTEXT_LOG_FILE = \"app_text.log\"\nJSON_LOG_FILE = \"app_json.jsonl\"\n\n# Clean up from previous runs\nif os.path.exists(CONSOLE_LOG_FILE): os.remove(CONSOLE_LOG_FILE)\nif os.path.exists(TEXT_LOG_FILE): os.remove(TEXT_LOG_FILE)\nif os.path.exists(JSON_LOG_FILE): os.remove(JSON_LOG_FILE)\n\n# Set up combined logging\nhandler = setup_catchery_logging(\n    level=logging.DEBUG,\n    text_log_path=TEXT_LOG_FILE,\n    json_log_path=JSON_LOG_FILE,\n    use_json_logging=True # Main logger will output JSON to console/text_log_path\n)\n\nlog_info(\"Combined setup: Application started.\", context={\"env\": \"development\"})\ntry:\n    # Simulate an error with complex context\n    data = {\"transaction_id\": \"tx-456\", \"amount\": 100.50}\n    raise RuntimeError(\"Payment gateway timeout\")\nexcept RuntimeError as e:\n    log_error(\n        \"Payment processing failed.\",\n        context={\"transaction_details\": data, \"gateway\": \"stripe\"},\n        exception=e\n    )\nlog_info(\"Combined setup: Application finished.\")\n\n# Don't forget to call shutdown if your script is short-lived\n# atexit automatically registers shutdown for the default handler\n# handler.shutdown() # Not strictly necessary here due to atexit, but good practice for clarity\n\n# In a real application, you would check the contents of the log files\n# os.remove(TEXT_LOG_FILE)\n# os.remove(JSON_LOG_FILE)\n```\n\n## Advanced Usage\n\n### Context Management\n\nUse `ErrorHandler.Context` to add temporary, thread-local context to your error logs:\n\n```python\nfrom catchery.error_handler import get_default_handler, log_error\n\nhandler = get_default_handler() # Assumes setup_catchery_logging has been called\n\nwith handler.Context(request_id=\"req-001\", user_session=\"sess-xyz\"):\n    log_error(\"Failed to process user request.\")\n    # All errors logged within this 'with' block will automatically include request_id and user_session\n```\n\n### Safe Operations\n\nPrevent crashes and provide fallback values for risky operations:\n\n```python\nfrom catchery.error_handler import safe_operation\n\n@safe_operation(default_value=None, error_message=\"Failed to fetch data from API\")\ndef fetch_data_from_api(url: str):\n    # Simulate an API call that might fail\n    if \"error\" in url:\n        raise ConnectionError(\"API connection failed\")\n    return {\"status\": \"success\", \"data\": \"some_data\"}\n\n# Successful call\ndata = fetch_data_from_api(\"http://api.example.com/data\")\nprint(f\"Fetched data: {data}\")\n\n# Failed call, returns default_value (None)\nerror_data = fetch_data_from_api(\"http://api.example.com/error\")\nprint(f\"Failed to fetch data: {error_data}\")\n```\n\n### Exception Chaining\n\nChain exceptions to provide a clear audit trail of error propagation:\n\n```python\nfrom catchery.error_handler import re_raise_chained, ChainedReRaiseError\n\nclass DatabaseError(Exception):\n    pass\n\nclass ServiceError(Exception):\n    pass\n\n@re_raise_chained(message=\"Failed to connect to database.\", new_exception_type=DatabaseError)\ndef connect_to_db():\n    raise ConnectionRefusedError(\"DB server not responding\")\n\n@re_raise_chained(message=\"Service operation failed.\", new_exception_type=ServiceError)\ndef perform_service_operation():\n    try:\n        connect_to_db()\n    except DatabaseError as e:\n        # Re-raise the DatabaseError as a ServiceError, chaining it\n        raise ServiceError(\"Underlying database issue.\") from e\n\ntry:\n    perform_service_operation()\nexcept ServiceError as e:\n    print(f\"Caught ServiceError: {e}\")\n    if e.__cause__:\n        print(f\"Caused by: {e.__cause__} ({type(e.__cause__).__name__})\")\n```\n\n## Development\n\n### Running Tests\n\nTo run the unit and integration tests, ensure you have `pytest` installed and run:\n\n```bash\nPYTHONPATH=./src pytest\n```\n\n### Linting with Ruff\n\nThis project uses [Ruff](https://beta.ruff.rs/docs/) for linting and code formatting. Ruff is configured via the `pyproject.toml` file in the project root.\n\nTo run the linter, execute the following command from the project root:\n\n```bash\nruff check src/catchery/\n```\n\n### Type Checking with MyPy\n\nThis project uses [MyPy](https://mypy.readthedocs.io/en/stable/) for static type checking. MyPy is configured via the `pyproject.toml` file.\n\nTo run type checks, execute the following command from the project root:\n\n```bash\nmypy src/catchery/\n```\n\n## Contributing\n\nWe welcome contributions! Please see our [CONTRIBUTING.md](CONTRIBUTING.md) (Coming soon) for guidelines.\n\n## License\n\nThis project is licensed under the MIT License - see the [LICENSE.md](LICENSE.md) file for details.\n",
    "bugtrack_url": null,
    "license": null,
    "summary": "A python error handler. Catch exceptions and handle them gracefully.",
    "version": "0.6.1",
    "project_urls": {
        "Bug Tracker": "https://github.com/Galfurian/catchery/issues",
        "Homepage": "https://github.com/Galfurian/catchery",
        "Repository": "https://github.com/Galfurian/catchery"
    },
    "split_keywords": [
        "error-handling",
        " exceptions",
        " python"
    ],
    "urls": [
        {
            "comment_text": null,
            "digests": {
                "blake2b_256": "cc67a2d2b499bd2d34f2b59045e3fbd52991dbe8bba75f763c559414d8790a95",
                "md5": "42754193f83652a1e8d3b4caab454dc2",
                "sha256": "21f47e15f65141e02f3d95fd4eb91b9ca7eef652cfcf0d03eca65c33c514bfb0"
            },
            "downloads": -1,
            "filename": "catchery-0.6.1-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "42754193f83652a1e8d3b4caab454dc2",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": ">=3.11",
            "size": 18373,
            "upload_time": "2025-08-19T19:05:38",
            "upload_time_iso_8601": "2025-08-19T19:05:38.527764Z",
            "url": "https://files.pythonhosted.org/packages/cc/67/a2d2b499bd2d34f2b59045e3fbd52991dbe8bba75f763c559414d8790a95/catchery-0.6.1-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": null,
            "digests": {
                "blake2b_256": "16e956a609fc9b65898aba05e71585752f695701a82b6aa8cc52b307f2d30b33",
                "md5": "40d91c35c812f09c040d677c91dce362",
                "sha256": "49181fd3325ede99a01ca271bb5b97b32bc20aecd37e95382548cc11733591fc"
            },
            "downloads": -1,
            "filename": "catchery-0.6.1.tar.gz",
            "has_sig": false,
            "md5_digest": "40d91c35c812f09c040d677c91dce362",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": ">=3.11",
            "size": 25769,
            "upload_time": "2025-08-19T19:05:40",
            "upload_time_iso_8601": "2025-08-19T19:05:40.179791Z",
            "url": "https://files.pythonhosted.org/packages/16/e9/56a609fc9b65898aba05e71585752f695701a82b6aa8cc52b307f2d30b33/catchery-0.6.1.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2025-08-19 19:05:40",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "Galfurian",
    "github_project": "catchery",
    "travis_ci": false,
    "coveralls": false,
    "github_actions": false,
    "requirements": [
        {
            "name": "ruff",
            "specs": []
        },
        {
            "name": "mypy",
            "specs": []
        },
        {
            "name": "pytest",
            "specs": []
        },
        {
            "name": "pylint",
            "specs": []
        }
    ],
    "lcname": "catchery"
}
        
Elapsed time: 2.67741s