# Catchery: A Robust Python Error Handling Library
[](https://pypi.org/project/catchery/)
[](https://pypi.org/project/catchery/)
[](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[](https://pypi.org/project/catchery/)\n[](https://pypi.org/project/catchery/)\n[](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"
}