From 80333406a3ce3ca2c3e3c1c63edf5de3c31b889b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 12 Jan 2026 14:12:10 +0000 Subject: [PATCH 1/4] Initial plan From 04a65823b830837582968e67cc5e1ad08ebc86cf Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 12 Jan 2026 14:20:16 +0000 Subject: [PATCH 2/4] Replace visible_choices with hidden_choices for clearer semantics Co-authored-by: AtR1an <12174376+AtR1an@users.noreply.github.com> --- .../python/unittest/test_knime_parameter.py | 175 ++++++++++-------- .../main/python/knime/extension/parameter.py | 111 ++++++----- 2 files changed, 163 insertions(+), 123 deletions(-) diff --git a/org.knime.python3.nodes.tests/src/test/python/unittest/test_knime_parameter.py b/org.knime.python3.nodes.tests/src/test/python/unittest/test_knime_parameter.py index 14b7a9169..7ca5d7f6c 100644 --- a/org.knime.python3.nodes.tests/src/test/python/unittest/test_knime_parameter.py +++ b/org.knime.python3.nodes.tests/src/test/python/unittest/test_knime_parameter.py @@ -1602,7 +1602,7 @@ def test_invalid_assignment_static(self): class TestModelOptions(kp.EnumParameterOptions): - """Test enum for visible_choices testing""" + """Test enum for hidden_choices testing""" LINEAR = ("Linear Regression", "Fits a linear model") RANDOM_FOREST = ("Random Forest", "Ensemble tree model") @@ -1610,40 +1610,40 @@ class TestModelOptions(kp.EnumParameterOptions): SVM = ("Support Vector Machine", "SVM model") -class ParameterizedWithVisibleChoices: - """Test class for EnumParameter with visible_choices callable""" +class ParameterizedWithHiddenChoices: + """Test class for EnumParameter with hidden_choices callable""" @staticmethod - def _filter_to_two(ctx): - """Filter to only LINEAR and RANDOM_FOREST""" - return [TestModelOptions.LINEAR, TestModelOptions.RANDOM_FOREST] + def _hide_two(ctx): + """Hide NEURAL_NET and SVM""" + return [TestModelOptions.NEURAL_NET, TestModelOptions.SVM] @staticmethod - def _filter_by_specs(ctx): - """Filter based on context specs""" + def _hide_by_specs(ctx): + """Hide based on context specs""" if ctx is None: - # No context - return subset (subsetting use-case) - return [TestModelOptions.LINEAR, TestModelOptions.SVM] + # No context - hide advanced option (hiding use-case) + return [TestModelOptions.NEURAL_NET, TestModelOptions.RANDOM_FOREST] specs = ctx.get_input_specs() if not specs or len(specs) == 0: - return list(TestModelOptions) + return [] # Hide nothing # Simulate filtering based on spec (e.g., spec has 'supported_models' attribute) # For testing, we'll use spec count as a proxy if len(specs) == 1: - return [TestModelOptions.LINEAR, TestModelOptions.RANDOM_FOREST] - else: return [TestModelOptions.NEURAL_NET, TestModelOptions.SVM] + else: + return [TestModelOptions.LINEAR, TestModelOptions.RANDOM_FOREST] @staticmethod - def _filter_invalid(): + def _hide_invalid(): """Returns invalid members for testing warnings""" return ["INVALID_OPTION", TestModelOptions.LINEAR] @staticmethod - def _filter_empty(ctx): - """Returns empty list""" + def _hide_empty(ctx): + """Returns empty list - hide nothing""" return [] param_filtered = kp.EnumParameter( @@ -1651,7 +1651,7 @@ def _filter_empty(ctx): description="Model with filtered choices", default_value=TestModelOptions.LINEAR.name, enum=TestModelOptions, - visible_choices=_filter_to_two.__func__, + hidden_choices=_hide_two.__func__, ) param_context_dependent = kp.EnumParameter( @@ -1659,7 +1659,7 @@ def _filter_empty(ctx): description="Choices depend on context", default_value=TestModelOptions.LINEAR, # Using enum member as default enum=TestModelOptions, - visible_choices=_filter_by_specs.__func__, + hidden_choices=_hide_by_specs.__func__, ) param_no_filter = kp.EnumParameter( @@ -1670,12 +1670,12 @@ def _filter_empty(ctx): ) -class TestEnumParameterVisibleChoices(unittest.TestCase): - """Test EnumParameter visible_choices functionality""" +class TestEnumParameterHiddenChoices(unittest.TestCase): + """Test EnumParameter hidden_choices functionality""" def test_filtered_schema_contains_subset(self): """Test that filtered options appear in schema oneOf""" - obj = ParameterizedWithVisibleChoices() + obj = ParameterizedWithHiddenChoices() schema = kp.extract_schema( obj, dialog_creation_context=DummyDialogCreationContext() ) @@ -1683,14 +1683,14 @@ def test_filtered_schema_contains_subset(self): self.assertIn("oneOf", s) values = {entry["const"] for entry in s["oneOf"]} - # Should only contain LINEAR and RANDOM_FOREST + # Should only contain LINEAR and RANDOM_FOREST (NEURAL_NET and SVM are hidden) self.assertEqual(values, {"LINEAR", "RANDOM_FOREST"}) self.assertNotIn("NEURAL_NET", values) self.assertNotIn("SVM", values) def test_no_filter_shows_all_options(self): - """Test that parameter without visible_choices shows all options""" - obj = ParameterizedWithVisibleChoices() + """Test that parameter without hidden_choices shows all options""" + obj = ParameterizedWithHiddenChoices() schema = kp.extract_schema( obj, dialog_creation_context=DummyDialogCreationContext() ) @@ -1702,25 +1702,25 @@ def test_no_filter_shows_all_options(self): def test_context_dependent_filtering(self): """Test that filtering works based on context""" - obj = ParameterizedWithVisibleChoices() + obj = ParameterizedWithHiddenChoices() - # With one spec + # With one spec - hide NEURAL_NET and SVM ctx_one = DummyDialogCreationContext(specs=[test_schema]) schema = kp.extract_schema(obj, dialog_creation_context=ctx_one) s = schema["properties"]["model"]["properties"]["param_context_dependent"] values = {entry["const"] for entry in s["oneOf"]} self.assertEqual(values, {"LINEAR", "RANDOM_FOREST"}) - # With two specs + # With two specs - hide LINEAR and RANDOM_FOREST ctx_two = DummyDialogCreationContext(specs=[test_schema, test_schema]) schema = kp.extract_schema(obj, dialog_creation_context=ctx_two) s = schema["properties"]["model"]["properties"]["param_context_dependent"] values = {entry["const"] for entry in s["oneOf"]} self.assertEqual(values, {"NEURAL_NET", "SVM"}) - def test_none_context_subsetting(self): - """Test that None context enables subsetting use-case""" - obj = ParameterizedWithVisibleChoices() + def test_none_context_hiding(self): + """Test that None context enables hiding use-case""" + obj = ParameterizedWithHiddenChoices() # Extract schema without context schema = kp.extract_schema(obj, dialog_creation_context=None) @@ -1728,48 +1728,48 @@ def test_none_context_subsetting(self): self.assertIn("oneOf", s) values = {entry["const"] for entry in s["oneOf"]} - # Should return subset defined for None case + # Should hide NEURAL_NET and RANDOM_FOREST (defined for None case) self.assertEqual(values, {"LINEAR", "SVM"}) - def test_description_respects_visible_choices(self): - """Test that description respects visible_choices based on None context""" + def test_description_respects_hidden_choices(self): + """Test that description respects hidden_choices based on None context""" - # param_filtered uses _filter_to_two which doesn't check context + # param_filtered uses _hide_two which doesn't check context # So description should show only LINEAR and RANDOM_FOREST - desc_dict = ParameterizedWithVisibleChoices.param_filtered._extract_description( + desc_dict = ParameterizedWithHiddenChoices.param_filtered._extract_description( "param_filtered", None ) description = desc_dict["description"] - # Description should contain only filtered options + # Description should contain only non-hidden options self.assertIn("Linear Regression", description) self.assertIn("Random Forest", description) - # Should NOT contain filtered-out options + # Should NOT contain hidden options self.assertNotIn("Neural Network", description) self.assertNotIn("Support Vector Machine", description) def test_description_with_context_dependent_filter(self): """Test description with context-dependent filter (None case)""" - # param_context_dependent returns subset for None context - desc_dict = ParameterizedWithVisibleChoices.param_context_dependent._extract_description( + # param_context_dependent hides NEURAL_NET and RANDOM_FOREST for None context + desc_dict = ParameterizedWithHiddenChoices.param_context_dependent._extract_description( "param_context_dependent", None ) description = desc_dict["description"] - # Description should show subset returned for None + # Description should show non-hidden options self.assertIn("Linear Regression", description) self.assertIn("Support Vector Machine", description) - # Should NOT contain other options + # Should NOT contain hidden options self.assertNotIn("Random Forest", description) self.assertNotIn("Neural Network", description) def test_description_without_filter_shows_all(self): """Test that description without filter shows all options""" - # param_no_filter has no visible_choices + # param_no_filter has no hidden_choices desc_dict = ( - ParameterizedWithVisibleChoices.param_no_filter._extract_description( + ParameterizedWithHiddenChoices.param_no_filter._extract_description( "param_no_filter", None ) ) @@ -1783,12 +1783,12 @@ def test_description_without_filter_shows_all(self): def test_validation_accepts_filtered_out_values(self): """Test that validation accepts any enum member, even if filtered out""" - obj = ParameterizedWithVisibleChoices() + obj = ParameterizedWithHiddenChoices() # Extract schema with filtering active kp.extract_schema(obj, dialog_creation_context=DummyDialogCreationContext()) - # NEURAL_NET is filtered out but should still be valid + # NEURAL_NET is hidden but should still be valid obj.param_filtered = "NEURAL_NET" self.assertEqual(obj.param_filtered, "NEURAL_NET") @@ -1798,23 +1798,23 @@ def test_validation_accepts_filtered_out_values(self): def test_default_as_enum_member(self): """Test that default_value accepts enum member directly""" - obj = ParameterizedWithVisibleChoices() + obj = ParameterizedWithHiddenChoices() # param_context_dependent uses enum member as default self.assertEqual(obj.param_context_dependent, "LINEAR") - def test_empty_filter_result_shows_empty(self): - """Test that empty filter result shows no options with warning""" + def test_empty_filter_result_shows_all(self): + """Test that empty filter result shows all options (hide nothing)""" def empty_filter(ctx): return [] param = kp.EnumParameter( label="Empty Filter", - description="Should be empty", + description="Should show all", default_value=TestModelOptions.LINEAR.name, enum=TestModelOptions, - visible_choices=empty_filter, + hidden_choices=empty_filter, ) class TestObj: @@ -1822,22 +1822,15 @@ class TestObj: obj = TestObj() - # Should log warning and return empty - with self.assertLogs("Python backend", level="WARNING") as log: - schema = kp.extract_schema( - obj, dialog_creation_context=DummyDialogCreationContext() - ) - - # Check warning was logged - self.assertTrue( - any( - "returned an empty list" in msg or "empty options" in msg - for msg in log.output - ) + # Should show all options (no warning expected for empty hide list) + schema = kp.extract_schema( + obj, dialog_creation_context=DummyDialogCreationContext() ) s = schema["properties"]["model"]["properties"]["empty_param"] - self.assertEqual(s["oneOf"], []) + values = {entry["const"] for entry in s["oneOf"]} + # Should show all options + self.assertEqual(values, {"LINEAR", "RANDOM_FOREST", "NEURAL_NET", "SVM"}) def test_invalid_members_filtered_with_warning(self): """Test that invalid members are filtered out with warning""" @@ -1854,7 +1847,7 @@ class FakeMember: description="Has invalid members", default_value=TestModelOptions.LINEAR.name, enum=TestModelOptions, - visible_choices=invalid_filter, + hidden_choices=invalid_filter, ) class TestObj: @@ -1873,23 +1866,25 @@ class TestObj: self.assertIn("invalid members", warning_msg.lower()) self.assertIn("Valid options", warning_msg) - # Schema should only contain valid member + # Schema should hide only the valid member (LINEAR) s = schema["properties"]["model"]["properties"]["invalid_param"] values = {entry["const"] for entry in s["oneOf"]} - self.assertEqual(values, {"LINEAR"}) + # LINEAR is hidden, others remain visible + self.assertEqual(values, {"RANDOM_FOREST", "NEURAL_NET", "SVM"}) def test_default_not_in_visible_options_warns(self): """Test that warning is logged when default is not in visible options""" - def filter_without_default(ctx): - return [TestModelOptions.RANDOM_FOREST, TestModelOptions.SVM] + def hide_including_default(ctx): + # Hide LINEAR (which is the default) and NEURAL_NET + return [TestModelOptions.LINEAR, TestModelOptions.NEURAL_NET] param = kp.EnumParameter( label="Default Not Visible", - description="Default filtered out", - default_value=TestModelOptions.LINEAR.name, # Not in visible choices + description="Default hidden", + default_value=TestModelOptions.LINEAR.name, # Will be hidden enum=TestModelOptions, - visible_choices=filter_without_default, + hidden_choices=hide_including_default, ) class TestObj: @@ -1906,19 +1901,19 @@ class TestObj: self.assertIn("not in the currently visible options", warning_msg) def test_caching_works(self): - """Test that visible_choices callable is cached per context""" + """Test that hidden_choices callable is cached per context""" call_count = [0] def counting_filter(ctx): call_count[0] += 1 - return [TestModelOptions.LINEAR, TestModelOptions.SVM] + return [TestModelOptions.NEURAL_NET, TestModelOptions.SVM] param = kp.EnumParameter( label="Cached", description="Should cache", default_value=TestModelOptions.LINEAR.name, enum=TestModelOptions, - visible_choices=counting_filter, + hidden_choices=counting_filter, ) class TestObj: @@ -1936,6 +1931,38 @@ class TestObj: # generation, and the result is cached after the first call self.assertEqual(call_count[0], 1) + def test_hiding_all_options_shows_empty(self): + """Test that hiding all options results in empty list with warning""" + + def hide_all(ctx): + return list(TestModelOptions) + + param = kp.EnumParameter( + label="Hide All", + description="All hidden", + default_value=TestModelOptions.LINEAR.name, + enum=TestModelOptions, + hidden_choices=hide_all, + ) + + class TestObj: + hide_all_param = param + + obj = TestObj() + + # Should log warning about hiding all options + with self.assertLogs("Python backend", level="WARNING") as log: + schema = kp.extract_schema( + obj, dialog_creation_context=DummyDialogCreationContext() + ) + + # Check warning was logged + warning_msg = " ".join(log.output) + self.assertIn("would hide all options", warning_msg.lower()) + + s = schema["properties"]["model"]["properties"]["hide_all_param"] + self.assertEqual(s["oneOf"], []) + class DummyDialogCreationContext: def __init__(self, specs: List = None) -> None: diff --git a/org.knime.python3.nodes/src/main/python/knime/extension/parameter.py b/org.knime.python3.nodes/src/main/python/knime/extension/parameter.py index 84cc52a57..d84371f52 100644 --- a/org.knime.python3.nodes/src/main/python/knime/extension/parameter.py +++ b/org.knime.python3.nodes/src/main/python/knime/extension/parameter.py @@ -1520,9 +1520,14 @@ def _generate_options_description(cls, docstring: str, visible_members=None): Parameters ---------- docstring : str - The parameter docstring + The parameter docstring. visible_members : list, optional List of member names to include. If None, all members are included. + + Returns + ------- + str + The formatted options description including the available options. """ # ensure that the options description is indented correctly if docstring: @@ -1595,13 +1600,14 @@ class attributes of the form `OPTION_NAME = (OPTION_LABEL, OPTION_DESCRIPTION)`. Dynamic Filtering ----------------- - The optional ``visible_choices`` callable allows filtering which enum members are displayed in the + The optional ``hidden_choices`` callable allows filtering which enum members are hidden in the dialog and node description based on the runtime context. This is useful for hiding options that are not applicable given the current input data. The callable receives a ``DialogCreationContext`` (or ``None`` if the node is not connected or - during node description generation at startup) and must return a list of enum members to display. - If the callable returns an empty list or only invalid members, a warning is logged. + during node description generation at startup) and must return a list of enum members to hide. + If ``None`` or an empty list is returned, all options are shown. If the callable returns only + invalid members or all members, a warning is logged. **Important:** Validation accepts any enum member regardless of filtering. This ensures that saved workflows remain valid even when the context changes and different options are filtered. @@ -1613,7 +1619,7 @@ class attributes of the form `OPTION_NAME = (OPTION_LABEL, OPTION_DESCRIPTION)`. Your callable should handle ``None`` gracefully. By returning different options based on whether the context is ``None``, you can control what appears in the node description versus the dialog. - For example, returning a subset when ``ctx is None`` effectively provides static subsetting in + For example, returning items to hide when ``ctx is None`` effectively provides static filtering in the description while still allowing dynamic filtering in the dialog based on actual input data. >>> class ModelOptions(EnumParameterOptions): @@ -1621,29 +1627,29 @@ class attributes of the form `OPTION_NAME = (OPTION_LABEL, OPTION_DESCRIPTION)`. ... RANDOM_FOREST = ("Random Forest", "Ensemble tree model") ... NEURAL_NET = ("Neural Network", "Deep learning model") ... - ... def filter_by_model_support(context): + ... def hide_by_model_support(context): ... # Handle None context (no connection or description generation) ... if context is None: - ... # Return subset for description - these are the "primary" options - ... return [ModelOptions.LINEAR, ModelOptions.RANDOM_FOREST] + ... # Hide advanced option in description + ... return [ModelOptions.NEURAL_NET] ... ... # Get input specifications for dialog filtering ... specs = context.get_input_specs() ... if not specs: - ... return list(ModelOptions) + ... return [] # Hide nothing, show all ... ... # Filter based on model capabilities from input ... model_spec = specs[0] # Assuming model is first input ... supported = model_spec.get_supported_options() # Hypothetical method ... - ... return [opt for opt in ModelOptions if opt.name in supported] + ... return [opt for opt in ModelOptions if opt.name not in supported] ... ... model_param = knext.EnumParameter( ... label="Model Type", ... description="Select the model to use.", ... default_value=ModelOptions.LINEAR, # Can use enum member directly ... enum=ModelOptions, - ... visible_choices=filter_by_model_support, + ... hidden_choices=hide_by_model_support, ... ) """ @@ -1672,17 +1678,17 @@ def __init__( since_version: Optional[Union[Version, str]] = None, is_advanced: bool = False, style: Optional[Style] = None, - visible_choices: Optional[ + hidden_choices: Optional[ Callable[[Optional[Any]], List[EnumParameterOptions]] ] = None, ): """ Parameters ---------- - visible_choices : Optional[Callable[[Optional[DialogCreationContext]], List[EnumParameterOptions]]] - Optional callable that filters which enum members are displayed in the dialog. + hidden_choices : Optional[Callable[[Optional[DialogCreationContext]], List[EnumParameterOptions]]] + Optional callable that filters which enum members are hidden in the dialog. The callable receives a DialogCreationContext (or None) and must return a list - of enum members to show. If None or not provided, all enum members are shown. + of enum members to hide. If None, an empty list, or not provided, all enum members are shown. """ if validator is None: validator = self._default_validator @@ -1702,12 +1708,12 @@ def __init__( self._style = style - # Store visible_choices callable wrapped with cache + # Store hidden_choices callable wrapped with cache # Cache size of 2 to handle both None (description) and actual context (schema) - if visible_choices is not None: - self._visible_choices = lru_cache(maxsize=2)(visible_choices) + if hidden_choices is not None: + self._hidden_choices = lru_cache(maxsize=2)(hidden_choices) else: - self._visible_choices = None + self._hidden_choices = None super().__init__( label, @@ -1721,37 +1727,42 @@ def __init__( def _get_visible_options(self, dialog_creation_context): """Get the list of enum members to display in the dialog. - Returns the full enum if no visible_choices callable is set, otherwise - calls the callable and validates the returned members. + Returns the full enum if no hidden_choices callable is set, otherwise + calls the callable and validates the returned members to hide, then returns + the remaining members. """ - if self._visible_choices is None: + if self._hidden_choices is None: return list(self._enum) # Call the cached callable with context (can be None) try: - filtered_members = self._visible_choices(dialog_creation_context) + hidden_members = self._hidden_choices(dialog_creation_context) except Exception as e: LOGGER.warning( - f"Error calling visible_choices for parameter '{self._label}': {e}. " + f"Error calling hidden_choices for parameter '{self._label}': {e}. " f"Showing all options." ) return list(self._enum) - if not isinstance(filtered_members, (list, tuple)): + if not isinstance(hidden_members, (list, tuple)): LOGGER.warning( - f"visible_choices for parameter '{self._label}' must return a list or tuple, " - f"got {type(filtered_members).__name__}. Showing all options." + f"hidden_choices for parameter '{self._label}' must return a list or tuple, " + f"got {type(hidden_members).__name__}. Showing all options." ) return list(self._enum) + # Handle empty list - show all options (nothing hidden) + if not hidden_members: + return list(self._enum) + # Validate that all returned members exist in the enum valid_names = set(self._enum._member_names_) - validated_members = [] + validated_hidden = [] invalid_members = [] - for member in filtered_members: + for member in hidden_members: if hasattr(member, "name") and member.name in valid_names: - validated_members.append(member) + validated_hidden.append(member) else: invalid_members.append(member) @@ -1759,28 +1770,24 @@ def _get_visible_options(self, dialog_creation_context): if invalid_members: valid_options = ", ".join(self._enum._member_names_) LOGGER.warning( - f"visible_choices for parameter '{self._label}' returned invalid members: " + f"hidden_choices for parameter '{self._label}' returned invalid members: " f"{invalid_members}. Valid options are: {valid_options}" ) - # Handle empty result - developer responsibility to implement correctly - if not validated_members: - if not filtered_members: - # Empty list returned - log warning and show empty - LOGGER.warning( - f"visible_choices for parameter '{self._label}' returned an empty list. " - f"Showing empty options." - ) - return [] - else: - # All members were invalid - already warned above - LOGGER.warning( - f"visible_choices for parameter '{self._label}' returned no valid members. " - f"Showing empty options." - ) - return [] + # If all members are hidden or all returned members were invalid, show empty + all_members = list(self._enum) + if len(validated_hidden) >= len(all_members): + LOGGER.warning( + f"hidden_choices for parameter '{self._label}' would hide all options. " + f"Showing empty options." + ) + return [] - return validated_members + # Return members that are not in the hidden list + hidden_names = {member.name for member in validated_hidden} + visible_members = [member for member in all_members if member.name not in hidden_names] + + return visible_members def _get_options(self, dialog_creation_context) -> dict: if self._style: @@ -1796,6 +1803,12 @@ def _generate_description(self, visible_options=None): List of enum members to include in description. If None, all members from self._enum are included. If provided, only these members appear in the description. + + Returns + ------- + str + A formatted description string containing the available options, + optionally restricted to the provided ``visible_options``. """ if visible_options is None: # No filtering - generate description for all options @@ -1831,7 +1844,7 @@ def _extract_schema(self, extension_version=None, dialog_creation_context=None): def _extract_description(self, name, parent_scope: _Scope): # Get visible options with None context for node description visible_options = ( - self._get_visible_options(None) if self._visible_choices else None + self._get_visible_options(None) if self._hidden_choices else None ) return { "name": self._label, From 7229885e784336dbf97a59bc3ed5fc6ccf162d3c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 12 Jan 2026 14:21:24 +0000 Subject: [PATCH 3/4] Fix minor docstring formatting issue Co-authored-by: AtR1an <12174376+AtR1an@users.noreply.github.com> --- .../src/main/python/knime/extension/parameter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/org.knime.python3.nodes/src/main/python/knime/extension/parameter.py b/org.knime.python3.nodes/src/main/python/knime/extension/parameter.py index d84371f52..e9993893b 100644 --- a/org.knime.python3.nodes/src/main/python/knime/extension/parameter.py +++ b/org.knime.python3.nodes/src/main/python/knime/extension/parameter.py @@ -1688,7 +1688,7 @@ def __init__( hidden_choices : Optional[Callable[[Optional[DialogCreationContext]], List[EnumParameterOptions]]] Optional callable that filters which enum members are hidden in the dialog. The callable receives a DialogCreationContext (or None) and must return a list - of enum members to hide. If None, an empty list, or not provided, all enum members are shown. + of enum members to hide. If None, empty list, or not provided, all enum members are shown. """ if validator is None: validator = self._default_validator From da43ba14a227d04dc4c051a2beb2189570c63495 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 12 Jan 2026 14:56:03 +0000 Subject: [PATCH 4/4] Rename validated_hidden to validated_members and clarify test comment Co-authored-by: AtR1an <12174376+AtR1an@users.noreply.github.com> --- .../src/test/python/unittest/test_knime_parameter.py | 3 ++- .../src/main/python/knime/extension/parameter.py | 8 ++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/org.knime.python3.nodes.tests/src/test/python/unittest/test_knime_parameter.py b/org.knime.python3.nodes.tests/src/test/python/unittest/test_knime_parameter.py index 7ca5d7f6c..33fa77302 100644 --- a/org.knime.python3.nodes.tests/src/test/python/unittest/test_knime_parameter.py +++ b/org.knime.python3.nodes.tests/src/test/python/unittest/test_knime_parameter.py @@ -1866,7 +1866,8 @@ class TestObj: self.assertIn("invalid members", warning_msg.lower()) self.assertIn("Valid options", warning_msg) - # Schema should hide only the valid member (LINEAR) + # Schema should hide only LINEAR (which is valid, but the only valid member in the hidden list) + # The invalid members in the list are filtered out s = schema["properties"]["model"]["properties"]["invalid_param"] values = {entry["const"] for entry in s["oneOf"]} # LINEAR is hidden, others remain visible diff --git a/org.knime.python3.nodes/src/main/python/knime/extension/parameter.py b/org.knime.python3.nodes/src/main/python/knime/extension/parameter.py index e9993893b..a6813a27c 100644 --- a/org.knime.python3.nodes/src/main/python/knime/extension/parameter.py +++ b/org.knime.python3.nodes/src/main/python/knime/extension/parameter.py @@ -1757,12 +1757,12 @@ def _get_visible_options(self, dialog_creation_context): # Validate that all returned members exist in the enum valid_names = set(self._enum._member_names_) - validated_hidden = [] + validated_members = [] invalid_members = [] for member in hidden_members: if hasattr(member, "name") and member.name in valid_names: - validated_hidden.append(member) + validated_members.append(member) else: invalid_members.append(member) @@ -1776,7 +1776,7 @@ def _get_visible_options(self, dialog_creation_context): # If all members are hidden or all returned members were invalid, show empty all_members = list(self._enum) - if len(validated_hidden) >= len(all_members): + if len(validated_members) >= len(all_members): LOGGER.warning( f"hidden_choices for parameter '{self._label}' would hide all options. " f"Showing empty options." @@ -1784,7 +1784,7 @@ def _get_visible_options(self, dialog_creation_context): return [] # Return members that are not in the hidden list - hidden_names = {member.name for member in validated_hidden} + hidden_names = {member.name for member in validated_members} visible_members = [member for member in all_members if member.name not in hidden_names] return visible_members