Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -1601,6 +1601,370 @@ def test_invalid_assignment_static(self):
self.assertEqual(obj.static_plain, "INVALID")


class TestModelOptions(kp.EnumParameterOptions):
"""Test enum for hidden_choices testing"""

LINEAR = ("Linear Regression", "Fits a linear model")
RANDOM_FOREST = ("Random Forest", "Ensemble tree model")
NEURAL_NET = ("Neural Network", "Deep learning model")
SVM = ("Support Vector Machine", "SVM model")


class ParameterizedWithHiddenChoices:
"""Test class for EnumParameter with hidden_choices callable"""

@staticmethod
def _hide_two(ctx):
"""Hide NEURAL_NET and SVM"""
return [TestModelOptions.NEURAL_NET, TestModelOptions.SVM]

@staticmethod
def _hide_by_specs(ctx):
"""Hide based on context specs"""
if ctx is None:
# 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 [] # 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.NEURAL_NET, TestModelOptions.SVM]
else:
return [TestModelOptions.LINEAR, TestModelOptions.RANDOM_FOREST]

@staticmethod
def _hide_invalid():
"""Returns invalid members for testing warnings"""
return ["INVALID_OPTION", TestModelOptions.LINEAR]

@staticmethod
def _hide_empty(ctx):
"""Returns empty list - hide nothing"""
return []

param_filtered = kp.EnumParameter(
label="Filtered Model",
description="Model with filtered choices",
default_value=TestModelOptions.LINEAR.name,
enum=TestModelOptions,
hidden_choices=_hide_two.__func__,
)

param_context_dependent = kp.EnumParameter(
label="Context Dependent",
description="Choices depend on context",
default_value=TestModelOptions.LINEAR, # Using enum member as default
enum=TestModelOptions,
hidden_choices=_hide_by_specs.__func__,
)

param_no_filter = kp.EnumParameter(
label="No Filter",
description="All options visible",
default_value=TestModelOptions.LINEAR.name,
enum=TestModelOptions,
)


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 = ParameterizedWithHiddenChoices()
schema = kp.extract_schema(
obj, dialog_creation_context=DummyDialogCreationContext()
)
s = schema["properties"]["model"]["properties"]["param_filtered"]

self.assertIn("oneOf", s)
values = {entry["const"] for entry in s["oneOf"]}
# 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 hidden_choices shows all options"""
obj = ParameterizedWithHiddenChoices()
schema = kp.extract_schema(
obj, dialog_creation_context=DummyDialogCreationContext()
)
s = schema["properties"]["model"]["properties"]["param_no_filter"]

self.assertIn("oneOf", s)
values = {entry["const"] for entry in s["oneOf"]}
self.assertEqual(values, {"LINEAR", "RANDOM_FOREST", "NEURAL_NET", "SVM"})

def test_context_dependent_filtering(self):
"""Test that filtering works based on context"""
obj = ParameterizedWithHiddenChoices()

# 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 - 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_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)
s = schema["properties"]["model"]["properties"]["param_context_dependent"]

self.assertIn("oneOf", s)
values = {entry["const"] for entry in s["oneOf"]}
# Should hide NEURAL_NET and RANDOM_FOREST (defined for None case)
self.assertEqual(values, {"LINEAR", "SVM"})

def test_description_respects_hidden_choices(self):
"""Test that description respects hidden_choices based on None context"""

# param_filtered uses _hide_two which doesn't check context
# So description should show only LINEAR and RANDOM_FOREST
desc_dict = ParameterizedWithHiddenChoices.param_filtered._extract_description(
"param_filtered", None
)
description = desc_dict["description"]

# Description should contain only non-hidden options
self.assertIn("Linear Regression", description)
self.assertIn("Random Forest", description)
# 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 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 non-hidden options
self.assertIn("Linear Regression", description)
self.assertIn("Support Vector Machine", description)
# 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 hidden_choices
desc_dict = ParameterizedWithHiddenChoices.param_no_filter._extract_description(
"param_no_filter", None
)
description = desc_dict["description"]

# Description should contain all enum options
self.assertIn("Linear Regression", description)
self.assertIn("Random Forest", description)
self.assertIn("Neural Network", description)
self.assertIn("Support Vector Machine", description)

def test_validation_accepts_filtered_out_values(self):
"""Test that validation accepts any enum member, even if filtered out"""
obj = ParameterizedWithHiddenChoices()

# Extract schema with filtering active
kp.extract_schema(obj, dialog_creation_context=DummyDialogCreationContext())

# NEURAL_NET is hidden but should still be valid
obj.param_filtered = "NEURAL_NET"
self.assertEqual(obj.param_filtered, "NEURAL_NET")

# Invalid value should still fail validation
with self.assertRaises(ValueError):
obj.param_filtered = "INVALID_OPTION"

def test_default_as_enum_member(self):
"""Test that default_value accepts enum member directly"""
obj = ParameterizedWithHiddenChoices()

# param_context_dependent uses enum member as default
self.assertEqual(obj.param_context_dependent, "LINEAR")

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 show all",
default_value=TestModelOptions.LINEAR.name,
enum=TestModelOptions,
hidden_choices=empty_filter,
)

class TestObj:
empty_param = param

obj = TestObj()

# 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"]
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"""

def invalid_filter(ctx):
# Return mix of valid and invalid
class FakeMember:
name = "INVALID"

return [TestModelOptions.LINEAR, FakeMember(), "not_a_member"]

param = kp.EnumParameter(
label="Invalid Filter",
description="Has invalid members",
default_value=TestModelOptions.LINEAR.name,
enum=TestModelOptions,
hidden_choices=invalid_filter,
)

class TestObj:
invalid_param = param

obj = TestObj()

# Should log warning about invalid members
with self.assertLogs("Python backend", level="WARNING") as log:
schema = kp.extract_schema(
obj, dialog_creation_context=DummyDialogCreationContext()
)

# Check warning was logged with valid options listed
warning_msg = " ".join(log.output)
self.assertIn("invalid members", warning_msg.lower())
self.assertIn("Valid options", warning_msg)

# 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
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 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 hidden",
default_value=TestModelOptions.LINEAR.name, # Will be hidden
enum=TestModelOptions,
hidden_choices=hide_including_default,
)

class TestObj:
param_with_hidden_default = param

obj = TestObj()

# Should log warning about default not visible
with self.assertLogs("Python backend", level="WARNING") as log:
kp.extract_schema(obj, dialog_creation_context=DummyDialogCreationContext())

warning_msg = " ".join(log.output)
self.assertIn("Default value", warning_msg)
self.assertIn("not in the currently visible options", warning_msg)

def test_caching_works(self):
"""Test that hidden_choices callable is cached per context"""
call_count = [0]

def counting_filter(ctx):
call_count[0] += 1
return [TestModelOptions.NEURAL_NET, TestModelOptions.SVM]

param = kp.EnumParameter(
label="Cached",
description="Should cache",
default_value=TestModelOptions.LINEAR.name,
enum=TestModelOptions,
hidden_choices=counting_filter,
)

class TestObj:
cached_param = param

obj = TestObj()
ctx = DummyDialogCreationContext()

# Extract schema multiple times with same context
kp.extract_schema(obj, dialog_creation_context=ctx)
kp.extract_schema(obj, dialog_creation_context=ctx)
kp.extract_schema(obj, dialog_creation_context=ctx)

# For a given dialog_creation_context, the callable should be invoked only once;
# subsequent uses with the same context reuse the cached result.
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:
class DummyJavaContext:
Expand Down
Loading