Skip to content

Conversation

@marc-lehner
Copy link
Contributor

Add support for the new python environment provider node and it's pixi port object to the python scripts

@marc-lehner marc-lehner requested a review from a team as a code owner January 26, 2026 12:38
@marc-lehner marc-lehner requested review from Copilot and knime-ghub-bot and removed request for a team January 26, 2026 12:38
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds support for a new Python environment provider node with a Pixi port object, integrating it into the Python scripting nodes ecosystem. The changes enable Python script nodes to receive Python environments through an optional input port instead of only relying on configured preferences.

Changes:

  • Introduced PythonProcessProvider as a replacement interface for the deprecated PythonCommand to support multiple environment sources
  • Added optional Python environment port support to Python Script and Python View nodes
  • Implemented environment port extraction and installation logic with progress reporting

Reviewed changes

Copilot reviewed 41 out of 41 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
org.knime.python3/src/main/java/org/knime/python3/PythonCommand.java Deprecated PythonCommand interface, now extends PythonProcessProvider for backward compatibility
org.knime.python3/src/main/java/org/knime/python3/SimplePythonCommand.java Updated documentation to reference PythonProcessProvider
org.knime.python3/src/main/java/org/knime/python3/QueuedPythonGatewayFactory.java Refactored to use PythonProcessProvider instead of PythonCommand
org.knime.python3/src/main/java/org/knime/python3/PythonGatewayFactory.java Updated gateway description to work with PythonProcessProvider
org.knime.python3.scripting.nodes/src/main/java/org/knime/python3/scripting/nodes2/script/PythonScriptNodeFactory.java Added optional Python environment port to Python Script node
org.knime.python3.scripting.nodes/src/main/java/org/knime/python3/scripting/nodes2/view/PythonViewNodeFactory.java Added optional Python environment port to Python View node
org.knime.python3.scripting.nodes/src/main/java/org/knime/python3/scripting/nodes2/PythonScriptNodeModel.java Implemented environment port extraction and prioritization over configured Python command
org.knime.python3.scripting.nodes/src/main/java/org/knime/python3/scripting/nodes2/PythonScriptingService.java Added environment port handling for interactive sessions
org.knime.python3.scripting.nodes/src/main/java/org/knime/python3/scripting/nodes2/PythonScriptingSession.java Added PythonCommandAdapter to bridge PythonProcessProvider to legacy PythonCommand
org.knime.python3.scripting.nodes/src/main/java/org/knime/python3/scripting/nodes/PortsConfigurationUtils.java Added utility methods for environment port detection and installation
org.knime.python3.scripting.nodes/src/main/java/org/knime/python3/scripting/nodes2/PythonIOUtils.java Updated to filter out environment ports from data processing
org.knime.python3.scripting.nodes/src/main/java/org/knime/python3/scripting/nodes2/PythonScriptingInputOutputModelUtils.java Added logic to skip environment ports in input/output model generation

