diff --git a/plugin-script-python/src/main/java/io/kestra/plugin/scripts/python/internals/PythonEnvironmentManager.java b/plugin-script-python/src/main/java/io/kestra/plugin/scripts/python/internals/PythonEnvironmentManager.java index d7faf52e..21b8e6bf 100644 --- a/plugin-script-python/src/main/java/io/kestra/plugin/scripts/python/internals/PythonEnvironmentManager.java +++ b/plugin-script-python/src/main/java/io/kestra/plugin/scripts/python/internals/PythonEnvironmentManager.java @@ -104,8 +104,16 @@ public ResolvedPythonEnvironment setup(final Property containerImage, fi } String pythonInterpreter = "python"; - if (pythonVersion != null && (taskRunner instanceof Process || RunnerType.PROCESS.equals(runnerType))) { - pythonInterpreter = resolver.getPythonPath(targetPythonVersion); + if (taskRunner instanceof Process || RunnerType.PROCESS.equals(runnerType)) { + // The Process runner executes commands directly on the worker host, which may only ship + // 'python3' (Debian/Ubuntu, the standard Kestra worker image) and not the bare 'python' + // literal. When an explicit pythonVersion is set, resolve it through the configured package + // manager (may provision a managed Python via 'uv'). Otherwise, probe for an interpreter + // that is already installed (python3, then python) rather than forcing a managed-Python + // download just to run a script with no dependencies. + pythonInterpreter = pythonVersion != null + ? resolver.getPythonPath(targetPythonVersion) + : PackageManagerType.PIP.getPythonPath(resolver, targetPythonVersion); } return new ResolvedPythonEnvironment(cached, resolvedPythonPackages, pythonInterpreter); diff --git a/plugin-script-python/src/test/java/io/kestra/plugin/scripts/python/internals/PythonEnvironmentManagerTest.java b/plugin-script-python/src/test/java/io/kestra/plugin/scripts/python/internals/PythonEnvironmentManagerTest.java new file mode 100644 index 00000000..aa3107d3 --- /dev/null +++ b/plugin-script-python/src/test/java/io/kestra/plugin/scripts/python/internals/PythonEnvironmentManagerTest.java @@ -0,0 +1,48 @@ +package io.kestra.plugin.scripts.python.internals; + +import java.util.Map; +import java.util.UUID; + +import org.junit.jupiter.api.Test; + +import io.kestra.core.junit.annotations.KestraTest; +import io.kestra.core.models.property.Property; +import io.kestra.core.runners.RunContext; +import io.kestra.core.runners.RunContextFactory; +import io.kestra.plugin.scripts.exec.scripts.models.RunnerType; +import io.kestra.plugin.scripts.python.Script; + +import jakarta.inject.Inject; + +import static io.kestra.core.utils.TestsUtils.mockRunContext; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.not; + +@KestraTest +class PythonEnvironmentManagerTest { + + @Inject + RunContextFactory runContextFactory; + + @Test + void shouldResolveARunnableInterpreterForProcessRunnerWithoutExplicitPythonVersion() throws Exception { + // Regression test: without an explicit pythonVersion, the Process runner used to default the + // interpreter to the literal "python", which doesn't exist on python3-only hosts (Debian/Ubuntu, + // the standard Kestra worker image), causing "/bin/sh: python: not found" (exit code 127). + Script task = Script.builder() + .id("python-env-manager-test-" + UUID.randomUUID()) + .type(Script.class.getName()) + .script(Property.ofValue("print('hello')")) + .build(); + RunContext runContext = mockRunContext(runContextFactory, task, Map.of()); + + PythonEnvironmentManager manager = new PythonEnvironmentManager(runContext, task); + PythonEnvironmentManager.ResolvedPythonEnvironment environment = manager.setup(task.getContainerImage(), null, RunnerType.PROCESS); + + assertThat(environment.interpreter(), is(not("python"))); + + Process process = new ProcessBuilder(environment.interpreter(), "--version").start(); + assertThat(process.waitFor(), is(0)); + } +} diff --git a/plugin-script-python/src/test/resources/sanity-checks/all_python.yaml b/plugin-script-python/src/test/resources/sanity-checks/all_python.yaml index 2906e804..0459b1d8 100644 --- a/plugin-script-python/src/test/resources/sanity-checks/all_python.yaml +++ b/plugin-script-python/src/test/resources/sanity-checks/all_python.yaml @@ -44,7 +44,7 @@ tasks: main.py: | print("Hello, World from the Commands Task and Process Task Runner") commands: - - python main.py + - python3 main.py - id: python_script_logs type: io.kestra.plugin.scripts.python.Script