mirror of
https://ghfast.top/https://github.com/aeroxw/easy_tdx_max.git
synced 2026-09-12 16:54:20 +08:00
feat(cli): serve 新增 --no-ui 纯 API 模式
- --no-ui:不托管 Web UI 前端(根路径 404),仅 /api/v1/* REST 接口, 且不自动打开浏览器——给 AI Agent / 程序化调用省去前端资源 - create_app/_create_app 新增 enable_ui 参数(默认 True 行为不变) - 测试锁定:no-ui 模式无 web-ui Mount 且 API 路由完整;四道门禁全绿 (ruff / ruff format / mypy / 1079 tests),实测根路径 404 + API 正常
This commit is contained in:
@@ -19,6 +19,12 @@ import click
|
||||
default=True,
|
||||
help="启动后自动打开浏览器(默认开启,PyInstaller 打包后老人双击即用)",
|
||||
)
|
||||
@click.option(
|
||||
"--no-ui",
|
||||
is_flag=True,
|
||||
help="纯 API 模式:不托管 Web UI 前端,仅提供 /api/v1/* REST 接口"
|
||||
"(给 AI Agent / 程序调用时省去前端资源;此时也不自动开浏览器)",
|
||||
)
|
||||
def serve(
|
||||
host: str,
|
||||
port: int,
|
||||
@@ -26,6 +32,7 @@ def serve(
|
||||
tdx_port: int | None,
|
||||
reload: bool,
|
||||
open_browser: bool,
|
||||
no_ui: bool,
|
||||
) -> None:
|
||||
"""启动 Web API 服务器(需要安装 easy-tdx[web])。"""
|
||||
try:
|
||||
@@ -39,7 +46,7 @@ def serve(
|
||||
|
||||
# 启动后延迟打开浏览器:uvicorn 需要约 1-2 秒绑定端口,过早打开会
|
||||
# 命中 connection refused。用后台 Timer 而非阻塞主线程。
|
||||
if open_browser and not reload:
|
||||
if open_browser and not reload and not no_ui:
|
||||
# 0.0.0.0 / 127.0.0.1 在浏览器里用 localhost 打开(更友好)。
|
||||
display_host = "localhost" if host in ("0.0.0.0", "127.0.0.1") else host
|
||||
url = f"http://{display_host}:{port}"
|
||||
@@ -57,5 +64,5 @@ def serve(
|
||||
else:
|
||||
from easy_tdx.web import create_app
|
||||
|
||||
app = create_app(host=tdx_host, port=tdx_port)
|
||||
app = create_app(host=tdx_host, port=tdx_port, enable_ui=not no_ui)
|
||||
uvicorn.run(app, host=host, port=port)
|
||||
|
||||
@@ -24,6 +24,8 @@ def create_app(
|
||||
host: str | None = None,
|
||||
port: int | None = None,
|
||||
timeout: float | None = None,
|
||||
*,
|
||||
enable_ui: bool = True,
|
||||
) -> FastAPI:
|
||||
"""Create and configure the FastAPI application.
|
||||
|
||||
@@ -31,13 +33,14 @@ def create_app(
|
||||
host: TDX server host (None = auto-detect best host).
|
||||
port: TDX server port (None = default 7709).
|
||||
timeout: Connection timeout in seconds.
|
||||
enable_ui: False 时不托管 Web UI 前端(纯 API 模式,serve --no-ui)。
|
||||
|
||||
Returns:
|
||||
Configured FastAPI application instance.
|
||||
"""
|
||||
from easy_tdx.web.app import _create_app
|
||||
|
||||
return _create_app(host=host, port=port, timeout=timeout)
|
||||
return _create_app(host=host, port=port, timeout=timeout, enable_ui=enable_ui)
|
||||
|
||||
|
||||
def app_factory() -> FastAPI:
|
||||
|
||||
@@ -172,8 +172,14 @@ def _create_app(
|
||||
*,
|
||||
enable_mac: bool = True,
|
||||
enable_ex: bool = False,
|
||||
enable_ui: bool = True,
|
||||
) -> FastAPI:
|
||||
"""创建并配置 FastAPI 应用实例。"""
|
||||
"""创建并配置 FastAPI 应用实例。
|
||||
|
||||
Args:
|
||||
enable_ui: 是否同源托管 Web UI 前端(``easy-tdx serve --no-ui``
|
||||
纯 API 模式传 False:不挂载静态 dist,根路径 404,仅 /api/v1/*)。
|
||||
"""
|
||||
from easy_tdx.config import get_best_host, get_port, get_timeout
|
||||
|
||||
if host is None:
|
||||
@@ -290,7 +296,7 @@ def _create_app(
|
||||
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
|
||||
dist_dir = _resolve_web_dist_dir()
|
||||
dist_dir = _resolve_web_dist_dir() if enable_ui else None
|
||||
if dist_dir is not None:
|
||||
# SPA fallback:前端用 createWebHistory(HTML5 history 模式),
|
||||
# 用户直接访问 /optimize、/portfolio 等前端路由或刷新时,后端必须
|
||||
|
||||
@@ -556,3 +556,24 @@ def test_openapi_schema_generated():
|
||||
# they are verified in test_full_app_routes_registered instead.
|
||||
# Just ensure REST paths are present.
|
||||
assert "/api/v1/fund-flow" in schema["paths"]
|
||||
|
||||
|
||||
def test_create_app_no_ui_mode():
|
||||
"""--no-ui 纯 API 模式:不挂载 Web UI 静态托管,API 路由完整。"""
|
||||
pytest.importorskip("fastapi")
|
||||
from easy_tdx.web import create_app
|
||||
|
||||
app = create_app(enable_ui=False)
|
||||
# 无 web-ui 静态 Mount(FastAPI 0.141+ 顶层 Mount 不受 _IncludedRouter 影响)
|
||||
mounts = [r for r in app.routes if type(r).__name__ == "Mount"]
|
||||
assert all(getattr(m, "name", "") != "web-ui" for m in mounts)
|
||||
|
||||
# API 路由完整可用
|
||||
paths = list(app.openapi()["paths"].keys())
|
||||
assert any("/api/v1/security" in p for p in paths)
|
||||
assert any("/api/v1/watchlist" in p for p in paths)
|
||||
|
||||
# 对照:默认模式挂载 web-ui(dist 存在时)
|
||||
app_ui = create_app()
|
||||
mounts_ui = [r for r in app_ui.routes if type(r).__name__ == "Mount"]
|
||||
assert any(getattr(m, "name", "") == "web-ui" for m in mounts_ui)
|
||||
|
||||
Reference in New Issue
Block a user