Skip to content

Commit ea8d8ab

Browse files
committed
docs(tiku): 为新增和修改的搜题方法及格式校验函数补全 Docstrings 以提高覆盖率
1 parent 57ac976 commit ea8d8ab

3 files changed

Lines changed: 141 additions & 7 deletions

File tree

api/answer.py

Lines changed: 86 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -210,13 +210,28 @@ def _get_conf(self):
210210

211211
@property
212212
def _is_manual_mode(self) -> bool:
213+
"""检查当前题库是否为手动模式或包含手动模式。
214+
215+
Returns:
216+
bool: 如果是手动模式或子题库中包含手动模式返回 True,否则返回 False。
217+
"""
213218
return (
214219
getattr(self, 'is_manual', False) or
215220
self.__class__.__name__ == 'TikuManual' or
216221
(self.__class__.__name__ == 'TikuFallback' and any(getattr(p, 'is_manual', False) or p.__class__.__name__ == 'TikuManual' for p in getattr(self, 'providers', [])))
217222
)
218223

219224
def query(self,q_info:dict) -> Optional[str]:
225+
"""单题搜题方法。
226+
227+
首先检索本地缓存,缓存未命中则调用 _query 从远程或本地题库获取。
228+
229+
Args:
230+
q_info: 题目信息字典,必须包含 'title' 和 'type'。
231+
232+
Returns:
233+
Optional[str]: 搜到的答案字符串,未搜到或校验失败返回 None。
234+
"""
220235
if self.DISABLE:
221236
return None
222237

@@ -250,6 +265,17 @@ def query(self,q_info:dict) -> Optional[str]:
250265
return None
251266

252267
def query_all(self, q_list: list[dict], query_delay: float = 0.0) -> list[Optional[str]]:
268+
"""批量搜题方法。
269+
270+
优先通过本地缓存过滤,缓存未命中的题目交由 _query_all 批量获取。
271+
272+
Args:
273+
q_list: 题目字典列表。
274+
query_delay: 每道题查询之间的延时(秒)。
275+
276+
Returns:
277+
list[Optional[str]]: 答案列表,与传入的题目列表一一对应。
278+
"""
253279
if self.DISABLE:
254280
return [None] * len(q_list)
255281

