Skip to content

Commit b685800

Browse files
authored
Merge pull request #3386 from mirpedrol/schema-allow-oneof-anyof-allof
Parameters schema validation: allow oneOf, anyOf and allOf with `required`
2 parents 40c77c7 + b1c7a8c commit b685800

File tree

3 files changed

+106
-0
lines changed

3 files changed

+106
-0
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414

1515
### General
1616

17+
- Parameters schema validation: allow oneOf, anyOf and allOf with `required` ([#3386](https://github.com/nf-core/tools/pull/3386))
18+
1719
### Version updates
1820

1921
## [v3.1.1 - Brass Boxfish Patch](https://github.com/nf-core/tools/releases/tag/3.1.1) - [2024-12-20]

nf_core/pipelines/schema.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -327,11 +327,32 @@ def validate_default_params(self):
327327
schema_no_required = copy.deepcopy(self.schema)
328328
if "required" in schema_no_required:
329329
schema_no_required.pop("required")
330+
for keyword in ["allOf", "anyOf", "oneOf"]:
331+
if keyword in schema_no_required:
332+
for i, kw_content in enumerate(schema_no_required[keyword]):
333+
if "required" in kw_content:
334+
schema_no_required[keyword][i].pop("required")
335+
schema_no_required[keyword] = [
336+
kw_content for kw_content in schema_no_required[keyword] if kw_content
337+
]
338+
if not schema_no_required[keyword]:
339+
schema_no_required.pop(keyword)
330340
for group_key, group in schema_no_required.get(self.defs_notation, {}).items():
331341
if "required" in group:
332342
schema_no_required[self.defs_notation][group_key].pop("required")
343+
for keyword in ["allOf", "anyOf", "oneOf"]:
344+
if keyword in group:
345+
for i, kw_content in enumerate(group[keyword]):
346+
if "required" in kw_content:
347+
schema_no_required[self.defs_notation][group_key][keyword][i].pop("required")
348+
schema_no_required[self.defs_notation][group_key][keyword] = [
349+
kw_content for kw_content in group[keyword] if kw_content
350+
]
351+
if not group[keyword]:
352+
schema_no_required[self.defs_notation][group_key].pop(keyword)
333353
jsonschema.validate(self.schema_defaults, schema_no_required)
334354
except jsonschema.exceptions.ValidationError as e:
355+
log.debug(f"Complete error message:\n{e}")
335356
raise AssertionError(f"Default parameters are invalid: {e.message}")
336357
for param, default in self.schema_defaults.items():
337358
if default in ("null", "", None, "None") or default is False:

tests/pipelines/test_schema.py

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -285,6 +285,89 @@ def test_remove_schema_notfound_configs_childschema(self):
285285
assert len(params_removed) == 1
286286
assert "foo" in params_removed
287287

288+
def test_validate_defaults(self):
289+
"""Test validating default values"""
290+
self.schema_obj.schema = {
291+
"properties": {"foo": {"type": "string"}, "bar": {"type": "string"}},
292+
"required": ["foo"],
293+
}
294+
self.schema_obj.schema_defaults = {"foo": "foo", "bar": "bar"}
295+
self.schema_obj.no_prompts = True
296+
try:
297+
self.schema_obj.validate_default_params()
298+
except AssertionError:
299+
self.fail("Error validating schema defaults")
300+
301+
def test_validate_defaults_required(self):
302+
"""Test validating default values when required params don't have a default"""
303+
self.schema_obj.schema = {
304+
"properties": {"foo": {"type": "string"}, "bar": {"type": "string"}},
305+
"required": ["foo"],
306+
}
307+
self.schema_obj.schema_defaults = {}
308+
self.schema_obj.no_prompts = True
309+
try:
310+
self.schema_obj.validate_default_params()
311+
except AssertionError:
312+
self.fail("Error validating schema defaults")
313+
314+
def test_validate_defaults_required_inside_group(self):
315+
"""Test validating default values when required params don't have a default, inside a group"""
316+
self.schema_obj.schema = {
317+
"$defs": {
318+
"subSchemaId": {
319+
"properties": {"foo": {"type": "string"}, "bar": {"type": "string"}},
320+
"required": ["foo"],
321+
},
322+
}
323+
}
324+
self.schema_obj.schema_defaults = {}
325+
self.schema_obj.no_prompts = True
326+
try:
327+
self.schema_obj.validate_default_params()
328+
except AssertionError:
329+
self.fail("Error validating schema defaults")
330+
331+
def test_validate_defaults_required_inside_group_with_anyof(self):
332+
"""Test validating default values when required params don't have a default, inside a group with anyOf"""
333+
self.schema_obj.schema = {
334+
"$defs": {
335+
"subSchemaId": {
336+
"anyOf": [{"required": ["foo"]}, {"required": ["bar"]}],
337+
"properties": {"foo": {"type": "string"}, "bar": {"type": "string"}},
338+
},
339+
}
340+
}
341+
self.schema_obj.schema_defaults = {}
342+
self.schema_obj.no_prompts = True
343+
try:
344+
self.schema_obj.validate_default_params()
345+
except AssertionError:
346+
self.fail("Error validating schema defaults")
347+
348+
def test_validate_defaults_required_with_anyof(self):
349+
"""Test validating default values when required params don't have a default, with anyOf"""
350+
self.schema_obj.schema = {
351+
"properties": {"foo": {"type": "string"}, "bar": {"type": "string"}, "baz": {"type": "string"}},
352+
"anyOf": [{"required": ["foo"]}, {"required": ["bar"]}],
353+
}
354+
self.schema_obj.schema_defaults = {"baz": "baz"}
355+
self.schema_obj.no_prompts = True
356+
try:
357+
self.schema_obj.validate_default_params()
358+
except AssertionError:
359+
self.fail("Error validating schema defaults")
360+
361+
def test_validate_defaults_error(self):
362+
"""Test validating default raises an exception when a default is not valid"""
363+
self.schema_obj.schema = {
364+
"properties": {"foo": {"type": "string"}},
365+
}
366+
self.schema_obj.schema_defaults = {"foo": 1}
367+
self.schema_obj.no_prompts = True
368+
with self.assertRaises(AssertionError):
369+
self.schema_obj.validate_default_params()
370+
288371
def test_add_schema_found_configs(self):
289372
"""Try adding a new parameter to the schema from the config"""
290373
self.schema_obj.pipeline_params = {"foo": "bar"}

0 commit comments

Comments
 (0)