mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-17 18:12:04 +00:00
fix(Code Node): Install python modules always in a user-writable folder (#6568)
* upgrade pyodide * install pyodide modules to a custom user-writable path * in `augmentObject` `newData` is never undefined
This commit is contained in:
committed by
GitHub
parent
071e56f7fd
commit
bf351243df
@@ -108,10 +108,8 @@ export class Code implements INodeType {
|
||||
}
|
||||
|
||||
if (language === 'python') {
|
||||
const modules = this.getNodeParameter('modules', index) as string;
|
||||
const moduleImports: string[] = modules ? modules.split(',').map((m) => m.trim()) : [];
|
||||
context.printOverwrite = workflowMode === 'manual' ? this.sendMessageToUI : null;
|
||||
return new PythonSandbox(context, code, moduleImports, index, this.helpers);
|
||||
return new PythonSandbox(context, code, index, this.helpers);
|
||||
} else {
|
||||
const sandbox = new JavaScriptSandbox(context, code, index, workflowMode, this.helpers);
|
||||
if (workflowMode === 'manual') {
|
||||
|
||||
@@ -2,26 +2,15 @@ import type { PyodideInterface } from 'pyodide';
|
||||
|
||||
let pyodideInstance: PyodideInterface | undefined;
|
||||
|
||||
export async function LoadPyodide(): Promise<PyodideInterface> {
|
||||
export async function LoadPyodide(packageCacheDir: string): Promise<PyodideInterface> {
|
||||
if (pyodideInstance === undefined) {
|
||||
// TODO: Find better way to suppress warnings
|
||||
//@ts-ignore
|
||||
globalThis.Blob = (await import('node:buffer')).Blob;
|
||||
|
||||
// From: https://github.com/nodejs/node/issues/30810
|
||||
const { emitWarning } = process;
|
||||
process.emitWarning = (warning, ...args) => {
|
||||
if (args[0] === 'ExperimentalWarning') {
|
||||
return;
|
||||
}
|
||||
if (args[0] && typeof args[0] === 'object' && args[0].type === 'ExperimentalWarning') {
|
||||
return;
|
||||
}
|
||||
return emitWarning(warning, ...(args as string[]));
|
||||
};
|
||||
|
||||
const { loadPyodide } = await import('pyodide');
|
||||
pyodideInstance = await loadPyodide();
|
||||
pyodideInstance = await loadPyodide({ packageCacheDir });
|
||||
|
||||
await pyodideInstance.runPythonAsync(`
|
||||
from _pyodide_core import jsproxy_typedict
|
||||
from js import Object
|
||||
`);
|
||||
}
|
||||
|
||||
return pyodideInstance;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { IExecuteFunctions, INodeExecutionData } from 'n8n-workflow';
|
||||
import type { PyProxyDict } from 'pyodide';
|
||||
import type { PyDict } from 'pyodide/ffi';
|
||||
import { LoadPyodide } from './Pyodide';
|
||||
import type { SandboxContext } from './Sandbox';
|
||||
import { Sandbox } from './Sandbox';
|
||||
@@ -18,7 +18,6 @@ export class PythonSandbox extends Sandbox {
|
||||
constructor(
|
||||
context: SandboxContext,
|
||||
private pythonCode: string,
|
||||
private moduleImports: string[],
|
||||
itemIndex: number | undefined,
|
||||
helpers: IExecuteFunctions['helpers'],
|
||||
) {
|
||||
@@ -51,47 +50,35 @@ export class PythonSandbox extends Sandbox {
|
||||
}
|
||||
|
||||
private async runCodeInPython<T>() {
|
||||
// Below workaround from here:
|
||||
// https://github.com/pyodide/pyodide/discussions/3537#discussioncomment-4864345
|
||||
const runCode = `
|
||||
from _pyodide_core import jsproxy_typedict
|
||||
from js import Object
|
||||
jsproxy_typedict[0] = type(Object.new().as_object_map())
|
||||
|
||||
if printOverwrite:
|
||||
print = printOverwrite
|
||||
|
||||
async def __main():
|
||||
${this.pythonCode
|
||||
.split('\n')
|
||||
.map((line) => ' ' + line)
|
||||
.join('\n')}
|
||||
await __main()
|
||||
`;
|
||||
const pyodide = await LoadPyodide();
|
||||
|
||||
const moduleImportsFiltered = this.moduleImports.filter(
|
||||
(importModule) => !['asyncio', 'pyodide', 'math'].includes(importModule),
|
||||
);
|
||||
|
||||
if (moduleImportsFiltered.length) {
|
||||
await pyodide.loadPackage('micropip');
|
||||
const micropip = pyodide.pyimport('micropip');
|
||||
await Promise.all(
|
||||
moduleImportsFiltered.map((importModule) => micropip.install(importModule)),
|
||||
);
|
||||
}
|
||||
const packageCacheDir = this.helpers.getStoragePath();
|
||||
const pyodide = await LoadPyodide(packageCacheDir);
|
||||
|
||||
let executionResult;
|
||||
try {
|
||||
await pyodide.runPythonAsync('jsproxy_typedict[0] = type(Object.new().as_object_map())');
|
||||
|
||||
await pyodide.loadPackagesFromImports(this.pythonCode);
|
||||
|
||||
const dict = pyodide.globals.get('dict');
|
||||
const globalsDict: PyProxyDict = dict();
|
||||
const globalsDict: PyDict = dict();
|
||||
for (const key of Object.keys(this.context)) {
|
||||
if ((key === '_env' && envAccessBlocked) || key === '_node') continue;
|
||||
const value = this.context[key];
|
||||
globalsDict.set(key, value);
|
||||
}
|
||||
|
||||
await pyodide.runPythonAsync(`
|
||||
if 'printOverwrite' in globals():
|
||||
print = printOverwrite
|
||||
`);
|
||||
|
||||
const runCode = `
|
||||
async def __main():
|
||||
${this.pythonCode
|
||||
.split('\n')
|
||||
.map((line) => ' ' + line)
|
||||
.join('\n')}
|
||||
await __main()`;
|
||||
executionResult = await pyodide.runPythonAsync(runCode, { globals: globalsDict });
|
||||
globalsDict.destroy();
|
||||
} catch (error) {
|
||||
|
||||
@@ -35,7 +35,7 @@ export abstract class Sandbox {
|
||||
constructor(
|
||||
private textKeys: SandboxTextKeys,
|
||||
protected itemIndex: number | undefined,
|
||||
private helpers: IExecuteFunctions['helpers'],
|
||||
protected helpers: IExecuteFunctions['helpers'],
|
||||
) {}
|
||||
|
||||
abstract runCodeAllItems(): Promise<INodeExecutionData[]>;
|
||||
|
||||
@@ -45,19 +45,4 @@ export const pythonCodeDescription: INodeProperties[] = [
|
||||
},
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Python Modules',
|
||||
name: 'modules',
|
||||
displayOptions: {
|
||||
show: {
|
||||
language: ['python'],
|
||||
},
|
||||
},
|
||||
type: 'string',
|
||||
default: '',
|
||||
placeholder: 'opencv-python',
|
||||
description:
|
||||
'Comma-separated list of Python modules to load. They have to be installed to be able to be loaded and imported.',
|
||||
noDataExpression: true,
|
||||
},
|
||||
];
|
||||
|
||||
Reference in New Issue
Block a user