Skip to content

Commit 396bdd0

Browse files
committed
Improve testing, add Decimal class as possible object
1 parent 080492c commit 396bdd0

3 files changed

Lines changed: 125 additions & 10 deletions

File tree

plugins/nf-python/src/main/nextflow/python/PythonExtension.groovy

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,10 +85,12 @@ class PythonExtension extends PluginExtensionPoint {
8585
if (obj instanceof String) return ["String", obj]
8686
if (obj instanceof Integer) return ["Integer", obj]
8787
if (obj instanceof Long) return ["Integer", obj]
88+
if (obj instanceof BigDecimal) return ["Decimal", obj]
8889
if (obj instanceof Double || obj instanceof Float) return ["Float", obj]
8990
if (obj instanceof Boolean) return ["Boolean", obj]
9091
if (obj instanceof java.nio.file.Path) return ["Path", obj.toString()]
9192
if (obj instanceof Duration) return ["Duration", obj.toMillis()]
93+
if (obj instanceof java.time.Duration) return ["Duration", obj.toMillis()]
9294
if (obj instanceof MemoryUnit) return ["MemoryUnit", obj.toBytes()]
9395
if (obj instanceof VersionNumber) return ["VersionNumber", [obj.getMajor(), obj.getMinor(), obj.getPatch()]]
9496
// Add more as needed
@@ -118,6 +120,7 @@ class PythonExtension extends PluginExtensionPoint {
118120
}
119121
case 'String': return data
120122
case 'Integer': return data
123+
case 'Decimal': return data instanceof String ? new BigDecimal(data as String) : null
121124
case 'Float': return data
122125
case 'Boolean': return data
123126
case 'Null': return null

py/nf_python/nextflow.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import sys
77
import pathlib
88
import datetime
9+
import decimal
910

1011
class MemoryUnit:
1112
UNITS = ["B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB"]
@@ -59,6 +60,8 @@ def parse_nf(serialized_object):
5960
return data
6061
elif nf_type == "Integer":
6162
return int(data)
63+
elif nf_type == "Decimal":
64+
return decimal.Decimal(data)
6265
elif nf_type == "Float":
6366
return data
6467
elif nf_type == "Boolean":
@@ -82,6 +85,8 @@ def pack_python(python_object):
8285
return ["String", python_object]
8386
elif isinstance(python_object, int):
8487
return ["Integer", python_object]
88+
elif isinstance(python_object, decimal.Decimal):
89+
return ["Decimal", str(python_object)]
8590
elif isinstance(python_object, float):
8691
return ["Float", python_object]
8792
elif isinstance(python_object, bool):

test/test_flow.nf

Lines changed: 117 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,18 +15,125 @@ process PyProcess {
1515
}
1616

1717
workflow {
18+
// Test pyFunction in a process: should return correct sum and difference
1819
PyProcess(10, 3)
19-
.view()
20+
.view { result ->
21+
assert result == [x: 10, y: 3]
22+
println "[TEST] PyProcess returned: $result"
23+
}
2024

21-
Channel.of([x: 10, y: 3], [x: 1, y: 5])
22-
.pyOperator(script: 'echo_kwargs.py') |
23-
view()
25+
// Test pyOperator with a map input
26+
Channel.of([x: 10, y: 3])
27+
.pyOperator(script: 'echo_kwargs.py')
28+
.view { result ->
29+
assert result == [x: 10, y: 3]
30+
println "[TEST] pyOperator map input returned: $result"
31+
}
2432

25-
// Test all supported types and roundtrip
26-
Channel.of(1, 2, 3)
27-
.pyOperator(script: 'echo_kwargs.py', foo: 'bar', bar: 123, baz: [1,2,3], qux: [a: 1, b: 2], path: '/tmp/example.txt', duration: 90)
28-
.view()
33+
// Test pyOperator with primitive input and options
34+
Channel.of(1)
35+
.pyOperator(script: 'echo_kwargs.py', foo: 'bar', bar: 123)
36+
.view { result ->
37+
assert result == [value: 1, foo: 'bar', bar: 123]
38+
println "[TEST] pyOperator primitive+opts returned: $result"
39+
}
2940

30-
// Example: Launch a Python script as a standalone function (no channel input)
31-
pyFunction(script: 'echo_kwargs.py', foo: 'standalone', bar: 42)
41+
// Test pyFunction as a standalone function
42+
def standalone = pyFunction(script: 'echo_kwargs.py', foo: 'standalone', bar: 42)
43+
assert standalone == [foo: 'standalone', bar: 42]
44+
println "[TEST] pyFunction standalone returned: $standalone"
45+
46+
// Test all supported types and roundtrip, one script call at a time
47+
Channel.of(42)
48+
.pyOperator(script: 'echo_kwargs.py')
49+
.view { result ->
50+
assert result.value == 42
51+
println "[TEST] int roundtrip: $result"
52+
}
53+
54+
Channel.of(3.14)
55+
.pyOperator(script: 'echo_kwargs.py')
56+
.view { result ->
57+
assert Math.abs(result.value - 3.14) < 1e-6
58+
println "[TEST] float roundtrip: $result"
59+
}
60+
61+
Channel.of('hello')
62+
.pyOperator(script: 'echo_kwargs.py')
63+
.view { result ->
64+
assert result.value == 'hello'
65+
println "[TEST] str roundtrip: $result"
66+
}
67+
68+
Channel.of(true)
69+
.pyOperator(script: 'echo_kwargs.py')
70+
.view { result ->
71+
assert result.value == true
72+
println "[TEST] bool roundtrip: $result"
73+
}
74+
75+
Channel.of(null)
76+
.pyOperator(script: 'echo_kwargs.py')
77+
.view { result ->
78+
assert result.value == null
79+
println "[TEST] null roundtrip: $result"
80+
}
81+
82+
Channel.of([1, 2, 3])
83+
.pyOperator(script: 'echo_kwargs.py')
84+
.view { result ->
85+
assert result.value == [1, 2, 3]
86+
println "[TEST] list roundtrip: $result"
87+
}
88+
89+
Channel.of([4, 5, 6] as Set)
90+
.pyOperator(script: 'echo_kwargs.py')
91+
.view { result ->
92+
assert (result.value as Set) == [4, 5, 6] as Set
93+
println "[TEST] set roundtrip: $result"
94+
}
95+
96+
Channel.of([a: 1, b: 2])
97+
.pyOperator(script: 'echo_kwargs.py')
98+
.view { result ->
99+
assert result == [a: 1, b: 2]
100+
println "[TEST] dict roundtrip: $result"
101+
}
102+
103+
Channel.of('/tmp/example.txt')
104+
.pyOperator(script: 'echo_kwargs.py')
105+
.view { result ->
106+
assert result.value.toString() == '/tmp/example.txt'
107+
println "[TEST] path roundtrip: $result"
108+
}
109+
110+
Channel.of(java.time.Duration.ofSeconds(90))
111+
.pyOperator(script: 'echo_kwargs.py')
112+
.view { result ->
113+
assert result.value.toString() == 'PT1M30S' || result.value.seconds == 90
114+
println "[TEST] duration roundtrip: $result"
115+
}
116+
117+
Channel.of(Duration.of(90000))
118+
.pyOperator(script: 'echo_kwargs.py')
119+
.view { result ->
120+
assert result.value.toString() == 'PT1M30S' || result.value.seconds == 90
121+
println "[TEST] duration roundtrip: $result"
122+
}
123+
124+
Channel.of(10241024)
125+
.pyOperator(script: 'echo_kwargs.py', type: 'memory')
126+
.view { result ->
127+
// memory is returned as int, check value
128+
assert result.value == 10241024
129+
println "[TEST] memory roundtrip: $result"
130+
}
131+
132+
Channel.of([82, 17, 49])
133+
.pyOperator(script: 'echo_kwargs.py', type: 'version')
134+
.view { result ->
135+
// version is returned as list, check value
136+
assert result.value == [82, 17, 49]
137+
println "[TEST] version roundtrip: $result"
138+
}
32139
}

0 commit comments

Comments
 (0)