Skip to content

Enh/ap 25245 pixi env creator#47

Open
marc-lehner wants to merge 34 commits intomasterfrom
enh/AP-25245-pixi-env-creator
Open

Enh/ap 25245 pixi env creator#47
marc-lehner wants to merge 34 commits intomasterfrom
enh/AP-25245-pixi-env-creator

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

Copilot AI review requested due to automatic review settings January 26, 2026 17:54
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.
/*

@marc-lehner marc-lehner force-pushed the enh/AP-25245-pixi-env-creator branch 2 times, most recently from 5297ab6 to a396f21 Compare February 10, 2026 14:29
Copilot AI review requested due to automatic review settings February 10, 2026 15:28
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 13 comments.

@sonarqubecloud
Copy link

Quality Gate Failed Quality Gate failed

Failed conditions
18.5% Coverage on New Code (required ≥ 85%)
13.2% Duplication on New Code (required ≤ 1%)

See analysis details on SonarQube Cloud

Copilot AI review requested due to automatic review settings February 11, 2026 08:35
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 9 comments.

Comment on lines 253 to 272
final var inputData = workflowControl.getInputData();

// Check if environment port is connected and extract Python command
PythonProcessProvider pythonCommand = null;
PortObject[] dataPortObjects = inputData; // By default, all inputs are data ports

if (m_ports.hasPythonEnvironmentPort() && inputData != null && inputData.length > 0) {
try {
// Environment port is always the last port when present
final PortObject lastPort = inputData[inputData.length - 1];
pythonCommand = PythonEnvironmentPortObject.extractPythonCommand(lastPort);
if (pythonCommand != null) {
LOGGER.debug("Using Python environment from connected port for interactive session");
// Filter out environment port from data ports (it's the last one)
dataPortObjects = java.util.Arrays.copyOf(inputData, inputData.length - 1);
}
} catch (Exception e) {
LOGGER.warn("Failed to extract Python command from environment port: " + e.getMessage());
}
}
Copy link

Copilot AI Feb 11, 2026

Choose a reason for hiding this comment

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

The logic assumes the environment port is always the last input port and filters it by truncating the array. This is brittle with PortsConfigurationBuilder/group ordering and can silently mis-handle inputs if ordering changes. Prefer identifying the environment port by its configured PortType (or reusing the same helper approach as PortsConfigurationUtils.extractPythonEnvironmentPort(...) / filterEnvironmentPort(...)) and log the exception with stack trace (pass e) to aid diagnostics.

Copilot uses AI. Check for mistakes.
@marc-lehner marc-lehner force-pushed the enh/AP-25245-pixi-env-creator branch from 732d9ac to 15d85da Compare February 11, 2026 09:37
Copilot AI review requested due to automatic review settings February 11, 2026 10:39
@marc-lehner marc-lehner force-pushed the enh/AP-25245-pixi-env-creator branch from 15d85da to 3e4affb Compare February 11, 2026 10:39
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.

Comment on lines 235 to 241
final PortObject[] filtered = new PortObject[inObjects.length - 1];
int filteredIndex = 0;
for (int i = 0; i < inTypes.length && i < inObjects.length; i++) {
if (!isPythonEnvironmentPort(inTypes[i])) {
filtered[filteredIndex++] = inObjects[i];
}
}
Copy link

Copilot AI Feb 11, 2026

Choose a reason for hiding this comment

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

filterEnvironmentPort assumes exactly one environment port and blindly allocates inObjects.length - 1. If the configuration ever contains 0 or >1 environment ports (e.g., future changes, misconfiguration, or different port typing), this can either drop a data port or return an array containing trailing nulls (risking downstream NPEs). Allocate the filtered array by counting non-environment ports (based on inTypes) and fill it accordingly, or build a dynamic list and convert to array to guarantee correct sizing.

Suggested change
final PortObject[] filtered = new PortObject[inObjects.length - 1];
int filteredIndex = 0;
for (int i = 0; i < inTypes.length && i < inObjects.length; i++) {
if (!isPythonEnvironmentPort(inTypes[i])) {
filtered[filteredIndex++] = inObjects[i];
}
}
final int max = Math.min(inTypes.length, inObjects.length);
// First pass: count non-environment ports within the overlapping range
int nonEnvCount = 0;
for (int i = 0; i < max; i++) {
if (!isPythonEnvironmentPort(inTypes[i])) {
nonEnvCount++;
}
}
// Any remaining PortObjects beyond the typed range are treated as non-environment ports
if (inObjects.length > max) {
nonEnvCount += inObjects.length - max;
}
final PortObject[] filtered = new PortObject[nonEnvCount];
int filteredIndex = 0;
// Second pass: copy non-environment ports from the overlapping range
for (int i = 0; i < max; i++) {
if (!isPythonEnvironmentPort(inTypes[i])) {
filtered[filteredIndex++] = inObjects[i];
}
}
// Third pass: copy any remaining ports beyond the typed range
for (int i = max; i < inObjects.length; i++) {
filtered[filteredIndex++] = inObjects[i];
}

Copilot uses AI. Check for mistakes.
Comment on lines 201 to 210
public static PythonEnvironmentPortObject extractPythonEnvironmentPort(
final PortsConfiguration config, final PortObject[] inObjects) {
final PortType[] inTypes = config.getInputPorts();
for (int i = 0; i < inTypes.length; i++) {
if (isPythonEnvironmentPort(inTypes[i]) && PythonEnvironmentPortObject.isPythonEnvironmentPortObject(inObjects[i])) {
return (PythonEnvironmentPortObject) inObjects[i];
}
}
return null;
}
Copy link

Copilot AI Feb 11, 2026

Choose a reason for hiding this comment

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

This utility method hard-links to PythonEnvironmentPortObject in its signature and body even though org.knime.pixi.port is declared optional. In OSGi, such direct type references can still trigger NoClassDefFoundError at class load/link time (not just at execution), which would prevent the scripting nodes bundle from loading when the optional bundle isn’t available. To keep the dependency truly optional, avoid exposing Pixi types in public method signatures and isolate direct references behind reflection or a separate fragment/bundle that is only resolved when org.knime.pixi.port is present.

Copilot uses AI. Check for mistakes.
Comment on lines 196 to 206
if (m_ports.hasPythonEnvironmentPort()) {
PortsConfigurationUtils.installPythonEnvironmentIfPresent(getPortsConfiguration(), inObjects, exec);
}

// Extract Python command from environment port if connected, otherwise use configured command
final PythonProcessProvider pythonCommand;
final PythonProcessProvider pythonCommandFromEnv = PythonEnvironmentPortObject.extractPythonCommand(
PortsConfigurationUtils.extractPythonEnvironmentPort(getPortsConfiguration(), inObjects));
if (pythonCommandFromEnv != null) {
LOGGER.debug("Using Python from environment port");
pythonCommand = pythonCommandFromEnv;
Copy link

Copilot AI Feb 11, 2026

Choose a reason for hiding this comment

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

New behavior is introduced for (1) early environment installation and (2) precedence of the environment-port-provided Python over the configured executable. Please add tests that cover: env port connected vs. not connected; extraction failure fallback to executable selection; and ensuring the environment port is not forwarded as a data input to the session (i.e., input port filtering). This helps prevent regressions given the multiple new paths and optional-dependency behavior.

Copilot uses AI. Check for mistakes.
@marc-lehner marc-lehner force-pushed the enh/AP-25245-pixi-env-creator branch from 3e4affb to 68c0942 Compare February 11, 2026 14:20
Copilot AI review requested due to automatic review settings February 12, 2026 10:41
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 7 comments.

Comments suppressed due to low confidence (1)

org.knime.python3.scripting.nodes/src/main/java/org/knime/python3/scripting/nodes/PortsConfigurationUtils.java:1

  • filterEnvironmentPort(...) assumes there is exactly one environment port and allocates inObjects.length - 1. If the config ever contains 0 (unexpected state), or more than 1 environment port (future extensibility), this produces an incorrectly sized array (either missing entries or leaving trailing nulls). A more robust approach is to compute the exact number of non-environment ports first (similar to createInputPorts) and allocate based on that count, or build a list and convert to array.
/*

Comment on lines 275 to 277
if (pythonCommand == null) {
pythonCommand = ExecutableSelectionUtils.getPythonCommand(getExecutableOption(m_executableSelection));
}
Copy link

Copilot AI Feb 12, 2026

Choose a reason for hiding this comment

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

When the environment port exists but no command can be extracted (e.g., optional port not connected, inactive placeholder, or extraction error), dataPortObjects is not filtered and the environment port object (or placeholder) is passed into setupIO(...). This can cause Unsupported port type connected failures or source/input misalignment. Filter out the environment port whenever m_ports.hasPythonEnvironmentPort() is true (independent of extraction success), and separately decide which pythonCommand to use.

Copilot uses AI. Check for mistakes.
Comment on lines 134 to 140

// Skip Python environment ports - they are not data ports
if (isPythonEnvironmentPort(type)) {
LOGGER.debugWithFormat("Skipping environment port at index %d (type: %s) - not exposed to Python script", i, type.getName());
continue;
}

Copy link

Copilot AI Feb 12, 2026

Choose a reason for hiding this comment

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

This adds multiple debug log statements inside tight per-port loops (including a log on every isPythonEnvironmentPort check). Even at debug level, this can generate a lot of log traffic and overhead in workflows with many ports or frequent UI refreshes. Consider removing the per-check logs or guarding them behind an explicit LOGGER.isDebugEnabled() (if available) and logging only when an environment port is actually detected.

Suggested change
// Skip Python environment ports - they are not data ports
if (isPythonEnvironmentPort(type)) {
LOGGER.debugWithFormat("Skipping environment port at index %d (type: %s) - not exposed to Python script", i, type.getName());
continue;
}
// Skip Python environment ports - they are not data ports
if (isPythonEnvironmentPort(type)) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debugWithFormat(
"Skipping environment port at index %d (type: %s) - not exposed to Python script",
i, type.getName());
}
continue;
}

Copilot uses AI. Check for mistakes.
Comment on lines 221 to 226
LOGGER.debugWithFormat("Checking if port type '%s' is environment port: %s",
portType.getName(), isPythonEnvPort);
return isPythonEnvPort;
} catch (NoClassDefFoundError e) {
// Python environment bundle not available
LOGGER.debugWithFormat("Python environment bundle not available for port type '%s'", portType.getName());
Copy link

Copilot AI Feb 12, 2026

Choose a reason for hiding this comment

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

This adds multiple debug log statements inside tight per-port loops (including a log on every isPythonEnvironmentPort check). Even at debug level, this can generate a lot of log traffic and overhead in workflows with many ports or frequent UI refreshes. Consider removing the per-check logs or guarding them behind an explicit LOGGER.isDebugEnabled() (if available) and logging only when an environment port is actually detected.

Suggested change
LOGGER.debugWithFormat("Checking if port type '%s' is environment port: %s",
portType.getName(), isPythonEnvPort);
return isPythonEnvPort;
} catch (NoClassDefFoundError e) {
// Python environment bundle not available
LOGGER.debugWithFormat("Python environment bundle not available for port type '%s'", portType.getName());
if (isPythonEnvPort && LOGGER.isDebugEnabled()) {
LOGGER.debugWithFormat("Checking if port type '%s' is environment port: %s",
portType.getName(), isPythonEnvPort);
}
return isPythonEnvPort;
} catch (NoClassDefFoundError e) {
// Python environment bundle not available
if (LOGGER.isDebugEnabled()) {
LOGGER.debugWithFormat("Python environment bundle not available for port type '%s'",
portType.getName());
}

Copilot uses AI. Check for mistakes.
AP-25245 (Python Environment Provider (Preview - Hidden))
AP-25245 (Python Environment Provider (Preview - Hidden))
AP-25245 (Python Environment Provider (Preview - Hidden))
AP-25245 (Python Environment Provider (Preview - Hidden))
AP-25245 (Python Environment Provider (Preview - Hidden))
AP-25245 (Python Environment Provider (Preview - Hidden))
AP-25245 (Python Environment Provider (Preview - Hidden))
AP-25245 (Python Environment Provider (Preview - Hidden))
AP-25245 (Python Environment Provider (Preview - Hidden))
AP-25245 (Python Environment Provider (Preview - Hidden))
AP-25245 (Python Environment Provider (Preview - Hidden))
Copilot AI review requested due to automatic review settings February 12, 2026 11:57
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 38 out of 38 changed files in this pull request and generated 7 comments.

Comment on lines 421 to 423
private static PythonGateway<PythonScriptingEntryPoint> createGateway(final ExternalProcessProvider pythonCommand)
throws IOException, InterruptedException {
if (pythonCommand.getPythonExecutablePath()
if (pythonCommand.getExecutablePath()
Copy link

Copilot AI Feb 12, 2026

Choose a reason for hiding this comment

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

The adapter is now unnecessary (and risky): PythonGatewayDescription.builder(...) has been updated to accept ExternalProcessProvider, so wrapping pythonCommand adds no value. Worse, PythonCommandAdapter.equals(...) can violate symmetry/transitivity (e.g., adapter.equals(delegate) may be true while delegate.equals(adapter) is false), which can break Map/queue lookups and eviction logic. Prefer passing pythonCommand directly to PythonGatewayDescription.builder(...) and removing PythonCommandAdapter entirely.

Copilot uses AI. Check for mistakes.
Comment on lines 533 to 534
private static final class PythonCommandAdapter implements ExternalProcessProvider {
private final ExternalProcessProvider m_delegate;
Copy link

Copilot AI Feb 12, 2026

Choose a reason for hiding this comment

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

The adapter is now unnecessary (and risky): PythonGatewayDescription.builder(...) has been updated to accept ExternalProcessProvider, so wrapping pythonCommand adds no value. Worse, PythonCommandAdapter.equals(...) can violate symmetry/transitivity (e.g., adapter.equals(delegate) may be true while delegate.equals(adapter) is false), which can break Map/queue lookups and eviction logic. Prefer passing pythonCommand directly to PythonGatewayDescription.builder(...) and removing PythonCommandAdapter entirely.

Copilot uses AI. Check for mistakes.
Comment on lines 224 to 239
public static PortObject[] filterEnvironmentPort(final PortsConfiguration config, final PortObject[] inObjects) {
if (!hasPythonEnvironmentPort(config)) {
return inObjects;
}

// Find and exclude the environment port
final PortType[] inTypes = config.getInputPorts();
final PortObject[] filtered = new PortObject[inObjects.length - 1];
int filteredIndex = 0;
for (int i = 0; i < inTypes.length && i < inObjects.length; i++) {
if (!isPythonEnvironmentPort(inTypes[i])) {
filtered[filteredIndex++] = inObjects[i];
}
}
return filtered;
}
Copy link

Copilot AI Feb 12, 2026

Choose a reason for hiding this comment

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

filterEnvironmentPort(...) assumes there is exactly one environment port whenever the configuration contains an environment port type, and blindly allocates inObjects.length - 1. This can drop a real data port or produce an array with trailing nulls if the environment port is absent/not matched, and it breaks if there are 0 or >1 environment ports. Compute the filtered array size by counting environment-port indices in inTypes (and only excluding those indices), rather than hard-coding - 1.

Copilot uses AI. Check for mistakes.

// Skip Python environment ports - they are not data ports
if (isPythonEnvironmentPort(type)) {
LOGGER.debugWithFormat("Skipping environment port at index %d (type: %s) - not exposed to Python script", i, type.getName());
Copy link

Copilot AI Feb 12, 2026

Choose a reason for hiding this comment

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

This adds multiple debugWithFormat(...) calls in code that is likely invoked frequently (e.g., dialog/model refresh). Even at debug level, formatted logging can become noisy/expensive and obscure other debug output. Consider removing the per-port debug logs (or gating them behind a more granular trace flag) and keep only the functional check.

Suggested change
LOGGER.debugWithFormat("Skipping environment port at index %d (type: %s) - not exposed to Python script", i, type.getName());

Copilot uses AI. Check for mistakes.
Comment on lines 314 to 316
private void pushNewFlowVariable(final FlowVariable variable) {
pushFlowVariable(variable.getName(), (VariableType)variable.getVariableType(),
variable.getValue(variable.getVariableType()));
Copy link

Copilot AI Feb 12, 2026

Choose a reason for hiding this comment

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

The code still uses a raw VariableType cast ((VariableType) variable.getVariableType()), which typically triggers both unchecked and rawtypes warnings. If the intent is to suppress both, consider restoring the previous @SuppressWarnings({\"unchecked\", \"rawtypes\"}), or refactor the cast to avoid raw types if possible.

Suggested change
private void pushNewFlowVariable(final FlowVariable variable) {
pushFlowVariable(variable.getName(), (VariableType)variable.getVariableType(),
variable.getValue(variable.getVariableType()));
private <T> void pushNewFlowVariable(final FlowVariable variable) {
final VariableType<T> type = (VariableType<T>)variable.getVariableType();
final T value = variable.getValue(type);
pushFlowVariable(variable.getName(), type, value);

Copilot uses AI. Check for mistakes.
Comment on lines 130 to 132
NodeLogger.getLogger(PythonIOUtils.class).debugWithFormat(
"Creating sources from %d input ports: %d tables, %d pickled objects, %d PythonEnvironment ports (filtered out)",
data.length, tablePortObjects.length, pickledPortObjects.length, pythonEnvPortObjects.length);
Copy link

Copilot AI Feb 12, 2026

Choose a reason for hiding this comment

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

Creating a logger instance inline on every call is avoidable overhead. Prefer a private static final NodeLogger LOGGER = NodeLogger.getLogger(PythonIOUtils.class); and reuse it for logging.

Copilot uses AI. Check for mistakes.
AP-25245 (Python Environment Provider (Preview - Hidden))
AP-25245 (Python Environment Provider (Preview - Hidden))
AP-25245 (Python Environment Provider (Preview - Hidden))
AP-25245 (Python Environment Provider (Preview - Hidden))
AP-25245 (Python Environment Provider (Preview - Hidden))
AP-25245 (Python Environment Provider (Preview - Hidden))
AP-25245 (Python Environment Provider (Preview - Hidden))
Copilot AI review requested due to automatic review settings February 12, 2026 16:19
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 36 out of 36 changed files in this pull request and generated 4 comments.

Comments suppressed due to low confidence (1)

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

  • The extractPythonEnvironmentPort method is called twice in this code block (lines 262 and 266). Store the result in a variable to avoid redundant traversal of the input array.
/*

PortObject[] dataPortObjects = inputData; // By default, all inputs are data ports

if (m_ports.hasPythonEnvironmentPort() && inputData != null && inputData.length > 0) {
// TODO only log about environment installation if it actually needs to be installed
Copy link

Copilot AI Feb 12, 2026

Choose a reason for hiding this comment

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

This TODO comment should be resolved. Consider checking if the environment is already installed before logging the installation message, or remove the TODO if the current behavior is acceptable.

Suggested change
// TODO only log about environment installation if it actually needs to be installed

Copilot uses AI. Check for mistakes.
try {
startNewInteractiveSession();
} catch (IOException | InterruptedException | CanceledExecutionException ex) {
// TODO cleanup
Copy link

Copilot AI Feb 12, 2026

Choose a reason for hiding this comment

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

This TODO comment is vague. Specify what cleanup is needed or implement the cleanup logic. This appears to be in error handling code where proper cleanup is critical.

Suggested change
// TODO cleanup
// Session startup failed. At this point no new interactive session has been
// registered in m_interactiveSession, so there are no additional resources
// to clean up here beyond preserving the interrupted status and reporting
// the error to the client.

Copilot uses AI. Check for mistakes.
Comment on lines +207 to +214
/*public static void installPythonEnvironmentIfPresent(final PortsConfiguration config, final PortObject[] inObjects,
final ExecutionMonitor exec) throws IOException, CanceledExecutionException {
final PythonEnvironmentPortObject envPort = extractPythonEnvironmentPort(config, inObjects);
if (envPort != null) {
PythonEnvironmentPortObject.installPythonEnvironmentWithProgress(config, inObjects, exec);
}
}*/

