Sfoglia il codice sorgente

refactor(conf): 环境变量外配到 conf/env.sh(bash+py 单源)

tianyu.chu 2 settimane fa
parent
commit
de679ec092
7 ha cambiato i file con 64 aggiunte e 65 eliminazioni
  1. 5 15
      bin/common/init.sh
  2. 2 3
      bin/publish.sh
  3. 10 0
      conf/env.sh
  4. 7 6
      dw_base/__init__.py
  5. 25 0
      dw_base/utils/env_loader.py
  6. 8 36
      kb/90-重构路线.md
  7. 7 5
      kb/92-重构进度.md

+ 5 - 15
bin/common/init.sh

@@ -5,16 +5,13 @@ if [ -z "${BASE_DIR}" ]; then
     pwd
   )
 fi
+. "${BASE_DIR}"/conf/env.sh
 . "${BASE_DIR}"/bin/common/functions.sh
 BANNED_USER="root"
-RELEASE_USER="alvis"
 USER="$(whoami)"
 CURRENT_HOST=$(hostname -s)
 RELEASE_HOST="m3"
-RELEASE_ROOT_DIR="/home/alvis/release"
 PROJECT_NAME=$(basename "${BASE_DIR}")
-PYTHON3_PATH="/usr/bin/python3"
-DATAX_HOME="${DATAX_HOME:-/opt/datax}"
 DATAX_WORKERS=(
   m3 d1 d2 d3 d4
 )
@@ -29,16 +26,13 @@ for key in ${!DATAX_WORKERS_WEIGHTS[*]}; do
     DATAX_WORKERS_QUEUE+=("$key")
   done
 done
-if [ "${USER}" == "${RELEASE_USER}" ]; then
-  LOG_ROOT_DIR="/opt/data/log"
-  IS_RUN_BY_RELEASE_USER="1"
-  pretty_print "${NORM_MGT}Project ${NORM_GRN}${PROJECT_NAME}${NORM_MGT} is running by release user ${NORM_GRN}${RELEASE_USER}"
-elif [ "${USER}" == "${BANNED_USER}" ]; then
-  LOG_ROOT_DIR="/opt/data/log"
+if [ "${USER}" == "${BANNED_USER}" ]; then
   pretty_print "${NORM_RED}Project ${NORM_GRN}${PROJECT_NAME}${NORM_RED} is running by banned user ${NORM_GRN}${BANNED_USER}${NORM_RED}, exit with error code ${NORM_GRN}18"
   exit 18
+elif [ "${USER}" == "${RELEASE_USER}" ]; then
+  IS_RUN_BY_RELEASE_USER="1"
+  pretty_print "${NORM_MGT}Project ${NORM_GRN}${PROJECT_NAME}${NORM_MGT} is running by release user ${NORM_GRN}${RELEASE_USER}"
 else
-  LOG_ROOT_DIR="/opt/data/log/users/${USER}"
   IS_RUN_BY_NORMAL_USER="1"
   . "${BASE_DIR}"/bin/common/print-constants.sh
   if [ "${CURRENT_HOST}" == "${RELEASE_HOST}" ] && [ -n "${IS_RUN_BY_NORMAL_USER}" ]; then
@@ -54,16 +48,12 @@ else
 fi
 
 export CURRENT_HOST
-export DATAX_HOME
 export DATAX_WORKERS
 export BA_LITTLE_CUTE
 export DCP_LITTLE_CUTE
 export ETL_LITTLE_CUTE
 export SKB_LITTLE_CUTE
 export REALTIME_LITTLE_CUTE
-export LOG_ROOT_DIR
-export PYTHON3_PATH
-export RELEASE_ROOT_DIR
 export IS_RUN_IN_RELEASE_DIR
 export IS_RUN_BY_RELEASE_USER
 echo -en "${NORM_GRN}"

+ 2 - 3
bin/publish.sh

