(
scriptPath: string,
method: string,
args: (string | number | object | undefined)[],
options: { pythonExecutable?: string } = {},
)
| 300 | * @throws An error if there's an issue running the Python script or parsing its output. |
| 301 | */ |
| 302 | export async function runPython<T = unknown>( |
| 303 | scriptPath: string, |
| 304 | method: string, |
| 305 | args: (string | number | object | undefined)[], |
| 306 | options: { pythonExecutable?: string } = {}, |
| 307 | ): Promise<T> { |
| 308 | const absPath = path.resolve(scriptPath); |
| 309 | const customPath = getConfiguredPythonPath(options.pythonExecutable); |
| 310 | let pythonPath = customPath || 'python'; |
| 311 | let tempDirectory: string | undefined; |
| 312 | |
| 313 | pythonPath = await validatePythonPath(pythonPath, typeof customPath === 'string'); |
| 314 | |
| 315 | try { |
| 316 | tempDirectory = await createSecureTempDirectory('promptfoo-python-'); |
| 317 | const tempJsonPath = await writeSecureTempFile( |
| 318 | tempDirectory, |
| 319 | 'input.json', |
| 320 | safeJsonStringify(args) as string, |
| 321 | ); |
| 322 | const outputPath = await writeSecureTempFile(tempDirectory, 'output.json', ''); |
| 323 | const pythonOptions: PythonShellOptions = { |
| 324 | args: [absPath, method, tempJsonPath, outputPath], |
| 325 | env: process.env, |
| 326 | mode: 'binary', |
| 327 | pythonPath, |
| 328 | scriptPath: getWrapperDir('python'), |
| 329 | // When `inherit` is used, `import pdb; pdb.set_trace()` will work. |
| 330 | ...(getEnvBool('PROMPTFOO_PYTHON_DEBUG_ENABLED') && { stdio: 'inherit' }), |
| 331 | }; |
| 332 | |
| 333 | logger.debug('[Python] Running script', { scriptPath: absPath, method }); |
| 334 | |
| 335 | await new Promise<void>((resolve, reject) => { |
| 336 | try { |
| 337 | const pyshell = new PythonShell('wrapper.py', pythonOptions); |
| 338 | const stderrLogger = new PythonStderrLogger(); |
| 339 | |
| 340 | pyshell.stdout?.on('data', (chunk: Buffer) => { |
| 341 | logger.debug(chunk.toString('utf-8').trim()); |
| 342 | }); |
| 343 | |
| 344 | pyshell.stderr?.on('data', (chunk: Buffer) => { |
| 345 | stderrLogger.handleData(chunk); |
| 346 | }); |
| 347 | |
| 348 | pyshell.end((err) => { |
| 349 | stderrLogger.flush(); |
| 350 | if (err) { |
| 351 | reject(err); |
| 352 | } else { |
| 353 | resolve(); |
| 354 | } |
| 355 | }); |
| 356 | } catch (error) { |
| 357 | reject(error); |
| 358 | } |
| 359 | }); |
no test coverage detected
searching dependent graphs…