Comment on lines +252 to +253
@Deprecated(since = "5.10", forRemoval = true)
public boolean hasPixiPort() {
Copy link

Copilot AI Jan 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The deprecation message should explain what to use instead. Add 'Use {@link #hasEnvironmentPort()} instead' to the @deprecated tag.

Copilot uses AI. Check for mistakes.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wait... you introduce this method, which is deprecated, and introduce the replacement at the same time?? How about getting rid of hasPixiPort?

Comment on lines +261 to +262
@Deprecated(since = "5.10", forRemoval = true)
public boolean hasPythonEnvPort() {
Copy link

Copilot AI Jan 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The deprecation message should explain what to use instead. Add 'Use {@link #hasEnvironmentPort()} instead' to the @deprecated tag.

Copilot uses AI. Check for mistakes.
Comment on lines +74 to 76
@Deprecated
@Override
ProcessBuilder createProcessBuilder();
Copy link

Copilot AI Jan 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The @deprecated annotation on interface methods should include forRemoval=true and since parameters for consistency with the interface-level deprecation at line 67.

Copilot uses AI. Check for mistakes.
protected PortObjectSpec[] configure(final PortObjectSpec[] inSpecs) throws InvalidSettingsException {
for (int i = 0; i < m_inPorts.length; i++) {
// The Pixi port (if present) is at the end of the input specs
final int numRegularPorts = m_inPorts.length;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should drop the pixi port here, right?

…uld be removed completely?

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings January 26, 2026 17:54
try {
// Check if any input port is a PythonEnvironmentPortObject
for (final PortType inType : inTypes) {
if (inType.equals(PythonEnvironmentPortObject.TYPE) || inType.equals(PythonEnvironmentPortObject.TYPE_OPTIONAL)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you could simply use the isPixiPort method from below here, then you don't need to write the check twice

* @throws IOException if installation fails
* @throws CanceledExecutionException if the operation is canceled
*/
public static void installPythonEnvironmentIfPresent(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd have expected this method inside the port object, so that the Python node only calls PythonEnvironmentPortObject.installEnvironment(exec) and that does its thing...

LOGGER.debugWithFormat("Checking if port type '%s' is environment port: %s",
portType.getName(), isPythonEnvPort);
return isPythonEnvPort;
} catch (NoClassDefFoundError e) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you have this check in many places but... don't we have a dependency in the MANIFEST.MF to the package defining the PythonEnvironmentPortObject? How should that ever be missing then?

if (pythonCommand != null) {
LOGGER.info("Using Python environment from connected Pixi port for interactive session");
// Filter out Pixi port from data ports - it's not a data port for setupIO
dataPortObjects = java.util.Arrays.copyOf(inputData, inputData.length - 1);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

import Arrays instead of using the full type here

if (portObject instanceof PythonEnvironmentPortObject) {
final PythonEnvironmentPortObject pythonEnvPort = (PythonEnvironmentPortObject)portObject;
try {
// PythonEnvironmentPortObject.getPythonCommand() returns org.knime.pixi.port.PythonCommand,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

comment is outdated

* @return the Python command, or null if the port is not connected or doesn't contain a valid Python executable
* @throws InvalidSettingsException if the Python executable path from the environment doesn't exist
*/
private static PythonProcessProvider extractPythonCommandFromPixiPort(final PortObject portObject)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

again: this is a copy, and it shouldn't live here but in the PortObject

// Count the number of the different ports (skip the flow var port)
var numInTables = 0;
var numInObjects = 0;
var hasEnvironmentPort = false;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

aha, see, here it's called hasEnvironmentPort :)

} else {
// Check if it's a Python environment port
try {
if (PythonEnvironmentPortObject.TYPE.equals(portType)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this check already exists somewhere else. Maybe even put the check for isPythonEnvironmentPortObject into the port object itself?

Comment on lines +252 to +253
@Deprecated(since = "5.10", forRemoval = true)
public boolean hasPixiPort() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wait... you introduce this method, which is deprecated, and introduce the replacement at the same time?? How about getting rid of hasPixiPort?

Eclipse-RegisterBuddy: org.knime.ext.py4j
Eclipse-BundleShape: dir
Bundle-Activator: org.knime.python3.nodes.Activator
Import-Package: org.knime.python3.processprovider
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why do we do it like this, and don't add a dependency to the plugin org.knime.python3.processprovider? Then we can also get rid of all the NoClassDefFound checks

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 41 out of 41 changed files in this pull request and generated 3 comments.

Comments suppressed due to low confidence (2)

org.knime.python3.scripting.nodes/src/main/java/org/knime/python3/scripting/nodes2/script/PythonScriptNodeFactory.java:1

  • The variable pythonEnvClass is declared but never used on line 130 and 139. This appears to be a leftover from development and should be removed to reduce code clutter.
/*

org.knime.python3.scripting.nodes/src/main/java/org/knime/python3/scripting/nodes2/view/PythonViewNodeFactory.java:1

  • The variable pythonEnvClass is declared on line 91 in the view factory but never used. This appears to be a leftover from development and should be removed to reduce code clutter.
/*

Comment on lines +138 to +144
try {
final Class<?> pythonEnvClass = PythonEnvironmentPortObject.class;
b.addOptionalInputPortGroup(PORTGR_ID_PYTHON_ENV, PythonEnvironmentPortObject.TYPE_OPTIONAL);
LOGGER.debug("Successfully added Python environment port");
} catch (NoClassDefFoundError e) {
LOGGER.debug("PythonEnvironmentPortObject not available: " + e.getMessage());
}
Copy link

Copilot AI Jan 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The variable pythonEnvClass is declared but never used. This appears to be a leftover from development and should be removed to reduce code clutter.

Copilot uses AI. Check for mistakes.
Comment on lines +129 to +135
try {
final Class<?> pythonEnvClass = PythonEnvironmentPortObject.class;
b.addOptionalInputPortGroup(PORTGR_ID_PYTHON_ENV, PythonEnvironmentPortObject.TYPE_OPTIONAL);
LOGGER.debug("Successfully added Python environment port");
} catch (NoClassDefFoundError e) {
LOGGER.debug("PythonEnvironmentPortObject not available: " + e.getMessage());
}
Copy link

Copilot AI Jan 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The variable pythonEnvClass is declared but never used. This appears to be a leftover from development and should be removed to reduce code clutter.

Copilot uses AI. Check for mistakes.
*/
public static BundledPythonCommand getBundledPythonCommand() {
return getBundledCondaEnvironmentConfig().getPythonCommand();
return (BundledPythonCommand)getBundledCondaEnvironmentConfig().getPythonCommand();
Copy link

Copilot AI Jan 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This unchecked cast from PythonProcessProvider to BundledPythonCommand is unsafe and could throw ClassCastException at runtime. Consider adding type validation or changing the method signature of getBundledCondaEnvironmentConfig().getPythonCommand() to return the specific type.

Suggested change
return (BundledPythonCommand)getBundledCondaEnvironmentConfig().getPythonCommand();
final var pythonCommand = getBundledCondaEnvironmentConfig().getPythonCommand();
if (!(pythonCommand instanceof BundledPythonCommand)) {
throw new IllegalStateException(
"Bundled Python environment '" + BUNDLED_PYTHON_ENV_ID
+ "' does not provide a BundledPythonCommand (got: "
+ (pythonCommand == null ? "null" : pythonCommand.getClass().getName()) + ")");
}
return (BundledPythonCommand)pythonCommand;

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

3 participants