From 81bb66f81d7f178762d2c69e806449444f824ae2 Mon Sep 17 00:00:00 2001 From: Max Kipp Date: Thu, 14 May 2026 07:29:27 -0400 Subject: [PATCH 1/5] Cast err_msg to str to handle None --- .../NextGen_Forcings_Engine/core/err_handler.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/NextGen_Forcings_Engine_BMI/NextGen_Forcings_Engine/core/err_handler.py b/NextGen_Forcings_Engine_BMI/NextGen_Forcings_Engine/core/err_handler.py index 3f6e7995..24128e4e 100755 --- a/NextGen_Forcings_Engine_BMI/NextGen_Forcings_Engine/core/err_handler.py +++ b/NextGen_Forcings_Engine_BMI/NextGen_Forcings_Engine/core/err_handler.py @@ -19,7 +19,7 @@ def in_exception_context() -> bool: return False -def err_out_screen(err_msg: str, exc: BaseException | None = None): +def err_out_screen(err_msg: str | None, exc: BaseException | None = None): """Print an error message to the screen and exit the program gracefully. Generic routine to exit the program gracefully. This specific error function does not log @@ -31,6 +31,7 @@ def err_out_screen(err_msg: str, exc: BaseException | None = None): Logan Karsten - National Center for Atmospheric Research, karsten@ucar.edu """ + err_msg = str(err_msg) if exc is not None: err_msg += f" - {exc}" err_msg_out = "ERROR: " + err_msg From 13786bb25d9ad02c468095e2781a468633b74157 Mon Sep 17 00:00:00 2001 From: Max Kipp Date: Thu, 14 May 2026 07:31:43 -0400 Subject: [PATCH 2/5] Fix typo in sys.excepthook assignment --- .../NextGen_Forcings_Engine/core/parallel.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NextGen_Forcings_Engine_BMI/NextGen_Forcings_Engine/core/parallel.py b/NextGen_Forcings_Engine_BMI/NextGen_Forcings_Engine/core/parallel.py index 8912fd11..2e758af0 100755 --- a/NextGen_Forcings_Engine_BMI/NextGen_Forcings_Engine/core/parallel.py +++ b/NextGen_Forcings_Engine_BMI/NextGen_Forcings_Engine/core/parallel.py @@ -145,7 +145,7 @@ def __register_exit_handlers(self) -> None: against potential deadlock conditions to be sure (would need to confirm that a non-0 rank initiating an abort would cause rank 0 break out of a collective call if it happens to be waiting at one).""" # Exceptions - sys.exepthook = self.__excepthook + sys.excepthook = self.__excepthook # Regular exits atexit.register(self._cleanup) # Signals From 5ef3b4987d858700ece3b1258a36ba30aeaf3a8e Mon Sep 17 00:00:00 2001 From: Max Kipp Date: Thu, 14 May 2026 07:34:35 -0400 Subject: [PATCH 3/5] excepthook behavior: extend existing custom hook (if applicable) rather than replace existing custom hook with raw sys.__excepthook__ call plus new behavior. --- .../NextGen_Forcings_Engine/core/parallel.py | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/NextGen_Forcings_Engine_BMI/NextGen_Forcings_Engine/core/parallel.py b/NextGen_Forcings_Engine_BMI/NextGen_Forcings_Engine/core/parallel.py index 2e758af0..18ee723a 100755 --- a/NextGen_Forcings_Engine_BMI/NextGen_Forcings_Engine/core/parallel.py +++ b/NextGen_Forcings_Engine_BMI/NextGen_Forcings_Engine/core/parallel.py @@ -46,6 +46,8 @@ def __init__(self, config_options: ConfigOptions): self.log_debug = partial(err_handler.log_msg, self.config_options, self, True) self.log_info = partial(err_handler.log_msg, self.config_options, self, False) self.log_warning = partial(err_handler.log_warning, self.config_options, self) + + self.original_excepthook = None # Will be set to the original sys.excepthook when __register_exit_handlers is called self.__register_exit_handlers() def initialize_comm(self, comm=None): @@ -145,6 +147,7 @@ def __register_exit_handlers(self) -> None: against potential deadlock conditions to be sure (would need to confirm that a non-0 rank initiating an abort would cause rank 0 break out of a collective call if it happens to be waiting at one).""" # Exceptions + self.original_excepthook = sys.excepthook sys.excepthook = self.__excepthook # Regular exits atexit.register(self._cleanup) @@ -154,13 +157,23 @@ def __register_exit_handlers(self) -> None: def __excepthook(self, ex_type, value, tb) -> None: """Custom excepthook which follows these steps: - 1. Call Python's built-in excepthook. + 1. Call original excepthook which had been saved earlier. 2. Log .errMsg as CRITICAL (unless it is None). 3. Cleanup. 4. MPI Abort. - To apply, set `sys.excepthook` to this method.""" - sys.__excepthook__(ex_type, value, tb) + To apply: + Set `self.original_excepthook = sys.excepthook`, + Then set `sys.excepthook = {this method}`. + """ + # Call original excepthook stored earlier (not raw sys.__excepthook__) + if self.original_excepthook is None: + raise RuntimeError( + "In custom __excepthook, but self.original_excepthook does not contain the saved original hook as expected." + ) + self.original_excepthook(ex_type, value, tb) + + # Perform additional actions if self.config_options.errMsg is not None: err_handler.log_critical( self.config_options, From d81413cb3709919c5788cafd93020260d401c2f6 Mon Sep 17 00:00:00 2001 From: Max Kipp Date: Thu, 14 May 2026 07:47:12 -0400 Subject: [PATCH 4/5] Cast err_msg to str to handle None --- .../NextGen_Forcings_Engine/core/err_handler.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/NextGen_Forcings_Engine_BMI/NextGen_Forcings_Engine/core/err_handler.py b/NextGen_Forcings_Engine_BMI/NextGen_Forcings_Engine/core/err_handler.py index 24128e4e..03a483e8 100755 --- a/NextGen_Forcings_Engine_BMI/NextGen_Forcings_Engine/core/err_handler.py +++ b/NextGen_Forcings_Engine_BMI/NextGen_Forcings_Engine/core/err_handler.py @@ -52,7 +52,7 @@ def err_out_screen(err_msg: str | None, exc: BaseException | None = None): sys.exit(1) -def err_out_screen_para(err_msg: str, MpiConfig, exc: BaseException | None = None): +def err_out_screen_para(err_msg: str | None, MpiConfig, exc: BaseException | None = None): """Print an error message to the screen and abort MPI. Generic function for printing an error message to the screen and aborting MPI. @@ -66,6 +66,7 @@ def err_out_screen_para(err_msg: str, MpiConfig, exc: BaseException | None = Non :param exc: Optional exception object to append to the error message. :return: None """ + err_msg = str(err_msg) if exc is not None: err_msg += f" - {exc}" err_msg_out = f"ERROR: RANK - {MpiConfig.rank} : {err_msg}" From a3ab3f933a099eb6810c76c970a2dd638e42d9ca Mon Sep 17 00:00:00 2001 From: Max Kipp Date: Thu, 14 May 2026 09:16:43 -0400 Subject: [PATCH 5/5] Add verbose handling of situation of non-str type passed as err_msg to function err_handler.err_out_screen or err_handler.err_out_screen_para --- .../NextGen_Forcings_Engine/core/err_handler.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/NextGen_Forcings_Engine_BMI/NextGen_Forcings_Engine/core/err_handler.py b/NextGen_Forcings_Engine_BMI/NextGen_Forcings_Engine/core/err_handler.py index 03a483e8..9bd8fafa 100755 --- a/NextGen_Forcings_Engine_BMI/NextGen_Forcings_Engine/core/err_handler.py +++ b/NextGen_Forcings_Engine_BMI/NextGen_Forcings_Engine/core/err_handler.py @@ -3,6 +3,7 @@ import os import sys import traceback +import typing import numpy as np from mpi4py import MPI @@ -19,7 +20,14 @@ def in_exception_context() -> bool: return False -def err_out_screen(err_msg: str | None, exc: BaseException | None = None): +def cast_err_msg_to_str(err_msg: str | typing.Any): + """If err_msg is not a string, cast it to string and add a note flagging the type provided.""" + if not isinstance(err_msg, str): + err_msg = f"Expected instance of str for err_msg, got type: {type(err_msg)}: {err_msg}" + return err_msg + + +def err_out_screen(err_msg: str, exc: BaseException | None = None): """Print an error message to the screen and exit the program gracefully. Generic routine to exit the program gracefully. This specific error function does not log @@ -31,7 +39,7 @@ def err_out_screen(err_msg: str | None, exc: BaseException | None = None): Logan Karsten - National Center for Atmospheric Research, karsten@ucar.edu """ - err_msg = str(err_msg) + err_msg = cast_err_msg_to_str(err_msg) if exc is not None: err_msg += f" - {exc}" err_msg_out = "ERROR: " + err_msg @@ -52,7 +60,7 @@ def err_out_screen(err_msg: str | None, exc: BaseException | None = None): sys.exit(1) -def err_out_screen_para(err_msg: str | None, MpiConfig, exc: BaseException | None = None): +def err_out_screen_para(err_msg: str, MpiConfig, exc: BaseException | None = None): """Print an error message to the screen and abort MPI. Generic function for printing an error message to the screen and aborting MPI. @@ -66,7 +74,7 @@ def err_out_screen_para(err_msg: str | None, MpiConfig, exc: BaseException | Non :param exc: Optional exception object to append to the error message. :return: None """ - err_msg = str(err_msg) + err_msg = cast_err_msg_to_str(err_msg) if exc is not None: err_msg += f" - {exc}" err_msg_out = f"ERROR: RANK - {MpiConfig.rank} : {err_msg}"