Archived
异步基础层(core/):
- db.py aiosqlite + WAL:读路径无锁并发、写路径 asyncio.Lock 串行,
busy_timeout 自旋——实现「读写同时进行」(旧实现用 RLock 全读串行)
- http_client.py aiohttp 连接池 + 有界重试(5xx 重试 / 4xx 不重试)+
响应体上限 + 流式接口;按事件循环隔离 session(支持迁移期双 loop)
工具链:
- pyproject.toml ruff(E/F/W/I/B/UP/C4/ASYNC/S/RUF) + pyright(basic) + pytest(asyncio auto)
- requirements-fastapi.txt FastAPI/aiohttp/aiosqlite/aiofiles 等目标栈
- requirements-dev.txt 开发依赖(pytest/ruff/pyright)
清理:删除无用脚本 _csscheck.py 与产物标记 _css_ok.txt/_pkg_ok.txt,同步 .gitignore
验证:pytest 10/10(含「写 200 条同时 4 读协程零异常」并发用例);
ruff All checks passed;pyright 0 errors。
48 lines
1.7 KiB
Python
48 lines
1.7 KiB
Python
# -*- coding: utf-8 -*-
|
||
"""FastAPI 应用工厂。
|
||
|
||
设计(strangler 迁移):
|
||
- 已迁移的路由挂在 `/api/v1` 前缀下,与遗留 Flask 的同名路由(根路径)并存,
|
||
便于逐端点比对响应契约(.zcode/refactor/parity.py)。
|
||
- `serve_flask_fallback=True` 时,把未迁移的遗留 Flask 应用整体挂到根路径兜底,
|
||
实现「新端点走 FastAPI、其余走 Flask」的单进程双框架运行;迁移完成后删掉该开关。
|
||
|
||
注意(默认关闭的原因):Flask fallback 会在 FastAPI 进程内 import main,
|
||
进而初始化 MusicAPIService / 打开 SQLite —— 与生产既有的 waitress 进程是
|
||
两套独立进程语义。生产切换前需按计划把状态与任务模型统一(见迁移计划阶段 C),
|
||
当前仅用于开发期联调。
|
||
"""
|
||
|
||
from fastapi import FastAPI
|
||
|
||
from core.config import APP_VERSION
|
||
from web.routers import system as system_router
|
||
|
||
|
||
def create_app(serve_flask_fallback: bool = False) -> FastAPI:
|
||
"""创建 FastAPI 应用。"""
|
||
app = FastAPI(
|
||
title='云村小助手API服务',
|
||
version=APP_VERSION,
|
||
docs_url='/api/v1/docs',
|
||
redoc_url=None,
|
||
openapi_url='/api/v1/openapi.json',
|
||
)
|
||
|
||
# 已迁移路由:统一挂 /api/v1(迁移期与 Flask 根路径并存,便于对比)
|
||
app.include_router(system_router.router, prefix='/api/v1', tags=['system'])
|
||
|
||
if serve_flask_fallback:
|
||
_mount_flask_fallback(app)
|
||
|
||
return app
|
||
|
||
|
||
def _mount_flask_fallback(app: FastAPI) -> None:
|
||
"""把遗留 Flask WSGI 应用挂到根路径,兜底未迁移的路由。"""
|
||
from starlette.middleware.wsgi import WSGIMiddleware
|
||
|
||
import main as legacy_flask
|
||
|
||
app.mount('/', WSGIMiddleware(legacy_flask.app))
|