| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130 |
- # -*- coding: utf-8 -*-
- """
- 阈值「写回代码」
- 需求:在【指标配置】面板改数字,就是改规则本身 —— 不再有「代码默认值」和「库里自定义值」
- 两套数字打架。
- 做法:
- 1. 面板保存 → 先写库(立即生效)
- 2. 再把新值写回 rules.py 的 DEFAULT_THRESHOLDS(代码里的「标准」跟着变)
- 3. 重新加载 rules 模块
- 出厂值单独留一份快照(data/factory_thresholds.json),
- 【恢复默认】= 回到出厂值,并且同样写回代码,保证任何时刻库和代码都一致。
- """
- import importlib
- import importlib.util
- import json
- import os
- import re
- BASE = os.path.dirname(os.path.abspath(__file__))
- RULES_PY = os.path.join(BASE, "rules.py")
- DATA_DIR = os.path.join(os.path.dirname(BASE), "data")
- FACTORY_FILE = os.path.join(DATA_DIR, "factory_thresholds.json")
- def _fmt(v):
- """把值格式化成 Python 源码里的写法"""
- if isinstance(v, (list, tuple)):
- return "[" + ", ".join(_fmt(x) for x in v) + "]"
- if isinstance(v, bool):
- return "True" if v else "False"
- if isinstance(v, float):
- if abs(v - round(v)) < 1e-12:
- return str(int(round(v)))
- return "%.10g" % v # 去掉浮点噪音:0.0028000000000000004 → 0.0028
- if isinstance(v, int):
- return str(v)
- return repr(v)
- def _value_re(v):
- """匹配某个值在源码里的形式(数字/列表),用于定位替换"""
- if isinstance(v, (list, tuple)):
- return r"\[[^\[\]]*\]"
- return r"[^,\]\}\n#]+"
- def write_defaults(T):
- """
- 把 T 写回 rules.py 的 DEFAULT_THRESHOLDS。
- 只改已有键的「值」,保留键名、缩进、行尾注释和其它代码。
- 返回 (ok, msg)。
- """
- try:
- with open(RULES_PY, encoding="utf-8") as f:
- src = f.read()
- except OSError as e:
- return False, "读不到 rules.py:%s" % e
- start = src.find("DEFAULT_THRESHOLDS = {")
- if start < 0:
- return False, "rules.py 里找不到 DEFAULT_THRESHOLDS"
- end = src.find("\n}", start)
- if end < 0:
- return False, "DEFAULT_THRESHOLDS 结构异常"
- head, block, tail = src[:start], src[start:end], src[end:]
- changed = 0
- for k, v in T.items():
- # 组1=键与冒号 组2=旧值 组3=逗号 + 行尾注释 + 换行
- pat = re.compile(
- r'("' + re.escape(k) + r'"\s*:\s*)(' + _value_re(v) + r')(\s*,[^\n]*\n)')
- m = pat.search(block)
- if not m:
- continue
- if m.group(2).strip() == _fmt(v):
- continue
- block = block[:m.start()] + m.group(1) + _fmt(v) + m.group(3) + block[m.end():]
- changed += 1
- if not changed:
- return True, "代码默认值已是最新,无需变更"
- try:
- with open(RULES_PY + ".bak", "w", encoding="utf-8") as f:
- f.write(src)
- tmp = RULES_PY + ".tmp"
- with open(tmp, "w", encoding="utf-8") as f:
- f.write(head + block + tail)
- os.replace(tmp, RULES_PY)
- except OSError as e:
- return False, "写 rules.py 失败:%s" % e
- # 关键:macOS 自带 Python 把 .pyc 放在 ~/Library/Caches/com.apple.python,
- # 不在这里清掉的话,同尺寸同秒的改动会被旧字节码顶掉(踩过)
- try:
- cache = importlib.util.cache_from_source(RULES_PY)
- if os.path.exists(cache):
- os.remove(cache)
- except Exception:
- pass
- try:
- import rules
- importlib.reload(rules)
- except Exception as e:
- return False, "已写入但重新加载失败:%s(重启服务后生效)" % e
- return True, "已同步写回代码(%d 项)" % changed
- def get_factory():
- """出厂阈值:首次运行时从代码快照一份,之后固定不变"""
- os.makedirs(DATA_DIR, exist_ok=True)
- if os.path.isfile(FACTORY_FILE):
- try:
- with open(FACTORY_FILE, encoding="utf-8") as f:
- return json.load(f)
- except Exception:
- pass
- import rules
- snap = dict(rules.DEFAULT_THRESHOLDS)
- try:
- with open(FACTORY_FILE, "w", encoding="utf-8") as f:
- json.dump(snap, f, ensure_ascii=False, indent=1)
- except OSError:
- pass
- return snap
|