@@ -306,15 +332,27 @@ def query_all(self, q_list: list[dict], query_delay: float = 0.0) -> list[Option
306332

307333
@abstractmethod
308334
def _query(self, q_info:dict) -> Optional[str]:
309-
"""
310-
查询接口, 交由自定义题库实现
335+
"""查询接口, 交由自定义题库实现
336+
337+
Args:
338+
q_info: 题目信息字典。
339+
340+
Returns:
341+
Optional[str]: 搜到的答案,未搜到返回 None。
311342
"""
312343
pass
313344

314345
def _query_all(self, q_list: list[dict], query_delay: float = 0.0) -> list[Optional[str]]:
315-
"""
316-
批量查询的实现接口,默认循环调用单个查询 _query。
346+
"""批量查询的实现接口,默认循环调用单个查询 _query。
347+
317348
子类若有批量查询或交互需求(如手动模式),可重写此方法。
349+
350+
Args:
351+
q_list: 待搜题列表。
352+
query_delay: 单题请求延时。
353+
354+
Returns:
355+
list[Optional[str]]: 搜到的批量答案列表。
318356
"""
319357
results = []
320358
for q in q_list:
@@ -480,6 +518,17 @@ def _query(self, q_info:dict) -> Optional[str]:
480518
return None
481519

482520
def _query_all(self, q_list: list[dict], query_delay: float = 0.0) -> list[Optional[str]]:
521+
"""实现多题库回退的批量查询逻辑。
522+
523+
根据 providers 配置的顺序,对仍未命中的题目列表依次分配给子题库查询,直至所有题目命中或已无可用题库。
524+
525+
Args:
526+
q_list: 待查询题目列表。
527+
query_delay: 单题请求延时。
528+
529+
Returns:
530+
list[Optional[str]]: 合并后的批量答案列表。
531+
"""
483532
results = [None] * len(q_list)
484533
pending_indices = list(range(len(q_list)))
485534

@@ -1460,16 +1509,27 @@ def __init__(self, config_path: Optional[str] = None) -> None:
14601509

14611510
@staticmethod
14621511
def _extract_option_letters(ans: str) -> list[str]:
1512+
"""从用户输入的答案中尝试提取纯选项字母。
1513+
1514+
当且仅当输入的除空白与标点符号外的字符全部为英文字母时才提取,否则返回空列表以指示为文本匹配模式。
1515+
1516+
Args:
1517+
ans: 用户手动输入的原始答案文本。
1518+
1519+
Returns:
1520+
list[str]: 提取出来的大写字母列表,或空列表。
1521+
"""
14631522
cleaned = re.sub(r'[\s,,;;、]+', '', ans)
14641523
if not cleaned or not re.fullmatch(r'[A-Za-z]+', cleaned):
14651524
return []
14661525
return [c.upper() for c in cleaned]
14671526

14681527
def _init_tiku(self):
1528+
"""根据配置初始化手动模式的参数(输入模式、分隔符等)。"""
14691529
self.default_mode = self._conf.get('manual_mode_default', 'batch').strip().lower()
14701530
if self.default_mode not in ['batch', 'single']:
14711531
self.default_mode = 'batch'
1472-
1532+
14731533
self.separator = self._conf.get('manual_mode_separator', ';')
14741534
if self.separator.lower() in ['\\n', 'newline', '换行']:
14751535
self.separator = '\n'
@@ -1480,7 +1540,7 @@ def _init_tiku(self):
14801540

14811541
@staticmethod
14821542
def _safe_close_tqdm_bars():
1483-
"""安全地清除并关闭所有活动的 tqdm 进度条,防止私有属性变更引发异常"""
1543+
"""安全地清除并关闭所有活动的 tqdm 进度条,防止私有属性变更异常。"""
14841544
try:
14851545
from tqdm import tqdm
14861546
if hasattr(tqdm, '_instances') and hasattr(tqdm._instances, '__iter__'):
@@ -1500,6 +1560,14 @@ def _safe_close_tqdm_bars():
15001560
logger.debug(f"获取/清理 tqdm 实例列表失败: {e}")
15011561

15021562
def _query(self, q_info: dict) -> Optional[str]:
1563+
"""手动单题搜题实现。
1564+
1565+
Args:
1566+
q_info: 题目信息字典。
1567+
1568+
Returns:
1569+
Optional[str]: 用户输入的答案字符串,跳过则返回 None。
1570+
"""
15031571
# 强行关闭清除所有当前活动的 tqdm 进度条
15041572
self._safe_close_tqdm_bars()
15051573

@@ -1509,6 +1577,17 @@ def _query(self, q_info: dict) -> Optional[str]:
15091577
return ans
15101578

15111579
def _query_all(self, q_list: list[dict], query_delay: float = 0.0) -> list[Optional[str]]:
1580+
"""手动批量搜题实现。
1581+
1582+
根据配置进入批量粘贴流或单题轮询流。
1583+
1584+
Args:
1585+
q_list: 待搜题列表。
1586+
query_delay: 单题延时(此处不生效)。
1587+
1588+
Returns:
1589+
list[Optional[str]]: 用户输入的批量答案列表。
1590+
"""
15121591
# 强行关闭清除所有当前活动的 tqdm 进度条
15131592
self._safe_close_tqdm_bars()
15141593

@@ -1523,6 +1602,7 @@ def _query_all(self, q_list: list[dict], query_delay: float = 0.0) -> list[Optio
15231602

15241603
@staticmethod
15251604
def _get_type_display(type_str: str) -> str:
1605+
"""获取题目类型的中文友好名称。"""
15261606
type_map = {
15271607
'single': '单选题',
15281608
'multiple': '多选题',

api/answer_check.py

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,12 @@
11
def check_single(answer):
2+
"""验证答案是否符合单选题的格式要求。
3+
4+
Args:
5+
answer: 待验证的答案文本。
6+
7+
Returns:
8+
bool: 如果答案不包含明显的多段答案分隔符,则返回 True;否则返回 False。
9+
"""
210
if answer is None:
311
return False
412

@@ -18,13 +26,31 @@ def check_single(answer):
1826

1927

2028
def check_multiple(answer):
29+
"""验证答案是否符合多选题的格式要求。
30+
31+
Args:
32+
answer: 待验证的答案文本。
33+
34+
Returns:
35+
bool: 如果答案可以被切分为多个有效的选项元素,则返回 True;否则返回 False。
36+
"""
2137
_t = cut(answer)
2238
if _t is not None and len(_t) > 0:
2339
return True
2440
return False
2541

2642

2743
def check_judgement(answer, true_list, false_list):
44+
"""验证并识别判断题答案。
45+
46+
Args:
47+
answer: 待验证的答案文本。
48+
true_list: 自定义正确判断词列表。
49+
false_list: 自定义错误判断词列表。
50+
51+
Returns:
52+
int: 如果是正确返回 1,如果是错误返回 0,无法判断返回 -1。
53+
"""
2854
val = str(answer).strip().lower()
2955
if val in ['true', 't', '1', '对', '正确', '√', '是', 'yes', 'y'] or val in [x.lower() for x in true_list]:
3056
return 1
@@ -35,13 +61,33 @@ def check_judgement(answer, true_list, false_list):
3561

3662

3763
def check_completion(answer):
64+
"""验证答案是否符合填空题的格式要求。
65+
66+
Args:
67+
answer: 待验证的答案文本。
68+
69+
Returns:
70+
bool: 如果答案长度大于 0,则返回 True;否则返回 False。
71+
"""
3872
if len(answer) > 0:
3973
return True
4074
else:
4175
return False
4276

4377

4478
def check_answer(answer, type, tiku): # 只会写小杯代码,这里用个tiku感觉怪怪的,但先这么写着
79+
"""对搜题得到的答案进行有效性与题型一致性校验。
80+
81+
如果是手动模式或多题库回退包装器,将直接放行信任答案。
82+
83+
Args:
84+
answer: 待验证的答案文本。
85+
type: 题目类型 ('single', 'multiple', 'completion', 'judgement')。
86+
tiku: 调用此校验的题库对象实例。
87+
88+
Returns:
89+
bool: 校验通过返回 True,校验失败舍弃则返回 False。
90+
"""
4591
# 如果是手动模式或多题库回退包装器,直接信任
4692
# (手动模式豁免常规校验;回退包装器因其子题库在各自环节均已单独校验过,此处无需二次校验,以防二次过滤误杀)
4793
if getattr(tiku, 'is_manual', False) or getattr(tiku, 'skip_answer_validation', False):
@@ -65,6 +111,14 @@ def check_answer(answer, type, tiku): # 只会写小杯代码,这里用个tik
65111

66112

67113
def cut(answer):
114+
"""根据高频分隔符切分多选或多空答案文本。
115+
116+
Args:
117+
answer: 待切分的答案文本。
118+
119+
Returns:
120+
list[str]: 切分后的子答案列表,如果无法切分则返回包含原文本的单元素列表,为 None 则返回 None。
121+
"""
68122
cut_char = [
69123
"\n",
70124
",",

api/base.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -906,7 +906,7 @@ def fetch_response_with_retry():
906906
questions = decode_questions_info(_resp.text)
907907

908908
if _resp.status_code == 200 and questions.get("questions"):
909-
return (_resp, questions)
909+
return _resp, questions
910910

911911
logger.warning(
912912
f"无效响应 (Code: {getattr(_resp, 'status_code', 'Unknown')}), 重试中...")

0 commit comments

Comments
 (0)