Files
WYF-koubo/lib/python-util.ts
T
2026-06-19 18:45:55 +08:00

187 lines
5.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { spawn } from 'child_process';
import * as fs from 'fs';
import * as path from 'path';
/**
* Python路径检测工具
* 支持在Windows、macOS、Linux等多个系统上检测Python
*/
let cachedPythonPath: string | null = null;
/**
* 获取系统中可用的Python可执行文件路径
* 优先级:
* 1. 环境变量 PYTHON_PATH
* 2. 环境变量 FUNASR_PYTHON_PATH
* 3. Windows特定位置
* 4. 系统PATH中的python3/python
*/
export function getPythonPath(): string {
// 如果已缓存,直接返回
if (cachedPythonPath) {
return cachedPythonPath;
}
// 1. 检查环境变量
if (process.env.PYTHON_PATH && fs.existsSync(process.env.PYTHON_PATH)) {
cachedPythonPath = process.env.PYTHON_PATH;
return cachedPythonPath;
}
if (process.env.FUNASR_PYTHON_PATH && fs.existsSync(process.env.FUNASR_PYTHON_PATH)) {
cachedPythonPath = process.env.FUNASR_PYTHON_PATH;
return cachedPythonPath;
}
// 2. Windows特定位置
if (process.platform === 'win32') {
const windowsPythonPaths = [
// 微软应用商店安装
path.join(process.env.LOCALAPPDATA || '', 'Programs', 'Python', 'Python312', 'python.exe'),
path.join(process.env.LOCALAPPDATA || '', 'Programs', 'Python', 'Python311', 'python.exe'),
path.join(process.env.LOCALAPPDATA || '', 'Programs', 'Python', 'Python310', 'python.exe'),
path.join(process.env.LOCALAPPDATA || '', 'Programs', 'Python', 'Python39', 'python.exe'),
path.join(process.env.LOCALAPPDATA || '', 'Programs', 'Python', 'Python38', 'python.exe'),
// 直接安装
'C:\\Python312\\python.exe',
'C:\\Python311\\python.exe',
'C:\\Python310\\python.exe',
'C:\\Python39\\python.exe',
'C:\\Python38\\python.exe',
// Anaconda/Miniconda
path.join(process.env.USERPROFILE || '', 'anaconda3', 'python.exe'),
path.join(process.env.USERPROFILE || '', 'miniconda3', 'python.exe'),
// WSL
path.join(process.env.USERPROFILE || '', 'AppData', 'Local', 'Microsoft', 'WindowsApps', 'python.exe'),
];
for (const pythonPath of windowsPythonPaths) {
if (fs.existsSync(pythonPath)) {
cachedPythonPath = pythonPath;
return cachedPythonPath;
}
}
}
// 3. macOS/Linux 特定位置
if (process.platform === 'darwin' || process.platform === 'linux') {
const unixPythonPaths = [
'/usr/bin/python3',
'/usr/bin/python',
'/usr/local/bin/python3',
'/usr/local/bin/python',
'/opt/homebrew/bin/python3', // M1/M2 Mac
path.join(process.env.HOME || '', '.pyenv', 'versions', 'default', 'bin', 'python'),
];
for (const pythonPath of unixPythonPaths) {
if (fs.existsSync(pythonPath)) {
cachedPythonPath = pythonPath;
return cachedPythonPath;
}
}
}
// 4. 默认尝试 python 或 python3
// 这会依赖系统 PATHWindows上可能不工作
if (process.platform === 'win32') {
cachedPythonPath = 'python';
} else {
cachedPythonPath = 'python3';
}
return cachedPythonPath;
}
/**
* 验证Python可执行文件是否可用
*/
export function isValidPythonPath(pythonPath: string): boolean {
return fs.existsSync(pythonPath);
}
/**
* 检查Python是否安装了特定的库
*/
export async function checkPythonModule(moduleName: string): Promise<boolean> {
return new Promise((resolve) => {
const pythonPath = getPythonPath();
const process = spawn(pythonPath, ['-m', 'pip', 'show', moduleName], {
stdio: ['pipe', 'pipe', 'pipe'],
shell: true
});
let stdout = '';
process.stdout.on('data', (data) => {
stdout += data.toString();
});
process.on('close', (code) => {
resolve(code === 0 && stdout.includes(`Name: ${moduleName}`));
});
process.on('error', () => {
resolve(false);
});
// 设置超时
setTimeout(() => {
process.kill();
resolve(false);
}, 5000);
});
}
/**
* 获取Python版本信息
*/
export async function getPythonVersion(): Promise<string | null> {
return new Promise((resolve) => {
const pythonPath = getPythonPath();
const process = spawn(pythonPath, ['--version'], {
stdio: ['pipe', 'pipe', 'pipe'],
shell: true
});
let stdout = '';
let stderr = '';
process.stdout.on('data', (data) => {
stdout += data.toString();
});
process.stderr.on('data', (data) => {
stderr += data.toString();
});
process.on('close', (code) => {
if (code === 0) {
resolve((stdout || stderr).trim());
} else {
resolve(null);
}
});
process.on('error', () => {
resolve(null);
});
// 设置超时
setTimeout(() => {
process.kill();
resolve(null);
}, 5000);
});
}
/**
* 清除缓存(用于测试)
*/
export function clearPythonPathCache(): void {
cachedPythonPath = null;
}