pancake-framework 0.1.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.
Files changed (53) hide show
  1. pancake/__init__.py +46 -0
  2. pancake/builder/__init__.py +11 -0
  3. pancake/builder/build.py +14 -0
  4. pancake/builder/load_dlc.py +157 -0
  5. pancake/builder/load_src.py +114 -0
  6. pancake/builder/safe_import.py +0 -0
  7. pancake/cli.py +212 -0
  8. pancake/initialize/__init__.py +10 -0
  9. pancake/initialize/check_dlc.py +10 -0
  10. pancake/initialize/check_env.py +111 -0
  11. pancake/initialize/check_struct.py +16 -0
  12. pancake/initialize/print_ico.py +12 -0
  13. pancake/oven/__init__.py +15 -0
  14. pancake/oven/default.py +66 -0
  15. pancake/oven/muffin.py +42 -0
  16. pancake/oven/pancake.py +47 -0
  17. pancake/ovenware/__init__.py +71 -0
  18. pancake/ovenware/ai_memory.py +876 -0
  19. pancake/ovenware/ai_model.py +509 -0
  20. pancake/ovenware/base.py +54 -0
  21. pancake/ovenware/broker.py +281 -0
  22. pancake/ovenware/cui.py +228 -0
  23. pancake/ovenware/embed.py +51 -0
  24. pancake/ovenware/external_plugin.py +59 -0
  25. pancake/ovenware/gui.py +265 -0
  26. pancake/ovenware/inject.py +306 -0
  27. pancake/ovenware/langgraph/__init__.py +20 -0
  28. pancake/ovenware/langgraph/core.py +366 -0
  29. pancake/ovenware/lifecycle.py +190 -0
  30. pancake/ovenware/mybatis/__init__.py +83 -0
  31. pancake/ovenware/mybatis/config.py +37 -0
  32. pancake/ovenware/mybatis/connection.py +78 -0
  33. pancake/ovenware/mybatis/mapper.py +509 -0
  34. pancake/ovenware/mybatis/sql_parser.py +196 -0
  35. pancake/ovenware/mybatis/types.py +44 -0
  36. pancake/ovenware/mybatis/wrapper.py +418 -0
  37. pancake/ovenware/redis_cache.py +583 -0
  38. pancake/ovenware/remote.py +203 -0
  39. pancake/ovenware/web.py +352 -0
  40. pancake/resource/__init__.py +9 -0
  41. pancake/resource/config.py +3 -0
  42. pancake/resource/json.py +17 -0
  43. pancake/resource/logging.py +20 -0
  44. pancake/resource/xml_config.py +167 -0
  45. pancake/resource/yml.py +65 -0
  46. pancake/run.py +65 -0
  47. pancake/settings.py +106 -0
  48. pancake/tool/__init__.py +1 -0
  49. pancake/tool/progress_show.py +20 -0
  50. pancake_framework-0.1.0.dist-info/METADATA +280 -0
  51. pancake_framework-0.1.0.dist-info/RECORD +53 -0
  52. pancake_framework-0.1.0.dist-info/WHEEL +4 -0
  53. pancake_framework-0.1.0.dist-info/entry_points.txt +3 -0
