Is there a way to force the eval to interpret it a...
# hackathon-oct-2025-versions-topics-adoption
j
Is there a way to force the eval to interpret it as a
string
instead of a
float
or an
integer
? Asking because I sometimes get versions like this:
Copy code
"biopython": 1.85, <- Interpreted as a float. 
"pandas": "2.2.3",
"python": "3.9.21"
Made with:
Copy code
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: versions
n
Hmm that also happens with parameters so it's probably built-in to Nextflow. You could updated your code handling the versions to convert all versions to a string:
Copy code
def string_versions = versions_map.collectEntries { tool, version ->
    [ tool, version.toString() ]
}
Where
versions_map
is the map containing the tools as key and versions as value
j
I tried this:
Copy code
def 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?
n
What's the error you get?
j
Ooh no errors, just no difference in the snapshot
n
Does it really matter that much in this case? It shouldn't, right?
j
(higlighted with
^
just to show what’s different)
👍 1
No it doesn’t really matter but it’s what @Louis Le Nézet suggested so wanted to know if there was a way
n
There probably is 😛
😂 1
l
Don't worry on that, it's just an inconsistency that was bugging me 🤣
➕ 1
n
I get that 🤣 It should be fixed once we have proper typing in processes
b
wait, you had an
eval()
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 string
👍 1
j
Update on this, it’s not
eval()
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:
Copy code
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:
Copy code
/ ===========================================================================
// 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()
}
Something to work on in the hackathon 😄
👍 1
b
so the problem is yaml 🙃
shakefist 1