pytest-dsl 0.15.3__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.
@@ -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) + value[match.end():]
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) + value[match.end():]
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
- from pytest_dsl.core.keyword_manager import keyword_manager
113
- current_context = getattr(keyword_manager, 'current_context', None)
114
- if current_context and current_context.has(var_name):
115
- return True
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
- """获取变量值,先从YAML变量中获取,再从全局上下文获取"""
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
- from pytest_dsl.core.keyword_manager import keyword_manager
131
- current_context = getattr(keyword_manager, 'current_context', None)
132
- if current_context and current_context.has(var_name):
133
- return current_context.get(var_name)
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
@@ -0,0 +1,202 @@
1
+ """变量提供者模块
2
+
3
+ 定义了变量提供者的接口和具体实现,用于将不同来源的变量注入到TestContext中。
4
+ 这样可以实现解耦,让关键字只需要通过context获取变量,而不需要直接依赖特定的变量源。
5
+ """
6
+
7
+ from abc import ABC, abstractmethod
8
+ from typing import Any, Optional, Dict
9
+
10
+
11
+ class VariableProvider(ABC):
12
+ """变量提供者接口
13
+
14
+ 所有的变量提供者都需要实现这个接口,以便可以注册到TestContext中。
15
+ """
16
+
17
+ @abstractmethod
18
+ def get_variable(self, key: str) -> Optional[Any]:
19
+ """获取变量值
20
+
21
+ Args:
22
+ key: 变量键
23
+
24
+ Returns:
25
+ 变量值,如果不存在返回None
26
+ """
27
+ pass
28
+
29
+ @abstractmethod
30
+ def has_variable(self, key: str) -> bool:
31
+ """检查变量是否存在
32
+
33
+ Args:
34
+ key: 变量键
35
+
36
+ Returns:
37
+ True如果变量存在,否则False
38
+ """
39
+ pass
40
+
41
+ def get_all_variables(self) -> Dict[str, Any]:
42
+ """获取所有变量(可选实现)
43
+
44
+ Returns:
45
+ 包含所有变量的字典
46
+ """
47
+ return {}
48
+
49
+
50
+ class YAMLVariableProvider(VariableProvider):
51
+ """YAML变量提供者
52
+
53
+ 将yaml_vars包装成变量提供者,使其可以注入到TestContext中。
54
+ """
55
+
56
+ def __init__(self):
57
+ # 延迟导入,避免循环依赖
58
+ from pytest_dsl.core.yaml_vars import yaml_vars
59
+ self.yaml_vars = yaml_vars
60
+
61
+ def get_variable(self, key: str) -> Optional[Any]:
62
+ """从YAML变量源获取变量值"""
63
+ return self.yaml_vars.get_variable(key)
64
+
65
+ def has_variable(self, key: str) -> bool:
66
+ """检查YAML变量源中是否存在变量"""
67
+ return self.yaml_vars.has_variable(key)
68
+
69
+ def get_all_variables(self) -> Dict[str, Any]:
70
+ """获取所有YAML变量"""
71
+ return self.yaml_vars.get_all_variables()
72
+
73
+
74
+ class GlobalContextVariableProvider(VariableProvider):
75
+ """全局上下文变量提供者
76
+
77
+ 将global_context包装成变量提供者,但需要避免和YAML变量重复。
78
+ """
79
+
80
+ def __init__(self):
81
+ # 延迟导入,避免循环依赖
82
+ from pytest_dsl.core.global_context import global_context
83
+ self.global_context = global_context
84
+
85
+ def get_variable(self, key: str) -> Optional[Any]:
86
+ """从全局上下文获取变量值"""
87
+ # 注意:global_context的get_variable方法内部也会调用yaml_vars
88
+ # 为了避免重复,这里直接访问存储的变量
89
+ try:
90
+ # 直接从全局变量存储中获取,跳过YAML变量
91
+ from filelock import FileLock
92
+ with FileLock(self.global_context._lock_file):
93
+ variables = self.global_context._load_variables()
94
+ return variables.get(key)
95
+ except Exception:
96
+ return None
97
+
98
+ def has_variable(self, key: str) -> bool:
99
+ """检查全局上下文中是否存在变量"""
100
+ try:
101
+ from filelock import FileLock
102
+ with FileLock(self.global_context._lock_file):
103
+ variables = self.global_context._load_variables()
104
+ return key in variables
105
+ except Exception:
106
+ return False
107
+
108
+ def get_all_variables(self) -> Dict[str, Any]:
109
+ """获取所有全局变量"""
110
+ try:
111
+ from filelock import FileLock
112
+ with FileLock(self.global_context._lock_file):
113
+ return self.global_context._load_variables()
114
+ except Exception:
115
+ return {}
116
+
117
+
118
+ class CompositeVariableProvider(VariableProvider):
119
+ """组合变量提供者
120
+
121
+ 可以将多个变量提供者组合在一起,按优先级顺序查找变量。
122
+ """
123
+
124
+ def __init__(self, providers: list = None):
125
+ """初始化组合变量提供者
126
+
127
+ Args:
128
+ providers: 变量提供者列表,按优先级排序(索引越小优先级越高)
129
+ """
130
+ self.providers = providers or []
131
+
132
+ def add_provider(self, provider: VariableProvider):
133
+ """添加变量提供者"""
134
+ if provider not in self.providers:
135
+ self.providers.append(provider)
136
+
137
+ def remove_provider(self, provider: VariableProvider):
138
+ """移除变量提供者"""
139
+ if provider in self.providers:
140
+ self.providers.remove(provider)
141
+
142
+ def get_variable(self, key: str) -> Optional[Any]:
143
+ """按优先级顺序从提供者中获取变量"""
144
+ for provider in self.providers:
145
+ try:
146
+ value = provider.get_variable(key)
147
+ if value is not None:
148
+ return value
149
+ except Exception:
150
+ continue
151
+ return None
152
+
153
+ def has_variable(self, key: str) -> bool:
154
+ """检查是否有任何提供者包含该变量"""
155
+ for provider in self.providers:
156
+ try:
157
+ if provider.has_variable(key):
158
+ return True
159
+ except Exception:
160
+ continue
161
+ return False
162
+
163
+ def get_all_variables(self) -> Dict[str, Any]:
164
+ """获取所有变量,优先级高的覆盖优先级低的"""
165
+ all_vars = {}
166
+
167
+ # 从后往前遍历,让优先级高的覆盖优先级低的
168
+ for provider in reversed(self.providers):
169
+ try:
170
+ vars_dict = provider.get_all_variables()
171
+ all_vars.update(vars_dict)
172
+ except Exception:
173
+ continue
174
+
175
+ return all_vars
176
+
177
+
178
+ # 创建默认的变量提供者实例
179
+ def create_default_variable_providers() -> list:
180
+ """创建默认的变量提供者列表
181
+
182
+ 按优先级排序:YAML变量 > 全局上下文变量
183
+
184
+ Returns:
185
+ 变量提供者列表
186
+ """
187
+ providers = [
188
+ YAMLVariableProvider(),
189
+ GlobalContextVariableProvider()
190
+ ]
191
+ return providers
192
+
193
+
194
+ def setup_context_with_default_providers(context):
195
+ """为TestContext设置默认的变量提供者
196
+
197
+ Args:
198
+ context: TestContext实例
199
+ """
200
+ providers = create_default_variable_providers()
201
+ for provider in providers:
202
+ context.register_external_variable_provider(provider)