From 50c249939cec59d5b990eff9edde04308b1660de Mon Sep 17 00:00:00 2001 From: Peter Sprygada Date: Wed, 18 Mar 2026 09:14:32 -0400 Subject: [PATCH] fix: replace sys.exit in fatal() with raise SystemExit MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Replace sys.exit(1) and print() with raise SystemExit(1) in fatal() - Change return type annotation from None to NoReturn - Add NoReturn import from typing - Remove stderr print — message is captured in the log at FATAL level - Update tests to assert SystemExit via pytest.raises instead of mocking sys.exit and builtins.print - Update docstring and module-level example to reflect new behavior --- src/ipsdk/logging.py | 19 +++++++++---------- tests/test_logging.py | 41 +++++++++++++++++------------------------ 2 files changed, 26 insertions(+), 34 deletions(-) diff --git a/src/ipsdk/logging.py b/src/ipsdk/logging.py index 0bee902..d071af5 100644 --- a/src/ipsdk/logging.py +++ b/src/ipsdk/logging.py @@ -70,7 +70,7 @@ def process_data(data): if critical_error: logging.fatal("Critical failure, cannot continue") - # This will log at FATAL level, print to console, and exit with code 1 + # Logs at FATAL level and raises SystemExit(1) Sensitive data filtering:: @@ -117,6 +117,7 @@ def process_data(data): from functools import partial from functools import wraps from typing import Any +from typing import NoReturn from typing import TypeVar from . import heuristics @@ -293,25 +294,23 @@ def exception(exc: Exception) -> None: log(logging.ERROR, tb_text) -def fatal(msg: str) -> None: +def fatal(msg: str) -> NoReturn: """Log a fatal error and exit the application. - A fatal error will log the message using level 90 (FATAL) and print - an error message to stderr. It will then exit the application with - return code 1. + A fatal error logs the message at FATAL level (90) and raises + SystemExit(1). Equivalent to sys.exit(1) but explicit and testable. Args: - msg (str): The message to print. + msg (str): The message to log. Returns: - None + NoReturn Raises: - SystemExit: Always raised with exit code 1 after logging the fatal error. + SystemExit: Always raised with exit code 1 after logging. """ log(FATAL, msg) - print(f"ERROR: {msg}", file=sys.stderr) - sys.exit(1) + raise SystemExit(1) @cache diff --git a/tests/test_logging.py b/tests/test_logging.py index 55cdcf5..a5ec59f 100644 --- a/tests/test_logging.py +++ b/tests/test_logging.py @@ -261,35 +261,28 @@ def test_exception_function_with_different_exceptions(self): class TestFatalFunction: """Test the fatal logging function.""" - def test_fatal_function_logs_and_exits(self): - """Test that fatal function logs message and exits.""" - with ( - patch("ipsdk.logging.log") as mock_log, - patch("builtins.print") as mock_print, - patch("sys.exit") as mock_exit, - ): - ipsdk_logging.fatal("fatal error") - + def test_fatal_raises_system_exit(self): + """Test that fatal raises SystemExit with code 1.""" + with patch("ipsdk.logging.log"): + with pytest.raises(SystemExit) as exc_info: + ipsdk_logging.fatal("fatal error") + assert exc_info.value.code == 1 + + def test_fatal_logs_before_raising(self): + """Test that fatal logs at FATAL level before raising.""" + with patch("ipsdk.logging.log") as mock_log: + with pytest.raises(SystemExit): + ipsdk_logging.fatal("fatal error") mock_log.assert_called_once_with(ipsdk_logging.FATAL, "fatal error") - mock_print.assert_called_once_with("ERROR: fatal error", file=sys.stderr) - mock_exit.assert_called_once_with(1) - def test_fatal_function_different_messages(self): - """Test fatal function with different messages.""" + def test_fatal_different_messages(self): + """Test fatal logs correct message before raising.""" messages = ["critical failure", "system error", "cannot continue"] - for message in messages: - with ( - patch("ipsdk.logging.log") as mock_log, - patch("builtins.print") as mock_print, - patch("sys.exit") as mock_exit, - ): - ipsdk_logging.fatal(message) - + with patch("ipsdk.logging.log") as mock_log: + with pytest.raises(SystemExit): + ipsdk_logging.fatal(message) mock_log.assert_called_once_with(ipsdk_logging.FATAL, message) - expected_msg = f"ERROR: {message}" - mock_print.assert_called_once_with(expected_msg, file=sys.stderr) - mock_exit.assert_called_once_with(1) class TestGetLogger: