pytest-dsl 0.15.2__py3-none-any.whl → 0.15.4__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pytest_dsl/__init__.py +52 -3
- pytest_dsl/cli.py +8 -1
- pytest_dsl/core/context.py +59 -5
- pytest_dsl/core/custom_keyword_manager.py +14 -8
- pytest_dsl/core/dsl_executor.py +28 -8
- pytest_dsl/core/global_context.py +38 -8
- pytest_dsl/core/http_client.py +29 -5
- pytest_dsl/core/keyword_utils.py +7 -0
- pytest_dsl/core/remote_server_registry.py +333 -0
- pytest_dsl/core/utils.py +34 -27
- pytest_dsl/core/validator.py +71 -1
- pytest_dsl/core/variable_providers.py +202 -0
- pytest_dsl/core/variable_utils.py +45 -38
- pytest_dsl/core/yaml_loader.py +176 -36
- pytest_dsl/keywords/http_keywords.py +9 -5
- pytest_dsl/remote/__init__.py +63 -1
- {pytest_dsl-0.15.2.dist-info → pytest_dsl-0.15.4.dist-info}/METADATA +1 -1
- {pytest_dsl-0.15.2.dist-info → pytest_dsl-0.15.4.dist-info}/RECORD +22 -20
- {pytest_dsl-0.15.2.dist-info → pytest_dsl-0.15.4.dist-info}/WHEEL +0 -0
- {pytest_dsl-0.15.2.dist-info → pytest_dsl-0.15.4.dist-info}/entry_points.txt +0 -0
- {pytest_dsl-0.15.2.dist-info → pytest_dsl-0.15.4.dist-info}/licenses/LICENSE +0 -0
- {pytest_dsl-0.15.2.dist-info → pytest_dsl-0.15.4.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,333 @@
|
|
1
|
+
"""
|
2
|
+
远程服务器注册模块
|
3
|
+
|
4
|
+
提供独立的远程服务器注册功能,方便其他系统集成pytest-dsl时使用自己的变量系统。
|
5
|
+
这个模块不依赖于YAML配置,完全通过编程方式进行服务器注册。
|
6
|
+
"""
|
7
|
+
|
8
|
+
from typing import Dict, List, Optional, Any, Callable
|
9
|
+
import logging
|
10
|
+
|
11
|
+
logger = logging.getLogger(__name__)
|
12
|
+
|
13
|
+
|
14
|
+
class RemoteServerRegistry:
|
15
|
+
"""远程服务器注册器
|
16
|
+
|
17
|
+
提供灵活的API用于注册和管理远程关键字服务器,
|
18
|
+
支持自定义变量获取方式,方便第三方系统集成。
|
19
|
+
"""
|
20
|
+
|
21
|
+
def __init__(self):
|
22
|
+
self._variable_providers = [] # 变量提供者列表
|
23
|
+
self._server_configs = [] # 服务器配置列表
|
24
|
+
self._connection_callbacks = [] # 连接成功后的回调
|
25
|
+
|
26
|
+
def add_variable_provider(self, provider: Callable[[], Dict[str, Any]]):
|
27
|
+
"""添加变量提供者
|
28
|
+
|
29
|
+
变量提供者是一个无参数的可调用对象,返回字典形式的变量。
|
30
|
+
这允许第三方系统提供自己的变量获取逻辑。
|
31
|
+
|
32
|
+
Args:
|
33
|
+
provider: 返回变量字典的可调用对象
|
34
|
+
|
35
|
+
Examples:
|
36
|
+
>>> def my_vars():
|
37
|
+
... return {'api_key': 'secret', 'env': 'prod'}
|
38
|
+
>>> registry.add_variable_provider(my_vars)
|
39
|
+
"""
|
40
|
+
if callable(provider):
|
41
|
+
self._variable_providers.append(provider)
|
42
|
+
else:
|
43
|
+
raise ValueError("变量提供者必须是可调用对象")
|
44
|
+
|
45
|
+
def add_connection_callback(self, callback: Callable[[str, bool], None]):
|
46
|
+
"""添加连接回调
|
47
|
+
|
48
|
+
连接回调会在每次连接远程服务器后被调用,
|
49
|
+
无论连接成功还是失败。
|
50
|
+
|
51
|
+
Args:
|
52
|
+
callback: 接受(alias, success)参数的回调函数
|
53
|
+
"""
|
54
|
+
if callable(callback):
|
55
|
+
self._connection_callbacks.append(callback)
|
56
|
+
else:
|
57
|
+
raise ValueError("连接回调必须是可调用对象")
|
58
|
+
|
59
|
+
def register_server(self,
|
60
|
+
url: str,
|
61
|
+
alias: str,
|
62
|
+
api_key: Optional[str] = None,
|
63
|
+
sync_global_vars: bool = True,
|
64
|
+
sync_custom_vars: bool = True,
|
65
|
+
exclude_patterns: Optional[List[str]] = None) -> bool:
|
66
|
+
"""注册单个远程服务器
|
67
|
+
|
68
|
+
Args:
|
69
|
+
url: 服务器URL
|
70
|
+
alias: 服务器别名
|
71
|
+
api_key: API密钥
|
72
|
+
sync_global_vars: 是否同步全局变量
|
73
|
+
sync_custom_vars: 是否同步自定义变量(通过变量提供者)
|
74
|
+
exclude_patterns: 要排除的变量名模式列表
|
75
|
+
|
76
|
+
Returns:
|
77
|
+
bool: 是否连接成功
|
78
|
+
"""
|
79
|
+
# 构建同步配置
|
80
|
+
sync_config = {
|
81
|
+
'sync_global_vars': sync_global_vars,
|
82
|
+
'sync_yaml_vars': False, # 不使用YAML变量
|
83
|
+
'sync_custom_vars': sync_custom_vars,
|
84
|
+
'exclude_patterns': exclude_patterns or ['password', 'secret', 'token']
|
85
|
+
}
|
86
|
+
|
87
|
+
# 收集要同步的变量
|
88
|
+
variables_to_sync = {}
|
89
|
+
|
90
|
+
if sync_custom_vars:
|
91
|
+
variables_to_sync.update(self._collect_custom_variables())
|
92
|
+
|
93
|
+
# 尝试连接
|
94
|
+
success = self._connect_to_server(
|
95
|
+
url, alias, api_key, sync_config, variables_to_sync)
|
96
|
+
|
97
|
+
# 调用连接回调
|
98
|
+
for callback in self._connection_callbacks:
|
99
|
+
try:
|
100
|
+
callback(alias, success)
|
101
|
+
except Exception as e:
|
102
|
+
logger.warning(f"连接回调执行失败: {e}")
|
103
|
+
|
104
|
+
if success:
|
105
|
+
# 保存配置以便后续使用
|
106
|
+
self._server_configs.append({
|
107
|
+
'url': url,
|
108
|
+
'alias': alias,
|
109
|
+
'api_key': api_key,
|
110
|
+
'sync_config': sync_config
|
111
|
+
})
|
112
|
+
|
113
|
+
return success
|
114
|
+
|
115
|
+
def register_servers_from_config(self, servers: List[Dict[str, Any]]) -> Dict[str, Dict[str, Any]]:
|
116
|
+
"""从配置列表批量注册服务器
|
117
|
+
|
118
|
+
Args:
|
119
|
+
servers: 服务器配置列表,每个配置包含url、alias等字段
|
120
|
+
|
121
|
+
Returns:
|
122
|
+
dict: 注册结果,键为alias,值为结果详情字典
|
123
|
+
|
124
|
+
Examples:
|
125
|
+
>>> servers = [
|
126
|
+
... {'url': 'http://server1:8270', 'alias': 'server1'},
|
127
|
+
... {'url': 'http://server2:8270', 'alias': 'server2', 'api_key': 'secret'}
|
128
|
+
... ]
|
129
|
+
>>> results = registry.register_servers_from_config(servers)
|
130
|
+
"""
|
131
|
+
results = {}
|
132
|
+
|
133
|
+
for server_config in servers:
|
134
|
+
if not isinstance(server_config, dict):
|
135
|
+
continue
|
136
|
+
|
137
|
+
url = server_config.get('url')
|
138
|
+
alias = server_config.get('alias')
|
139
|
+
|
140
|
+
if not url or not alias:
|
141
|
+
logger.warning(f"服务器配置缺少必要字段: {server_config}")
|
142
|
+
results[alias or 'unknown'] = {
|
143
|
+
'success': False,
|
144
|
+
'url': url or 'unknown',
|
145
|
+
'alias': alias or 'unknown',
|
146
|
+
'error': '缺少必要字段'
|
147
|
+
}
|
148
|
+
continue
|
149
|
+
|
150
|
+
api_key = server_config.get('api_key')
|
151
|
+
sync_global_vars = server_config.get('sync_global_vars', True)
|
152
|
+
sync_custom_vars = server_config.get('sync_custom_vars', True)
|
153
|
+
exclude_patterns = server_config.get('exclude_patterns')
|
154
|
+
|
155
|
+
success = self.register_server(
|
156
|
+
url=url,
|
157
|
+
alias=alias,
|
158
|
+
api_key=api_key,
|
159
|
+
sync_global_vars=sync_global_vars,
|
160
|
+
sync_custom_vars=sync_custom_vars,
|
161
|
+
exclude_patterns=exclude_patterns
|
162
|
+
)
|
163
|
+
|
164
|
+
results[alias] = {
|
165
|
+
'success': success,
|
166
|
+
'url': url,
|
167
|
+
'alias': alias
|
168
|
+
}
|
169
|
+
|
170
|
+
return results
|
171
|
+
|
172
|
+
def _collect_custom_variables(self) -> Dict[str, Any]:
|
173
|
+
"""收集自定义变量"""
|
174
|
+
variables = {}
|
175
|
+
|
176
|
+
for provider in self._variable_providers:
|
177
|
+
try:
|
178
|
+
provider_vars = provider()
|
179
|
+
if isinstance(provider_vars, dict):
|
180
|
+
variables.update(provider_vars)
|
181
|
+
else:
|
182
|
+
logger.warning(f"变量提供者返回了非字典类型: {type(provider_vars)}")
|
183
|
+
except Exception as e:
|
184
|
+
logger.warning(f"变量提供者执行失败: {e}")
|
185
|
+
|
186
|
+
return variables
|
187
|
+
|
188
|
+
def _connect_to_server(self,
|
189
|
+
url: str,
|
190
|
+
alias: str,
|
191
|
+
api_key: Optional[str],
|
192
|
+
sync_config: Dict[str, Any],
|
193
|
+
variables: Dict[str, Any]) -> bool:
|
194
|
+
"""连接到远程服务器"""
|
195
|
+
try:
|
196
|
+
# 导入远程关键字管理器
|
197
|
+
from pytest_dsl.remote import remote_keyword_manager
|
198
|
+
|
199
|
+
# 创建扩展的同步配置
|
200
|
+
extended_sync_config = sync_config.copy()
|
201
|
+
extended_sync_config['custom_variables'] = variables
|
202
|
+
|
203
|
+
# 注册服务器
|
204
|
+
success = remote_keyword_manager.register_remote_server(
|
205
|
+
url=url,
|
206
|
+
alias=alias,
|
207
|
+
api_key=api_key,
|
208
|
+
sync_config=extended_sync_config
|
209
|
+
)
|
210
|
+
|
211
|
+
if success:
|
212
|
+
logger.info(f"成功连接到远程服务器: {alias} ({url})")
|
213
|
+
else:
|
214
|
+
logger.error(f"连接远程服务器失败: {alias} ({url})")
|
215
|
+
|
216
|
+
return success
|
217
|
+
|
218
|
+
except ImportError:
|
219
|
+
logger.error("远程功能不可用,请检查依赖安装")
|
220
|
+
return False
|
221
|
+
except Exception as e:
|
222
|
+
logger.error(f"连接远程服务器时发生错误: {e}")
|
223
|
+
return False
|
224
|
+
|
225
|
+
def get_registered_servers(self) -> List[Dict[str, Any]]:
|
226
|
+
"""获取已注册的服务器列表"""
|
227
|
+
return self._server_configs.copy()
|
228
|
+
|
229
|
+
def clear_variable_providers(self):
|
230
|
+
"""清空所有变量提供者"""
|
231
|
+
self._variable_providers.clear()
|
232
|
+
|
233
|
+
def clear_connection_callbacks(self):
|
234
|
+
"""清空所有连接回调"""
|
235
|
+
self._connection_callbacks.clear()
|
236
|
+
|
237
|
+
|
238
|
+
# 创建全局注册器实例
|
239
|
+
remote_server_registry = RemoteServerRegistry()
|
240
|
+
|
241
|
+
|
242
|
+
# 便捷函数
|
243
|
+
def register_remote_server_with_variables(url: str,
|
244
|
+
alias: str,
|
245
|
+
variables: Dict[str, Any],
|
246
|
+
api_key: Optional[str] = None) -> bool:
|
247
|
+
"""使用指定变量注册远程服务器的便捷函数
|
248
|
+
|
249
|
+
Args:
|
250
|
+
url: 服务器URL
|
251
|
+
alias: 服务器别名
|
252
|
+
variables: 要同步的变量字典
|
253
|
+
api_key: API密钥
|
254
|
+
|
255
|
+
Returns:
|
256
|
+
bool: 是否连接成功
|
257
|
+
"""
|
258
|
+
# 创建临时变量提供者
|
259
|
+
def temp_provider():
|
260
|
+
return variables
|
261
|
+
|
262
|
+
# 临时添加变量提供者
|
263
|
+
original_providers = remote_server_registry._variable_providers.copy()
|
264
|
+
remote_server_registry._variable_providers = [temp_provider]
|
265
|
+
|
266
|
+
try:
|
267
|
+
return remote_server_registry.register_server(url, alias, api_key)
|
268
|
+
finally:
|
269
|
+
# 恢复原来的变量提供者
|
270
|
+
remote_server_registry._variable_providers = original_providers
|
271
|
+
|
272
|
+
|
273
|
+
def create_database_variable_provider(connection_string: str):
|
274
|
+
"""创建数据库变量提供者示例
|
275
|
+
|
276
|
+
这是一个示例函数,展示如何创建从数据库获取变量的提供者。
|
277
|
+
实际使用时需要根据具体的数据库类型进行调整。
|
278
|
+
|
279
|
+
Args:
|
280
|
+
connection_string: 数据库连接字符串
|
281
|
+
|
282
|
+
Returns:
|
283
|
+
callable: 变量提供者函数
|
284
|
+
"""
|
285
|
+
def database_provider():
|
286
|
+
# 这里是示例代码,实际需要根据数据库类型实现
|
287
|
+
# import sqlite3
|
288
|
+
# conn = sqlite3.connect(connection_string)
|
289
|
+
# cursor = conn.cursor()
|
290
|
+
# cursor.execute("SELECT key, value FROM variables")
|
291
|
+
# variables = dict(cursor.fetchall())
|
292
|
+
# conn.close()
|
293
|
+
# return variables
|
294
|
+
|
295
|
+
return {
|
296
|
+
'db_host': 'localhost',
|
297
|
+
'db_port': '5432',
|
298
|
+
'db_name': 'test_db'
|
299
|
+
}
|
300
|
+
|
301
|
+
return database_provider
|
302
|
+
|
303
|
+
|
304
|
+
def create_config_file_variable_provider(config_file_path: str):
|
305
|
+
"""创建配置文件变量提供者
|
306
|
+
|
307
|
+
从JSON或其他配置文件读取变量。
|
308
|
+
|
309
|
+
Args:
|
310
|
+
config_file_path: 配置文件路径
|
311
|
+
|
312
|
+
Returns:
|
313
|
+
callable: 变量提供者函数
|
314
|
+
"""
|
315
|
+
import json
|
316
|
+
import os
|
317
|
+
|
318
|
+
def config_file_provider():
|
319
|
+
if not os.path.exists(config_file_path):
|
320
|
+
return {}
|
321
|
+
|
322
|
+
try:
|
323
|
+
with open(config_file_path, 'r', encoding='utf-8') as f:
|
324
|
+
if config_file_path.endswith('.json'):
|
325
|
+
return json.load(f)
|
326
|
+
else:
|
327
|
+
# 可以扩展支持其他格式
|
328
|
+
return {}
|
329
|
+
except Exception as e:
|
330
|
+
logger.warning(f"读取配置文件失败: {e}")
|
331
|
+
return {}
|
332
|
+
|
333
|
+
return config_file_provider
|
pytest_dsl/core/utils.py
CHANGED
@@ -69,7 +69,8 @@ def _legacy_replace_variables_in_string(value: str) -> str:
|
|
69
69
|
break
|
70
70
|
|
71
71
|
# 替换变量引用
|
72
|
-
value = value[:match.start()] + str(var_value) +
|
72
|
+
value = value[:match.start()] + str(var_value) + \
|
73
|
+
value[match.end():]
|
73
74
|
|
74
75
|
# 再处理基本引用
|
75
76
|
matches = list(re.finditer(basic_pattern, value))
|
@@ -77,7 +78,8 @@ def _legacy_replace_variables_in_string(value: str) -> str:
|
|
77
78
|
var_name = match.group(1)
|
78
79
|
if context_has_variable(var_name):
|
79
80
|
var_value = get_variable(var_name)
|
80
|
-
value = value[:match.start()] + str(var_value) +
|
81
|
+
value = value[:match.start()] + str(var_value) + \
|
82
|
+
value[match.end():]
|
81
83
|
|
82
84
|
return value
|
83
85
|
|
@@ -102,37 +104,42 @@ def replace_variables_in_dict(data: Union[Dict, List, str]) -> Union[Dict, List,
|
|
102
104
|
|
103
105
|
|
104
106
|
def context_has_variable(var_name: str) -> bool:
|
105
|
-
"""
|
106
|
-
# 先检查YAML变量
|
107
|
-
from pytest_dsl.core.yaml_vars import yaml_vars
|
108
|
-
if yaml_vars.get_variable(var_name) is not None:
|
109
|
-
return True
|
107
|
+
"""检查变量是否存在于上下文中
|
110
108
|
|
109
|
+
检查顺序:
|
110
|
+
1. 测试上下文
|
111
|
+
2. 全局上下文(包含YAML变量)
|
112
|
+
"""
|
111
113
|
# 检查测试上下文
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
114
|
+
try:
|
115
|
+
from pytest_dsl.core.keyword_manager import keyword_manager
|
116
|
+
current_context = getattr(keyword_manager, 'current_context', None)
|
117
|
+
if current_context and current_context.has(var_name):
|
118
|
+
return True
|
119
|
+
except ImportError:
|
120
|
+
pass
|
121
|
+
|
122
|
+
# 检查全局上下文(包含YAML变量的统一访问)
|
118
123
|
return global_context.has_variable(var_name)
|
119
124
|
|
120
125
|
|
121
126
|
def get_variable(var_name: str) -> Any:
|
122
|
-
"""
|
123
|
-
# 先从YAML变量中获取
|
124
|
-
from pytest_dsl.core.yaml_vars import yaml_vars
|
125
|
-
yaml_value = yaml_vars.get_variable(var_name)
|
126
|
-
if yaml_value is not None:
|
127
|
-
return yaml_value
|
127
|
+
"""获取变量值
|
128
128
|
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
129
|
+
获取顺序:
|
130
|
+
1. 测试上下文
|
131
|
+
2. 全局上下文(包含YAML变量)
|
132
|
+
"""
|
133
|
+
# 先从测试上下文获取
|
134
|
+
try:
|
135
|
+
from pytest_dsl.core.keyword_manager import keyword_manager
|
136
|
+
current_context = getattr(keyword_manager, 'current_context', None)
|
137
|
+
if current_context and current_context.has(var_name):
|
138
|
+
return current_context.get(var_name)
|
139
|
+
except ImportError:
|
140
|
+
pass
|
141
|
+
|
142
|
+
# 再从全局上下文获取(包含对YAML变量的统一访问)
|
136
143
|
if global_context.has_variable(var_name):
|
137
144
|
return global_context.get_variable(var_name)
|
138
145
|
|
@@ -163,4 +170,4 @@ def deep_merge(base: Dict, override: Dict) -> Dict:
|
|
163
170
|
# 覆盖或添加值
|
164
171
|
result[key] = value
|
165
172
|
|
166
|
-
return result
|
173
|
+
return result
|
pytest_dsl/core/validator.py
CHANGED
@@ -47,6 +47,7 @@ class DSLValidator:
|
|
47
47
|
def __init__(self):
|
48
48
|
self.errors: List[DSLValidationError] = []
|
49
49
|
self.warnings: List[DSLValidationError] = []
|
50
|
+
self._temp_registered_keywords = [] # 记录临时注册的关键字,用于清理
|
50
51
|
|
51
52
|
def validate(self, content: str, dsl_id: Optional[str] = None
|
52
53
|
) -> Tuple[bool, List[DSLValidationError]]:
|
@@ -61,6 +62,7 @@ class DSLValidator:
|
|
61
62
|
"""
|
62
63
|
self.errors = []
|
63
64
|
self.warnings = []
|
65
|
+
self._temp_registered_keywords = []
|
64
66
|
|
65
67
|
# 基础验证
|
66
68
|
self._validate_basic_format(content)
|
@@ -68,8 +70,12 @@ class DSLValidator:
|
|
68
70
|
# 语法验证
|
69
71
|
ast = self._validate_syntax(content)
|
70
72
|
|
71
|
-
#
|
73
|
+
# 如果语法验证通过,进行预处理和语义验证
|
72
74
|
if ast and not self.errors:
|
75
|
+
# 预注册自定义关键字
|
76
|
+
self._preregister_custom_keywords(ast)
|
77
|
+
|
78
|
+
# 语义验证
|
73
79
|
self._validate_semantics(ast)
|
74
80
|
|
75
81
|
# 元数据验证
|
@@ -80,8 +86,72 @@ class DSLValidator:
|
|
80
86
|
if ast and not self.errors:
|
81
87
|
self._validate_keywords(ast)
|
82
88
|
|
89
|
+
# 清理临时注册的关键字
|
90
|
+
self._cleanup_temp_keywords()
|
91
|
+
|
83
92
|
return len(self.errors) == 0, self.errors + self.warnings
|
84
93
|
|
94
|
+
def _preregister_custom_keywords(self, ast: Node) -> None:
|
95
|
+
"""预注册AST中定义的自定义关键字
|
96
|
+
|
97
|
+
Args:
|
98
|
+
ast: 解析后的AST
|
99
|
+
"""
|
100
|
+
try:
|
101
|
+
from pytest_dsl.core.custom_keyword_manager import custom_keyword_manager
|
102
|
+
|
103
|
+
# 查找并注册自定义关键字
|
104
|
+
self._find_and_register_custom_keywords(ast)
|
105
|
+
|
106
|
+
except Exception as e:
|
107
|
+
# 如果预注册失败,记录警告但不影响验证流程
|
108
|
+
self.warnings.append(DSLValidationError(
|
109
|
+
"关键字预处理警告",
|
110
|
+
f"预注册自定义关键字时出现警告: {str(e)}"
|
111
|
+
))
|
112
|
+
|
113
|
+
def _find_and_register_custom_keywords(self, node: Node) -> None:
|
114
|
+
"""递归查找并注册自定义关键字
|
115
|
+
|
116
|
+
Args:
|
117
|
+
node: 当前节点
|
118
|
+
"""
|
119
|
+
# 检查当前节点是否是自定义关键字定义
|
120
|
+
if node.type in ['CustomKeyword', 'Function']:
|
121
|
+
try:
|
122
|
+
from pytest_dsl.core.custom_keyword_manager import custom_keyword_manager
|
123
|
+
|
124
|
+
# 注册自定义关键字
|
125
|
+
custom_keyword_manager._register_custom_keyword(
|
126
|
+
node, "validation_temp")
|
127
|
+
|
128
|
+
# 记录已注册的关键字名称,用于后续清理
|
129
|
+
keyword_name = node.value
|
130
|
+
self._temp_registered_keywords.append(keyword_name)
|
131
|
+
|
132
|
+
except Exception as e:
|
133
|
+
self.warnings.append(DSLValidationError(
|
134
|
+
"关键字注册警告",
|
135
|
+
f"注册自定义关键字 '{node.value}' 时出现警告: {str(e)}"
|
136
|
+
))
|
137
|
+
|
138
|
+
# 递归处理子节点
|
139
|
+
if hasattr(node, 'children') and node.children:
|
140
|
+
for child in node.children:
|
141
|
+
if isinstance(child, Node):
|
142
|
+
self._find_and_register_custom_keywords(child)
|
143
|
+
|
144
|
+
def _cleanup_temp_keywords(self) -> None:
|
145
|
+
"""清理临时注册的关键字"""
|
146
|
+
try:
|
147
|
+
for keyword_name in self._temp_registered_keywords:
|
148
|
+
# 从关键字管理器中移除临时注册的关键字
|
149
|
+
if keyword_name in keyword_manager._keywords:
|
150
|
+
del keyword_manager._keywords[keyword_name]
|
151
|
+
except Exception as e:
|
152
|
+
# 清理失败不影响验证结果,只记录警告
|
153
|
+
pass
|
154
|
+
|
85
155
|
def _validate_basic_format(self, content: str) -> None:
|
86
156
|
"""基础格式验证"""
|
87
157
|
if not content or not content.strip():
|