From fca05785aa84f9b9ff0bc24ea634c27e29543f08 Mon Sep 17 00:00:00 2001 From: Jonathan Lessinger Date: Thu, 21 Dec 2023 17:01:08 -0500 Subject: [PATCH] [AIC-py][editor] bug fix: catch aiconfigruntime load exc --- python/src/aiconfig/editor/server/server.py | 60 +++++++++++++++------ python/src/aiconfig/scripts/aiconfig_cli.py | 39 +++++++++----- 2 files changed, 69 insertions(+), 30 deletions(-) diff --git a/python/src/aiconfig/editor/server/server.py b/python/src/aiconfig/editor/server/server.py index 167576ea3..becd72ba4 100644 --- a/python/src/aiconfig/editor/server/server.py +++ b/python/src/aiconfig/editor/server/server.py @@ -167,7 +167,7 @@ def _run_with_path(path: str) -> HttpPostResponse: return result.to_flask_format() -def _get_validated_request_path(raw_path: str) -> Result[str, str]: +def _get_validated_path(raw_path: str) -> Result[str, str]: if not raw_path: return Err("No path provided") resolved = _resolve_path(raw_path) @@ -176,11 +176,14 @@ def _get_validated_request_path(raw_path: str) -> Result[str, str]: return Ok(resolved) -def _http_response_with_path(path_fn: Callable[[str], HttpPostResponse]) -> HttpPostResponse: +def _validated_request_path() -> Result[str, str]: request_json = request.get_json() path = request_json.get("path", None) + return _get_validated_path(path) + - validated_path = _get_validated_request_path(path) +def _http_response_with_path(path_fn: Callable[[str], HttpPostResponse]) -> HttpPostResponse: + validated_path = _validated_request_path() match validated_path: case Ok(path): return path_fn(path) @@ -190,9 +193,9 @@ def _http_response_with_path(path_fn: Callable[[str], HttpPostResponse]) -> Http @app.route("/api/load", methods=["POST"]) def load(): + state = _get_server_state(app) + def _run_with_path(path: str) -> HttpPostResponse: - LOGGER.info(f"Loading AIConfig from {path}") - state = _get_server_state(app) try: state.aiconfig = AIConfigRuntime.load(path) # type: ignore return HttpPostResponse(message="Done") @@ -270,16 +273,21 @@ def run_backend_server(edit_config: EditServerConfig) -> Result[str, str]: LOGGER.info(f"Starting server on http://localhost:{edit_config.server_port}") app.server_state = ServerState() # type: ignore - _init_server_state(app, edit_config) - - debug = edit_config.server_mode in [ServerMode.DEBUG_BACKEND, ServerMode.DEBUG_SERVERS] - LOGGER.info(f"Running in {edit_config.server_mode} mode") - app.run(port=edit_config.server_port, debug=debug, use_reloader=True) - return Ok("Done") + res_server_state_init = _init_server_state(app, edit_config) + match res_server_state_init: + case Ok(_): + LOGGER.info("Initialized server state") + debug = edit_config.server_mode in [ServerMode.DEBUG_BACKEND, ServerMode.DEBUG_SERVERS] + LOGGER.info(f"Running in {edit_config.server_mode} mode") + app.run(port=edit_config.server_port, debug=debug, use_reloader=True) + return Ok("Done") + case Err(e): + LOGGER.error(f"Failed to initialize server state: {e}") + return Err(f"Failed to initialize server state: {e}") def _load_user_parser_module_if_exists(parsers_module_path: str) -> None: - res = _get_validated_request_path(parsers_module_path).and_then(_load_user_parser_module) + res = _get_validated_path(parsers_module_path).and_then(_load_user_parser_module) match res: case Ok(_): LOGGER.info(f"Loaded parsers module from {parsers_module_path}") @@ -287,7 +295,20 @@ def _load_user_parser_module_if_exists(parsers_module_path: str) -> None: LOGGER.warning(f"Failed to load parsers module: {e}") -def _init_server_state(app: Flask, edit_config: EditServerConfig) -> None: +def _safe_load_from_disk(aiconfig_path: str) -> Result[AIConfigRuntime, str]: + validated_path = _get_validated_path(aiconfig_path) + + def _load(path: str) -> Result[AIConfigRuntime, str]: + try: + aiconfig = AIConfigRuntime.load(path) # type: ignore + return Ok(aiconfig) + except Exception as e: + return core_utils.ErrWithTraceback(e) + + return validated_path.and_then(_load) + + +def _init_server_state(app: Flask, edit_config: EditServerConfig) -> Result[None, str]: LOGGER.info("Initializing server state") _load_user_parser_module_if_exists(edit_config.parsers_module_path) state = _get_server_state(app) @@ -295,9 +316,16 @@ def _init_server_state(app: Flask, edit_config: EditServerConfig) -> None: assert state.aiconfig is None if edit_config.aiconfig_path: LOGGER.info(f"Loading AIConfig from {edit_config.aiconfig_path}") - aiconfig_runtime = AIConfigRuntime.load(edit_config.aiconfig_path) # type: ignore - state.aiconfig = aiconfig_runtime - LOGGER.info(f"Loaded AIConfig from {edit_config.aiconfig_path}") + aiconfig_runtime = _safe_load_from_disk(edit_config.aiconfig_path) + LOGGER.debug(f"{aiconfig_runtime.is_ok()=}") + match aiconfig_runtime: + case Ok(aiconfig_runtime_): + state.aiconfig = aiconfig_runtime_ + LOGGER.info(f"Loaded AIConfig from {edit_config.aiconfig_path}") + return Ok(None) + case Err(e): + LOGGER.error(f"Failed to load AIConfig from {edit_config.aiconfig_path}: {e}") + return Err(f"Failed to load AIConfig from {edit_config.aiconfig_path}: {e}") else: aiconfig_runtime = AIConfigRuntime.create() # type: ignore state.aiconfig = aiconfig_runtime diff --git a/python/src/aiconfig/scripts/aiconfig_cli.py b/python/src/aiconfig/scripts/aiconfig_cli.py index ce646077e..9b5a07340 100644 --- a/python/src/aiconfig/scripts/aiconfig_cli.py +++ b/python/src/aiconfig/scripts/aiconfig_cli.py @@ -5,6 +5,7 @@ import sys import lastmile_utils.lib.core.api as core_utils +import result from aiconfig.editor.server.server import EditServerConfig, ServerMode, run_backend_server from result import Err, Ok, Result @@ -29,6 +30,7 @@ async def main(argv: list[str]) -> int: def run_subcommand(argv: list[str]) -> Result[str, str]: + LOGGER.info("Running subcommand") subparser_record_types = {"edit": EditServerConfig} main_parser = core_utils.argparsify(AIConfigCLIConfig, subparser_record_types=subparser_record_types) @@ -39,9 +41,17 @@ def run_subcommand(argv: list[str]) -> Result[str, str]: LOGGER.info(f"Running subcommand: {subparser_name}") if subparser_name == "edit": + LOGGER.debug("Running edit subcommand") res_edit_config = core_utils.parse_args(main_parser, argv[1:], EditServerConfig) + LOGGER.debug(f"{res_edit_config.is_ok()=}") res_servers = res_edit_config.and_then(_run_editor_servers) - return res_servers + out: Result[str, str] = result.do( + # + Ok(",".join(res_servers_ok)) + # + for res_servers_ok in res_servers + ) + return out else: return Err(f"Unknown subparser: {subparser_name}") @@ -53,27 +63,28 @@ def _sigint(procs: list[subprocess.Popen[bytes]]) -> Result[str, str]: return Ok("Sent SIGINT to frontend servers.") -def _run_editor_servers(edit_config: EditServerConfig) -> Result[str, str]: +def _run_editor_servers(edit_config: EditServerConfig) -> Result[list[str], str]: LOGGER.info("Running editor servers") frontend_procs = _run_frontend_server_background() if edit_config.server_mode in [ServerMode.DEBUG_SERVERS] else Ok([]) + match frontend_procs: + case Ok(_): + pass + case Err(e): + return Err(e) + results: list[Result[str, str]] = [] backend_res = run_backend_server(edit_config) match backend_res: - case Ok(msg): - LOGGER.info("Backend server res: Ok:\n%s", msg) - out = Ok(msg) + case Ok(_): + pass case Err(e): - LOGGER.critical("Backend server err: %s", e) - out = Err(e) + return Err(e) - sigint_res = frontend_procs.and_then(_sigint) - match sigint_res: - case Ok(msg): - LOGGER.info("SIGINT res: Ok:\n%s", msg) - case Err(e): - LOGGER.critical("SIGINT err: %s", e) + results.append(backend_res) - return out + sigint_res = frontend_procs.and_then(_sigint) + results.append(sigint_res) + return core_utils.result_reduce_list_all_ok(results) def _process_cli_config(cli_config: AIConfigCLIConfig) -> Result[bool, str]: