refactor: Replace Pyodide with native Python with env flag is enabled (#19403)

This commit is contained in:
Iván Ovejero
2025-09-11 11:28:12 +02:00
committed by GitHub
parent 18cccb29ea
commit 052e24ef0e
7 changed files with 42 additions and 14 deletions

View File

@@ -69,4 +69,19 @@ export class TaskRunnersConfig {
*/ */
@Env('N8N_RUNNERS_INSECURE_MODE') @Env('N8N_RUNNERS_INSECURE_MODE')
insecureMode: boolean = false; insecureMode: boolean = false;
/**
* Whether to enable the Python task runner (beta). This will replace the
* Pyodide option with the native Python option in the Code node. Expects a
* Python task runner to be available, typically in a sidecar container.
*
* Actions required:
* - Any Code node set to the legacy `python` parameter will need to be manually
* updated to use the new `pythonNative` parameter.
* - Any Code node script relying on Pyodide syntax is likely to need to be manually
* adjusted to account for breaking changes:
* https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.code/#python-native-beta
*/
@Env('N8N_NATIVE_PYTHON_RUNNER')
isNativePythonRunnerEnabled: boolean = false;
} }

View File

@@ -260,6 +260,7 @@ describe('GlobalConfig', () => {
taskTimeout: 300, taskTimeout: 300,
heartbeatInterval: 30, heartbeatInterval: 30,
insecureMode: false, insecureMode: false,
isNativePythonRunnerEnabled: false,
}, },
sentry: { sentry: {
backendDsn: '', backendDsn: '',

View File

@@ -132,7 +132,8 @@ export class FrontendService {
versionCli: N8N_VERSION, versionCli: N8N_VERSION,
concurrency: this.globalConfig.executions.concurrency.productionLimit, concurrency: this.globalConfig.executions.concurrency.productionLimit,
isNativePythonRunnerEnabled: isNativePythonRunnerEnabled:
this.globalConfig.taskRunners.enabled && process.env.N8N_NATIVE_PYTHON_RUNNER === 'true', this.globalConfig.taskRunners.enabled &&
this.globalConfig.taskRunners.isNativePythonRunnerEnabled,
authCookie: { authCookie: {
secure: this.globalConfig.auth.cookie.secure, secure: this.globalConfig.auth.cookie.secure,
}, },

View File

@@ -118,7 +118,7 @@ export class TaskRunnerModule {
await this.jsRunnerProcess.start(); await this.jsRunnerProcess.start();
if (process.env.N8N_NATIVE_PYTHON_RUNNER === 'true') { if (this.runnerConfig.isNativePythonRunnerEnabled) {
const { PyTaskRunnerProcess } = await import('@/task-runners/task-runner-process-py'); const { PyTaskRunnerProcess } = await import('@/task-runners/task-runner-process-py');
this.pyRunnerProcess = Container.get(PyTaskRunnerProcess); this.pyRunnerProcess = Container.get(PyTaskRunnerProcess);
this.pyRunnerProcessRestartLoopDetector = new TaskRunnerProcessRestartLoopDetector( this.pyRunnerProcessRestartLoopDetector = new TaskRunnerProcessRestartLoopDetector(

View File

@@ -111,9 +111,9 @@ function operationsCategory(nodeTypeDescription: INodeTypeDescription): ActionTy
nodeTypeDescription, nodeTypeDescription,
); );
if (customParsedItems) { if (customParsedItems) {
// temporary filter until native Python runner is GA // temporary until native Python runner is GA
return useSettingsStore().isNativePythonRunnerEnabled return useSettingsStore().isNativePythonRunnerEnabled
? customParsedItems ? customParsedItems.filter((item) => item.actionKey !== 'language_python')
: customParsedItems.filter((item) => item.actionKey !== 'language_pythonNative'); : customParsedItems.filter((item) => item.actionKey !== 'language_pythonNative');
} }
} }

View File

@@ -241,9 +241,13 @@ const parameterOptions = computed(() => {
const options = hasRemoteMethod.value ? remoteParameterOptions.value : props.parameter.options; const options = hasRemoteMethod.value ? remoteParameterOptions.value : props.parameter.options;
const safeOptions = (options ?? []).filter(isValidParameterOption); const safeOptions = (options ?? []).filter(isValidParameterOption);
// temporary filter until native Python runner is GA // temporary until native Python runner is GA
if (props.parameter.name === 'language' && !settingsStore.isNativePythonRunnerEnabled) { if (props.parameter.name === 'language') {
return safeOptions.filter((o) => o.value !== 'pythonNative'); if (settingsStore.isNativePythonRunnerEnabled) {
return safeOptions.filter((o) => o.value !== 'python');
} else {
return safeOptions.filter((o) => o.value !== 'pythonNative');
}
} }
return safeOptions; return safeOptions;

View File

@@ -123,19 +123,22 @@ export class Code implements INodeType {
? (this.getNodeParameter('language', 0) as CodeNodeLanguageOption) ? (this.getNodeParameter('language', 0) as CodeNodeLanguageOption)
: 'javaScript'; : 'javaScript';
if (language === 'python' && !Container.get(NodesConfig).pythonEnabled) { const isJsLang = language === 'javaScript';
const isPyLang = language === 'python' || language === 'pythonNative';
const runnersConfig = Container.get(TaskRunnersConfig);
const isJsRunner = runnersConfig.enabled;
const isPyRunner = runnersConfig.isNativePythonRunnerEnabled;
if (isPyLang && !Container.get(NodesConfig).pythonEnabled) {
throw new PythonDisabledError(); throw new PythonDisabledError();
} }
const runnersConfig = Container.get(TaskRunnersConfig);
const isRunnerEnabled = runnersConfig.enabled;
const nodeMode = this.getNodeParameter('mode', 0) as CodeExecutionMode; const nodeMode = this.getNodeParameter('mode', 0) as CodeExecutionMode;
const workflowMode = this.getMode(); const workflowMode = this.getMode();
const codeParameterName = const codeParameterName =
language === 'python' || language === 'pythonNative' ? 'pythonCode' : 'jsCode'; language === 'python' || language === 'pythonNative' ? 'pythonCode' : 'jsCode';
if (language === 'javaScript' && isRunnerEnabled) { if (isJsLang && isJsRunner) {
const code = this.getNodeParameter(codeParameterName, 0) as string; const code = this.getNodeParameter(codeParameterName, 0) as string;
const sandbox = new JsTaskRunnerSandbox(code, nodeMode, workflowMode, this); const sandbox = new JsTaskRunnerSandbox(code, nodeMode, workflowMode, this);
const numInputItems = this.getInputData().length; const numInputItems = this.getInputData().length;
@@ -145,9 +148,13 @@ export class Code implements INodeType {
: [await sandbox.runCodeForEachItem(numInputItems)]; : [await sandbox.runCodeForEachItem(numInputItems)];
} }
if (language === 'pythonNative' && !isRunnerEnabled) throw new NativePythonWithoutRunnerError(); if (language === 'pythonNative' && !isPyRunner) {
throw new NativePythonWithoutRunnerError();
}
if (language === 'pythonNative') { if (isPyLang && isPyRunner) {
// When the native Python runner is enabled, both `python` and `pythonNative` are
// sent to the runner, to ensure there is no path to run Pyodide in this scenario.
const code = this.getNodeParameter(codeParameterName, 0) as string; const code = this.getNodeParameter(codeParameterName, 0) as string;
const sandbox = new PythonTaskRunnerSandbox(code, nodeMode, workflowMode, this); const sandbox = new PythonTaskRunnerSandbox(code, nodeMode, workflowMode, this);