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')
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,
heartbeatInterval: 30,
insecureMode: false,
isNativePythonRunnerEnabled: false,
},
sentry: {
backendDsn: '',

View File

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

View File

@@ -118,7 +118,7 @@ export class TaskRunnerModule {
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');
this.pyRunnerProcess = Container.get(PyTaskRunnerProcess);
this.pyRunnerProcessRestartLoopDetector = new TaskRunnerProcessRestartLoopDetector(

View File

@@ -111,9 +111,9 @@ function operationsCategory(nodeTypeDescription: INodeTypeDescription): ActionTy
nodeTypeDescription,
);
if (customParsedItems) {
// temporary filter until native Python runner is GA
// temporary until native Python runner is GA
return useSettingsStore().isNativePythonRunnerEnabled
? customParsedItems
? customParsedItems.filter((item) => item.actionKey !== 'language_python')
: 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 safeOptions = (options ?? []).filter(isValidParameterOption);
// temporary filter until native Python runner is GA
if (props.parameter.name === 'language' && !settingsStore.isNativePythonRunnerEnabled) {
return safeOptions.filter((o) => o.value !== 'pythonNative');
// temporary until native Python runner is GA
if (props.parameter.name === 'language') {
if (settingsStore.isNativePythonRunnerEnabled) {
return safeOptions.filter((o) => o.value !== 'python');
} else {
return safeOptions.filter((o) => o.value !== 'pythonNative');
}
}
return safeOptions;

View File

@@ -123,19 +123,22 @@ export class Code implements INodeType {
? (this.getNodeParameter('language', 0) as CodeNodeLanguageOption)
: '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();
}
const runnersConfig = Container.get(TaskRunnersConfig);
const isRunnerEnabled = runnersConfig.enabled;
const nodeMode = this.getNodeParameter('mode', 0) as CodeExecutionMode;
const workflowMode = this.getMode();
const codeParameterName =
language === 'python' || language === 'pythonNative' ? 'pythonCode' : 'jsCode';
if (language === 'javaScript' && isRunnerEnabled) {
if (isJsLang && isJsRunner) {
const code = this.getNodeParameter(codeParameterName, 0) as string;
const sandbox = new JsTaskRunnerSandbox(code, nodeMode, workflowMode, this);
const numInputItems = this.getInputData().length;
@@ -145,9 +148,13 @@ export class Code implements INodeType {
: [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 sandbox = new PythonTaskRunnerSandbox(code, nodeMode, workflowMode, this);