P1-13 连接池位泄漏(Scripts/bin/backfill.py) --------------------------------------------- 三处 `get_conn()` 都把 `put_conn(conn)` 放在**成功路径**,异常时连接永不归还: get_source_map / get_existing_ids / insert_news 连接池有容量上限,异常路径不归还即永久占住池位,反复失败会耗尽池, 表现为后续所有取连接的操作阻塞(与 db_pool 取连接超时修复是同一类问题)。 修法:全部改为 try/finally 无条件归还。 **自查发现并修正一处残留缺陷**:`finally` 里写成 `cur.close(); put_conn(conn)`, 若 close 本身抛错(连接已断时常见)仍会跳过归还 → 改为嵌套 try/finally, 归还作为无条件最后一步。回归用例正是靠这一点抓出来的。 P1-13 后端 stderr 句柄泄漏(Scripts/bin/newsflash_collect.py) -------------------------------------------------------------- `stderr=open(BACKEND_STDERR_LOG, "w")` 把 open() 返回值直接丢给 Popen, 父进程这份句柄没有任何引用可关 → 每启动一次后端泄漏一个 fd。 改为先存局部变量、Popen 后立即 close(子进程已持有独立 fd 副本,写入不受影响)。 P2-16 bundle-python.sh 版本契约冲突 ------------------------------------ `uv venv --python 3.14` 与 .python-version(=3.12) 及所有 pyproject 的 requires-python=">=3.12,<3.13" 冲突。改为读取 .python-version 作为单一事实来源。 同时 `set -e` → `set -eo pipefail`:原先 `uv venv ... | tail -3` 会吞掉失败退出码, 与文件头注释声称的"确保失败中止"相矛盾。 并注明该脚本**当前不在构建链路内**(build-app.sh 未调用,全仓仅文档提及), 避免后人误以为它是打包的一个阶段。 stk_factor / stk_factor_pro 假启用 ---------------------------------- config.yaml 与 config/default.yaml 写 `true`,但 fetcher.py:306 对该表显式跳过 (接口限 5 次/天,无法批量)。改为 false 并注明原因。 另外查明:`tables:` 开关只在「未显式指定表」时才生效(fetcher.py:239-243), 调度脚本总是显式传参 → **不能以 config 为准判断某项是否在跑**。 回归 ---- Scripts/bin 213 passed(新增 6 条连接归还用例,已证伪: 旧写法下 `test_get_source_map_returns_conn_on_error` 报 assert 0 == 1)、 core 121、tushare 174、akshare 183、kuaixun 407 —— 合计 1098 passed。 本次一并完成的非代码清理 ------------------------ - P2-17 陈旧可写 venv:`~/Library/Application Support/FinDataHub/venvs/` 下 akshare_fetcher(3.14.6, 308M) 与 kuaixun-service(3.14.5, 156M) 两个副本 来自 v1.4.7 时期,与 bundle 的 3.12 不符、且每次启动刷 WARN。 已移出(464M → 12K,保留 venv_versions.json)。应用日志本身提示 "如需 pip install,请在设置中重建可写副本",故可安全清理。 - P2-21 构建残留:findatahub-core/build、findatahub_core.egg-info、 findatahub_client.egg-info 已移出(移出前先 mv 到 /tmp 再后台删除,规避沙箱批量删除限制)。
114 lines
4.4 KiB
Bash
114 lines
4.4 KiB
Bash
#!/bin/bash
|
||
# ==============================================================
|
||
# bundle-python.sh — 将 Python 运行时 + akshare-fetcher 打包进 .app
|
||
#
|
||
# ⚠️ 当前**不在构建链路内**:`Scripts/build-app.sh` 未调用本脚本,
|
||
# 全仓仅 Docs/DEVLOG 提到它。保留作为独立打包工具的参考实现。
|
||
# 若要重新启用,请先确认它与 build-app.sh 的分工,避免产物重复。
|
||
# ==============================================================
|
||
# pipefail:否则 `uv venv ... | tail -3` 这类管道会吞掉失败退出码,
|
||
# 与本文件头部注释声称的"确保失败中止"相矛盾。
|
||
set -eo pipefail
|
||
|
||
APP_DIR="${1:-.build/FinDataHub.app}"
|
||
PROJECT_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||
# 数据源项目路径可通过环境变量覆盖
|
||
AKSHARE_SRC="${AKSHARE_SRC:-${PROJECT_ROOT}/Services/akshare_fetcher}"
|
||
FRAMEWORKS_DIR="${APP_DIR}/Contents/Frameworks"
|
||
RESOURCES_DIR="${APP_DIR}/Contents/Resources"
|
||
VENV_DIR="${FRAMEWORKS_DIR}/akshare-venv"
|
||
AKSHARE_DST="${RESOURCES_DIR}/akshare_fetcher"
|
||
|
||
echo "=== 打包 Python 运行时 ==="
|
||
echo " .app: ${APP_DIR}"
|
||
echo ""
|
||
|
||
# ---------- 1. 创建 venv ----------
|
||
if [ -d "${VENV_DIR}/bin" ]; then
|
||
echo "[1/3] 虚拟环境已存在,跳过创建"
|
||
else
|
||
echo "[1/3] 创建 Python 虚拟环境..."
|
||
mkdir -p "${FRAMEWORKS_DIR}"
|
||
cd "${PROJECT_ROOT}"
|
||
# uv venv 创建独立虚拟环境(与 bundle 内实际运行时保持一致)
|
||
# 版本必须取 .python-version(=3.12):此前这里写死 3.14,
|
||
# 与 .python-version 及所有 pyproject 的 requires-python=">=3.12,<3.13" 冲突,
|
||
# 会造出一个与 bundle 内 3.12 不一致的运行时(详见项目记忆"全项目锁定 3.12")。
|
||
PY_VER="$(tr -d '[:space:]' < "${PROJECT_ROOT}/.python-version")"
|
||
uv venv --python "${PY_VER}" "${VENV_DIR}" 2>&1 | tail -3
|
||
echo " ✅ 虚拟环境已创建 (Python ${PY_VER})"
|
||
fi
|
||
|
||
source "${VENV_DIR}/bin/activate"
|
||
echo " Python: $(python3 --version)"
|
||
|
||
# ---------- 2. 安装依赖 ----------
|
||
echo "[2/3] 安装 akshare-fetcher 依赖..."
|
||
cd "${AKSHARE_SRC}"
|
||
|
||
# 从 pyproject.toml 安装依赖(版本约束由 pyproject.toml 管理,避免无版本锁定)
|
||
pip install --quiet --upgrade pip 2>/dev/null || true
|
||
|
||
install_time=$(date +%s)
|
||
# 以可编辑模式安装项目,读取 pyproject.toml 中声明的依赖及版本约束
|
||
pip install --quiet -e "${AKSHARE_SRC}" 2>&1 | tail -3
|
||
|
||
# 验证
|
||
python3 -c "import akshare; print(f' ✅ akshare {akshare.__version__} 可导入')" 2>&1
|
||
python3 -c "import pandas; print(f' ✅ pandas {pandas.__version__}')" 2>&1
|
||
python3 -c "import numpy; print(f' ✅ numpy {numpy.__version__}')" 2>&1
|
||
|
||
du -sh "${VENV_DIR}"
|
||
echo ""
|
||
|
||
# ---------- 3. 复制项目代码 ----------
|
||
echo "[3/3] 部署 akshare-fetcher 项目代码..."
|
||
mkdir -p "${AKSHARE_DST}"
|
||
rsync -a --delete \
|
||
--exclude='.venv' \
|
||
--exclude='.git' \
|
||
--exclude='__pycache__' \
|
||
--exclude='*.pyc' \
|
||
--exclude='*.pyo' \
|
||
--exclude='.DS_Store' \
|
||
--exclude='uv.lock' \
|
||
"${AKSHARE_SRC}/" "${AKSHARE_DST}/"
|
||
echo " ✅ 项目代码已部署 (${AKSHARE_DST})"
|
||
|
||
# ---------- 4. 创建入口脚本 ----------
|
||
echo " 创建入口脚本..."
|
||
mkdir -p "${RESOURCES_DIR}/bin"
|
||
cat > "${RESOURCES_DIR}/bin/akshare-fetcher.sh" << 'SCRIPT'
|
||
#!/bin/bash
|
||
# FinDataHub 内置 akshare-fetcher 入口
|
||
# 使用捆绑的虚拟环境
|
||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||
APP_DIR="$(cd "${SCRIPT_DIR}/../.." && pwd)"
|
||
VENV_PYTHON="${APP_DIR}/Contents/Frameworks/akshare-venv/bin/python3"
|
||
PROJECT_DIR="${APP_DIR}/Contents/Resources/akshare_fetcher"
|
||
export PYTHONUNBUFFERED=1
|
||
export AKSHARE_HOME="${PROJECT_DIR}"
|
||
export PYTHONPATH="${PROJECT_DIR}/src"
|
||
cd "${PROJECT_DIR}"
|
||
exec "${VENV_PYTHON}" -m akshare_fetcher "$@"
|
||
SCRIPT
|
||
chmod +x "${RESOURCES_DIR}/bin/akshare-fetcher.sh"
|
||
|
||
# 同时创建快捷入口: akshare-fetcher → 直接调项目源码
|
||
cat > "${AKSHARE_DST}/run_bundled.sh" << 'SCRIPT'
|
||
#!/bin/bash
|
||
# 捆绑环境入口 — 使用 FinDataHub 内建的 venv
|
||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||
VENV_PYTHON="${SCRIPT_DIR}/../../Frameworks/akshare-venv/bin/python3"
|
||
export PYTHONPATH="${SCRIPT_DIR}/src"
|
||
cd "${SCRIPT_DIR}"
|
||
exec "${VENV_PYTHON}" -m akshare_fetcher "$@"
|
||
SCRIPT
|
||
chmod +x "${AKSHARE_DST}/run_bundled.sh"
|
||
|
||
echo ""
|
||
echo "=== Python 运行时打包完成 ==="
|
||
du -sh "${VENV_DIR}" "${AKSHARE_DST}" 2>/dev/null
|
||
echo ""
|
||
echo "总大小:"
|
||
du -sh "${FRAMEWORKS_DIR}" "${RESOURCES_DIR}" 2>/dev/null |