pytest-dsl 0.13.0__py3-none-any.whl → 0.14.0__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,87 @@
1
+ """
2
+ pytest-dsl hook管理器
3
+
4
+ 管理插件的注册、发现和调用
5
+ """
6
+ import pluggy
7
+ from typing import Optional, List, Any
8
+ from .hookspecs import DSLHookSpecs
9
+
10
+
11
+ class DSLHookManager:
12
+ """DSL Hook管理器"""
13
+
14
+ _instance: Optional['DSLHookManager'] = None
15
+
16
+ def __init__(self):
17
+ self.pm: pluggy.PluginManager = pluggy.PluginManager("pytest_dsl")
18
+ self.pm.add_hookspecs(DSLHookSpecs)
19
+ self._initialized = False
20
+
21
+ @classmethod
22
+ def get_instance(cls) -> 'DSLHookManager':
23
+ """获取单例实例"""
24
+ if cls._instance is None:
25
+ cls._instance = cls()
26
+ return cls._instance
27
+
28
+ def register_plugin(self, plugin: Any, name: Optional[str] = None) -> None:
29
+ """注册插件
30
+
31
+ Args:
32
+ plugin: 插件实例或模块
33
+ name: 插件名称(可选)
34
+ """
35
+ self.pm.register(plugin, name=name)
36
+
37
+ def unregister_plugin(self, plugin: Any = None,
38
+ name: Optional[str] = None) -> None:
39
+ """注销插件
40
+
41
+ Args:
42
+ plugin: 插件实例或模块
43
+ name: 插件名称
44
+ """
45
+ self.pm.unregister(plugin=plugin, name=name)
46
+
47
+ def is_registered(self, plugin: Any) -> bool:
48
+ """检查插件是否已注册"""
49
+ return self.pm.is_registered(plugin)
50
+
51
+ def load_setuptools_entrypoints(self, group: str = "pytest_dsl") -> int:
52
+ """加载setuptools入口点插件
53
+
54
+ Args:
55
+ group: 入口点组名
56
+
57
+ Returns:
58
+ 加载的插件数量
59
+ """
60
+ return self.pm.load_setuptools_entrypoints(group)
61
+
62
+ def get_plugins(self) -> List[Any]:
63
+ """获取所有已注册的插件"""
64
+ return self.pm.get_plugins()
65
+
66
+ def hook(self) -> Any:
67
+ """获取hook调用器"""
68
+ return self.pm.hook
69
+
70
+ def initialize(self) -> None:
71
+ """初始化hook管理器"""
72
+ if self._initialized:
73
+ return
74
+
75
+ # 尝试加载setuptools入口点插件
76
+ try:
77
+ loaded = self.load_setuptools_entrypoints()
78
+ if loaded > 0:
79
+ print(f"加载了 {loaded} 个插件")
80
+ except Exception as e:
81
+ print(f"加载插件时出现错误: {e}")
82
+
83
+ self._initialized = True
84
+
85
+
86
+ # 全局hook管理器实例
87
+ hook_manager = DSLHookManager.get_instance()
@@ -0,0 +1,134 @@
1
+ """
2
+ 可扩展的DSL执行器
3
+
4
+ 支持hook机制的DSL执行器,提供统一的执行接口
5
+ """
6
+ from typing import Dict, List, Optional, Any
7
+ from .dsl_executor import DSLExecutor
8
+ from .hook_manager import hook_manager
9
+
10
+
11
+ class HookableExecutor:
12
+ """支持Hook机制的DSL执行器"""
13
+
14
+ def __init__(self):
15
+ self.executor = None
16
+ self._ensure_initialized()
17
+
18
+ def _ensure_initialized(self):
19
+ """确保执行器已初始化"""
20
+ if self.executor is None:
21
+ self.executor = DSLExecutor(enable_hooks=True)
22
+
23
+ def execute_dsl(self, dsl_id: str, context: Optional[Dict[str, Any]] = None) -> Any:
24
+ """执行DSL用例
25
+
26
+ Args:
27
+ dsl_id: DSL标识符
28
+ context: 执行上下文(可选)
29
+
30
+ Returns:
31
+ 执行结果
32
+ """
33
+ self._ensure_initialized()
34
+
35
+ # 通过hook获取执行上下文扩展
36
+ if self.executor.enable_hooks and self.executor.hook_manager:
37
+ context_results = self.executor.hook_manager.pm.hook.dsl_get_execution_context(
38
+ dsl_id=dsl_id, base_context=context or {}
39
+ )
40
+ for extended_context in context_results:
41
+ if extended_context:
42
+ context = extended_context
43
+ break
44
+
45
+ # 执行DSL(内容为空,通过hook加载)
46
+ return self.executor.execute_from_content(
47
+ content="",
48
+ dsl_id=dsl_id,
49
+ context=context
50
+ )
51
+
52
+ def list_dsl_cases(self, project_id: Optional[int] = None,
53
+ filters: Optional[Dict[str, Any]] = None) -> List[Dict[str, Any]]:
54
+ """列出DSL用例
55
+
56
+ Args:
57
+ project_id: 项目ID(可选)
58
+ filters: 过滤条件(可选)
59
+
60
+ Returns:
61
+ 用例列表
62
+ """
63
+ self._ensure_initialized()
64
+
65
+ if not (self.executor.enable_hooks and self.executor.hook_manager):
66
+ return []
67
+
68
+ all_cases = []
69
+ case_results = self.executor.hook_manager.pm.hook.dsl_list_cases(
70
+ project_id=project_id, filters=filters
71
+ )
72
+
73
+ for result in case_results:
74
+ if result:
75
+ all_cases.extend(result)
76
+
77
+ return all_cases
78
+
79
+ def validate_dsl_content(self, dsl_id: str, content: str) -> List[str]:
80
+ """验证DSL内容
81
+
82
+ Args:
83
+ dsl_id: DSL标识符
84
+ content: DSL内容
85
+
86
+ Returns:
87
+ 验证错误列表,空列表表示验证通过
88
+ """
89
+ self._ensure_initialized()
90
+
91
+ if not (self.executor.enable_hooks and self.executor.hook_manager):
92
+ return []
93
+
94
+ all_errors = []
95
+ validation_results = self.executor.hook_manager.pm.hook.dsl_validate_content(
96
+ dsl_id=dsl_id, content=content
97
+ )
98
+
99
+ for errors in validation_results:
100
+ if errors:
101
+ all_errors.extend(errors)
102
+
103
+ return all_errors
104
+
105
+ def transform_dsl_content(self, dsl_id: str, content: str) -> str:
106
+ """转换DSL内容
107
+
108
+ Args:
109
+ dsl_id: DSL标识符
110
+ content: 原始DSL内容
111
+
112
+ Returns:
113
+ 转换后的DSL内容
114
+ """
115
+ self._ensure_initialized()
116
+
117
+ if not (self.executor.enable_hooks and self.executor.hook_manager):
118
+ return content
119
+
120
+ transformed_content = content
121
+ transform_results = self.executor.hook_manager.pm.hook.dsl_transform_content(
122
+ dsl_id=dsl_id, content=content
123
+ )
124
+
125
+ for result in transform_results:
126
+ if result:
127
+ transformed_content = result
128
+ break
129
+
130
+ return transformed_content
131
+
132
+
133
+ # 全局实例
134
+ hookable_executor = HookableExecutor()
@@ -0,0 +1,106 @@
1
+ """
2
+ 可扩展的关键字管理器
3
+
4
+ 支持通过hook机制注册自定义关键字
5
+ """
6
+ from typing import Dict, List, Optional, Any
7
+ from .keyword_manager import keyword_manager
8
+ from .hook_manager import hook_manager
9
+
10
+
11
+ class HookableKeywordManager:
12
+ """支持Hook机制的关键字管理器"""
13
+
14
+ def __init__(self):
15
+ self.hook_keywords = {} # 存储通过hook注册的关键字
16
+ self._initialized = False
17
+
18
+ def initialize(self):
19
+ """初始化,调用hook注册关键字"""
20
+ if self._initialized:
21
+ return
22
+
23
+ if hook_manager and hook_manager._initialized:
24
+ try:
25
+ # 调用hook注册自定义关键字
26
+ hook_manager.pm.hook.dsl_register_custom_keywords()
27
+ print(f"通过Hook注册了 {len(self.hook_keywords)} 个自定义关键字")
28
+ except Exception as e:
29
+ print(f"Hook关键字注册失败: {e}")
30
+
31
+ self._initialized = True
32
+
33
+ def register_hook_keyword(self, keyword_name: str, dsl_content: str,
34
+ source_info: Optional[Dict[str, Any]] = None):
35
+ """通过Hook注册自定义关键字
36
+
37
+ Args:
38
+ keyword_name: 关键字名称
39
+ dsl_content: DSL内容定义
40
+ source_info: 来源信息
41
+ """
42
+ # 检查是否已经注册过
43
+ if keyword_name in self.hook_keywords:
44
+ print(f"Hook关键字 {keyword_name} 已存在,跳过重复注册")
45
+ return
46
+
47
+ # 使用custom_keyword_manager的公共方法注册关键字
48
+ try:
49
+ from .custom_keyword_manager import custom_keyword_manager
50
+
51
+ # 准备来源名称
52
+ source_name = (source_info.get('source_name', 'Hook插件')
53
+ if source_info else 'Hook插件')
54
+
55
+ # 使用公共方法注册指定关键字
56
+ success = custom_keyword_manager.register_specific_keyword_from_dsl_content(
57
+ keyword_name, dsl_content, source_name
58
+ )
59
+
60
+ if success:
61
+ # 更新来源信息
62
+ if keyword_name in keyword_manager._keywords:
63
+ keyword_info = keyword_manager._keywords[keyword_name]
64
+ if source_info:
65
+ keyword_info.update(source_info)
66
+ else:
67
+ keyword_info.update({
68
+ 'source_type': 'hook',
69
+ 'source_name': 'Hook插件'
70
+ })
71
+
72
+ # 记录到hook关键字列表
73
+ self.hook_keywords[keyword_name] = {
74
+ 'dsl_content': dsl_content,
75
+ 'source_info': source_info or {
76
+ 'source_type': 'hook',
77
+ 'source_name': 'Hook插件'
78
+ }
79
+ }
80
+
81
+ print(f"注册Hook关键字: {keyword_name}")
82
+
83
+ except Exception as e:
84
+ print(f"注册Hook关键字失败 {keyword_name}: {e}")
85
+ raise
86
+
87
+ def get_hook_keywords(self) -> Dict[str, Dict]:
88
+ """获取所有通过Hook注册的关键字"""
89
+ return self.hook_keywords.copy()
90
+
91
+ def is_hook_keyword(self, keyword_name: str) -> bool:
92
+ """检查是否为Hook关键字"""
93
+ return keyword_name in self.hook_keywords
94
+
95
+ def unregister_hook_keyword(self, keyword_name: str):
96
+ """注销Hook关键字"""
97
+ if keyword_name in self.hook_keywords:
98
+ del self.hook_keywords[keyword_name]
99
+ # 从关键字管理器中移除
100
+ if hasattr(keyword_manager, '_keywords'):
101
+ keyword_manager._keywords.pop(keyword_name, None)
102
+ print(f"注销Hook关键字: {keyword_name}")
103
+
104
+
105
+ # 全局实例
106
+ hookable_keyword_manager = HookableKeywordManager()
@@ -0,0 +1,175 @@
1
+ """
2
+ pytest-dsl hook规范定义
3
+
4
+ 使用pluggy定义hook接口,允许外部框架扩展DSL功能
5
+ """
6
+ import pluggy
7
+ from typing import Dict, List, Optional, Any
8
+
9
+ # 创建pytest-dsl的hook标记器
10
+ hookspec = pluggy.HookspecMarker("pytest_dsl")
11
+ hookimpl = pluggy.HookimplMarker("pytest_dsl")
12
+
13
+
14
+ class DSLHookSpecs:
15
+ """DSL Hook规范"""
16
+
17
+ @hookspec
18
+ def dsl_load_content(self, dsl_id: str) -> Optional[str]:
19
+ """加载DSL内容
20
+
21
+ Args:
22
+ dsl_id: DSL标识符(可以是文件路径、数据库ID等)
23
+
24
+ Returns:
25
+ DSL内容字符串,如果无法加载返回None
26
+ """
27
+
28
+ @hookspec
29
+ def dsl_list_cases(self, project_id: Optional[int] = None,
30
+ filters: Optional[Dict[str, Any]] = None
31
+ ) -> List[Dict[str, Any]]:
32
+ """列出DSL用例
33
+
34
+ Args:
35
+ project_id: 项目ID,用于过滤(可选)
36
+ filters: 其他过滤条件(可选)
37
+
38
+ Returns:
39
+ 用例列表,每个用例包含id、name、description等字段
40
+ """
41
+
42
+ @hookspec
43
+ def dsl_register_custom_keywords(self,
44
+ project_id: Optional[int] = None) -> None:
45
+ """注册自定义关键字
46
+
47
+ Args:
48
+ project_id: 项目ID,用于过滤(可选)
49
+ """
50
+
51
+ @hookspec
52
+ def dsl_get_execution_context(self, dsl_id: str,
53
+ base_context: Dict[str, Any]
54
+ ) -> Dict[str, Any]:
55
+ """获取执行上下文
56
+
57
+ Args:
58
+ dsl_id: DSL标识符
59
+ base_context: 基础上下文
60
+
61
+ Returns:
62
+ 扩展后的执行上下文
63
+ """
64
+
65
+ @hookspec(firstresult=True) # 只使用第一个返回结果
66
+ def dsl_create_executor(self) -> Optional[Any]:
67
+ """创建自定义DSL执行器
68
+
69
+ Returns:
70
+ 自定义执行器实例,如果返回None则使用默认执行器
71
+ """
72
+
73
+ @hookspec
74
+ def dsl_before_execution(self, dsl_id: str,
75
+ context: Dict[str, Any]) -> None:
76
+ """DSL执行前的hook
77
+
78
+ Args:
79
+ dsl_id: DSL标识符
80
+ context: 执行上下文
81
+ """
82
+
83
+ @hookspec
84
+ def dsl_after_execution(self, dsl_id: str, context: Dict[str, Any],
85
+ result: Any,
86
+ exception: Optional[Exception] = None) -> None:
87
+ """DSL执行后的hook
88
+
89
+ Args:
90
+ dsl_id: DSL标识符
91
+ context: 执行上下文
92
+ result: 执行结果
93
+ exception: 如果执行失败,包含异常信息
94
+ """
95
+
96
+ @hookspec
97
+ def dsl_transform_content(self, dsl_id: str, content: str) -> str:
98
+ """转换DSL内容
99
+
100
+ Args:
101
+ dsl_id: DSL标识符
102
+ content: 原始DSL内容
103
+
104
+ Returns:
105
+ 转换后的DSL内容
106
+ """
107
+
108
+ @hookspec
109
+ def dsl_validate_content(self, dsl_id: str, content: str) -> List[str]:
110
+ """验证DSL内容
111
+
112
+ Args:
113
+ dsl_id: DSL标识符
114
+ content: DSL内容
115
+
116
+ Returns:
117
+ 验证错误列表,空列表表示验证通过
118
+ """
119
+
120
+ @hookspec
121
+ def dsl_load_variables(self, project_id: Optional[int] = None,
122
+ environment: Optional[str] = None,
123
+ filters: Optional[Dict[str, Any]] = None
124
+ ) -> Dict[str, Any]:
125
+ """加载变量配置
126
+
127
+ Args:
128
+ project_id: 项目ID,用于过滤(可选)
129
+ environment: 环境名称,如dev、test、prod等(可选)
130
+ filters: 其他过滤条件(可选)
131
+
132
+ Returns:
133
+ 变量字典,键为变量名,值为变量值
134
+ """
135
+
136
+ @hookspec
137
+ def dsl_get_variable(self, var_name: str, project_id: Optional[int] = None,
138
+ environment: Optional[str] = None
139
+ ) -> Optional[Any]:
140
+ """获取单个变量值
141
+
142
+ Args:
143
+ var_name: 变量名
144
+ project_id: 项目ID,用于过滤(可选)
145
+ environment: 环境名称(可选)
146
+
147
+ Returns:
148
+ 变量值,如果变量不存在返回None
149
+ """
150
+
151
+ @hookspec
152
+ def dsl_list_variable_sources(self, project_id: Optional[int] = None
153
+ ) -> List[Dict[str, Any]]:
154
+ """列出可用的变量源
155
+
156
+ Args:
157
+ project_id: 项目ID,用于过滤(可选)
158
+
159
+ Returns:
160
+ 变量源列表,每个源包含name、type、description等字段
161
+ """
162
+
163
+ @hookspec
164
+ def dsl_validate_variables(self, variables: Dict[str, Any],
165
+ project_id: Optional[int] = None
166
+ ) -> List[str]:
167
+ """验证变量配置
168
+
169
+ Args:
170
+ variables: 变量字典
171
+ project_id: 项目ID,用于过滤(可选)
172
+
173
+ Returns:
174
+ 验证错误列表,空列表表示验证通过
175
+ """