@@ -12,10 +12,9 @@ if [ "root" = "$(whoami)" ]; then
   exit 18
 fi
 . "${project_root_dir}/bin/common/functions.sh"
-RELEASE_ROOT_DIR="/home/alvis/release"
-project_name=$(basename "${project_root_dir}")
-# 自定义分发所有节点的函数脚本,目前放在bin/common 目录中
 source /etc/.bash_profile
+. "${project_root_dir}/conf/env.sh"
+project_name=$(basename "${project_root_dir}")
 function display() {
   du -d 0 "${RELEASE_ROOT_DIR}/${project_name}"
   re-all du -d 0 "${RELEASE_ROOT_DIR}/${project_name}"

+ 10 - 0
conf/env.sh

@@ -0,0 +1,10 @@
+#!/bin/bash
+# poyee-data-warehouse 环境变量单源(bash + Python 双语言消费)
+# bash 入口:bin/common/init.sh / bin/publish.sh 顶部 `. "${BASE_DIR}"/conf/env.sh`
+# Python 入口:dw_base/__init__.py 通过 dw_base.utils.env_loader.bootstrap_env() 注入 os.environ
+RELEASE_USER="bigdata"
+RELEASE_ROOT_DIR="/home/bigdata/release"
+PYTHON3_PATH="/usr/bin/python3"
+DATAX_HOME="${DATAX_HOME:-/opt/datax}"
+LOG_ROOT_DIR="${HOME}/log"
+export RELEASE_USER RELEASE_ROOT_DIR PYTHON3_PATH DATAX_HOME LOG_ROOT_DIR

+ 7 - 6
dw_base/__init__.py

@@ -8,6 +8,10 @@ import time
 
 import findspark
 
+from dw_base.utils.env_loader import bootstrap_env
+
+bootstrap_env()
+
 
 def cow_says():
     os.system(f'source {PROJECT_ROOT_PATH}/bin/common/functions.sh')
@@ -17,8 +21,6 @@ def cow_says():
 os.environ['HADOOP_CONF_DIR'] = '/etc/hadoop/conf'
 # SPARK_CONF_DIR:pip pyspark 默认指向自身空 conf/,显式指到集群配置才能加载 hive-site.xml,否则 enableHiveSupport 回落 in-memory metastore
 os.environ.setdefault('SPARK_CONF_DIR', '/etc/spark/conf')
-# os.environ['HIVE_CONF_DIR'] = '/etc/hive/conf'
-# os.environ['JAVA_HOME'] = '/usr/local/java'
 os.environ["PYSPARK_DRIVER_PYTHON"] = "/usr/bin/python3"
 os.environ["PYSPARK_PYTHON"] = "/usr/bin/python3"
 os.environ['PYTHONUNBUFFERED'] = 'x'
@@ -28,13 +30,13 @@ sys.path.append(PROJECT_ROOT_PATH)
 # 公用的Spark UDF文件
 COMMON_SPARK_UDF_FILE = 'dw_base/udf/common/spark_common_udf.py'
 BANNED_USER = 'root'
-RELEASE_USER = 'alvis'
+RELEASE_USER = os.environ['RELEASE_USER']
 USER = os.environ['USER']
 HOME = os.environ['HOME']
 if USER == BANNED_USER and HOME.startswith('/home'):
     USER = os.path.basename(HOME)
 HOST = socket.gethostname()
-RELEASE_ROOT_DIR = '/home/alvis/release'
+RELEASE_ROOT_DIR = os.environ['RELEASE_ROOT_DIR']
 
 if not PROJECT_ROOT_PATH.startswith(RELEASE_ROOT_DIR) or USER != RELEASE_USER:
     DO_RESET: str = '\033[0m'
@@ -90,8 +92,8 @@ else:
     BGRD_WHT: str = ''
 IS_RUN_BY_RELEASE_USER = False
 IS_RUN_BY_NORMAL_USER = False
+LOG_ROOT_DIR = os.environ['LOG_ROOT_DIR']
 if USER == RELEASE_USER:
-    LOG_ROOT_DIR = "/opt/data/log"
     IS_RUN_BY_RELEASE_USER = True
 elif USER == BANNED_USER:
     ERROR_CODE = 18
@@ -103,7 +105,6 @@ elif USER == BANNED_USER:
     exit(ERROR_CODE)
 else:
     IS_RUN_BY_NORMAL_USER = True
-    LOG_ROOT_DIR = f'{HOME}/data/log'
     cow_says()
     print(f'{NORM_CYN}{time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())} '
           f'{NORM_MGT}Project {NORM_GRN}{PROJECT_NAME} '

+ 25 - 0
dw_base/utils/env_loader.py

@@ -0,0 +1,25 @@
+"""
+conf/env.sh 跨语言单源 bootstrap。
+
+用法:在 dw_base/__init__.py 顶部调用一次,之后 Python 侧通过 os.environ[X] 读取。
+实现:让 bash 自己解析 env.sh(`bash -c '. env.sh && env -0'`),避免重写解析器导致 bash/python 双解析漂移。
+"""
+import os
+import subprocess
+from pathlib import Path
+
+
+def bootstrap_env(env_sh_path: str = None) -> None:
+    if env_sh_path is None:
+        project_root = Path(__file__).resolve().parents[2]
+        env_sh_path = str(project_root / 'conf' / 'env.sh')
+    result = subprocess.run(
+        ['bash', '-c', f'. "{env_sh_path}" && env -0'],
+        capture_output=True, check=True,
+    )
+    for entry in result.stdout.split(b'\x00'):
+        if not entry:
+            continue
+        key, _, value = entry.decode().partition('=')
+        # setdefault: 若 shell 侧已 export(如 init.sh 已 source env.sh),保留 shell 值不覆盖
+        os.environ.setdefault(key, value)

+ 8 - 36
kb/90-重构路线.md

@@ -116,14 +116,9 @@ D 基础设施 ─────┘
 
 | 硬编码内容 | 所在位置 | 建议方案 |
 |-----------|---------|---------|
-| `DATAX_HOME=/opt/datax`(2026-04-20 默认值对齐新环境 + 改条件赋值 `${DATAX_HOME:-/opt/datax}`) | `bin/common/init.sh` | 条件赋值铺垫已就位;完整迁入 `conf/env.sh` 待 §2.1 正式推进 |
-| `PYTHON3_PATH="/usr/bin/python3"` | `bin/common/init.sh` | 移入 `conf/env.sh` |
-| `RELEASE_USER="alvis"` | `bin/common/init.sh` | 改为 `RELEASE_USER="bigdata"` 并移入 `conf/env.sh` |
-| `RELEASE_ROOT_DIR="/home/alvis/release"` | `init.sh`、`__init__.py` | 改为 `/home/bigdata/release` 并移入 `conf/env.sh` |
 | 项目部署目录 `poyee-data-warehouse/` | `bin/publish.sh`(2026-04-20 从根目录挪入 `bin/`) | 新项目发布目录为 `/home/bigdata/release/poyee-data-warehouse/` |
-| `DATAX_WORKERS=(m3 d1 d2 d3 d4)` + `DATAX_WORKERS_WEIGHTS` 权重 map | `init.sh:18-31`(含展开 `DATAX_WORKERS_QUEUE` 的循环) | workers 列表 + 权重 map **整体**移入 `conf/workers.ini`(ini 格式),`init.sh` 仅保留读取 + 展开逻辑 |
+| `DATAX_WORKERS=(m3 d1 d2 d3 d4)` + `DATAX_WORKERS_WEIGHTS` 权重 map | `init.sh`(含展开 `DATAX_WORKERS_QUEUE` 的循环) | workers 列表 + 权重 map **整体**移入 `conf/workers.ini`(ini 格式),`init.sh` 仅保留读取 + 展开逻辑 |
 | `HADOOP_CONF_DIR='/etc/hadoop/conf'` | `__init__.py` | 使用系统环境变量 |
-| `LOG_ROOT_DIR="/opt/data/log"` + whoami 分流 | `init.sh`、`__init__.py` | 删除 whoami 分支,单值改为 `${HOME}/log` 并迁入 `conf/env.sh`,见 §7.2.1 |
 | 告警 Webhook(钉钉 / 企微 Key) | `dw_base/common/alerter_constants.py`(老告警模块已于 2026-04-20 删除,含 `dingtalk_notifier.py` / `ent_interface_dingtalk*` / `bin/dingtalk-work-alert.sh`) | 新告警模块重写时 Webhook Key 外移到 `conf/alerter.ini`(**入库**——部署靠 git pull,gitignore 会拉不到;webhook key 不算高敏感,最多被拿去发垃圾消息),Python 侧改 ConfigParser 加载;`alerter_constants.py` 整个删除;新项目不再使用钉钉 |
 | Spark 默认参数(executor/driver/shuffle/sql.*) | `dw_base/spark/spark_sql.py` 构造函数 + `.config(...)` 链 | 移入 `conf/spark-defaults.conf`,SQL 文件可用 `SET` 覆盖,见 §2.3 |
 | DataX ini 路径前缀剥离 `conf/datax/config/` | `bin/datax-single-job-starter.sh`(TEMP 处理)、`bin/datax-job-config-generator.py`(`replace('conf/datax/config/', '')`)、`bin/datax-multiple-job-starter.sh`(日志路径派生) | 原目录已整体挪到 `conf/bak/` 并 gitignore,脚本里 replace 现在是 no-op 死逻辑。去除前缀假设,改为靠 ini 文件名(= 任务唯一标识,见 `21-命名规范.md` §3.9)识别用途 |
@@ -141,8 +136,7 @@ D 基础设施 ─────┘
 
 ```
 conf/
-├── env.sh                    # Shell 环境变量(路径、用户、日志目录等)
-├── env.py                    # Python 环境变量(或直接读 env.sh)
+├── env.sh                    # Shell + Python 环境变量单源(Python 侧由 dw_base/utils/env_loader.py 通过 bash 子进程解析注入 os.environ)
 ├── workers.ini               # DataX Worker 列表与权重
 ├── alerter.ini               # 告警 Webhook 配置(入库;见 §2.1)
 └── spark-defaults.conf       # Spark 默认参数(Spark 原生格式)
@@ -701,37 +695,15 @@ tests/
 - `Logging` 类定义了但很少使用
 - **建议**:统一使用 Python `logging` 模块,配置 handler 实现控制台+文件双输出
 
-### 7.2.1 日志路径按 whoami 分流的硬编码逻辑
+### 7.2.1 日志路径约定
 
-**现状:** `bin/common/init.sh` 和 `dw_base/__init__.py` 硬编码 `RELEASE_USER="alvis"`,并按 `whoami` 是否等于该用户分流日志目录
+日志文件统一落 `${LOG_ROOT_DIR}/{module}/{dt}/{file}.log`
 
-```bash
-if [ "$(whoami)" = "${RELEASE_USER}" ]; then
-    LOG_ROOT_DIR="/opt/data/log"     # release 用户走系统日志目录
-else
-    LOG_ROOT_DIR="${HOME}/data/log"  # 其他用户走自己家目录
-fi
-```
-
-**方向:删除 whoami 分流,统一落 `${HOME}/log/{module}/{dt}/{file}.log`**
-
-- release 用户 `bigdata`:`$HOME` = `/home/bigdata`,日志落 `/home/bigdata/log/{module}/{dt}/{file}.log`
-- 个人调试用户:`$HOME` = 各自家目录,日志落 `/home/{user}/log/{module}/{dt}/{file}.log`
-- `$HOME` 本身就按用户隔离,无需代码再判断 `whoami`
-
-**为什么去掉 `/opt/data/log` 这条路**:原来 release 用户走系统级 `/opt/data/log` 的理由是"生产日志不应混在个人 home",但 `bigdata` 本身就是专属调度账号,它的 `$HOME` 就是生产日志的合法归宿,不需要再多开一条系统目录。路径统一后,权限 / 轮转 / 清理策略只需按一套做。
-
-**为什么保留 `LOG_ROOT_DIR` 在 `conf/env.sh` 里**:虽然默认值只有 `${HOME}/log` 一条,但仍作为**单一默认值**外配到 `conf/env.sh`,保留后期改路径的口子(比如某天运维要求共享一块专用盘,改一处即可,无需改代码)。
-
-**为什么改目的地形态为 `{module}/{dt}/{file}.log`**:
-- 当前老结构 `/opt/data/log/datax/20260418/xxx.log` 已按 `{module}/{dt}/` 分,但不是所有入口都遵守(spark、ds 等散落在各自子结构下)
-- 新结构强制三级 `{module}/{dt}/{file}.log`,便于按天归档 + 按模块清理
-- `{module}` 取值:`datax` / `spark` / `ds` / `csv` / `export` 等顶层入口名
+- `LOG_ROOT_DIR` 默认 `${HOME}/log`,外配在 `conf/env.sh`
+- `{module}` 取值 `datax` / `spark` / `ds` / `csv` / `export` 等顶层入口名
+- `{dt}` 格式 `yyyymmdd`
 
-**代码改动:**
-1. 删除 `whoami == RELEASE_USER` 分支逻辑,`LOG_ROOT_DIR` 单值从 `conf/env.sh` 读,默认 `${HOME}/log`
-2. 日志文件路径拼接统一走一个工具函数 `log_path(module, dt, file)`(Python 和 Shell 各一份),避免入口脚本各自拼
-3. `RELEASE_USER` 作为单一来源定义在 `conf/env.sh`,与 publish.sh 共用(日志路径已不依赖它,但 publish.sh 仍要)
+**待建**:`log_path(module, dt, file)` 工具函数 Python / Shell 各一份,避免入口脚本各自拼。
 
 ### 7.3 部署改进
 

+ 7 - 5
kb/92-重构进度.md

@@ -62,8 +62,8 @@
 **目标**:消除对老环境(`alvis` 用户、`/home/alvis/release` 路径、硬编码的 worker 列表)的代码耦合。
 
 - [ ] 新建项目根 `.gitignore`(清单与注意事项见 `90-重构路线.md` §2.4)
-- [ ] 建立 `conf/env.sh`(Shell 环境变量
-- [ ] 建立 `conf/env.py` 或 Python 读 `env.sh` 的桥接
+- [x] 建立 `conf/env.sh`(Shell + Python 环境变量单源,5 vars:RELEASE_USER / RELEASE_ROOT_DIR / PYTHON3_PATH / DATAX_HOME / LOG_ROOT_DIR
+- [x] Python 侧由 `dw_base/utils/env_loader.py` 通过 bash 子进程解析注入 `os.environ`(单源,不做双份配置)
 - [ ] 建立 `conf/workers.ini`(DataX Worker 列表 + 权重 map,整体迁出 `bin/common/init.sh:18-31`)
 - [ ] 建立 `conf/alerter.ini`(企微 Webhook,**入库**;格式见 `90-重构路线.md` §2.1)
 - [ ] `dw_base/__init__.py` 瘦身(拆分初始化逻辑,见 `90-重构路线.md` §3)**— 必须先做,下面 spark-defaults 依赖瘦身后的 `PROJECT_ROOT_PATH`**
@@ -71,10 +71,10 @@
 - [ ] 改造 `dw_base/spark/spark_sql.py`:构造函数 fall back 到 conf;实现 L1(conf) < L2(SQL 内 SET,仅 `spark.sql.*` 系生效) < L3(命令行 -sc / 构造函数传参) 三级覆盖
 - [ ] 验证:同一条 SQL 在无 SET、有 SET、命令行 -sc 三种场景下 `spark.conf.get(...)` 返回值符合优先级预期
 - [ ] 验证:`SET spark.executor.memory=Xg` 不会影响已启动 executor(文档里说清楚这条限制)
-- [ ] `RELEASE_USER="alvis"` → `RELEASE_USER="bigdata"` 并迁入 `conf/env.sh`
-- [ ] `RELEASE_ROOT_DIR="/home/alvis/release"` → `/home/bigdata/release` 并迁入 `conf/env.sh`
+- [x] `RELEASE_USER="alvis"` → `RELEASE_USER="bigdata"` 并迁入 `conf/env.sh`
+- [x] `RELEASE_ROOT_DIR="/home/alvis/release"` → `/home/bigdata/release` 并迁入 `conf/env.sh`
 - [ ] `DATAX_WORKERS=(m3 d1 d2 d3 d4)` + 权重 map 迁入 `conf/workers.ini`
-- [ ] 删除 `whoami == RELEASE_USER` 分流,`LOG_ROOT_DIR` 单值 `${HOME}/log` 放入 `conf/env.sh`(见 `90-重构路线.md` §7.2.1)
+- [x] 删除 `whoami == RELEASE_USER` 分流,`LOG_ROOT_DIR` 单值 `${HOME}/log` 放入 `conf/env.sh`(见 `90-重构路线.md` §7.2.1)
 - [ ] 日志路径统一模板 `${LOG_ROOT_DIR}/{module}/{dt}/{file}.log`(3 层)
 - [ ] 实现 `log_path(module, dt, file)` 工具函数(Python / Shell 各一份,单一来源)
 - [ ] 企微 Webhook Key 从 `alerter_constants.py` 移入 `conf/alerter.ini`(钉钉已于 2026-04-20 全部删除,不再迁移)
@@ -185,3 +185,5 @@
 | 2026-04-21 | **datasource 目录结构收敛到 kb/00 §1 一处**:老 kb 内 datasource 目录树在 kb/00 §1(扁平老结构)、kb/00 §6.4(带 env 层目标态)、kb/02 §4(带 env 层)三处平行画,一改就易漏。kb/00 §1 子树补齐 env 层 + 实例举例(`postgresql/{prod,test,dev}/hobby.ini`)作为唯一真源;kb/00 §6.4 删内嵌目录示例块 + 删"当前状态:尚未落地"段(按"不标现状/目标态,以目标态为准"约定);kb/02 §4 删重复目录树,改跨文档引 `kb/00 §1` | — |
 | 2026-04-21 | **kb/31-UDF手册.md 骨架建立**(聚簇 B / §2.12(b) 启动):新建独立文档作为 `dw_base/udf/common/` 通用 UDF 自查表的稳定锚点;仅骨架(概述 + 准入规则占位 + 空表头),分类不预划由 Codex 按 `spark_common_udf.py` 实际划分登记;`kb/README.md` §3x 加入口。配套修正 `kb/90 §2.12`:(a) 文件名 `kb/31-UDF 手册.md` → `kb/31-UDF手册.md`(无空格);(b) 准入规则落点候选从 `kb/30 或 CLAUDE.md` 改为 `kb/30 或 kb/31 内部`(规范类条款不进 CLAUDE.md)。首版全量登记 + 硬约束正文待 Codex UDF 注释统一化落地后另起 changelog | — |
 | 2026-04-21 | **kb/31 表头定稿 + 合并 common/business + 取消 UDF 准入规则**:(a) kb/31 表头改为 `函数编号 \| 函数名 \| 分类 \| 入参 \| 返回 \| 描述 \| 示例 \| 补注释状态`,函数编号由 Codex 在 `spark_common_udf.py` 以顺序注释注入(不依赖行号,避免改动污染其他函数);(b) business UDF 纳入 kb/31 第二段(原"business 在子目录 README 维护,不走此表"规划取消);(c) UDF 准入规则取消(无执行主体,规则无意义)。联动 `kb/90 §2.12`:删"业务 UDF 进 common 准入标准缺失"问题项、同步新表头、删"business 不走此表"表述、删"注册准入规则"整点、关系节去"准入规则"字样;`kb/92` L181 历史条目 (c) 改软标注指向本条。另 `kb/30 §5` 删"jobs/ 里没有 CREATE TABLE 可改"冗余句与"归档策略"防御性条款 | — |
+| 2026-04-21 | **聚簇 A.1 环境变量外配到 `conf/env.sh`(bash + Python 单源)**:新建 `conf/env.sh` 统一 5 vars —— `RELEASE_USER="bigdata"`(替换 `"alvis"`)/ `RELEASE_ROOT_DIR="/home/bigdata/release"` / `PYTHON3_PATH` / `DATAX_HOME`(保持原有 `${DATAX_HOME:-/opt/datax}` 条件赋值)/ `LOG_ROOT_DIR="${HOME}/log"`。Python 侧新建 `dw_base/utils/env_loader.bootstrap_env()` 通过 `subprocess.run(['bash', '-c', '. env.sh && env -0'])` 让 bash 自己解析 env.sh,再 `os.environ.setdefault(...)` 注入(避免重写一套解析器导致 bash/py 双解析漂移;setdefault 允许 shell 侧已 export 的值优先)。`dw_base/__init__.py` 顶部加 `bootstrap_env()` 调用 + 读 `os.environ['RELEASE_USER' / 'RELEASE_ROOT_DIR' / 'LOG_ROOT_DIR']`,删 `/opt/data/log` vs `${HOME}/data/log` 的 whoami 分流(对齐 kb/90 §7.2.1 的单值决策)。`bin/common/init.sh` 顶部加 `. conf/env.sh` + 删对应硬编码变量 + 删 whoami 分流 + 删 `export LOG_ROOT_DIR / PYTHON3_PATH / RELEASE_ROOT_DIR / DATAX_HOME`(env.sh 已 export);`bin/publish.sh` 加 `. conf/env.sh` + 删 L15 `RELEASE_ROOT_DIR="/home/alvis/release"` 硬编码。联动 kb/90 §2.1 删 5 行已完成硬编码(DATAX_HOME / PYTHON3_PATH / RELEASE_USER / RELEASE_ROOT_DIR / LOG_ROOT_DIR)+ §2.2 config 结构删 `env.py`(单源不做双份)+ §7.2.1 大幅瘦身(去掉现状/为什么/代码改动三段,仅保留日志路径 `{module}/{dt}/{file}.log` 约定)+ 本文阶段 2 checklist 4 处 [x] | — |
+| 2026-04-21 | **`dw_base/common/__init__.py` 撤销删除(反转先前空壳判断)**:早先 `ls` 只看到 `__init__.py` 0 字节就判定"空壳"、按 CLAUDE.md "空 __init__.py + 无 README 下次清理直接删"规则挂入 A.1 执行清单第 9 项。`grep` 查证发现 `dw_base/common/` 下 `config_constants.py` / `container.py` / `template_constants.py` 分别被 `bin/spark-sql-starter.py` / `bin/datax-job-config-generator.py` / `dw_base/utils/log_utils.py` / `dw_base/datax/plugins/reader/mysql_reader.py` import,`__init__.py` 0 字节是 Python 正常的 package 标识(`dw_base/__init__.py` 含 bootstrap 逻辑,保持 regular package 一致性优于切 namespace package)。结论:不删。教训:空壳判断必须 `ls` 整个目录 + grep 所有子模块引用,不能只看 `__init__.py` 字节数 | — |