mirror of
https://github.com/OpenBB-finance/OpenBB.git
synced 2026-10-27 12:40:35 +00:00
fix(core): purge partially generated assets on build failure (#7462)
* fix(core): purge partially generated assets on build failure * feat(core): enhance build error reporting with traceback and user instructions * feat(core): handle interruptions and SIGTERM during build to ensure asset purge * fix mypy * temp pylint disable --------- Co-authored-by: Danglewood <85772166+deeleeramone@users.noreply.github.com>
This commit is contained in:
@@ -7,8 +7,10 @@ import inspect
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import signal
|
||||
import sys
|
||||
import textwrap
|
||||
import traceback
|
||||
import typing as typing_module
|
||||
from collections import OrderedDict
|
||||
from collections.abc import Callable
|
||||
@@ -186,8 +188,16 @@ class PackageBuilder:
|
||||
lock_file.write(str(os.getpid()))
|
||||
lock_file.flush()
|
||||
|
||||
# Actual build steps
|
||||
self.console.log("\nBuilding extensions package...\n")
|
||||
# Signal handler for SIGTERM
|
||||
def _handle_term(signum, _):
|
||||
self._clean(modules)
|
||||
sys.exit(signum)
|
||||
|
||||
if hasattr(signal, "SIGTERM"):
|
||||
original_sigterm = signal.getsignal(signal.SIGTERM)
|
||||
signal.signal(signal.SIGTERM, _handle_term)
|
||||
|
||||
try:
|
||||
self._clean(modules)
|
||||
ext_map = self._get_extension_map()
|
||||
self._save_modules(modules, ext_map)
|
||||
@@ -195,6 +205,21 @@ class PackageBuilder:
|
||||
self._save_package()
|
||||
if self.lint:
|
||||
self._run_linters()
|
||||
except BaseException as e:
|
||||
if not isinstance(e, (KeyboardInterrupt, SystemExit)):
|
||||
self.console.error("\nBuild failed!") # type: ignore # pylint: disable=E1101
|
||||
self.console.error(f"Error: {e}") # type: ignore # pylint: disable=E1101
|
||||
self.console.error(traceback.format_exc()) # type: ignore # pylint: disable=E1101
|
||||
self.console.error("\nInstruction:") # type: ignore # pylint: disable=E1101
|
||||
self.console.error( # type: ignore # pylint: disable=E1101
|
||||
"Set OPENBB_DEBUG_MODE='true' environment variable and run "
|
||||
"'openbb-build' again to see verbose output."
|
||||
)
|
||||
self._clean(modules)
|
||||
raise
|
||||
finally:
|
||||
if hasattr(signal, "SIGTERM"):
|
||||
signal.signal(signal.SIGTERM, original_sigterm)
|
||||
except BlockingIOError:
|
||||
raise RuntimeError( # noqa # pylint: disable=W0707
|
||||
f"Another build process is running and has locked {self._lock_path}"
|
||||
|
||||
@@ -14,3 +14,7 @@ class Console:
|
||||
"""Console log method."""
|
||||
if self.verbose or Env().DEBUG_MODE:
|
||||
print(message, **kwargs) # noqa: T201
|
||||
|
||||
def error(self, message: str, **kwargs):
|
||||
"""Console error method."""
|
||||
print(message, **kwargs) # noqa: T201
|
||||
|
||||
@@ -988,3 +988,47 @@ def test_get_field_type_unwraps_forward_ref(docstring_generator):
|
||||
assert "ForwardRef" not in result2, (
|
||||
f"ForwardRef should be unwrapped in docstring types, got: {result2}"
|
||||
)
|
||||
|
||||
|
||||
def test_build_purges_on_failure(tmp_openbb_dir):
|
||||
"""Test that build purges incomplete assets on failure."""
|
||||
builder = PackageBuilder(tmp_openbb_dir)
|
||||
|
||||
# Mocking _save_modules to fail
|
||||
with (
|
||||
patch.object(builder, "_clean") as mock_clean,
|
||||
patch.object(builder.console, "error") as mock_error,
|
||||
patch.object(builder, "_get_extension_map"),
|
||||
patch.object(builder, "_save_modules", side_effect=Exception("Generation failed")),
|
||||
):
|
||||
with pytest.raises(Exception, match="Generation failed"):
|
||||
builder.build()
|
||||
|
||||
# _clean should be called twice: once at the start, once after failure
|
||||
assert mock_clean.call_count == 2
|
||||
# console.error should be called for error message, traceback and instruction
|
||||
assert mock_error.call_count >= 3
|
||||
mock_error.assert_any_call("\nBuild failed!")
|
||||
assert any("Generation failed" in str(call) for call in mock_error.call_args_list)
|
||||
|
||||
|
||||
def test_build_purges_on_keyboard_interrupt(tmp_openbb_dir):
|
||||
"""Test that build purges incomplete assets on KeyboardInterrupt."""
|
||||
builder = PackageBuilder(tmp_openbb_dir)
|
||||
|
||||
# Mocking _save_modules to raise KeyboardInterrupt
|
||||
with (
|
||||
patch.object(builder, "_clean") as mock_clean,
|
||||
patch.object(builder.console, "error") as mock_error,
|
||||
patch.object(builder, "_get_extension_map"),
|
||||
patch.object(builder, "_save_modules", side_effect=KeyboardInterrupt()),
|
||||
):
|
||||
with pytest.raises(KeyboardInterrupt):
|
||||
builder.build()
|
||||
|
||||
# _clean should be called twice: once at the start, once after interruption
|
||||
assert mock_clean.call_count == 2
|
||||
# console.error should NOT be called for KeyboardInterrupt
|
||||
mock_error.assert_not_called()
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user