fix: use FastAPI app instance for startup

parent a8e42dbf
...@@ -178,7 +178,7 @@ def main(): ...@@ -178,7 +178,7 @@ def main():
from codai.api.app import set_load_mode as set_load_mode_app from codai.api.app import set_load_mode as set_load_mode_app
# Store config reference globally for access # Store config reference globally for access
fastapi_app = app fastapi_app = app.app
fastapi_app.state.config_mgr = config_mgr fastapi_app.state.config_mgr = config_mgr
fastapi_app.state.config = config fastapi_app.state.config = config
...@@ -685,14 +685,14 @@ def main(): ...@@ -685,14 +685,14 @@ def main():
except Exception as e: except Exception as e:
print(f"Warning: Could not generate certificate: {e}") print(f"Warning: Could not generate certificate: {e}")
print("Falling back to HTTP...") print("Falling back to HTTP...")
uvicorn.run(app, host=config.server.host, port=config.server.port) uvicorn.run(fastapi_app, host=config.server.host, port=config.server.port)
return return
ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER) ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
ssl_context.load_cert_chain(ssl_certfile, ssl_keyfile) ssl_context.load_cert_chain(ssl_certfile, ssl_keyfile)
uvicorn.run(app, host=config.server.host, port=config.server.port, ssl_context=ssl_context) uvicorn.run(fastapi_app, host=config.server.host, port=config.server.port, ssl_context=ssl_context)
else: else:
uvicorn.run(app, host=config.server.host, port=config.server.port) uvicorn.run(fastapi_app, host=config.server.host, port=config.server.port)
if __name__ == "__main__": if __name__ == "__main__":
......
...@@ -20,6 +20,10 @@ from tools.manual_multimodal_test_client import ( ...@@ -20,6 +20,10 @@ from tools.manual_multimodal_test_client import (
import pytest import pytest
class _MainExit(Exception):
pass
def test_parse_args_accepts_direct_mode_and_global_overrides(): def test_parse_args_accepts_direct_mode_and_global_overrides():
args = parse_args([ args = parse_args([
"llm", "llm",
...@@ -75,6 +79,139 @@ def test_resolve_mode_config_uses_mode_defaults_when_overrides_absent(tmp_path): ...@@ -75,6 +79,139 @@ def test_resolve_mode_config_uses_mode_defaults_when_overrides_absent(tmp_path):
assert "file" not in config assert "file" not in config
def test_main_uses_fastapi_app_instance_for_state_and_server(monkeypatch):
import codai.main as main_module
from codai.api import app as app_module
fake_args = type("Args", (), {
"config": "/tmp/coderai-test",
"dump": False,
"debug": False,
"list_models": False,
"list_devices": False,
"list_cached_models": False,
"remove_all_models": False,
"remove_model": None,
"download_model": None,
"download_file_pattern": None,
"vulkan_list_devices": False,
"download_gguf": None,
})()
fake_server = type("ServerCfg", (), {
"host": "127.0.0.1",
"port": 8765,
"https": False,
"https_key_path": None,
"https_cert_path": None,
"queue_max_size": 1,
"max_parallel_requests": 1,
})()
fake_backend = type("BackendCfg", (), {"type": "vulkan"})()
fake_config = type("Config", (), {
"server": fake_server,
"backend": fake_backend,
"system_prompt": "",
"tools_closer_prompt": "",
"grammar_guided": False,
"file_path": "/tmp",
"models": type("ModelsCfg", (), {"load_mode": "ondemand", "default_load_mode": "ondemand", "hf_cache_dir": None, "gguf_cache_dir": None})(),
"offload": type("OffloadCfg", (), {
"directory": "/tmp",
"manual_ram_gb": None,
"strategy": "auto",
"no_ram": False,
"load_in_4bit": False,
"load_in_8bit": False,
"flash_attention": False,
"max_gpu_percent": None,
})(),
"image": type("ImageCfg", (), {
"sample_method": "res_multistep",
"steps": 4,
"width": 512,
"height": 512,
"cfg_scale": 1.0,
"precision": "f32",
"cpu_offload": False,
"seed": None,
"vae_tiling": False,
"clip_on_cpu": False,
})(),
"parser": None,
"hf_chat_templates": None,
"reasoning_options": None,
"whisper": type("WhisperCfg", (), {"server_path": None, "server_port": 8744})(),
"vulkan": type("VulkanCfg", (), {"n_gpu_layers": -1, "n_ctx": 2048, "device_id": 0, "single_gpu": False})(),
"nvidia": type("NvidiaCfg", (), {})(),
})()
fake_config_mgr = type("ConfigMgr", (), {
"models_data": {},
"pipelines_data": [],
})()
monkeypatch.setattr(main_module, "parse_args", lambda: fake_args)
monkeypatch.setattr(main_module, "ConfigManager", lambda path: type("CfgMgr", (), {
"load": lambda self: fake_config,
"models_data": {},
"pipelines_data": [],
})())
monkeypatch.setattr(main_module, "init_session_manager", lambda *args, **kwargs: None)
monkeypatch.setattr(main_module, "set_config_manager", lambda mgr: None)
monkeypatch.setattr("codai.backends.detect_available_backends", lambda: {"vulkan": True})
monkeypatch.setattr("codai.api.state.set_global_args", lambda *args, **kwargs: None)
monkeypatch.setattr("codai.api.state.set_global_debug", lambda *args, **kwargs: None)
monkeypatch.setattr("codai.api.state.set_global_system_prompt", lambda *args, **kwargs: None, raising=False)
monkeypatch.setattr("codai.api.state.set_global_tools_closer_prompt", lambda *args, **kwargs: None, raising=False)
monkeypatch.setattr("codai.api.state.set_global_file_path", lambda *args, **kwargs: None)
monkeypatch.setattr("codai.api.state.set_load_mode", lambda *args, **kwargs: None)
monkeypatch.setattr("codai.api.state.set_grammar_guided_gen", lambda *args, **kwargs: None)
monkeypatch.setattr("codai.api.text.set_global_args", lambda *args, **kwargs: None)
monkeypatch.setattr("codai.api.text.set_global_debug", lambda *args, **kwargs: None)
monkeypatch.setattr("codai.api.text.set_global_system_prompt", lambda *args, **kwargs: None)
monkeypatch.setattr("codai.api.text.set_global_tools_closer_prompt", lambda *args, **kwargs: None)
monkeypatch.setattr("codai.api.app.set_load_mode", lambda *args, **kwargs: None)
monkeypatch.setattr("codai.api.app.set_global_file_path_wrapper", lambda *args, **kwargs: None)
monkeypatch.setattr("codai.api.images.set_global_file_path", lambda *args, **kwargs: None)
monkeypatch.setattr("codai.api.images.set_global_args", lambda *args, **kwargs: None)
monkeypatch.setattr("codai.api.video.set_global_args", lambda *args, **kwargs: None)
monkeypatch.setattr("codai.api.video.set_global_file_path", lambda *args, **kwargs: None)
monkeypatch.setattr("codai.api.audio_gen.set_global_args", lambda *args, **kwargs: None)
monkeypatch.setattr("codai.api.audio_gen.set_global_file_path", lambda *args, **kwargs: None)
monkeypatch.setattr("codai.api.audio_stems.set_global_args", lambda *args, **kwargs: None)
monkeypatch.setattr("codai.api.audio_stems.set_global_file_path", lambda *args, **kwargs: None)
monkeypatch.setattr("codai.api.audio_clean.set_global_args", lambda *args, **kwargs: None)
monkeypatch.setattr("codai.api.audio_clean.set_global_file_path", lambda *args, **kwargs: None)
monkeypatch.setattr("codai.api.voice_clone.set_global_args", lambda *args, **kwargs: None)
monkeypatch.setattr("codai.api.voice_clone.set_global_file_path", lambda *args, **kwargs: None)
monkeypatch.setattr("codai.api.voice_convert.set_global_args", lambda *args, **kwargs: None)
monkeypatch.setattr("codai.api.voice_convert.set_global_file_path", lambda *args, **kwargs: None)
monkeypatch.setattr("codai.api.faceswap.set_global_args", lambda *args, **kwargs: None)
monkeypatch.setattr("codai.api.faceswap.set_global_file_path", lambda *args, **kwargs: None)
monkeypatch.setattr("codai.api.characters.set_global_args", lambda *args, **kwargs: None)
monkeypatch.setattr("codai.api.embeddings.set_global_args", lambda *args, **kwargs: None)
monkeypatch.setattr("codai.models.manager.model_manager", type("MM", (), {"backend": None, "backend_type": None})())
monkeypatch.setattr("codai.models.manager.multi_model_manager", type("MMM", (), {"set_load_mode": lambda self, mode: None, "list_models": lambda self: []})())
monkeypatch.setattr("codai.queue.manager.queue_manager", type("Q", (), {"max_size": 0, "max_parallel_requests": 0})())
captured = {}
def fake_run(target, host, port, ssl_context=None):
captured["target"] = target
captured["host"] = host
captured["port"] = port
raise _MainExit()
monkeypatch.setattr("uvicorn.run", fake_run)
try:
main_module.main()
except _MainExit:
pass
assert captured["target"] is app_module.app
assert app_module.app.state.config is fake_config
def test_resolve_mode_config_normalizes_url_and_uses_default_input_files(tmp_path): def test_resolve_mode_config_normalizes_url_and_uses_default_input_files(tmp_path):
args = parse_args([ args = parse_args([
"transcription", "transcription",
......
...@@ -309,7 +309,7 @@ def test_admin_status_includes_recent_activity(studio_client, monkeypatch): ...@@ -309,7 +309,7 @@ def test_admin_status_includes_recent_activity(studio_client, monkeypatch):
def test_chat_template_wires_preview_shells_for_new_runnable_panels(): def test_chat_template_wires_preview_shells_for_new_runnable_panels():
template_path = "/storage/coderai/.worktrees/web-admin-polish/codai/admin/templates/chat.html" template_path = "/storage/coderai/codai/admin/templates/chat.html"
text = open(template_path, "r", encoding="utf-8").read() text = open(template_path, "r", encoding="utf-8").read()
assert "id=\"at-preview\"" in text assert "id=\"at-preview\"" in text
...@@ -329,7 +329,7 @@ def test_chat_template_wires_preview_shells_for_new_runnable_panels(): ...@@ -329,7 +329,7 @@ def test_chat_template_wires_preview_shells_for_new_runnable_panels():
def test_chat_template_marks_full_quality_audio_panels_with_runtime_backend_metadata(): def test_chat_template_marks_full_quality_audio_panels_with_runtime_backend_metadata():
template_path = "/storage/coderai/.worktrees/web-admin-polish/codai/admin/templates/chat.html" template_path = "/storage/coderai/codai/admin/templates/chat.html"
text = open(template_path, "r", encoding="utf-8").read() text = open(template_path, "r", encoding="utf-8").read()
assert "audioBackendHealth" in text assert "audioBackendHealth" in text
...@@ -340,7 +340,7 @@ def test_chat_template_marks_full_quality_audio_panels_with_runtime_backend_meta ...@@ -340,7 +340,7 @@ def test_chat_template_marks_full_quality_audio_panels_with_runtime_backend_meta
def test_chat_template_exposes_ml_preview_and_artifact_markers(): def test_chat_template_exposes_ml_preview_and_artifact_markers():
template_path = "/storage/coderai/.worktrees/web-admin-polish/codai/admin/templates/chat.html" template_path = "/storage/coderai/codai/admin/templates/chat.html"
text = open(template_path, "r", encoding="utf-8").read() text = open(template_path, "r", encoding="utf-8").read()
assert "buildStemPreviewData" in text assert "buildStemPreviewData" in text
...@@ -352,7 +352,7 @@ def test_chat_template_exposes_ml_preview_and_artifact_markers(): ...@@ -352,7 +352,7 @@ def test_chat_template_exposes_ml_preview_and_artifact_markers():
def test_studio_generation_panel_uses_wider_control_column(): def test_studio_generation_panel_uses_wider_control_column():
template_path = "/storage/coderai/.worktrees/web-admin-polish/codai/admin/templates/chat.html" template_path = "/storage/coderai/codai/admin/templates/chat.html"
text = open(template_path, "r", encoding="utf-8").read() text = open(template_path, "r", encoding="utf-8").read()
assert ".gen-ctrl { width:min(380px,36vw); min-width:340px; max-width:420px;" in text assert ".gen-ctrl { width:min(380px,36vw); min-width:340px; max-width:420px;" in text
...@@ -360,7 +360,7 @@ def test_studio_generation_panel_uses_wider_control_column(): ...@@ -360,7 +360,7 @@ def test_studio_generation_panel_uses_wider_control_column():
def test_studio_output_surfaces_capability_warnings(): def test_studio_output_surfaces_capability_warnings():
template_path = "/storage/coderai/.worktrees/web-admin-polish/codai/admin/templates/chat.html" template_path = "/storage/coderai/codai/admin/templates/chat.html"
text = open(template_path, "r", encoding="utf-8").read() text = open(template_path, "r", encoding="utf-8").read()
assert "cap-output-note" in text assert "cap-output-note" in text
...@@ -369,7 +369,7 @@ def test_studio_output_surfaces_capability_warnings(): ...@@ -369,7 +369,7 @@ def test_studio_output_surfaces_capability_warnings():
def test_pipeline_tab_exposes_create_action_and_empty_state(): def test_pipeline_tab_exposes_create_action_and_empty_state():
template_path = "/storage/coderai/.worktrees/web-admin-polish/codai/admin/templates/chat.html" template_path = "/storage/coderai/codai/admin/templates/chat.html"
text = open(template_path, "r", encoding="utf-8").read() text = open(template_path, "r", encoding="utf-8").read()
assert "Create pipeline" in text assert "Create pipeline" in text
...@@ -378,7 +378,7 @@ def test_pipeline_tab_exposes_create_action_and_empty_state(): ...@@ -378,7 +378,7 @@ def test_pipeline_tab_exposes_create_action_and_empty_state():
def test_pipeline_tab_exposes_editor_shell(): def test_pipeline_tab_exposes_editor_shell():
template_path = "/storage/coderai/.worktrees/web-admin-polish/codai/admin/templates/chat.html" template_path = "/storage/coderai/codai/admin/templates/chat.html"
text = open(template_path, "r", encoding="utf-8").read() text = open(template_path, "r", encoding="utf-8").read()
assert "pipe-editor" in text assert "pipe-editor" in text
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment