diff --git a/src/heurams/__init__.py b/src/heurams/__init__.py index 7d381b6..5ca3b70 100644 --- a/src/heurams/__init__.py +++ b/src/heurams/__init__.py @@ -2,5 +2,6 @@ print("欢迎使用 HeurAMS 及其组件!") # 补充日志记录 from heurams.services.logger import get_logger + logger = get_logger(__name__) logger.info("欢迎使用 HeurAMS 及其组件!") diff --git a/src/heurams/interface/__main__.py b/src/heurams/interface/__main__.py index 79032ef..6bbe0bd 100644 --- a/src/heurams/interface/__main__.py +++ b/src/heurams/interface/__main__.py @@ -8,6 +8,7 @@ from heurams.services.logger import get_logger logger = get_logger(__name__) + class HeurAMSApp(App): TITLE = "潜进" CSS_PATH = "css/main.tcss" @@ -37,8 +38,10 @@ class HeurAMSApp(App): print("DO NOTHING") self.refresh() + def environment_check(): from pathlib import Path + logger.debug("检查环境路径") for i in config_var.get()["paths"].values(): diff --git a/src/heurams/interface/screens/about.py b/src/heurams/interface/screens/about.py index 15cbf70..ac3ba7f 100644 --- a/src/heurams/interface/screens/about.py +++ b/src/heurams/interface/screens/about.py @@ -34,12 +34,12 @@ class AboutScreen(Screen): 开发人员: - - [@pluvium27](https://github.com/pluvium27) (Wang Zhiyu) +- Wang Zhiyu([@pluvium27](https://github.com/pluvium27)): 项目作者 特别感谢: - - [Piotr A. Woźniak](https://supermemo.guru/wiki/Piotr_Wozniak): SuperMemo-2 算法 - - [Thoughts Memo](https://www.zhihu.com/people/L.M.Sherlock): 文献参考 +- [Piotr A. Woźniak](https://supermemo.guru/wiki/Piotr_Wozniak): SuperMemo-2 算法 +- [Thoughts Memo](https://www.zhihu.com/people/L.M.Sherlock): 文献参考 # 参与贡献 @@ -47,7 +47,7 @@ class AboutScreen(Screen): 通过我们协力开发的软件为所有人谋取福祉. -此项目不是 KDE 软件, 但上述工作不可避免地让我们确立了和 KDE 宣言相同的下列价值观: +上述工作不可避免地让我们确立了下列价值观 (取自 KDE 宣言): - 开放治理 确保更多人能参与我们的领导和决策进程; @@ -68,8 +68,13 @@ class AboutScreen(Screen): 我们的共同目标是为人人带来高品质的辅助记忆 & 学习软件. 不管您来自何方, 我们都欢迎您加入社区并做出贡献. - """ + + # """ + # 学术数据 + + # "潜进" 的用户数据可用于科学方面的研究, 我们将在未来版本添加学术数据的收集和展示平台 + # """ yield Markdown(about_text, classes="about-markdown") yield Button( diff --git a/src/heurams/interface/screens/dashboard.py b/src/heurams/interface/screens/dashboard.py index fec13e0..6721c5f 100644 --- a/src/heurams/interface/screens/dashboard.py +++ b/src/heurams/interface/screens/dashboard.py @@ -24,8 +24,10 @@ import pathlib logger = get_logger(__name__) + class DashboardScreen(Screen): SUB_TITLE = "仪表盘" + def compose(self) -> ComposeResult: yield Header(show_clock=True) yield ScrollableContainer( @@ -71,6 +73,7 @@ class DashboardScreen(Screen): nextdate = 0x3F3F3F3F for i in electron_dict.values(): i: pt.Electron + logger.debug(i, i.is_due()) if i.is_due(): is_due = 1 if i.is_activated(): diff --git a/src/heurams/interface/screens/memorizor.py b/src/heurams/interface/screens/memorizor.py index d7cf15c..3c51647 100644 --- a/src/heurams/interface/screens/memorizor.py +++ b/src/heurams/interface/screens/memorizor.py @@ -18,8 +18,10 @@ class AtomState(Enum): FAILED = auto() NORMAL = auto() + logger = get_logger(__name__) + class MemScreen(Screen): BINDINGS = [ ("q", "pop_screen", "返回"), @@ -45,6 +47,7 @@ class MemScreen(Screen): self.phaser = Phaser(atoms) # logger.debug(self.phaser.state) self.procession: Procession = self.phaser.current_procession() # type: ignore + self.atom: pt.Atom = self.procession.current_atom # logger.debug(self.phaser.state) # self.procession.forward(1) for i in atoms: @@ -58,12 +61,12 @@ class MemScreen(Screen): try: logger.debug(self.phaser.state) logger.debug(self.procession.cursor) - logger.debug(self.procession.current_atom) - self.fission = Fission(self.procession.current_atom, self.phaser.state) + logger.debug(self.atom) + self.fission = Fission(self.atom, self.phaser.state) puzzle_debug = next(self.fission.generate()) - #logger.debug(puzzle_debug) + # logger.debug(puzzle_debug) return shim.puzzle2widget[puzzle_debug["puzzle"]]( - atom=self.procession.current_atom, alia=puzzle_debug["alia"] + atom=self.atom, alia=puzzle_debug["alia"] ) except (KeyError, StopIteration, AttributeError) as e: logger.debug(f"调度展开出错: {e}") @@ -74,7 +77,7 @@ class MemScreen(Screen): yield Header(show_clock=True) with ScrollableContainer(): yield Label(self._get_progress_text(), id="progress") - + # self.mount(self.current_widget()) # type: ignore yield ScrollableContainer(id="puzzle-container") # yield Button("重新学习此单元", id="re-recognize", variant="warning") @@ -85,7 +88,7 @@ class MemScreen(Screen): def update_display(self): progress_widget = self.query_one("#progress") - progress_widget.update(self._get_progress_text()) # type: ignore + progress_widget.update(self._get_progress_text()) # type: ignore def load_puzzle(self): container = self.query_one("#puzzle-container") @@ -98,6 +101,7 @@ class MemScreen(Screen): for i in container.children: i.remove() from heurams.interface.widgets.finished import Finished + container.mount(Finished()) def on_button_pressed(self, event): @@ -108,11 +112,12 @@ class MemScreen(Screen): return forwards = 1 if new_rating >= 4 else 0 self.rating = -1 + logger.debug(f"试图前进: {"允许" if forwards else "禁止"}") if forwards: ret = self.procession.forward(1) - if ret == 0: + if ret == 0: # 若结束了此次队列 self.procession = self.phaser.current_procession() # type: ignore - if self.procession == 0: + if self.procession == 0: # 若所有队列都结束了 logger.debug(f"记忆进程结束") for i in self.atoms: i: pt.Atom @@ -121,10 +126,10 @@ class MemScreen(Screen): return else: logger.debug(f"建立新队列 {self.procession.phase}") - else: + self.load_puzzle() + else: # 若不通过 self.procession.append() self.update_display() - self.load_puzzle() def action_play_voice(self): """朗读当前内容""" diff --git a/src/heurams/interface/screens/nucreator.py b/src/heurams/interface/screens/nucreator.py index 47e662b..cedec3f 100644 --- a/src/heurams/interface/screens/nucreator.py +++ b/src/heurams/interface/screens/nucreator.py @@ -94,28 +94,28 @@ class NucleonCreatorScreen(Screen): template_select = self.query_one("#template_select") author_input = self.query_one("#author_input") desc_input = self.query_one("#desc_input") - - name = name_input.value.strip() # type: ignore - author = author_input.value.strip() # type: ignore - desc = desc_input.value.strip() # type: ignore - selected = template_select.value # type: ignore - + + name = name_input.value.strip() # type: ignore + author = author_input.value.strip() # type: ignore + desc = desc_input.value.strip() # type: ignore + selected = template_select.value # type: ignore + # 验证 if not name: self.notify("单元集名称不能为空", severity="error") return - + # 获取配置路径 config = config_var.get() nucleon_dir = Path(config["paths"]["nucleon_dir"]) template_dir = Path(config["paths"]["template_dir"]) - + # 检查文件是否已存在 nucleon_path = nucleon_dir / f"{name}.toml" if nucleon_path.exists(): self.notify(f"单元集 '{name}' 已存在", severity="error") return - + # 确定模板文件 if selected is None: self.notify("请选择一个模板", severity="error") @@ -123,7 +123,8 @@ class NucleonCreatorScreen(Screen): # selected 是描述字符串,格式如 "描述 (filename.toml)" # 提取文件名 import re - match = re.search(r'\(([^)]+)\)$', selected) + + match = re.search(r"\(([^)]+)\)$", selected) if not match: self.notify("模板选择格式无效", severity="error") return @@ -132,15 +133,15 @@ class NucleonCreatorScreen(Screen): if not template_path.exists(): self.notify(f"模板文件不存在: {template_filename}", severity="error") return - + # 加载模板 try: - with open(template_path, 'r', encoding='utf-8') as f: + with open(template_path, "r", encoding="utf-8") as f: template_data = toml.load(f) except Exception as e: self.notify(f"加载模板失败: {e}", severity="error") return - + # 更新元数据 metadata = template_data.get("__metadata__", {}) attribution = metadata.get("attribution", {}) @@ -153,17 +154,17 @@ class NucleonCreatorScreen(Screen): attribution["version"] = ver metadata["attribution"] = attribution template_data["__metadata__"] = metadata - + # 确保 nucleon_dir 存在 nucleon_dir.mkdir(parents=True, exist_ok=True) - + # 写入新文件 try: - with open(nucleon_path, 'w', encoding='utf-8') as f: + with open(nucleon_path, "w", encoding="utf-8") as f: toml.dump(template_data, f) except Exception as e: self.notify(f"保存单元集失败: {e}", severity="error") return - + self.notify(f"单元集 '{name}' 创建成功") self.app.pop_screen() diff --git a/src/heurams/interface/screens/preparation.py b/src/heurams/interface/screens/preparation.py index ab64444..a850e87 100644 --- a/src/heurams/interface/screens/preparation.py +++ b/src/heurams/interface/screens/preparation.py @@ -19,7 +19,7 @@ from heurams.context import * class PreparationScreen(Screen): SUB_TITLE = "准备记忆集" - + BINDINGS = [ ("q", "go_back", "返回"), ("p", "precache", "预缓存音频"), @@ -70,7 +70,6 @@ class PreparationScreen(Screen): def action_go_back(self): self.app.pop_screen() - def action_precache(self): from ..screens.precache import PrecachingScreen diff --git a/src/heurams/interface/widgets/base_puzzle_widget.py b/src/heurams/interface/widgets/base_puzzle_widget.py index e43e710..332115b 100644 --- a/src/heurams/interface/widgets/base_puzzle_widget.py +++ b/src/heurams/interface/widgets/base_puzzle_widget.py @@ -1,3 +1,5 @@ +from typing import Iterable +from textual.app import ComposeResult from textual.widget import Widget import heurams.kernel.particles as pt @@ -22,3 +24,9 @@ class BasePuzzleWidget(Widget): markup=markup ) self.atom = atom + + def compose(self) -> Iterable[Widget]: + return super().compose() + + def handler(self, rating) -> None: + pass \ No newline at end of file diff --git a/src/heurams/interface/widgets/cloze_puzzle.py b/src/heurams/interface/widgets/cloze_puzzle.py index 5358e54..3280186 100644 --- a/src/heurams/interface/widgets/cloze_puzzle.py +++ b/src/heurams/interface/widgets/cloze_puzzle.py @@ -15,12 +15,14 @@ from typing import TypedDict logger = get_logger(__name__) + class Setting(TypedDict): __origin__: str __hint__: str text: str delimiter: str - min_denominator: str + min_denominator: str + class ClozePuzzle(BasePuzzleWidget): @@ -58,7 +60,7 @@ class ClozePuzzle(BasePuzzleWidget): min_denominator=int(setting["min_denominator"]), ) self.puzzle.refresh() - self.ans = copy.copy(self.puzzle.answer) # 乱序 + self.ans = copy.copy(self.puzzle.answer) # 乱序 random.shuffle(self.ans) def compose(self): @@ -78,7 +80,6 @@ class ClozePuzzle(BasePuzzleWidget): preview = self.query_one("#inputpreview") preview.update(f"当前输入: {self.inputlist}") # type: ignore - def on_button_pressed(self, event: Button.Pressed) -> None: button_id = event.button.id @@ -87,16 +88,22 @@ class ClozePuzzle(BasePuzzleWidget): self.inputlist.pop() self.update_display() else: - answer_text = self.hashmap[button_id[7:]] # type: ignore + answer_text = self.hashmap[button_id[7:]] # type: ignore self.inputlist.append(answer_text) self.update_display() if len(self.inputlist) >= len(self.puzzle.answer): is_correct = self.inputlist == self.puzzle.answer rating = 4 if is_correct else 2 - - self.screen.rating = rating # type: ignore + self.handler(rating) + self.screen.rating = rating # type: ignore if not is_correct: self.inputlist = [] self.update_display() + + def handler(self, rating): + if self.atom.lock(): + pass + else: + self.atom.minimize(rating) \ No newline at end of file diff --git a/src/heurams/interface/widgets/mcq_puzzle.py b/src/heurams/interface/widgets/mcq_puzzle.py index b0d11b5..8c3e088 100644 --- a/src/heurams/interface/widgets/mcq_puzzle.py +++ b/src/heurams/interface/widgets/mcq_puzzle.py @@ -14,6 +14,7 @@ from heurams.services.logger import get_logger logger = get_logger(__name__) + class Setting(TypedDict): __origin__: str __hint__: str @@ -49,6 +50,7 @@ class MCQPuzzle(BasePuzzleWidget): self.alia = alia self.hashmap = dict() self.cursor = 0 + self.atom = atom self._load() def _load(self): @@ -79,7 +81,7 @@ class MCQPuzzle(BasePuzzleWidget): yield Button("退格", id="delete") - def update_display(self, error = 0): + def update_display(self, error=0): # 更新预览标签 preview = self.query_one("#inputpreview") preview.update(f"当前输入: {self.inputlist}") # type: ignore @@ -113,7 +115,7 @@ class MCQPuzzle(BasePuzzleWidget): rating = 4 if is_correct else 2 self.screen.rating = rating # type: ignore - + self.handler(rating) # 重置输入(如果回答错误) if not is_correct: self.inputlist = [] @@ -138,7 +140,7 @@ class MCQPuzzle(BasePuzzleWidget): for button in buttons_to_remove: logger.info(button) - container.remove_children("#"+button.id) # type: ignore + container.remove_children("#" + button.id) # type: ignore # 添加当前题目的选项按钮 current_question_index = len(self.inputlist) @@ -150,3 +152,10 @@ class MCQPuzzle(BasePuzzleWidget): self.hashmap[button_id] = option new_button = Button(option, id=button_id) container.mount(new_button) + + def handler(self, rating): + if self.atom.lock(): + pass + else: + self.atom.minimize(rating) + \ No newline at end of file diff --git a/src/heurams/interface/widgets/recognition.py b/src/heurams/interface/widgets/recognition.py index 38a9da8..67cf3cd 100644 --- a/src/heurams/interface/widgets/recognition.py +++ b/src/heurams/interface/widgets/recognition.py @@ -13,7 +13,9 @@ import re from .base_puzzle_widget import BasePuzzleWidget from typing import TypedDict, List from textual.message import Message +from heurams.services.logger import get_logger +logger = get_logger(__name__) class RecognitionConfig(TypedDict): __origin__: str @@ -100,3 +102,11 @@ class Recognition(BasePuzzleWidget): def on_button_pressed(self, event: Button.Pressed) -> None: if event.button.id == "ok": self.screen.rating = 5 # type: ignore + self.handler(5) + + def handler(self, rating): + if not self.atom.registry["electron"].is_activated() and not self.atom.registry["runtime"]["locked"]: + self.atom.registry["electron"].activate() + logger.debug(f"激活原子 {self.atom}") + self.atom.lock(1) + self.atom.minimize(5) diff --git a/src/heurams/kernel/algorithms/base.py b/src/heurams/kernel/algorithms/base.py index 8a4c194..c9f1dbd 100644 --- a/src/heurams/kernel/algorithms/base.py +++ b/src/heurams/kernel/algorithms/base.py @@ -33,27 +33,37 @@ class BaseAlgorithm: cls, algodata: dict, feedback: int = 5, is_new_activation: bool = False ) -> None: """迭代记忆数据""" - logger.debug("BaseAlgorithm.revisor 被调用,algodata keys: %s, feedback: %d, is_new_activation: %s", - list(algodata.keys()) if algodata else [], feedback, is_new_activation) + logger.debug( + "BaseAlgorithm.revisor 被调用,algodata keys: %s, feedback: %d, is_new_activation: %s", + list(algodata.keys()) if algodata else [], + feedback, + is_new_activation, + ) pass @classmethod def is_due(cls, algodata) -> int: """是否应该复习""" - logger.debug("BaseAlgorithm.is_due 被调用,algodata keys: %s", - list(algodata.keys()) if algodata else []) + logger.debug( + "BaseAlgorithm.is_due 被调用,algodata keys: %s", + list(algodata.keys()) if algodata else [], + ) return 1 @classmethod def rate(cls, algodata) -> str: """获取评分信息""" - logger.debug("BaseAlgorithm.rate 被调用,algodata keys: %s", - list(algodata.keys()) if algodata else []) + logger.debug( + "BaseAlgorithm.rate 被调用,algodata keys: %s", + list(algodata.keys()) if algodata else [], + ) return "" @classmethod def nextdate(cls, algodata) -> int: """获取下一次记忆时间戳""" - logger.debug("BaseAlgorithm.nextdate 被调用,algodata keys: %s", - list(algodata.keys()) if algodata else []) + logger.debug( + "BaseAlgorithm.nextdate 被调用,algodata keys: %s", + list(algodata.keys()) if algodata else [], + ) return -1 diff --git a/src/heurams/kernel/algorithms/sm2.py b/src/heurams/kernel/algorithms/sm2.py index db32421..74e4b63 100644 --- a/src/heurams/kernel/algorithms/sm2.py +++ b/src/heurams/kernel/algorithms/sm2.py @@ -41,8 +41,12 @@ class SM2Algorithm(BaseAlgorithm): Args: quality (int): 记忆保留率量化参数 """ - logger.debug("SM2.revisor 开始,feedback: %d, is_new_activation: %s", feedback, is_new_activation) - + logger.debug( + "SM2.revisor 开始,feedback: %d, is_new_activation: %s", + feedback, + is_new_activation, + ) + if feedback == -1: logger.debug("feedback 为 -1,跳过更新") return @@ -81,24 +85,32 @@ class SM2Algorithm(BaseAlgorithm): algodata[cls.algo_name]["interval"] = round( algodata[cls.algo_name]["interval"] * algodata[cls.algo_name]["efactor"] ) - logger.debug("rept>1,计算 interval: %d", algodata[cls.algo_name]["interval"]) + logger.debug( + "rept>1,计算 interval: %d", algodata[cls.algo_name]["interval"] + ) algodata[cls.algo_name]["last_date"] = timer.get_daystamp() algodata[cls.algo_name]["next_date"] = ( timer.get_daystamp() + algodata[cls.algo_name]["interval"] ) algodata[cls.algo_name]["last_modify"] = timer.get_timestamp() - - logger.debug("更新日期: last_date=%d, next_date=%d, last_modify=%f", - algodata[cls.algo_name]["last_date"], - algodata[cls.algo_name]["next_date"], - algodata[cls.algo_name]["last_modify"]) + + logger.debug( + "更新日期: last_date=%d, next_date=%d, last_modify=%f", + algodata[cls.algo_name]["last_date"], + algodata[cls.algo_name]["next_date"], + algodata[cls.algo_name]["last_modify"], + ) @classmethod def is_due(cls, algodata): result = algodata[cls.algo_name]["next_date"] <= timer.get_daystamp() - logger.debug("SM2.is_due: next_date=%d, current_daystamp=%d, result=%s", - algodata[cls.algo_name]["next_date"], timer.get_daystamp(), result) + logger.debug( + "SM2.is_due: next_date=%d, current_daystamp=%d, result=%s", + algodata[cls.algo_name]["next_date"], + timer.get_daystamp(), + result, + ) return result @classmethod diff --git a/src/heurams/kernel/particles/__init__.py b/src/heurams/kernel/particles/__init__.py index 4fca964..ac90bd9 100644 --- a/src/heurams/kernel/particles/__init__.py +++ b/src/heurams/kernel/particles/__init__.py @@ -5,6 +5,7 @@ Particle 模块 - 粒子对象系统 """ from heurams.services.logger import get_logger + logger = get_logger(__name__) logger.debug("粒子模块已加载") diff --git a/src/heurams/kernel/particles/atom.py b/src/heurams/kernel/particles/atom.py index ac62556..3c97ea4 100644 --- a/src/heurams/kernel/particles/atom.py +++ b/src/heurams/kernel/particles/atom.py @@ -12,6 +12,9 @@ from heurams.services.logger import get_logger logger = get_logger(__name__) +class AtomRegister_runtime(TypedDict): + locked: bool # 只读锁定标识符 + min_rate: int # 最低评分 class AtomRegister(TypedDict): nucleon: Nucleon @@ -23,7 +26,7 @@ class AtomRegister(TypedDict): orbital: Orbital orbital_path: pathlib.Path orbital_fmt: str - runtime: dict + runtime: AtomRegister_runtime class Atom: @@ -51,6 +54,7 @@ class Atom: "orbital": None, "orbital_path": None, # 允许设置为 None, 此时使用 nucleon 文件内的推荐配置 "orbital_fmt": "toml", + "runtime": {"locked": False, "min_rate": 0x3f3f3f3f} } self.do_eval() logger.debug("Atom 初始化完成") @@ -65,6 +69,38 @@ class Atom: logger.error("尝试链接不受支持的键: '%s'", key) raise ValueError("不受支持的原子元数据链接操作") + def minimize(self, rating): + """效果等同于 self.registry['runtime']['min_rate'] = min(rating, self.registry['runtime']['min_rate']) + + Args: + rating (int): 评分 + """ + self.registry['runtime']['min_rate'] = min(rating, self.registry['runtime']['min_rate']) + + def lock(self, locked = -1): + """锁定, 效果等同于 self.registry['runtime']['locked'] = locked 或者返回是否锁定 + """ + if locked == 1: + self.registry['runtime']['locked'] = True + return 1 + elif locked == 0: + self.registry['runtime']['locked'] = False + return 1 + elif locked == -1: + return self.registry['runtime']["locked"] + return 0 + + def revise(self): + """执行最终评分 + PuzzleWidget 的 handler 除了测试, 严禁直接执行 Electron 的 revisor 函数, 否则造成逻辑混乱 + """ + if self.registry["runtime"]["locked"]: + logger.debug(f"允许总评分: {self.registry['runtime']['min_rate']}") + self.registry["electron"].revisor(self.registry['runtime']["min_rate"]) + else: + logger.debug("禁止总评分") + + def do_eval(self): """ 执行并以结果替换当前单元的所有 eval 语句 @@ -85,13 +121,17 @@ class Atom: # 如果无法获取配置或元数据,使用空字典 logger.debug("无法获取配置或元数据,使用空字典") pass - try: + try: eval_value = eval(s) if isinstance(eval_value, (list, dict)): ret = eval_value else: ret = str(eval_value) - logger.debug("eval 执行成功: '%s' -> '%s'", s, str(ret)[:50] + '...' if len(ret) > 50 else ret) + logger.debug( + "eval 执行成功: '%s' -> '%s'", + s, + str(ret)[:50] + "..." if len(ret) > 50 else ret, + ) except Exception as e: ret = f"此 eval 实例发生错误: {e}" logger.warning("eval 执行错误: '%s' -> %s", s, e) @@ -117,16 +157,16 @@ class Atom: # 如果 nucleon 存在且有 do_eval 方法,调用它 nucleon = self.registry["nucleon"] - if nucleon is not None and hasattr(nucleon, 'do_eval'): + if nucleon is not None and hasattr(nucleon, "do_eval"): nucleon.do_eval() logger.debug("已调用 nucleon.do_eval") - + # 如果 electron 存在且其 algodata 包含 eval 字符串,遍历它 electron = self.registry["electron"] - if electron is not None and hasattr(electron, 'algodata'): + if electron is not None and hasattr(electron, "algodata"): traverse(electron.algodata, eval_with_env) logger.debug("已处理 electron algodata eval") - + # 如果 orbital 存在且是字典,遍历它 orbital = self.registry["orbital"] if orbital is not None and isinstance(orbital, dict): @@ -173,7 +213,9 @@ class Atom: raise KeyError(f"不支持的键: {key}") def __setitem__(self, key, value): - logger.debug("Atom.__setitem__: key='%s', value type: %s", key, type(value).__name__) + logger.debug( + "Atom.__setitem__: key='%s', value type: %s", key, type(value).__name__ + ) if key in self.registry: self.registry[key] = value logger.debug("键 '%s' 已设置", key) diff --git a/src/heurams/kernel/particles/electron.py b/src/heurams/kernel/particles/electron.py index 15c2780..ecd10b2 100644 --- a/src/heurams/kernel/particles/electron.py +++ b/src/heurams/kernel/particles/electron.py @@ -17,7 +17,9 @@ class Electron: algodata: 算法数据字典, 包含算法的各项参数和设置 algo: 使用的算法模块标识 """ - logger.debug("创建 Electron 实例,ident: '%s', algo_name: '%s'", ident, algo_name) + logger.debug( + "创建 Electron 实例,ident: '%s', algo_name: '%s'", ident, algo_name + ) self.algodata = algodata self.ident = ident self.algo = algorithms[algo_name] @@ -31,11 +33,15 @@ class Electron: self._default_init(self.algo.defaults) else: logger.debug("算法数据已存在,跳过默认初始化") - logger.debug("Electron 初始化完成,algodata keys: %s", list(self.algodata.keys())) + logger.debug( + "Electron 初始化完成,algodata keys: %s", list(self.algodata.keys()) + ) def _default_init(self, defaults: dict): """默认初始化包装""" - logger.debug("Electron._default_init: 使用默认值,keys: %s", list(defaults.keys())) + logger.debug( + "Electron._default_init: 使用默认值,keys: %s", list(defaults.keys()) + ) self.algodata[self.algo.algo_name] = defaults.copy() def activate(self): @@ -88,10 +94,16 @@ class Electron: quality (int): 记忆保留率量化参数 (0-5) is_new_activation (bool): 是否为初次激活 """ - logger.debug("Electron.revisor: ident='%s', quality=%d, is_new_activation=%s", - self.ident, quality, is_new_activation) + logger.debug( + "Electron.revisor: ident='%s', quality=%d, is_new_activation=%s", + self.ident, + quality, + is_new_activation, + ) self.algo.revisor(self.algodata, quality, is_new_activation) - logger.debug("revisor 完成,更新后的 algodata: %s", self.algodata.get(self.algo, {})) + logger.debug( + "revisor 完成,更新后的 algodata: %s", self.algodata.get(self.algo, {}) + ) def __str__(self): return ( diff --git a/src/heurams/kernel/particles/loader.py b/src/heurams/kernel/particles/loader.py index cf701dd..a7ce025 100644 --- a/src/heurams/kernel/particles/loader.py +++ b/src/heurams/kernel/particles/loader.py @@ -40,9 +40,7 @@ def load_nucleon(path: pathlib.Path, fmt="toml"): logger.debug("处理项目: %s", item) lst.append( ( - Nucleon( - item, attr, deepcopy(nested_data["__metadata__"]) - ), + Nucleon(item, attr, deepcopy(nested_data["__metadata__"])), deepcopy(nested_data["__metadata__"]["orbital"]), ) ) diff --git a/src/heurams/kernel/particles/nucleon.py b/src/heurams/kernel/particles/nucleon.py index a403d7f..cdd090b 100644 --- a/src/heurams/kernel/particles/nucleon.py +++ b/src/heurams/kernel/particles/nucleon.py @@ -14,8 +14,12 @@ class Nucleon: payload: 记忆内容信息 metadata: 可选元数据信息 """ - logger.debug("创建 Nucleon 实例,ident: '%s', payload keys: %s, metadata keys: %s", - ident, list(payload.keys()) if payload else [], list(metadata.keys()) if metadata else []) + logger.debug( + "创建 Nucleon 实例,ident: '%s', payload keys: %s, metadata keys: %s", + ident, + list(payload.keys()) if payload else [], + list(metadata.keys()) if metadata else [], + ) self.metadata = metadata self.payload = payload self.ident = ident @@ -28,7 +32,9 @@ class Nucleon: return self.ident if key in self.payload: value = self.payload[key] - logger.debug("返回 payload['%s'], value type: %s", key, type(value).__name__) + logger.debug( + "返回 payload['%s'], value type: %s", key, type(value).__name__ + ) return value else: logger.error("键 '%s' 未在 payload 中找到", key) @@ -59,7 +65,11 @@ class Nucleon: ret = str(eval_value) else: ret = eval_value - logger.debug("eval 执行成功: '%s' -> '%s'", s, str(ret)[:50] + '...' if len(ret) > 50 else ret) + logger.debug( + "eval 执行成功: '%s' -> '%s'", + s, + str(ret)[:50] + "..." if len(ret) > 50 else ret, + ) except Exception as e: ret = f"此 eval 实例发生错误: {e}" logger.warning("eval 执行错误: '%s' -> %s", s, e) diff --git a/src/heurams/kernel/puzzles/__init__.py b/src/heurams/kernel/puzzles/__init__.py index 0ecc628..2d4342e 100644 --- a/src/heurams/kernel/puzzles/__init__.py +++ b/src/heurams/kernel/puzzles/__init__.py @@ -5,6 +5,7 @@ Puzzle 模块 - 谜题生成系统 """ from heurams.services.logger import get_logger + logger = get_logger(__name__) from .base import BasePuzzle @@ -41,7 +42,9 @@ def create_by_dict(config_dict: dict) -> BasePuzzle: Raises: ValueError: 当配置无效时抛出 """ - logger.debug("puzzles.create_by_dict: config_dict keys=%s", list(config_dict.keys())) + logger.debug( + "puzzles.create_by_dict: config_dict keys=%s", list(config_dict.keys()) + ) puzzle_type = config_dict.get("type") if puzzle_type == "cloze": diff --git a/src/heurams/kernel/puzzles/cloze.py b/src/heurams/kernel/puzzles/cloze.py index 96035f1..5ee5db4 100644 --- a/src/heurams/kernel/puzzles/cloze.py +++ b/src/heurams/kernel/puzzles/cloze.py @@ -14,8 +14,12 @@ class ClozePuzzle(BasePuzzle): """ def __init__(self, text: str, min_denominator: int, delimiter: str = "/"): - logger.debug("ClozePuzzle.__init__: text length=%d, min_denominator=%d, delimiter='%s'", - len(text), min_denominator, delimiter) + logger.debug( + "ClozePuzzle.__init__: text length=%d, min_denominator=%d, delimiter='%s'", + len(text), + min_denominator, + delimiter, + ) self.text = text self.min_denominator = min_denominator self.wording = "填空题 - 尚未刷新谜题" diff --git a/src/heurams/kernel/puzzles/mcq.py b/src/heurams/kernel/puzzles/mcq.py index 3f2245d..68718bc 100644 --- a/src/heurams/kernel/puzzles/mcq.py +++ b/src/heurams/kernel/puzzles/mcq.py @@ -6,6 +6,7 @@ from heurams.services.logger import get_logger logger = get_logger(__name__) + class MCQPuzzle(BasePuzzle): """选择题谜题生成器 @@ -37,7 +38,12 @@ class MCQPuzzle(BasePuzzle): max_riddles_num: 每次生成的最大题目数量, 范围限制在1-5之间 prefix: 题目前缀文本, 会显示在每个题目之前 """ - logger.debug("MCQPuzzle.__init__: mapping size=%d, jammer size=%d, max_riddles_num=%d", len(mapping), len(jammer), max_riddles_num) + logger.debug( + "MCQPuzzle.__init__: mapping size=%d, jammer size=%d, max_riddles_num=%d", + len(mapping), + len(jammer), + max_riddles_num, + ) self.prefix = prefix self.mapping = mapping self.max_riddles_num = max(1, min(max_riddles_num, 5)) diff --git a/src/heurams/kernel/reactor/fission.py b/src/heurams/kernel/reactor/fission.py index 3aac045..e7c4140 100644 --- a/src/heurams/kernel/reactor/fission.py +++ b/src/heurams/kernel/reactor/fission.py @@ -11,7 +11,7 @@ class Fission: def __init__(self, atom: pt.Atom, phase=PhaserState.RECOGNITION): self.logger = get_logger(__name__) self.atom = atom - #print(f"{phase.value}") + # print(f"{phase.value}") self.orbital_schedule = atom.registry["orbital"]["schedule"][phase.value] # type: ignore self.orbital_puzzles = atom.registry["orbital"]["puzzles"] # print(self.orbital_schedule) @@ -38,7 +38,6 @@ class Fission: ) print(f"ok:{item}") self.logger.debug(f"orbital 项处理完成: {item}") - def generate(self): yield from self.puzzles diff --git a/src/heurams/kernel/reactor/procession.py b/src/heurams/kernel/reactor/procession.py index 1b70f13..84c180f 100644 --- a/src/heurams/kernel/reactor/procession.py +++ b/src/heurams/kernel/reactor/procession.py @@ -9,8 +9,12 @@ class Procession: """队列: 标识单次记忆流程""" def __init__(self, atoms: list, phase: PhaserState, name: str = ""): - logger.debug("Procession.__init__: 原子数量=%d, phase=%s, name='%s'", - len(atoms), phase.value, name) + logger.debug( + "Procession.__init__: 原子数量=%d, phase=%s, name='%s'", + len(atoms), + phase.value, + name, + ) self.atoms = atoms self.queue = atoms.copy() self.current_atom = atoms[0] diff --git a/src/heurams/kernel/reactor/states.py b/src/heurams/kernel/reactor/states.py index ee4ac51..9587582 100644 --- a/src/heurams/kernel/reactor/states.py +++ b/src/heurams/kernel/reactor/states.py @@ -16,4 +16,5 @@ class ProcessionState(Enum): RUNNING = auto() FINISHED = auto() + logger.debug("状态枚举定义已加载") diff --git a/src/heurams/providers/llm/base.py b/src/heurams/providers/llm/base.py index 92a8ee0..b7c50c8 100644 --- a/src/heurams/providers/llm/base.py +++ b/src/heurams/providers/llm/base.py @@ -2,4 +2,4 @@ from heurams.services.logger import get_logger logger = get_logger(__name__) -logger.debug("LLM 基类模块已加载") \ No newline at end of file +logger.debug("LLM 基类模块已加载") diff --git a/src/heurams/providers/llm/openai.py b/src/heurams/providers/llm/openai.py index a082daa..910ef0b 100644 --- a/src/heurams/providers/llm/openai.py +++ b/src/heurams/providers/llm/openai.py @@ -2,4 +2,4 @@ from heurams.services.logger import get_logger logger = get_logger(__name__) -logger.debug("OpenAI provider 模块已加载(未实现)") \ No newline at end of file +logger.debug("OpenAI provider 模块已加载(未实现)") diff --git a/src/heurams/services/audio_service.py b/src/heurams/services/audio_service.py index de07adf..27c85c0 100644 --- a/src/heurams/services/audio_service.py +++ b/src/heurams/services/audio_service.py @@ -7,4 +7,6 @@ from heurams.services.logger import get_logger logger = get_logger(__name__) play_by_path: Callable = prov[config_var.get()["services"]["audio"]].play_by_path -logger.debug("音频服务初始化完成,使用 provider: %s", config_var.get()["services"]["audio"]) +logger.debug( + "音频服务初始化完成,使用 provider: %s", config_var.get()["services"]["audio"] +) diff --git a/src/heurams/services/logger.py b/src/heurams/services/logger.py index 74b769d..4357c17 100644 --- a/src/heurams/services/logger.py +++ b/src/heurams/services/logger.py @@ -50,7 +50,7 @@ def setup_logging( filename=log_path, maxBytes=max_bytes, backupCount=backup_count, - encoding='utf-8' + encoding="utf-8", ) file_handler.setFormatter(formatter) file_handler.setLevel(log_level) @@ -58,7 +58,7 @@ def setup_logging( # 配置root logger - 设置为 WARNING 级别(只记录重要信息) root_logger = logging.getLogger() root_logger.setLevel(logging.WARNING) # 这里改为 WARNING - + # 移除所有现有handler for handler in root_logger.handlers[:]: root_logger.removeHandler(handler) @@ -67,22 +67,22 @@ def setup_logging( app_logger = logging.getLogger("heurams") app_logger.setLevel(log_level) # 保持DEBUG级别 app_logger.addHandler(file_handler) - + # 禁止传播到root logger,避免双重记录 app_logger.propagate = False - + # 设置第三方库的日志级别为WARNING,避免调试信息干扰 third_party_loggers = [ "markdown_it", "markdown_it.rules_block", - "markdown_it.rules_core", + "markdown_it.rules_core", "markdown_it.rules_inline", "asyncio", ] - + for logger_name in third_party_loggers: logging.getLogger(logger_name).setLevel(logging.WARNING) - + # 记录日志系统初始化 app_logger.info("日志系统已初始化, 日志文件: %s", log_path) @@ -100,7 +100,7 @@ def get_logger(name: Optional[str] = None) -> logging.Logger: """ if name is None: return logging.getLogger() - + # 确保使用 heurams 作为前缀,继承应用logger的配置 if not name.startswith("heurams") and name != "": logger_name = f"heurams.{name}" @@ -142,7 +142,7 @@ def critical(msg: str, *args, **kwargs) -> None: def exception(msg: str, *args, **kwargs) -> None: - """记录异常信息 (ERROR级别) """ + """记录异常信息 (ERROR级别)""" get_logger().exception(msg, *args, **kwargs) @@ -152,4 +152,4 @@ setup_logging() # 模块级别的logger实例 logger = get_logger(__name__) -logger.info("HeurAMS日志服务模块已加载") \ No newline at end of file +logger.info("HeurAMS日志服务模块已加载") diff --git a/src/heurams/services/tts_service.py b/src/heurams/services/tts_service.py index ef1671f..0df8fb6 100644 --- a/src/heurams/services/tts_service.py +++ b/src/heurams/services/tts_service.py @@ -7,4 +7,6 @@ from heurams.services.logger import get_logger logger = get_logger(__name__) convert: Callable = TTSs[config_var.get().get("tts_provider")] -logger.debug("TTS服务初始化完成,使用 provider: %s", config_var.get().get("tts_provider")) +logger.debug( + "TTS服务初始化完成,使用 provider: %s", config_var.get().get("tts_provider") +) diff --git a/tests/kernel/algorithms/test_sm2.py b/tests/kernel/algorithms/test_sm2.py index 17dfbb8..71cdca2 100644 --- a/tests/kernel/algorithms/test_sm2.py +++ b/tests/kernel/algorithms/test_sm2.py @@ -9,15 +9,19 @@ class TestSM2Algorithm(unittest.TestCase): def setUp(self): # 模拟 timer 函数 - self.timestamp_patcher = patch('heurams.kernel.algorithms.sm2.timer.get_timestamp') - self.daystamp_patcher = patch('heurams.kernel.algorithms.sm2.timer.get_daystamp') + self.timestamp_patcher = patch( + "heurams.kernel.algorithms.sm2.timer.get_timestamp" + ) + self.daystamp_patcher = patch( + "heurams.kernel.algorithms.sm2.timer.get_daystamp" + ) self.mock_get_timestamp = self.timestamp_patcher.start() self.mock_get_daystamp = self.daystamp_patcher.start() - + # 设置固定返回值 self.mock_get_timestamp.return_value = 1000.0 self.mock_get_daystamp.return_value = 100 - + def tearDown(self): self.timestamp_patcher.stop() self.daystamp_patcher.stop() @@ -46,7 +50,14 @@ class TestSM2Algorithm(unittest.TestCase): def test_revisor_feedback_less_than_3(self): """测试 feedback < 3 重置 rept 和 interval""" - algodata = {SM2Algorithm.algo_name: {"efactor": 2.5, "rept": 5, "interval": 10, "real_rept": 3}} + algodata = { + SM2Algorithm.algo_name: { + "efactor": 2.5, + "rept": 5, + "interval": 10, + "real_rept": 3, + } + } SM2Algorithm.revisor(algodata, feedback=2) self.assertEqual(algodata[SM2Algorithm.algo_name]["rept"], 0) # rept=0 导致 interval 被设置为 1 @@ -55,7 +66,14 @@ class TestSM2Algorithm(unittest.TestCase): def test_revisor_feedback_greater_equal_3(self): """测试 feedback >= 3 递增 rept""" - algodata = {SM2Algorithm.algo_name: {"efactor": 2.5, "rept": 2, "interval": 6, "real_rept": 2}} + algodata = { + SM2Algorithm.algo_name: { + "efactor": 2.5, + "rept": 2, + "interval": 6, + "real_rept": 2, + } + } SM2Algorithm.revisor(algodata, feedback=4) self.assertEqual(algodata[SM2Algorithm.algo_name]["rept"], 3) self.assertEqual(algodata[SM2Algorithm.algo_name]["real_rept"], 3) @@ -65,7 +83,14 @@ class TestSM2Algorithm(unittest.TestCase): def test_revisor_new_activation(self): """测试 is_new_activation 重置 rept 和 efactor""" - algodata = {SM2Algorithm.algo_name: {"efactor": 3.0, "rept": 5, "interval": 20, "real_rept": 5}} + algodata = { + SM2Algorithm.algo_name: { + "efactor": 3.0, + "rept": 5, + "interval": 20, + "real_rept": 5, + } + } SM2Algorithm.revisor(algodata, feedback=5, is_new_activation=True) self.assertEqual(algodata[SM2Algorithm.algo_name]["rept"], 0) self.assertEqual(algodata[SM2Algorithm.algo_name]["efactor"], 2.5) @@ -74,11 +99,20 @@ class TestSM2Algorithm(unittest.TestCase): def test_revisor_efactor_calculation(self): """测试 efactor 计算""" - algodata = {SM2Algorithm.algo_name: {"efactor": 2.5, "rept": 1, "interval": 6, "real_rept": 1}} + algodata = { + SM2Algorithm.algo_name: { + "efactor": 2.5, + "rept": 1, + "interval": 6, + "real_rept": 1, + } + } SM2Algorithm.revisor(algodata, feedback=5) # efactor = 2.5 + (0.1 - (5-5)*(0.08 + (5-5)*0.02)) = 2.5 + 0.1 = 2.6 - self.assertAlmostEqual(algodata[SM2Algorithm.algo_name]["efactor"], 2.6, places=6) - + self.assertAlmostEqual( + algodata[SM2Algorithm.algo_name]["efactor"], 2.6, places=6 + ) + # 测试 efactor 下限为 1.3 algodata[SM2Algorithm.algo_name]["efactor"] = 1.2 SM2Algorithm.revisor(algodata, feedback=5) @@ -86,18 +120,32 @@ class TestSM2Algorithm(unittest.TestCase): def test_revisor_interval_calculation(self): """测试 interval 计算规则""" - algodata = {SM2Algorithm.algo_name: {"efactor": 2.5, "rept": 0, "interval": 0, "real_rept": 0}} + algodata = { + SM2Algorithm.algo_name: { + "efactor": 2.5, + "rept": 0, + "interval": 0, + "real_rept": 0, + } + } SM2Algorithm.revisor(algodata, feedback=4) # rept 从 0 递增到 1,因此 interval 应为 6 self.assertEqual(algodata[SM2Algorithm.algo_name]["interval"], 6) - + # 现在 rept=1,再次调用 revisor 递增到 2 SM2Algorithm.revisor(algodata, feedback=4) # rept=2,interval = round(6 * 2.5) = 15 self.assertEqual(algodata[SM2Algorithm.algo_name]["interval"], 15) - + # 单独测试 rept=1 的情况 - algodata2 = {SM2Algorithm.algo_name: {"efactor": 2.5, "rept": 1, "interval": 0, "real_rept": 0}} + algodata2 = { + SM2Algorithm.algo_name: { + "efactor": 2.5, + "rept": 1, + "interval": 0, + "real_rept": 0, + } + } SM2Algorithm.revisor(algodata2, feedback=4) # rept 递增到 2,interval = round(0 * 2.5) = 0 self.assertEqual(algodata2[SM2Algorithm.algo_name]["interval"], 0) @@ -108,7 +156,10 @@ class TestSM2Algorithm(unittest.TestCase): self.mock_get_daystamp.return_value = 200 SM2Algorithm.revisor(algodata, feedback=5) self.assertEqual(algodata[SM2Algorithm.algo_name]["last_date"], 200) - self.assertEqual(algodata[SM2Algorithm.algo_name]["next_date"], 200 + algodata[SM2Algorithm.algo_name]["interval"]) + self.assertEqual( + algodata[SM2Algorithm.algo_name]["next_date"], + 200 + algodata[SM2Algorithm.algo_name]["interval"], + ) self.assertEqual(algodata[SM2Algorithm.algo_name]["last_modify"], 1000.0) def test_is_due(self): @@ -116,7 +167,7 @@ class TestSM2Algorithm(unittest.TestCase): algodata = {SM2Algorithm.algo_name: {"next_date": 100}} self.mock_get_daystamp.return_value = 150 self.assertTrue(SM2Algorithm.is_due(algodata)) - + algodata[SM2Algorithm.algo_name]["next_date"] = 200 self.assertFalse(SM2Algorithm.is_due(algodata)) @@ -131,5 +182,5 @@ class TestSM2Algorithm(unittest.TestCase): self.assertEqual(SM2Algorithm.nextdate(algodata), 12345) -if __name__ == '__main__': - unittest.main() \ No newline at end of file +if __name__ == "__main__": + unittest.main() diff --git a/tests/kernel/particles/test_atom.py b/tests/kernel/particles/test_atom.py index ad78609..a4c4f90 100644 --- a/tests/kernel/particles/test_atom.py +++ b/tests/kernel/particles/test_atom.py @@ -21,18 +21,20 @@ class TestAtom(unittest.TestCase): # 创建临时目录用于持久化测试 self.temp_dir = tempfile.TemporaryDirectory() self.temp_path = pathlib.Path(self.temp_dir.name) - + # 创建默认配置 - self.config = ConfigFile(pathlib.Path(__file__).parent.parent.parent.parent / - "src/heurams/default/config/config.toml") - + self.config = ConfigFile( + pathlib.Path(__file__).parent.parent.parent.parent + / "src/heurams/default/config/config.toml" + ) + # 使用 ConfigContext 设置配置 self.config_ctx = ConfigContext(self.config) self.config_ctx.__enter__() - + # 清空全局注册表 atom_registry.clear() - + def tearDown(self): """在每个测试之后运行""" self.config_ctx.__exit__(None, None, None) @@ -45,7 +47,7 @@ class TestAtom(unittest.TestCase): self.assertEqual(atom.ident, "test_atom") self.assertIn("test_atom", atom_registry) self.assertEqual(atom_registry["test_atom"], atom) - + # 检查 registry 默认值 self.assertIsNone(atom.registry["nucleon"]) self.assertIsNone(atom.registry["electron"]) @@ -58,10 +60,10 @@ class TestAtom(unittest.TestCase): """测试 link 方法""" atom = Atom("test_link") nucleon = Nucleon("test_nucleon", {"content": "test content"}) - + atom.link("nucleon", nucleon) self.assertEqual(atom.registry["nucleon"], nucleon) - + # 测试链接不支持的键 with self.assertRaises(ValueError): atom.link("invalid_key", "value") @@ -70,8 +72,8 @@ class TestAtom(unittest.TestCase): """测试 link 后触发 do_eval""" atom = Atom("test_eval_trigger") nucleon = Nucleon("test_nucleon", {"content": "eval:1+1"}) - - with patch.object(atom, 'do_eval') as mock_do_eval: + + with patch.object(atom, "do_eval") as mock_do_eval: atom.link("nucleon", nucleon) mock_do_eval.assert_called_once() @@ -80,16 +82,16 @@ class TestAtom(unittest.TestCase): atom = Atom("test_persist_toml") nucleon = Nucleon("test_nucleon", {"content": "test"}) atom.link("nucleon", nucleon) - + # 设置路径 test_path = self.temp_path / "test.toml" atom.link("nucleon_path", test_path) - + atom.persist("nucleon") - + # 验证文件存在且内容正确 self.assertTrue(test_path.exists()) - with open(test_path, 'r') as f: + with open(test_path, "r") as f: data = toml.load(f) self.assertEqual(data["ident"], "test_nucleon") self.assertEqual(data["payload"]["content"], "test") @@ -99,14 +101,14 @@ class TestAtom(unittest.TestCase): atom = Atom("test_persist_json") electron = Electron("test_electron", {}) atom.link("electron", electron) - + test_path = self.temp_path / "test.json" atom.link("electron_path", test_path) - + atom.persist("electron") - + self.assertTrue(test_path.exists()) - with open(test_path, 'r') as f: + with open(test_path, "r") as f: data = json.load(f) self.assertIn("supermemo2", data) @@ -117,7 +119,7 @@ class TestAtom(unittest.TestCase): atom.link("nucleon", nucleon) atom.link("nucleon_path", self.temp_path / "test.txt") atom.registry["nucleon_fmt"] = "invalid" - + with self.assertRaises(KeyError): atom.persist("nucleon") @@ -127,7 +129,7 @@ class TestAtom(unittest.TestCase): nucleon = Nucleon("test_nucleon", {}) atom.link("nucleon", nucleon) # 不设置 nucleon_path - + with self.assertRaises(TypeError): atom.persist("nucleon") @@ -135,26 +137,26 @@ class TestAtom(unittest.TestCase): """测试 __getitem__ 和 __setitem__""" atom = Atom("test_getset") nucleon = Nucleon("test_nucleon", {}) - + atom["nucleon"] = nucleon self.assertEqual(atom["nucleon"], nucleon) - + # 测试不支持的键 with self.assertRaises(KeyError): _ = atom["invalid_key"] - + with self.assertRaises(KeyError): atom["invalid_key"] = "value" def test_do_eval_with_eval_string(self): """测试 do_eval 处理 eval: 字符串""" atom = Atom("test_do_eval") - nucleon = Nucleon("test_nucleon", { - "content": "eval:'hello' + ' world'", - "number": "eval:2 + 3" - }) + nucleon = Nucleon( + "test_nucleon", + {"content": "eval:'hello' + ' world'", "number": "eval:2 + 3"}, + ) atom.link("nucleon", nucleon) - + # do_eval 应该在链接时自动调用 # 检查 eval 表达式是否被求值 self.assertEqual(nucleon.payload["content"], "hello world") @@ -163,11 +165,11 @@ class TestAtom(unittest.TestCase): def test_do_eval_with_config_access(self): """测试 do_eval 访问配置""" atom = Atom("test_eval_config") - nucleon = Nucleon("test_nucleon", { - "max_riddles": "eval:default['mcq']['max_riddles_num']" - }) + nucleon = Nucleon( + "test_nucleon", {"max_riddles": "eval:default['mcq']['max_riddles_num']"} + ) atom.link("nucleon", nucleon) - + # 配置中 puzzles.mcq.max_riddles_num = 2 self.assertEqual(nucleon.payload["max_riddles"], 2) @@ -185,15 +187,15 @@ class TestAtom(unittest.TestCase): # 创建多个 Atom atom1 = Atom("atom1") atom2 = Atom("atom2") - + self.assertEqual(len(atom_registry), 2) self.assertEqual(atom_registry["atom1"], atom1) self.assertEqual(atom_registry["atom2"], atom2) - + # 测试 bidict 的反向查找 self.assertEqual(atom_registry.inverse[atom1], "atom1") self.assertEqual(atom_registry.inverse[atom2], "atom2") -if __name__ == '__main__': - unittest.main() \ No newline at end of file +if __name__ == "__main__": + unittest.main() diff --git a/tests/kernel/particles/test_electron.py b/tests/kernel/particles/test_electron.py index 5a02d7a..1fa8f7b 100644 --- a/tests/kernel/particles/test_electron.py +++ b/tests/kernel/particles/test_electron.py @@ -11,10 +11,12 @@ class TestElectron(unittest.TestCase): def setUp(self): # 模拟 timer.get_timestamp 返回固定值 - self.timestamp_patcher = patch('heurams.kernel.particles.electron.timer.get_timestamp') + self.timestamp_patcher = patch( + "heurams.kernel.particles.electron.timer.get_timestamp" + ) self.mock_get_timestamp = self.timestamp_patcher.start() self.mock_get_timestamp.return_value = 1234567890.0 - + def tearDown(self): self.timestamp_patcher.stop() @@ -67,9 +69,9 @@ class TestElectron(unittest.TestCase): electron.modify("interval", 5) self.assertEqual(electron.algodata[electron.algo]["interval"], 5) self.assertEqual(electron.algodata[electron.algo]["last_modify"], 1234567890.0) - + # 修改不存在的字段应记录警告但不引发异常 - with patch('heurams.kernel.particles.electron.logger.warning') as mock_warning: + with patch("heurams.kernel.particles.electron.logger.warning") as mock_warning: electron.modify("unknown_field", 99) mock_warning.assert_called_once() @@ -85,7 +87,7 @@ class TestElectron(unittest.TestCase): def test_is_due(self): """测试 is_due 方法""" electron = Electron("test_electron") - with patch.object(electron.algo, 'is_due') as mock_is_due: + with patch.object(electron.algo, "is_due") as mock_is_due: mock_is_due.return_value = 1 result = electron.is_due() mock_is_due.assert_called_once_with(electron.algodata) @@ -94,7 +96,7 @@ class TestElectron(unittest.TestCase): def test_rate(self): """测试 rate 方法""" electron = Electron("test_electron") - with patch.object(electron.algo, 'rate') as mock_rate: + with patch.object(electron.algo, "rate") as mock_rate: mock_rate.return_value = "good" result = electron.rate() mock_rate.assert_called_once_with(electron.algodata) @@ -103,7 +105,7 @@ class TestElectron(unittest.TestCase): def test_nextdate(self): """测试 nextdate 方法""" electron = Electron("test_electron") - with patch.object(electron.algo, 'nextdate') as mock_nextdate: + with patch.object(electron.algo, "nextdate") as mock_nextdate: mock_nextdate.return_value = 1234568000 result = electron.nextdate() mock_nextdate.assert_called_once_with(electron.algodata) @@ -112,7 +114,7 @@ class TestElectron(unittest.TestCase): def test_revisor(self): """测试 revisor 方法""" electron = Electron("test_electron") - with patch.object(electron.algo, 'revisor') as mock_revisor: + with patch.object(electron.algo, "revisor") as mock_revisor: electron.revisor(quality=3, is_new_activation=True) mock_revisor.assert_called_once_with(electron.algodata, 3, True) @@ -144,7 +146,7 @@ class TestElectron(unittest.TestCase): electron.activate() self.assertEqual(electron["ident"], "test_electron") self.assertEqual(electron["is_activated"], 1) - + with self.assertRaises(KeyError): _ = electron["nonexistent_key"] @@ -154,7 +156,7 @@ class TestElectron(unittest.TestCase): electron["interval"] = 10 self.assertEqual(electron.algodata[electron.algo]["interval"], 10) self.assertEqual(electron.algodata[electron.algo]["last_modify"], 1234567890.0) - + with self.assertRaises(AttributeError): electron["ident"] = "new_ident" @@ -173,5 +175,5 @@ class TestElectron(unittest.TestCase): self.assertEqual(placeholder.algo, algorithms["supermemo2"]) -if __name__ == '__main__': - unittest.main() \ No newline at end of file +if __name__ == "__main__": + unittest.main() diff --git a/tests/kernel/particles/test_nucleon.py b/tests/kernel/particles/test_nucleon.py index d1912f0..c059b21 100644 --- a/tests/kernel/particles/test_nucleon.py +++ b/tests/kernel/particles/test_nucleon.py @@ -9,7 +9,9 @@ class TestNucleon(unittest.TestCase): def test_init(self): """测试初始化""" - nucleon = Nucleon("test_id", {"content": "hello", "note": "world"}, {"author": "test"}) + nucleon = Nucleon( + "test_id", {"content": "hello", "note": "world"}, {"author": "test"} + ) self.assertEqual(nucleon.ident, "test_id") self.assertEqual(nucleon.payload, {"content": "hello", "note": "world"}) self.assertEqual(nucleon.metadata, {"author": "test"}) @@ -27,7 +29,7 @@ class TestNucleon(unittest.TestCase): self.assertEqual(nucleon["ident"], "test_id") self.assertEqual(nucleon["content"], "hello") self.assertEqual(nucleon["note"], "world") - + with self.assertRaises(KeyError): _ = nucleon["nonexistent"] @@ -58,16 +60,23 @@ class TestNucleon(unittest.TestCase): def test_do_eval_with_metadata_access(self): """测试 do_eval 访问元数据""" - nucleon = Nucleon("test_id", {"result": "eval:nucleon.metadata.get('value', 0)"}, {"value": 42}) + nucleon = Nucleon( + "test_id", + {"result": "eval:nucleon.metadata.get('value', 0)"}, + {"value": 42}, + ) nucleon.do_eval() self.assertEqual(nucleon.payload["result"], "42") def test_do_eval_nested(self): """测试 do_eval 处理嵌套结构""" - nucleon = Nucleon("test_id", { - "list": ["eval:2*3", "normal"], - "dict": {"key": "eval:'hello' + ' world'"} - }) + nucleon = Nucleon( + "test_id", + { + "list": ["eval:2*3", "normal"], + "dict": {"key": "eval:'hello' + ' world'"}, + }, + ) nucleon.do_eval() self.assertEqual(nucleon.payload["list"][0], "6") self.assertEqual(nucleon.payload["list"][1], "normal") @@ -95,5 +104,5 @@ class TestNucleon(unittest.TestCase): self.assertEqual(placeholder.metadata, {}) -if __name__ == '__main__': - unittest.main() \ No newline at end of file +if __name__ == "__main__": + unittest.main() diff --git a/tests/kernel/puzzles/test_base.py b/tests/kernel/puzzles/test_base.py index 2163cc5..28b70f0 100644 --- a/tests/kernel/puzzles/test_base.py +++ b/tests/kernel/puzzles/test_base.py @@ -19,5 +19,5 @@ class TestBasePuzzle(unittest.TestCase): self.assertEqual(str(puzzle), "谜题: BasePuzzle") -if __name__ == '__main__': - unittest.main() \ No newline at end of file +if __name__ == "__main__": + unittest.main() diff --git a/tests/kernel/puzzles/test_cloze.py b/tests/kernel/puzzles/test_cloze.py index 62221ec..8e9417a 100644 --- a/tests/kernel/puzzles/test_cloze.py +++ b/tests/kernel/puzzles/test_cloze.py @@ -16,13 +16,13 @@ class TestClozePuzzle(unittest.TestCase): self.assertEqual(puzzle.wording, "填空题 - 尚未刷新谜题") self.assertEqual(puzzle.answer, ["填空题 - 尚未刷新谜题"]) - @patch('random.sample') + @patch("random.sample") def test_refresh(self, mock_sample): """测试 refresh 方法""" mock_sample.return_value = [0, 2] # 选择索引 0 和 2 puzzle = ClozePuzzle("hello/world/test", min_denominator=2, delimiter="/") puzzle.refresh() - + # 检查 wording 和 answer self.assertNotEqual(puzzle.wording, "填空题 - 尚未刷新谜题") self.assertNotEqual(puzzle.answer, ["填空题 - 尚未刷新谜题"]) @@ -47,5 +47,5 @@ class TestClozePuzzle(unittest.TestCase): self.assertIn("填空题 - 尚未刷新谜题", str_repr) -if __name__ == '__main__': - unittest.main() \ No newline at end of file +if __name__ == "__main__": + unittest.main() diff --git a/tests/kernel/puzzles/test_mcq.py b/tests/kernel/puzzles/test_mcq.py index 72a3111..b7dcbbf 100644 --- a/tests/kernel/puzzles/test_mcq.py +++ b/tests/kernel/puzzles/test_mcq.py @@ -38,8 +38,8 @@ class TestMCQPuzzle(unittest.TestCase): self.assertEqual(len(puzzle.jammer), 4) self.assertEqual(set(puzzle.jammer), {" "}) # 三个空格?实际上循环填充空格 - @patch('random.sample') - @patch('random.shuffle') + @patch("random.sample") + @patch("random.shuffle") def test_refresh(self, mock_shuffle, mock_sample): """测试 refresh 方法生成题目""" mapping = {"q1": "a1", "q2": "a2", "q3": "a3"} @@ -51,7 +51,7 @@ class TestMCQPuzzle(unittest.TestCase): ["j1", "j2", "j3"], # 为每个问题选择干扰项(实际调用两次) ] puzzle.refresh() - + # 检查 wording 是列表 self.assertIsInstance(puzzle.wording, list) self.assertEqual(len(puzzle.wording), 2) @@ -110,7 +110,7 @@ class TestMCQPuzzle(unittest.TestCase): puzzle.answer = ["选择题 - 尚未刷新谜题"] self.assertIn("选择题 - 尚未刷新谜题", str(puzzle)) self.assertIn("正确答案", str(puzzle)) - + puzzle.wording = ["Q1", "Q2"] puzzle.answer = ["A1", "A2"] str_repr = str(puzzle) @@ -118,5 +118,5 @@ class TestMCQPuzzle(unittest.TestCase): self.assertIn("A1, A2", str_repr) -if __name__ == '__main__': - unittest.main() \ No newline at end of file +if __name__ == "__main__": + unittest.main() diff --git a/tests/kernel/reactor/test_phaser.py b/tests/kernel/reactor/test_phaser.py index cb8410c..7e1eba0 100644 --- a/tests/kernel/reactor/test_phaser.py +++ b/tests/kernel/reactor/test_phaser.py @@ -15,15 +15,15 @@ class TestPhaser(unittest.TestCase): self.atom_new = Mock(spec=Atom) self.atom_new.registry = {"electron": Mock(spec=Electron)} self.atom_new.registry["electron"].is_activated.return_value = False - + self.atom_old = Mock(spec=Atom) self.atom_old.registry = {"electron": Mock(spec=Electron)} self.atom_old.registry["electron"].is_activated.return_value = True - + # 模拟 Procession 类以避免复杂依赖 - self.procession_patcher = patch('heurams.kernel.reactor.phaser.Procession') + self.procession_patcher = patch("heurams.kernel.reactor.phaser.Procession") self.mock_procession_class = self.procession_patcher.start() - + def tearDown(self): self.procession_patcher.stop() @@ -31,10 +31,10 @@ class TestPhaser(unittest.TestCase): """测试混合新旧原子的初始化""" atoms = [self.atom_old, self.atom_new, self.atom_old] phaser = Phaser(atoms) - + # 应该创建两个 Procession:一个用于旧原子,一个用于新原子,以及一个总体复习 self.assertEqual(self.mock_procession_class.call_count, 3) - + # 检查调用参数 calls = self.mock_procession_class.call_args_list # 第一个调用应该是旧原子的初始复习 @@ -51,7 +51,7 @@ class TestPhaser(unittest.TestCase): """测试只有旧原子""" atoms = [self.atom_old, self.atom_old] phaser = Phaser(atoms) - + # 应该创建两个 Procession:一个初始复习,一个总体复习 self.assertEqual(self.mock_procession_class.call_count, 2) calls = self.mock_procession_class.call_args_list @@ -64,7 +64,7 @@ class TestPhaser(unittest.TestCase): """测试只有新原子""" atoms = [self.atom_new, self.atom_new] phaser = Phaser(atoms) - + self.assertEqual(self.mock_procession_class.call_count, 2) calls = self.mock_procession_class.call_args_list self.assertEqual(calls[0][0][0], atoms) @@ -80,10 +80,10 @@ class TestPhaser(unittest.TestCase): mock_proc2 = Mock() mock_proc2.state = ProcessionState.RUNNING mock_proc2.phase = PhaserState.QUICK_REVIEW - + phaser = Phaser([]) phaser.processions = [mock_proc1, mock_proc2] - + result = phaser.current_procession() self.assertEqual(result, mock_proc2) self.assertEqual(phaser.state, PhaserState.QUICK_REVIEW) @@ -92,10 +92,10 @@ class TestPhaser(unittest.TestCase): """测试所有 Procession 都完成""" mock_proc = Mock() mock_proc.state = ProcessionState.FINISHED - + phaser = Phaser([]) phaser.processions = [mock_proc] - + result = phaser.current_procession() self.assertEqual(result, 0) self.assertEqual(phaser.state, PhaserState.FINISHED) @@ -104,11 +104,11 @@ class TestPhaser(unittest.TestCase): """测试没有 Procession""" phaser = Phaser([]) phaser.processions = [] - + result = phaser.current_procession() self.assertEqual(result, 0) self.assertEqual(phaser.state, PhaserState.FINISHED) -if __name__ == '__main__': - unittest.main() \ No newline at end of file +if __name__ == "__main__": + unittest.main()