Joon Klaps
03/03/2026, 12:07 PMstring instead of a float or an integer?
Asking because I sometimes get versions like this:
"biopython": 1.85, <- Interpreted as a float.
"pandas": "2.2.3",
"python": "3.9.21"
Made with:
tuple val("${task.process}"), val('python'), eval("python --version | sed 's/Python //g'"), topic: versions
tuple val("${task.process}"), val('pandas'), eval("pip show pandas | sed '2!d;s/Version: //g'"), topic: versions
tuple val("${task.process}"), val('biopython'), eval("pip show biopython | sed '2!d;s/Version: //g'"), topic: versionsNicolas Vannieuwkerke
03/03/2026, 12:21 PMdef string_versions = versions_map.collectEntries { tool, version ->
[ tool, version.toString() ]
}
Where versions_map is the map containing the tools as key and versions as valueJoon Klaps
03/03/2026, 12:28 PMdef topic_versions_string = topic_versions.versions_tuple
.map { process, tool, version ->
[ process[process.lastIndexOf(':')+1..-1], " ${tool}: ${version.toString()}" ]
} ^^^^^^^^^^^
.groupTuple(by:0)
.map { process, tool_versions ->
tool_versions.unique().sort()
"${process}:\n${tool_versions.join('\n')}"
}
But to no avail, don’t think you’re suggestion is any different right?Nicolas Vannieuwkerke
03/03/2026, 12:35 PMJoon Klaps
03/03/2026, 12:35 PMNicolas Vannieuwkerke
03/03/2026, 12:36 PMJoon Klaps
03/03/2026, 12:36 PM^ just to show what’s different)Joon Klaps
03/03/2026, 12:37 PMNicolas Vannieuwkerke
03/03/2026, 12:38 PMLouis Le Nézet
03/03/2026, 12:42 PMNicolas Vannieuwkerke
03/03/2026, 12:48 PMBen Sherman
03/03/2026, 2:02 PMeval() that was returning a number instead of a string? that sounds weird 😆
if you can reproduce it in a small example, I would submit a nextflow issue for it. eval() should always return a stringJoon Klaps
03/10/2026, 9:42 AMeval() that is causing this issue. The module snapshots all still have quotes, but it’s only in the main pipeline-wide tests where this occurs.
According to seqera AI the root lies within the nf-core template of the function processVersionsFromYAML and the bugchain is:
1. eval("pip show biopython | sed ...") runs in the shell → emits the plain string "1.85" ✅
2. Nextflow receives that into a channel tuple — here Groovy infers the type from the value content, and "1.85" is coerced to Double(1.85) 🐛
3. In topic_versions_string, the line " ${tool}: ${version}" does GString interpolation of Double(1.85) → produces the YAML line biopython: 1.85 (no quotes)
4. processVersionsFromYAML() in utils_nfcore_pipeline then calls new org.yaml.snakeyaml.Yaml().load(...) which reads 1.85 as Double again, and yaml.dumpAsMap() with MINIMIZE_QUOTES re-serialises it as the bare scalar 1.85
5. nf-test snapshots store this as a JSON number 1.85
And these are the suggested solutions:
/ ===========================================================================
// VERSION 1 – current nf-core implementation (processVersionsFromYAML)
// Copied verbatim from:
// subworkflows/nf-core/utils_nfcore_pipeline/main.nf
//
// BUG: dumpAsMap() uses MINIMIZE_QUOTES – it only adds quotes when the value
// would be re-parsed as a different type. SnakeYAML's Resolver decides:
// - "1" → !!int → needs quotes → '1'
// - "1.85" → !!float → needs quotes → '1.85' (but unquoted input stays Double!)
// - "1.2.3"→ !!str → no quotes → 1.2.3 (safe, but inconsistent)
// The result: Integer/Double values that came in unquoted are dumped UNQUOTED,
// so nf-test's JSON serialiser stores them as numbers → snapshot type mismatch.
// ===========================================================================
def processVersionsFromYAML_ORIGINAL(String yaml_file) {
def yaml = new org.yaml.snakeyaml.Yaml()
def versions = yaml.load(yaml_file).collectEntries { k, v -> [k.tokenize(':')[-1], v] }
return yaml.dumpAsMap(versions).trim()
}
// ===========================================================================
// VERSION 2 – fixed implementation
// Two changes:
// 1. Stringify every version value after load() so Integer/Double → String.
// 2. Use a custom Representer that always DOUBLE_QUOTES String scalars,
// so the dump is safe regardless of what the value looks like.
// This means "1", "1.85", "1.2.3", "1.2.3.4" are all output identically.
// ===========================================================================
def processVersionsFromYAML_FIXED(String yaml_file) {
// --- loader (unchanged) ---
def yamlIn = new org.yaml.snakeyaml.Yaml()
// --- dumper: always quote strings ---
def opts = new DumperOptions()
opts.defaultFlowStyle = DumperOptions.FlowStyle.BLOCK
def rep = new Representer(opts)
rep.representers[String] = { data ->
rep.representScalar(Tag.STR, (String) data, DumperOptions.ScalarStyle.DOUBLE_QUOTED)
} as Represent
def yamlOut = new Yaml(rep, opts)
// --- load → stringify all values → dump ---
def versions = yamlIn.load(yaml_file)
.collectEntries { k, v -> [k.tokenize(':')[-1], v] } // strip process prefix
.collectEntries { proc, tools ->
// Stringify: Integer(1) → "1", Double(1.85) → "1.85", String → unchanged
[proc, tools instanceof Map
? tools.collectEntries { tool, ver -> [tool, ver.toString()] }
: tools.toString()]
}
return yamlOut.dumpAsMap(versions).trim()
}Joon Klaps
03/10/2026, 9:42 AMBen Sherman
03/10/2026, 12:01 PM