pancake/__init__.py ADDED
@@ -0,0 +1,46 @@
1
+ import sys
2
+ import os
3
+
4
+ disable_check = []
5
+
6
+ current_dir = os.path.dirname(os.path.abspath(__file__))
7
+ if current_dir not in sys.path:
8
+ sys.path.insert(0, current_dir)
9
+
10
+ _initialized = False
11
+
12
+ def init():
13
+ global _initialized
14
+ if _initialized:
15
+ return
16
+ _initialized = True
17
+
18
+ import initialize
19
+ initialize.print_ico()
20
+
21
+ init_rask = {"check_environment":initialize.check_environment, # 检查环境
22
+ "check_struct":initialize.check_struct, # 检查项目结构完整性
23
+ }
24
+
25
+ # 检查运行环境
26
+ print("检查运行环境")
27
+ import tool
28
+ progress = tool.ProgressBar(len(init_rask), prefix="初始化环境")
29
+ for task in init_rask.keys():
30
+ if task in disable_check:
31
+ continue
32
+ init_rask[task]()
33
+ progress.update(1, f"{task} 完成")
34
+
35
+ progress.finish()
36
+
37
+ # 初始化 dotenv 和 logging
38
+ from resource import config # noqa: F401
39
+ import resource.logging as resource_logging # noqa: F401
40
+
41
+
42
+ # 延迟导入,避免循环依赖
43
+ def run():
44
+ init()
45
+ from .run import run as _run
46
+ _run()
@@ -0,0 +1,11 @@
1
+ import importlib
2
+ import pkgutil
3
+
4
+ # 当前包下的所有模块
5
+ for _, module_name, _ in pkgutil.iter_modules(__path__):
6
+ module = importlib.import_module(f".{module_name}", __name__)
7
+
8
+ for attr_name in dir(module):
9
+ if not attr_name.startswith('_'):
10
+ attr = getattr(module, attr_name)
11
+ globals()[f"{module_name}.{attr_name}"] = attr
@@ -0,0 +1,14 @@
1
+ from pancake import oven
2
+
3
+
4
+ def build():
5
+
6
+ # 构建所有服务实例
7
+ for classes in oven.pancake_dough["Service"]:
8
+ oven.pancake_pie["Service"][str(classes)] = oven.pancake_dough["Service"][classes].build()
9
+
10
+ for build_method_name in oven.muffin_egg["BuildOrder"]:
11
+ oven.muffin_egg["Builder"][build_method_name[0]]()
12
+
13
+
14
+
@@ -0,0 +1,157 @@
1
+ import sys
2
+
3
+ from pancake import oven
4
+ from pancake.initialize import check_dlc
5
+
6
+ import importlib
7
+ import inspect
8
+ import logging
9
+ import os
10
+
11
+ logger = logging.getLogger(__name__)
12
+
13
+
14
+ def _load_from_xml():
15
+ """从 XML 配置加载插件列表"""
16
+ plugins = oven.pancake_xml.get("plugins", [])
17
+ if not plugins:
18
+ return None # 无 XML 配置,回退到目录扫描
19
+
20
+ main_classes = {}
21
+
22
+ for plugin_info in plugins:
23
+ name = plugin_info["name"]
24
+ source = plugin_info["source"]
25
+ enabled = plugin_info.get("enabled", True)
26
+ init_order = plugin_info.get("init_order", 0)
27
+ build_order = plugin_info.get("build_order", 0)
28
+
29
+ # 导入插件模块(无论是否启用都需要导入,以注册装饰器)
30
+ try:
31
+ plugin = importlib.import_module(source)
32
+ except ImportError as e:
33
+ if enabled:
34
+ logger.error(f"Failed to import plugin {name} ({source}): {e}")
35
+ sys.exit(1)
36
+ else:
37
+ logger.debug(f"Disabled plugin {name} not available: {e}")
38
+ continue
39
+
40
+ # 收集模块中的公开成员
41
+ all_items = {}
42
+ for attr_name, member in inspect.getmembers(plugin):
43
+ if (
44
+ (not attr_name.startswith("_"))
45
+ and (inspect.isclass(member) or inspect.isfunction(member))
46
+ and (member.__module__ == plugin.__name__ or member.__module__.startswith(plugin.__name__ + "."))
47
+ ):
48
+ all_items[attr_name] = member
49
+
50
+ # 注册装饰器(无论是否启用都注册)
51
+ for item_name, obj in all_items.items():
52
+ if item_name.startswith("_"):
53
+ continue
54
+ if inspect.isclass(obj) and hasattr(obj, 'build') and hasattr(obj, 'init_order'):
55
+ continue # Main 类下面单独处理
56
+ oven.muffin_flour[item_name] = obj
57
+
58
+ # 注册 Main 类(仅启用时)
59
+ if enabled:
60
+ for item_name, obj in all_items.items():
61
+ if item_name.startswith("_"):
62
+ continue
63
+ if inspect.isclass(obj) and hasattr(obj, 'build') and hasattr(obj, 'init_order'):
64
+ effective_init = init_order
65
+ effective_build = build_order
66
+
67
+ oven.muffin_egg["BuildOrder"].append([name, effective_build])
68
+ oven.muffin_egg["InitOrder"].append([name, effective_init])
69
+ main_classes[name] = obj
70
+ break
71
+ else:
72
+ logger.info(f"Plugin {name} loaded (no Main class)")
73
+ else:
74
+ logger.info(f"Plugin {name} disabled (decorators still loaded)")
75
+
76
+ return main_classes
77
+
78
+
79
+ def _load_from_directory():
80
+ """从 ovenware 目录扫描加载插件(回退模式)"""
81
+ dlc_dir = os.path.join(os.path.dirname(__file__), "../", "ovenware")
82
+ entries = os.listdir(dlc_dir)
83
+ # 单文件插件
84
+ plugin_files = [f[:-3] for f in entries if f.endswith(".py") and f not in ["__init__.py"]]
85
+ # 子包插件(目录含 __init__.py)
86
+ plugin_dirs = [d for d in entries
87
+ if os.path.isdir(os.path.join(dlc_dir, d))
88
+ and os.path.exists(os.path.join(dlc_dir, d, "__init__.py"))
89
+ and not d.startswith("_")]
90
+ plugin_names = plugin_files + plugin_dirs
91
+
92
+ main_classes = {}
93
+
94
+ for plugin_name in plugin_names:
95
+ if plugin_name in oven.pancake_yaml.get("framework.disable_dlc", []):
96
+ continue
97
+ plugin = importlib.import_module(f"ovenware.{plugin_name}")
98
+ all_items = {}
99
+ for name, member in inspect.getmembers(plugin):
100
+ if (
101
+ (not name.startswith("_"))
102
+ and (inspect.isclass(member) or inspect.isfunction(member))
103
+ and (member.__module__ == plugin.__name__ or member.__module__.startswith(plugin.__name__ + "."))
104
+ ):
105
+ all_items[name] = member
106
+
107
+ # 加载 all_items 中的所有 修饰器和 main 类
108
+ for decorator in all_items.keys():
109
+ if not decorator.startswith("_"):
110
+ obj = all_items[decorator]
111
+ if inspect.isclass(obj) and hasattr(obj, 'build') and hasattr(obj, 'init_order'):
112
+ oven.muffin_egg["BuildOrder"].append([plugin_name, all_items[decorator].build_order])
113
+ oven.muffin_egg["InitOrder"].append([plugin_name, all_items[decorator].init_order])
114
+ main_classes[plugin_name] = all_items[decorator]
115
+ continue
116
+ oven.muffin_flour[decorator] = all_items[decorator]
117
+
118
+ return main_classes
119
+
120
+
121
+ def run():
122
+ """加载插件:优先 XML 配置,回退到目录扫描"""
123
+ has_xml = bool(oven.pancake_xml.get("plugins"))
124
+
125
+ if has_xml:
126
+ logger.info("Loading plugins from XML config")
127
+ main_classes = _load_from_xml()
128
+ else:
129
+ logger.info("No XML config, scanning ovenware directory")
130
+ main_classes = _load_from_directory()
131
+
132
+ oven.muffin_egg["BuildOrder"].sort(key=lambda x: x[1], reverse=True)
133
+ oven.muffin_egg["InitOrder"].sort(key=lambda x: x[1])
134
+
135
+ for plugin_name in oven.muffin_egg["InitOrder"]:
136
+ try:
137
+ check_dlc(main_classes[plugin_name[0]].check)
138
+ except AttributeError:
139
+ pass
140
+ except Exception as e:
141
+ logger.error(f"Plugin check failed for {plugin_name[0]}: {e}")
142
+ import sys
143
+ sys.exit(1)
144
+
145
+ new_main = main_classes[plugin_name[0]]()
146
+
147
+ oven.muffin_egg["Builder"][plugin_name[0]] = new_main.build
148
+
149
+ # 尝试获取 loop_method,verify_method
150
+ try:
151
+ oven.muffin_egg["LoopMethod"][plugin_name[0]] = new_main.loop_method
152
+ except AttributeError:
153
+ pass
154
+ try:
155
+ oven.muffin_egg["VerifyMethod"][plugin_name[0]] = new_main.verify
156
+ except AttributeError:
157
+ pass
@@ -0,0 +1,114 @@
1
+ import ast
2
+ import builtins
3
+ import os
4
+ import sys
5
+ from pancake import oven
6
+
7
+ # 所有 src 文件共享的全局命名空间
8
+ _shared_globals = {
9
+ "__builtins__": builtins,
10
+ "__name__": "__not_main__",
11
+ }
12
+
13
+
14
+ def scan_py_files(folder="."):
15
+ files = []
16
+ for root, _, filenames in os.walk(folder):
17
+ for f in filenames:
18
+ if f.endswith(".py") and f != os.path.basename(__file__):
19
+ files.append(os.path.abspath(os.path.join(root, f)))
20
+ return files
21
+
22
+ def parse_file(filepath):
23
+ try:
24
+ with open(filepath, "r", encoding="utf-8") as f:
25
+ tree = ast.parse(f.read())
26
+ except (OSError, IOError, SyntaxError):
27
+ return []
28
+
29
+ dirname = os.path.dirname(filepath)
30
+ if dirname not in sys.path:
31
+ sys.path.insert(0, dirname)
32
+
33
+ results = []
34
+ for node in ast.walk(tree):
35
+ # 扫描 类 + 函数(含 async def)
36
+ if not isinstance(node, (ast.ClassDef, ast.FunctionDef, ast.AsyncFunctionDef)):
37
+ continue
38
+
39
+ obj_type = "class" if isinstance(node, ast.ClassDef) else "function"
40
+ obj_name = node.name
41
+
42
+ # 遍历所有装饰器
43
+ for dec in node.decorator_list:
44
+ dec_name = None
45
+ if isinstance(dec, ast.Name):
46
+ dec_name = dec.id
47
+ elif isinstance(dec, ast.Call) and hasattr(dec.func, 'id'):
48
+ dec_name = dec.func.id
49
+
50
+ # 匹配 muffin_flour 中的装饰器
51
+ if dec_name in oven.muffin_flour.keys():
52
+ results.append((dec_name, obj_type, obj_name, filepath))
53
+ return results
54
+
55
+ def safe_register(filepath):
56
+ """在共享命名空间中执行文件,所有定义对其他文件可见"""
57
+ try:
58
+ with open(filepath, "r", encoding="utf-8") as f:
59
+ source = f.read()
60
+ except (OSError, IOError):
61
+ return
62
+
63
+ try:
64
+ tree = ast.parse(source)
65
+ except SyntaxError:
66
+ return
67
+
68
+ # 收集所有顶级定义
69
+ definitions = []
70
+ for node in tree.body:
71
+ if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef, ast.Import, ast.ImportFrom)):
72
+ definitions.append(node)
73
+
74
+ if not definitions:
75
+ return
76
+
77
+ new_tree = ast.Module(body=definitions, type_ignores=[])
78
+ ast.fix_missing_locations(new_tree)
79
+
80
+ try:
81
+ code = compile(new_tree, filepath, 'exec')
82
+ exec(code, _shared_globals)
83
+ except Exception:
84
+ import traceback
85
+ traceback.print_exc()
86
+ return
87
+
88
+ # 将文件中定义的名称注入 builtins,实现跨文件零 import
89
+ for node in definitions:
90
+ if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef)):
91
+ if node.name in _shared_globals:
92
+ builtins.__dict__[node.name] = _shared_globals[node.name]
93
+
94
+ def run():
95
+ from pancake.settings import get_path
96
+ src_dir = get_path("src_dir")
97
+ files = scan_py_files(src_dir)
98
+
99
+ # 预解析所有文件,按装饰器的 _load_priority 排序(值越小越先加载,默认 50)
100
+ file_items = []
101
+ for f in files:
102
+ items = parse_file(f)
103
+ if items:
104
+ min_priority = 50
105
+ for dec_name, _, _, _ in items:
106
+ dec_obj = oven.muffin_flour.get(dec_name)
107
+ if dec_obj and hasattr(dec_obj, '_load_priority'):
108
+ min_priority = min(min_priority, dec_obj._load_priority)
109
+ file_items.append((min_priority, f, items))
110
+
111
+ file_items.sort(key=lambda x: x[0])
112
+
113
+ for _, path, _ in file_items:
114
+ safe_register(path)
File without changes
pancake/cli.py ADDED
@@ -0,0 +1,212 @@
1
+ """
2
+ Pancake CLI - 命令行工具
3
+ 支持 create / check / run / build 等子命令
4
+ """
5
+
6
+ import argparse
7
+ import os
8
+ import sys
9
+ import shutil
10
+ import subprocess
11
+
12
+
13
+ def cmd_create(args):
14
+ """创建新项目"""
15
+ name = args.name
16
+ project_dir = os.path.join(os.getcwd(), name)
17
+
18
+ if os.path.exists(project_dir):
19
+ print(f"错误: 目录 '{name}' 已存在")
20
+ sys.exit(1)
21
+
22
+ print(f"创建项目: {name}")
23
+
24
+ # 创建目录结构
25
+ dirs = [
26
+ os.path.join(project_dir, "src", "resource", "yaml"),
27
+ os.path.join(project_dir, "src", "resource", "json"),
28
+ os.path.join(project_dir, "src", "mapper"),
29
+ os.path.join(project_dir, "src", "controller"),
30
+ ]
31
+ for d in dirs:
32
+ os.makedirs(d)
33
+
34
+ # 创建 main.py
35
+ with open(os.path.join(project_dir, "main.py"), "w", encoding="utf-8") as f:
36
+ f.write('import pancake\n\npancake.run()\n')
37
+
38
+ # 创建 pancake.xml
39
+ with open(os.path.join(project_dir, "pancake.xml"), "w", encoding="utf-8") as f:
40
+ f.write('''<?xml version="1.0" encoding="UTF-8"?>
41
+ <pancake>
42
+ <global>
43
+ <service.title>''' + name + '''</service.title>
44
+ <service.version>1.0.0</service.version>
45
+ <service.host>127.0.0.1</service.host>
46
+ <service.port>8080</service.port>
47
+ </global>
48
+ <plugins>
49
+ <plugin name="embed" init-order="0"/>
50
+ <plugin name="mybatis" init-order="1"/>
51
+ <plugin name="web" init-order="2"/>
52
+ </plugins>
53
+ </pancake>
54
+ ''')
55
+
56
+ # 创建 service.yaml
57
+ with open(os.path.join(project_dir, "src", "resource", "yaml", "service.yaml"), "w", encoding="utf-8") as f:
58
+ f.write('''service:
59
+ title: ''' + name + '''
60
+ version: 1.0.0
61
+ host: 127.0.0.1
62
+ port: 8080
63
+ ''')
64
+
65
+ # 创建 pyproject.toml
66
+ with open(os.path.join(project_dir, "pyproject.toml"), "w", encoding="utf-8") as f:
67
+ f.write('''[tool.poetry]
68
+ name = "''' + name + '''"
69
+ version = "0.1.0"
70
+ description = "A Pancake framework project"
71
+ authors = ["Your Name <you@example.com>"]
72
+
73
+ [tool.poetry.dependencies]
74
+ python = "^3.13"
75
+ pancake = "*"
76
+
77
+ [build-system]
78
+ requires = ["poetry-core"]
79
+ build-backend = "poetry.core.masonry.api"
80
+ ''')
81
+
82
+ print(f"项目 '{name}' 创建成功!")
83
+ print(f" cd {name}")
84
+ print(f" pip install pancake")
85
+ print(f" python main.py")
86
+
87
+
88
+ def cmd_check(args):
89
+ """检查项目结构和环境"""
90
+ print("检查项目结构...")
91
+
92
+ errors = []
93
+ warnings = []
94
+
95
+ # 检查 main.py
96
+ if not os.path.exists("main.py"):
97
+ errors.append("缺少 main.py")
98
+ else:
99
+ with open("main.py", "r", encoding="utf-8") as f:
100
+ content = f.read()
101
+ if "import pancake" not in content:
102
+ warnings.append("main.py 中未找到 'import pancake'")
103
+
104
+ # 检查 pancake.xml
105
+ if not os.path.exists("pancake.xml"):
106
+ warnings.append("缺少 pancake.xml(可选,用于插件配置)")
107
+
108
+ # 检查 src 目录
109
+ if not os.path.isdir("src"):
110
+ errors.append("缺少 src 目录")
111
+ else:
112
+ yaml_dir = os.path.join("src", "resource", "yaml")
113
+ if not os.path.isdir(yaml_dir):
114
+ warnings.append(f"缺少 {yaml_dir} 目录")
115
+ else:
116
+ yaml_files = [f for f in os.listdir(yaml_dir) if f.endswith(('.yaml', '.yml'))]
117
+ if not yaml_files:
118
+ warnings.append(f"{yaml_dir} 中没有 YAML 配置文件")
119
+
120
+ # 检查 pancake 是否安装
121
+ try:
122
+ import pancake # noqa: F401
123
+ print(" [OK] pancake 已安装")
124
+ except ImportError:
125
+ errors.append("pancake 未安装,请运行: pip install pancake")
126
+
127
+ # 输出结果
128
+ if errors:
129
+ print("\n错误:")
130
+ for e in errors:
131
+ print(f" [ERROR] {e}")
132
+
133
+ if warnings:
134
+ print("\n警告:")
135
+ for w in warnings:
136
+ print(f" [WARN] {w}")
137
+
138
+ if not errors and not warnings:
139
+ print(" 项目结构正常!")
140
+
141
+ return len(errors) == 0
142
+
143
+
144
+ def cmd_run(args):
145
+ """运行项目"""
146
+ if not os.path.exists("main.py"):
147
+ print("错误: 当前目录没有 main.py,请在项目根目录运行")
148
+ sys.exit(1)
149
+
150
+ print("启动 Pancake 项目...")
151
+ import pancake
152
+ pancake.run()
153
+
154
+
155
+ def cmd_build(args):
156
+ """打包项目为 wheel"""
157
+ if not os.path.exists("pyproject.toml"):
158
+ print("错误: 当前目录没有 pyproject.toml")
159
+ sys.exit(1)
160
+
161
+ print("打包项目...")
162
+ result = subprocess.run(
163
+ [sys.executable, "-m", "poetry", "build"],
164
+ capture_output=True, text=True
165
+ )
166
+ if result.returncode == 0:
167
+ print("打包成功!")
168
+ print(result.stdout)
169
+ else:
170
+ print("打包失败:")
171
+ print(result.stderr)
172
+ sys.exit(1)
173
+
174
+
175
+ def main():
176
+ parser = argparse.ArgumentParser(
177
+ prog="pancake",
178
+ description="Pancake Framework - 命令行工具",
179
+ )
180
+ subparsers = parser.add_subparsers(dest="command", help="可用命令")
181
+
182
+ # create
183
+ create_parser = subparsers.add_parser("create", help="创建新项目")
184
+ create_parser.add_argument("name", help="项目名称")
185
+
186
+ # check
187
+ subparsers.add_parser("check", help="检查项目结构和环境")
188
+
189
+ # run
190
+ subparsers.add_parser("run", help="运行项目")
191
+
192
+ # build
193
+ subparsers.add_parser("build", help="打包项目为 wheel")
194
+
195
+ args = parser.parse_args()
196
+
197
+ if args.command is None:
198
+ parser.print_help()
199
+ sys.exit(0)
200
+
201
+ commands = {
202
+ "create": cmd_create,
203
+ "check": cmd_check,
204
+ "run": cmd_run,
205
+ "build": cmd_build,
206
+ }
207
+
208
+ commands[args.command](args)
209
+
210
+
211
+ if __name__ == "__main__":
212
+ main()
@@ -0,0 +1,10 @@
1
+ """
2
+ 初始化环境
3
+ - 检查环境
4
+ - 初始化项目结构
5
+ """
6
+
7
+ from .check_struct import check_struct
8
+ from .check_env import check_environment
9
+ from .print_ico import print_ico
10
+ from .check_dlc import check_dlc
@@ -0,0 +1,10 @@
1
+ import os
2
+ import logging
3
+
4
+ def check_dlc(check_method):
5
+ logger = logging.getLogger("check")
6
+ try:
7
+ check_method()
8
+ except ValueError as e:
9
+ logger.error(e)
10
+ raise e
@@ -0,0 +1,111 @@
1
+ import subprocess
2
+ import sys
3
+ import os
4
+ import platform
5
+ import logging
6
+
7
+ logger = logging.getLogger(__name__)
8
+
9
+
10
+ def check_poetry_installed() -> bool:
11
+ """检查 Poetry 是否已安装"""
12
+ try:
13
+ subprocess.run(
14
+ ["poetry", "--version"],
15
+ check=True,
16
+ stdout=subprocess.PIPE,
17
+ stderr=subprocess.PIPE,
18
+ text=True
19
+ )
20
+ return True
21
+ except (subprocess.CalledProcessError, FileNotFoundError):
22
+ return False
23
+
24
+
25
+ def install_poetry():
26
+ """跨平台自动安装 Poetry"""
27
+ print("正在安装 Poetry...")
28
+ os_name = platform.system()
29
+
30
+ try:
31
+ if os_name == "Windows":
32
+ install_cmd = "(Invoke-WebRequest -Uri https://install.python-poetry.org -UseBasicParsing).Content | python -"
33
+ subprocess.run(["powershell", "-Command", install_cmd], check=True, shell=True)
34
+ else:
35
+ subprocess.run(["curl", "-sSL", "https://install.python-poetry.org"], check=True)
36
+ print("Poetry 安装成功!")
37
+ except subprocess.CalledProcessError:
38
+ try:
39
+ subprocess.run([sys.executable, "-m", "pip", "install", "poetry"], check=True)
40
+ except subprocess.CalledProcessError as e:
41
+ print(f"安装失败: {e}")
42
+ sys.exit(1)
43
+
44
+
45
+ def setup_current_directory(dependencies: list):
46
+ """直接在当前目录初始化环境"""
47
+ current_dir = os.getcwd()
48
+ print(f"当前工作目录: {current_dir}")
49
+
50
+ # 1. 初始化 Poetry
51
+ print("正在初始化 Poetry...")
52
+ subprocess.run(["poetry", "init", "-n"],
53
+ capture_output=True,
54
+ text=True)
55
+
56
+ # 2. 配置虚拟环境在当前目录下 (.venv)
57
+ print("配置虚拟环境路径...")
58
+ subprocess.run(["poetry", "config", "virtualenvs.in-project", "true", "--local"], check=True)
59
+
60
+ # 3. 安装依赖
61
+ if dependencies:
62
+ print(f"正在安装依赖: {dependencies}")
63
+ subprocess.run(["poetry", "add"] + dependencies, capture_output=True, text=True)
64
+
65
+ print("\n环境配置完成!")
66
+ print(f"虚拟环境位置: {os.path.join(current_dir, '.venv')}")
67
+
68
+ def find_module(module_names: list) -> bool:
69
+ """检查模块是否已安装"""
70
+ import importlib.util
71
+
72
+ for module_name in module_names:
73
+ spec = importlib.util.find_spec(module_name)
74
+
75
+ if spec is None:
76
+ return False
77
+ return True
78
+
79
+
80
+ def check_environment():
81
+ """检查运行环境"""
82
+ REQUIRED_LIBRARIES = ["python-dotenv", "pyyaml"]
83
+ REQUIRED_MODULES = ["dotenv", "yaml"]
84
+ if not find_module(REQUIRED_MODULES):
85
+ # Check for non-interactive mode
86
+ if os.getenv("PANCAKE_AUTO_INSTALL", "").lower() in ("1", "true", "yes"):
87
+ auto_install = True
88
+ elif not sys.stdin.isatty():
89
+ logger.error("Missing required libraries and running in non-interactive mode. Set PANCAKE_AUTO_INSTALL=1 to auto-install.")
90
+ sys.exit(1)
91
+ else:
92
+ auto_install = input("缺少必要的库,是否自动安装? (y/n)").lower().startswith("y")
93
+
94
+ if not auto_install:
95
+ sys.exit(1)
96
+
97
+ # 1. 检查 Poetry
98
+ if not check_poetry_installed():
99
+ install_poetry()
100
+ if platform.system() == "Windows":
101
+ import ctypes
102
+ ctypes.windll.user32.SendMessageTimeoutW(0xFFFF, 0x1A, 0, "Environment", 0x0002, 5000, None)
103
+ else:
104
+ os.environ["PATH"] = f"{os.path.expanduser('~/.local/bin')}:{os.environ['PATH']}"
105
+
106
+ # 2. 直接在当前目录搭建
107
+ setup_current_directory(REQUIRED_LIBRARIES)
108
+
109
+ print("环境配置完成!", f"虚拟环境位置: {os.path.join(os.getcwd(), '.venv')}")
110
+ print("请重新启动项目 例如: poetry run python main.py")
111
+ sys.exit(1)