Copy link

Copilot AI Feb 12, 2026

Choose a reason for hiding this comment

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

Remove this commented-out code block. Dead code should be eliminated rather than left in comments, especially in a version-controlled codebase.

Suggested change
/*public static void installPythonEnvironmentIfPresent(final PortsConfiguration config, final PortObject[] inObjects,
final ExecutionMonitor exec) throws IOException, CanceledExecutionException {
final PythonEnvironmentPortObject envPort = extractPythonEnvironmentPort(config, inObjects);
if (envPort != null) {
PythonEnvironmentPortObject.installPythonEnvironmentWithProgress(config, inObjects, exec);
}
}*/

Copilot uses AI. Check for mistakes.
Comment on lines +223 to +228
// Find and exclude the environment port
final PortObject[] filtered = new PortObject[inObjects.length - 1];
int filteredIndex = 0;
for (int i = 0; i < inObjects.length; i++) {
if (!(inObjects[i] instanceof PythonEnvironmentPortObject)) {
filtered[filteredIndex++] = inObjects[i];
Copy link

Copilot AI Feb 12, 2026

Choose a reason for hiding this comment

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

This code assumes exactly one environment port exists, but doesn't validate this assumption. If there are zero or multiple environment ports, this will create an incorrectly-sized array and may lead to ArrayIndexOutOfBoundsException.

Suggested change
// Find and exclude the environment port
final PortObject[] filtered = new PortObject[inObjects.length - 1];
int filteredIndex = 0;
for (int i = 0; i < inObjects.length; i++) {
if (!(inObjects[i] instanceof PythonEnvironmentPortObject)) {
filtered[filteredIndex++] = inObjects[i];
// First count how many environment ports are present
int envPortCount = 0;
for (final PortObject inObject : inObjects) {
if (inObject instanceof PythonEnvironmentPortObject) {
envPortCount++;
}
}
// If there is no environment port, nothing to filter out
if (envPortCount == 0) {
return inObjects;
}
// Find and exclude all environment ports
final PortObject[] filtered = new PortObject[inObjects.length - envPortCount];
int filteredIndex = 0;
for (final PortObject inObject : inObjects) {
if (!(inObject instanceof PythonEnvironmentPortObject)) {
filtered[filteredIndex++] = inObject;

Copilot uses AI. Check for mistakes.
AP-25245 (Python Environment Provider (Preview - Hidden))
AP-25245 (Python Environment Provider (Preview - Hidden))
Copilot AI review requested due to automatic review settings February 13, 2026 14:14
new PortType[]{BufferedDataTable.TYPE}, BufferedDataTable.TYPE);

// Add Python environment port
b.addOptionalInputPortGroup(PORTGR_ID_PYTHON_ENV, 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.

Please move this port to the top for both nodes.

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 36 out of 36 changed files in this pull request and generated 5 comments.

Comments suppressed due to low confidence (1)

org.knime.python3.scripting.nodes/src/main/java/org/knime/python3/scripting/nodes2/PythonIOUtils.java:117

  • The JavaDoc states that PythonEnvironmentPortObjects are filtered out, but this filtering doesn't actually happen in this method. The filtering must be done by the caller before passing the data array to this method. Update the JavaDoc to clarify that the caller is responsible for filtering out environment ports, or that this method expects only data ports (BufferedDataTable and PickledObjectFileStorePortObject).
    /**
     * Create an array of Python data sources for the given input ports. The input ports can be either a
     * {@link BufferedDataTable}, a {@link PickledObjectFileStorePortObject}, or {@link PythonEnvironmentPortObject}.
     * Note that {@link PythonEnvironmentPortObject}s are filtered out as they are only used for environment
     * configuration and not passed to Python as data.
     *
     * @param data a list of port objects. Only {@link BufferedDataTable}, {@link PickledObjectFileStorePortObject}, and
     *            {@link PythonEnvironmentPortObject} are supported.
     * @param tableConverter a table converter that is used to convert the {@link BufferedDataTable}s to Python sources
     * @param exec for progress reporting and cancellation
     * @return an array of Python data sources
     * @throws IOException if the path to a pickled file could not be created or a {@link BufferedDataTable} couldn't be
     *             written
     * @throws CanceledExecutionException if the execution was canceled
     * @throws IllegalArgumentException if the data contains unsupported port types
     */

Comment on lines +222 to +232
public static PortObject[] filterEnvironmentPort(final PortObject[] inObjects) {
// Find and exclude the environment port
final PortObject[] filtered = new PortObject[inObjects.length - 1];
int filteredIndex = 0;
for (int i = 0; i < inObjects.length; i++) {
if (!(inObjects[i] instanceof PythonEnvironmentPortObject)) {
filtered[filteredIndex++] = inObjects[i];
}
}
return filtered;
}
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

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

The filterEnvironmentPort method assumes exactly one environment port exists and creates an array with length - 1. If there's no environment port in the array, this will create an array that's too small and cause an ArrayIndexOutOfBoundsException. The method should either count the number of environment ports first or check if any exist before creating the filtered array.

Copilot uses AI. Check for mistakes.
Comment on lines +181 to +186
var pythonEnvironmentPort = PortsConfigurationUtils.extractPythonEnvironmentPort(inObjects);

// Install Python environment early to avoid timeout issues during gateway connection
final ExecutionMonitor remainingProgress;
final ExternalProcessProvider pythonCommand;
if (m_ports.hasPythonEnvironmentPort()) {
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

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

The extractPythonEnvironmentPort method is called unconditionally but always throws an exception if no environment port is found. This will cause the execute method to fail even when hasPythonEnvironmentPort() returns false. The call should only be made inside the if block at line 186, or the method should be changed to return null when no port is found.

Suggested change
var pythonEnvironmentPort = PortsConfigurationUtils.extractPythonEnvironmentPort(inObjects);
// Install Python environment early to avoid timeout issues during gateway connection
final ExecutionMonitor remainingProgress;
final ExternalProcessProvider pythonCommand;
if (m_ports.hasPythonEnvironmentPort()) {
// Install Python environment early to avoid timeout issues during gateway connection
final ExecutionMonitor remainingProgress;
final ExternalProcessProvider pythonCommand;
if (m_ports.hasPythonEnvironmentPort()) {
var pythonEnvironmentPort = PortsConfigurationUtils.extractPythonEnvironmentPort(inObjects);

Copilot uses AI. Check for mistakes.
Comment on lines +256 to +275
PortObject[] dataPortObjects = inputData; // By default, all inputs are data ports

if (m_ports.hasPythonEnvironmentPort() && inputData != null && inputData.length > 0) {
// TODO only log about environment installation if it actually needs to be installed
addConsoleOutputEvent(
new ConsoleText("Installing Python environment from environment port...\n", false));
var envPort = PortsConfigurationUtils.extractPythonEnvironmentPort(inputData);
envPort.installPythonEnvironment(new ExecutionMonitor()); // Do not report the progress
addConsoleOutputEvent(
new ConsoleText("Successfully installed Python environment from environment port.\n", false));
pythonCommand = PortsConfigurationUtils.extractPythonEnvironmentPort(inputData).getPythonCommand();
} else {
pythonCommand = ExecutableSelectionUtils.getPythonCommand(getExecutableOption(m_executableSelection));
}

// TODO report the progress of converting the tables using the ExecutionMonitor?
m_interactiveSession = new PythonScriptingSession(pythonCommand,
PythonScriptingService.this::addConsoleOutputEvent, new DialogFileStoreHandlerSupplier());
m_interactiveSession.setupIO(workflowControl.getInputData(), getSupportedFlowVariables(),
m_ports.getNumOutTables(), m_ports.getNumOutImages(), m_ports.getNumOutObjects(), m_hasView,
new ExecutionMonitor());
m_interactiveSession.setupIO(dataPortObjects, getSupportedFlowVariables(), m_ports.getNumOutTables(),
m_ports.getNumOutImages(), m_ports.getNumOutObjects(), m_hasView, new ExecutionMonitor());
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

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

The dataPortObjects variable is never filtered to remove the environment port when hasPythonEnvironmentPort() is true. This means the environment port will be passed to setupIO, which will likely cause issues since it's not a data port. Add filtering logic similar to PythonScriptNodeModel lines 204-208 to filter out the environment port before passing to setupIO.

Copilot uses AI. Check for mistakes.
* @author Benjamin Wilhelm, KNIME GmbH, Konstanz, Germany
*/
abstract class AbstractPythonCommand implements PythonCommand {
abstract class AbstractPythonCommand implements ExternalProcessProvider {
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

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

AbstractPythonCommand now implements ExternalProcessProvider directly instead of PythonCommand. This breaks backward compatibility because concrete subclasses like SimplePythonCommand, CondaPythonCommand, and BundledPythonCommand will no longer implement the PythonCommand interface. Since PythonCommand is deprecated but marked for removal in 5.11, existing code that expects these classes to implement PythonCommand will break. AbstractPythonCommand should implement PythonCommand instead (which extends ExternalProcessProvider), and should also implement getPythonExecutablePath() which delegates to getExecutablePath().

Copilot uses AI. Check for mistakes.
Comment on lines +27 to +28
org.knime.core.ui;bundle-version="[5.10.0,6.0.0)",
org.knime.workbench.editor;bundle-version="[5.9.0,6.0.0)",
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

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

The minimum version requirement for org.knime.core.ui has been lowered from 5.11.0 to 5.10.0, and for org.knime.workbench.editor from 5.10.0 to 5.9.0. These changes appear unrelated to the PR's purpose of adding pixi environment support. Verify that these version downgrades are intentional and necessary, as they may allow the bundle to run with older, potentially incompatible versions of these dependencies.

Suggested change
org.knime.core.ui;bundle-version="[5.10.0,6.0.0)",
org.knime.workbench.editor;bundle-version="[5.9.0,6.0.0)",
org.knime.core.ui;bundle-version="[5.11.0,6.0.0)",
org.knime.workbench.editor;bundle-version="[5.10.0,6.0.0)",

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants