refactor : FastAPI 懒加载单例,消除模块级副作用

- import astrai.inference 不再在模块加载时创建 FastAPI 实例
- 路由移至 APIRouter;get_app() 首次调用时懒构造单例
- _create_engine 和 run_server 的 param_path 改为必填
- 更新测试改用 get_app() 替代模块级 app
This commit is contained in:
2026-06-04 15:52:27 +08:00
parent b36a78c612
commit dc7d2cfbca
5 changed files with 54 additions and 31 deletions
+6 -5
View File
@@ -5,21 +5,22 @@ from unittest.mock import MagicMock
import pytest
from fastapi.testclient import TestClient
from astrai.inference import app
from astrai.inference import get_app
@pytest.fixture
def client():
"""Provide a test client for the FastAPI app."""
app.state.server_config = {
_app = get_app()
_app.state.server_config = {
"device": "cpu",
"dtype": "bfloat16",
"param_path": None,
"max_batch_size": 1,
"_test": True,
}
app.state.engine = None
return TestClient(app)
_app.state.engine = None
return TestClient(_app)
@pytest.fixture
@@ -49,5 +50,5 @@ def mock_engine():
@pytest.fixture
def loaded_model(client, mock_engine):
"""Simulate that the engine is loaded."""
app.state.engine = mock_engine
get_app().state.engine = mock_engine
return mock_engine
+9 -9
View File
@@ -2,12 +2,12 @@
import pytest
from astrai.inference import app
from astrai.inference import get_app
def test_health_no_model(client):
"""GET /health should return 200 even when engine not loaded."""
app.state.engine = None
get_app().state.engine = None
response = client.get("/health")
assert response.status_code == 200
data = response.json()
@@ -30,7 +30,7 @@ def test_chat_completions_non_stream(client, loaded_model):
async def async_gen():
yield "Assistant reply"
app.state.engine = loaded_model
get_app().state.engine = loaded_model
loaded_model.generate_async.return_value = async_gen()
response = client.post(
"/v1/chat/completions",
@@ -56,7 +56,7 @@ def test_chat_completions_stream(client, loaded_model):
yield "cumulative1"
yield "cumulative2"
app.state.engine = loaded_model
get_app().state.engine = loaded_model
loaded_model.generate_async.return_value = async_gen()
response = client.post(
"/v1/chat/completions",
@@ -83,7 +83,7 @@ def test_messages_non_stream(client, loaded_model):
async def async_gen():
yield "Assistant reply"
app.state.engine = loaded_model
get_app().state.engine = loaded_model
loaded_model.generate_async.return_value = async_gen()
response = client.post(
"/v1/messages",
@@ -111,7 +111,7 @@ def test_messages_stream(client, loaded_model):
yield "cumulative1"
yield "cumulative2"
app.state.engine = loaded_model
get_app().state.engine = loaded_model
loaded_model.generate_async.return_value = async_gen()
response = client.post(
"/v1/messages",
@@ -141,7 +141,7 @@ def test_messages_with_system(client, loaded_model):
async def async_gen():
yield "Reply"
app.state.engine = loaded_model
get_app().state.engine = loaded_model
loaded_model.generate_async.return_value = async_gen()
response = client.post(
"/v1/messages",
@@ -165,7 +165,7 @@ def test_chat_completions_stop_sequence(client, loaded_model):
yield "X"
yield "world"
app.state.engine = loaded_model
get_app().state.engine = loaded_model
loaded_model.generate_async.return_value = async_gen()
response = client.post(
"/v1/chat/completions",
@@ -191,7 +191,7 @@ def test_chat_completions_stop_sequence_stream(client, loaded_model):
yield "X"
yield "world"
app.state.engine = loaded_model
get_app().state.engine = loaded_model
loaded_model.generate_async.return_value = async_gen()
response = client.post(
"/v1/chat/completions",