Skip to content

Commit e190bc2

Browse files
committed
add number tests
1 parent 037aeb5 commit e190bc2

2 files changed

Lines changed: 65 additions & 3 deletions

File tree

biomero/schema_parsers.py

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,14 +99,15 @@ def adapt_to_biomero_schema(
9999
value_key = input_param.get("value-key", f"@{param_id.upper()}")
100100

101101
# Build the parameter data with alias names for Pydantic validation
102+
raw_default = input_param.get("default-value")
102103
param_data = {
103104
"id": param_id,
104105
"type": param_type,
105106
"name": input_param.get("name") or param_id,
106107
"description": input_param.get("description", ""),
107108
"value-key": value_key, # Use alias name
108109
"command-line-flag": cmd_flag, # Use alias name
109-
"default-value": input_param.get("default-value"), # Alias
110+
"default-value": raw_default, # Alias
110111
"optional": input_param.get("optional", False),
111112
"set-by-server": input_param.get("set-by-server", False),
112113
"value-choices": input_param.get("value-choices"),
@@ -297,6 +298,26 @@ def _map_bilayers(self, param, is_output=False):
297298
if any(str(lbl) != str(val) for lbl, val in zip(labels, param_data["value-choices"])):
298299
param_data["value-choices-labels"] = [str(lbl) if lbl is not None else None for lbl in labels]
299300

301+
# Ensure the default value type is consistent with value-choices.
302+
# When all choices are integers the default MUST also be an int so
303+
# OMERO's scripts framework can match str(default) against the
304+
# stringified choices list. We coerce unconditionally here (not just
305+
# when already a float) because Pydantic on older schema versions
306+
# re-coerces any int→float if int is absent from the Union — and
307+
# there is no way to prevent that without fixing the schema.
308+
# Fixing the schema (adding int to Union) is the real fix; this is
309+
# defence-in-depth that also pre-aligns the raw value before Pydantic.
310+
choices = param_data.get("value-choices", [])
311+
default = param_data.get("default-value")
312+
if choices and all(isinstance(c, int) for c in choices) and default is not None:
313+
try:
314+
coerced = int(default)
315+
# only coerce if it's a lossless conversion (e.g. 0.0→0 ok, 0.5 not)
316+
if coerced == default:
317+
param_data["default-value"] = coerced
318+
except (ValueError, TypeError):
319+
pass
320+
300321
param_format = param.get("format")
301322
if param_format:
302323
param_data["format"] = param_format

tests/unit/test_schema_parsers.py

Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,24 @@ def test_biaflows_parsing(self, biaflows_descriptor):
6666
assert "Radius" in param_names
6767
assert "Threshold" in param_names
6868

69+
def test_biaflows_number_int_default_stays_integer(self, biaflows_descriptor):
70+
"""Number param with int default parses as type 'integer' with int default_value."""
71+
parsed = DescriptorParserFactory.parse_descriptor(biaflows_descriptor)
72+
radius = next(p for p in parsed.inputs if p.id == 'ij_radius')
73+
assert radius.type == 'integer'
74+
assert isinstance(radius.default_value, int), (
75+
f"default_value must be int, got {type(radius.default_value)}: {radius.default_value!r}"
76+
)
77+
assert radius.default_value == 5
78+
79+
def test_biaflows_number_float_default_stays_float(self, biaflows_descriptor):
80+
"""Number param with float default parses as type 'float' with float default_value."""
81+
parsed = DescriptorParserFactory.parse_descriptor(biaflows_descriptor)
82+
threshold = next(p for p in parsed.inputs if p.id == 'ij_threshold')
83+
assert threshold.type == 'float'
84+
assert isinstance(threshold.default_value, float)
85+
assert threshold.default_value == -0.5
86+
6987

7088
class TestBiomeroSchemaParser:
7189
"""Test cases for biomero-schema format parsing."""
@@ -232,14 +250,37 @@ def test_bilayers_value_choices_labels_none_when_labels_equal_values(self, bilay
232250
assert channel_axis.value_choices == [0, 2]
233251
assert channel_axis.value_choices_labels is None
234252

253+
def test_bilayers_integer_radio_default_is_int_not_float(self, bilayers_descriptor):
254+
"""channel_axis default value is int, not float."""
255+
parsed = DescriptorParserFactory.parse_descriptor(bilayers_descriptor)
256+
channel_axis = next(p for p in parsed.inputs if p.id == 'channel_axis')
257+
assert isinstance(channel_axis.default_value, int), (
258+
f"default_value must be int, got {type(channel_axis.default_value)}: "
259+
f"{channel_axis.default_value!r}"
260+
)
261+
assert channel_axis.default_value == 0
262+
263+
def test_bilayers_integer_radio_default_str_matches_string_choices(self, bilayers_descriptor):
264+
"""str(default_value) is in [str(c) for c in value_choices]."""
265+
parsed = DescriptorParserFactory.parse_descriptor(bilayers_descriptor)
266+
channel_axis = next(p for p in parsed.inputs if p.id == 'channel_axis')
267+
str_choices = [str(c) for c in channel_axis.value_choices]
268+
assert str(channel_axis.default_value) in str_choices, (
269+
f"str({channel_axis.default_value!r}) not in {str_choices}"
270+
)
271+
235272
def test_bilayers_value_choices_labels_survive_model_dump(self, bilayers_descriptor):
236-
"""value-choices-labels must survive model_dump(by_alias=True) for downstream use."""
273+
"""value-choices-labels survives model_dump(by_alias=True)."""
237274
parsed = DescriptorParserFactory.parse_descriptor(bilayers_descriptor)
238275
dumped = parsed.model_dump(by_alias=True)
239276
pretrained = next(p for p in dumped['inputs'] if p['id'] == 'pretrained_model')
240277
assert pretrained['value-choices-labels'] == ["Cyto", "Nuclei", "Cyto2", "Ignore"]
278+
279+
280+
281+
class TestDescriptorParserFactory:
241282
"""Test cases for the descriptor parser factory."""
242-
283+
243284
def test_factory_parse_descriptor(self, biaflows_descriptor,
244285
biomero_descriptor):
245286
"""Test end-to-end parsing through factory."""

0 commit comments

Comments
 (0)