path_config.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330
  1. #!/usr/bin/env python3
  2. # -*- coding: utf-8 -*-
  3. """
  4. 路径配置管理工具
  5. 提供统一的路径管理,支持多账号批量处理
  6. """
  7. import json
  8. from pathlib import Path
  9. from typing import Dict, Optional, List
  10. import os
  11. class PathConfig:
  12. """路径配置管理类"""
  13. def __init__(self, account_name: Optional[str] = None, output_version: Optional[str] = None):
  14. """
  15. 初始化路径配置
  16. Args:
  17. account_name: 账号名称,如果不指定则使用默认账号或环境变量
  18. output_version: 输出版本,如果不指定则使用项目根目录名称
  19. """
  20. # 获取项目根目录
  21. self.project_root = Path(__file__).parent.parent.parent
  22. self.config_file = self.project_root / "config" / "accounts.json"
  23. # 加载配置
  24. self._load_config()
  25. # 获取数据根目录
  26. self.data_root = self._get_data_root()
  27. # 确定账号名称
  28. self.account_name = self._determine_account_name(account_name)
  29. # 确定输出版本(默认使用项目根目录名)
  30. self.output_version = self._determine_output_version(output_version)
  31. # 构建路径
  32. account_base = self.config["paths"]["account_base"]
  33. self.account_dir = self.data_root / account_base / self.account_name
  34. def _load_config(self):
  35. """加载配置文件"""
  36. if not self.config_file.exists():
  37. raise FileNotFoundError(f"配置文件不存在: {self.config_file}")
  38. with open(self.config_file, "r", encoding="utf-8") as f:
  39. self.config = json.load(f)
  40. def _get_data_root(self) -> Path:
  41. """
  42. 获取数据根目录
  43. 优先级:
  44. 1. 环境变量 DATA_ROOT
  45. 2. 配置文件 data_root
  46. 3. 默认值 project_root/data(向后兼容)
  47. """
  48. # 1. 环境变量
  49. data_root = os.environ.get("DATA_ROOT")
  50. if data_root:
  51. return Path(os.path.expanduser(data_root))
  52. # 2. 配置文件
  53. data_root_config = self.config.get("data_root")
  54. if data_root_config:
  55. # 支持 ~ 和环境变量
  56. expanded = os.path.expandvars(os.path.expanduser(data_root_config))
  57. path = Path(expanded)
  58. if path.is_absolute():
  59. return path
  60. else:
  61. return self.project_root / path
  62. # 3. 默认值(向后兼容)
  63. return self.project_root / "data"
  64. def _determine_account_name(self, account_name: Optional[str]) -> str:
  65. """
  66. 确定要使用的账号名称
  67. 优先级:
  68. 1. 函数参数指定的账号名
  69. 2. 环境变量 ACCOUNT_NAME
  70. 3. 配置文件中的默认账号
  71. Args:
  72. account_name: 参数指定的账号名
  73. Returns:
  74. 最终确定的账号名称
  75. """
  76. # 1. 参数指定
  77. if account_name:
  78. return account_name
  79. # 2. 环境变量
  80. env_account = os.environ.get("ACCOUNT_NAME")
  81. if env_account:
  82. return env_account
  83. # 3. 配置文件默认值
  84. default_account = self.config.get("default_account")
  85. if default_account:
  86. return default_account
  87. # 4. 如果都没有,抛出错误
  88. raise ValueError(
  89. "未指定账号名称!请通过以下方式之一指定:\n"
  90. "1. 参数: PathConfig(account_name='账号名')\n"
  91. "2. 环境变量: export ACCOUNT_NAME='账号名'\n"
  92. "3. 配置文件: 在 config/accounts.json 中设置 default_account"
  93. )
  94. def _determine_output_version(self, output_version: Optional[str]) -> str:
  95. """
  96. 确定输出版本
  97. 优先级:
  98. 1. 函数参数
  99. 2. 环境变量 OUTPUT_VERSION
  100. 3. 配置文件中的 output_version
  101. 4. 项目根目录名称(默认)
  102. """
  103. # 1. 参数指定
  104. if output_version:
  105. return output_version
  106. # 2. 环境变量
  107. env_version = os.environ.get("OUTPUT_VERSION")
  108. if env_version:
  109. return env_version
  110. # 3. 配置文件指定
  111. config_version = self.config.get("output_version")
  112. if config_version:
  113. return config_version
  114. # 4. 使用项目根目录名称(默认)
  115. project_dir_name = self.project_root.name
  116. return project_dir_name
  117. def _replace_version_var(self, path_template: str) -> str:
  118. """替换路径模板中的 {version} 变量"""
  119. return path_template.replace("{version}", self.output_version)
  120. def get_enabled_accounts(self) -> List[str]:
  121. """获取所有启用的账号列表"""
  122. accounts = self.config.get("accounts", [])
  123. return [acc["name"] for acc in accounts if acc.get("enabled", True)]
  124. def get_all_accounts(self) -> List[str]:
  125. """获取所有账号列表(包括未启用的)"""
  126. accounts = self.config.get("accounts", [])
  127. return [acc["name"] for acc in accounts]
  128. @property
  129. def filter_mode(self) -> str:
  130. """
  131. 获取过滤模式
  132. Returns:
  133. 过滤模式名称:
  134. - "exclude_current_posts": 过滤当前帖子ID(默认,推荐)
  135. - "time_based": 基于时间过滤
  136. - "none": 不过滤
  137. """
  138. return self.config.get("filter_mode", "exclude_current_posts")
  139. # ===== 输入路径 =====
  140. @property
  141. def current_posts_dir(self) -> Path:
  142. """当前帖子what解构结果目录"""
  143. rel_path = self.config["paths"]["input"]["current_posts"]
  144. return self.account_dir / rel_path
  145. @property
  146. def historical_posts_dir(self) -> Path:
  147. """过去帖子what解构结果目录"""
  148. rel_path = self.config["paths"]["input"]["historical_posts"]
  149. return self.account_dir / rel_path
  150. @property
  151. def pattern_cluster_file(self) -> Path:
  152. """pattern聚合结果文件"""
  153. rel_path = self.config["paths"]["input"]["pattern_cluster"]
  154. return self.account_dir / rel_path
  155. # ===== 输出路径 =====
  156. @property
  157. def intermediate_dir(self) -> Path:
  158. """中间结果目录"""
  159. rel_path = self.config["paths"]["output"]["intermediate"]
  160. rel_path = self._replace_version_var(rel_path)
  161. return self.account_dir / rel_path
  162. @property
  163. def feature_category_mapping_file(self) -> Path:
  164. """特征名称_分类映射.json"""
  165. return self.intermediate_dir / "特征名称_分类映射.json"
  166. @property
  167. def category_hierarchy_file(self) -> Path:
  168. """分类层级映射.json"""
  169. return self.intermediate_dir / "分类层级映射.json"
  170. @property
  171. def feature_source_mapping_file(self) -> Path:
  172. """特征名称_帖子来源.json"""
  173. return self.intermediate_dir / "特征名称_帖子来源.json"
  174. @property
  175. def task_list_file(self) -> Path:
  176. """当前帖子_解构任务列表.json"""
  177. return self.intermediate_dir / "当前帖子_解构任务列表.json"
  178. @property
  179. def how_results_dir(self) -> Path:
  180. """how解构结果目录"""
  181. rel_path = self.config["paths"]["output"]["how_results"]
  182. rel_path = self._replace_version_var(rel_path)
  183. return self.account_dir / rel_path
  184. @property
  185. def visualization_dir(self) -> Path:
  186. """可视化结果目录"""
  187. rel_path = self.config["paths"]["output"]["visualization"]
  188. rel_path = self._replace_version_var(rel_path)
  189. return self.account_dir / rel_path
  190. @property
  191. def visualization_file(self) -> Path:
  192. """可视化HTML文件"""
  193. return self.visualization_dir / "how解构结果_可视化.html"
  194. # ===== 工具方法 =====
  195. def ensure_dirs(self):
  196. """确保所有输出目录存在"""
  197. self.intermediate_dir.mkdir(parents=True, exist_ok=True)
  198. self.how_results_dir.mkdir(parents=True, exist_ok=True)
  199. self.visualization_dir.mkdir(parents=True, exist_ok=True)
  200. def validate_input_paths(self) -> Dict[str, bool]:
  201. """
  202. 验证输入路径是否存在
  203. Returns:
  204. 验证结果字典
  205. """
  206. results = {
  207. "当前帖子目录": self.current_posts_dir.exists(),
  208. "过去帖子目录": self.historical_posts_dir.exists(),
  209. "pattern聚合文件": self.pattern_cluster_file.exists(),
  210. }
  211. return results
  212. def print_paths(self):
  213. """打印所有路径信息(用于调试)"""
  214. print("="*60)
  215. print(f"项目根目录: {self.project_root}")
  216. print(f"项目名称: {self.project_root.name}")
  217. print(f"数据根目录: {self.data_root}")
  218. print(f"输出版本: {self.output_version}")
  219. print(f"账号: {self.account_name}")
  220. print(f"过滤模式: {self.filter_mode}")
  221. print(f"账号根目录: {self.account_dir}")
  222. print("\n输入路径:")
  223. print(f" 当前帖子目录: {self.current_posts_dir}")
  224. print(f" 过去帖子目录: {self.historical_posts_dir}")
  225. print(f" pattern聚合文件: {self.pattern_cluster_file}")
  226. print("\n输出路径:")
  227. print(f" 中间结果目录: {self.intermediate_dir}")
  228. print(f" how解构结果目录: {self.how_results_dir}")
  229. print(f" 可视化结果目录: {self.visualization_dir}")
  230. print("="*60)
  231. def check_and_print_status(self):
  232. """检查并打印路径状态"""
  233. self.print_paths()
  234. print("\n输入路径验证:")
  235. validation = self.validate_input_paths()
  236. for name, exists in validation.items():
  237. status = "✓ 存在" if exists else "✗ 不存在"
  238. print(f" {name}: {status}")
  239. if not all(validation.values()):
  240. print("\n⚠️ 警告: 部分输入路径不存在!")
  241. return False
  242. else:
  243. print("\n✓ 所有输入路径验证通过")
  244. return True
  245. def get_path_config(account_name: Optional[str] = None) -> PathConfig:
  246. """
  247. 获取路径配置对象(便捷函数)
  248. Args:
  249. account_name: 账号名称,可选
  250. Returns:
  251. PathConfig对象
  252. """
  253. return PathConfig(account_name)
  254. if __name__ == "__main__":
  255. # 测试代码
  256. import sys
  257. account = sys.argv[1] if len(sys.argv) > 1 else None
  258. try:
  259. config = PathConfig(account)
  260. config.check_and_print_status()
  261. print("\n所有启用的账号:")
  262. for acc in config.get_enabled_accounts():
  263. print(f" - {acc}")
  264. except Exception as e:
  265. print(f"错误: {e}")
  266. sys.exit(1)