Skip to content

Commit 0476293

Browse files
committed
* Fix bool Python ser bug
* Add stricter type validation in PythonExtension.groovy * Add protocol doc
1 parent d4a1013 commit 0476293

File tree

4 files changed

+65
-8
lines changed

4 files changed

+65
-8
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,5 @@
77
build
88
work
99
out
10+
11+
py/*.egg-info/

doc/protocol.md

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
# nf-python Protocol Specification
2+
3+
`nf-python` currently uses environment variables and a custom JSON-based serialization protocol
4+
for data passage between Nextflow and the Python code. This is subject to changes in the future.
5+
The protocol is described here:
6+
7+
## Environment Variables
8+
9+
- `NEXTFLOW_INFILE`: Path to a temporary JSON file containing serialized input arguments
10+
- `NEXTFLOW_OUTFILE`: Path to a temporary JSON file where serialized output are written by the Python code
11+
- `NEXTFLOW_PYTHON_COMPAT_VER`: Protocol compatibility version (currently "1")
12+
13+
## JSON Type System
14+
15+
Data is exchanged using typed JSON arrays in the format `[type, value]`, where:
16+
- `type`: String identifying the data type
17+
- `value`: The actual data, possibly containing nested typed values
18+
19+
## Type Conversions
20+
21+
The following types are supported for usage as arguments and output values,
22+
and are automatically serialized and deserialized between native runtime objects.
23+
24+
| Nextflow/Groovy Type | JSON Type | Python Type |
25+
|-------------|-----------|------------|
26+
| `null` | `["Null", null]` | `None` |
27+
| `List` | `["List", [...]]` | `list` |
28+
| `Set` | `["Set", [...]]` | `set` |
29+
| `Map` | `["Map", [[k1, v1], [k2, v2]]]` | `dict` |
30+
| `String` | `["String", "..."]` | `str` |
31+
| `Integer(*)`, `Long` | `["Integer", n]` | `int` |
32+
| `BigDecimal` | `["Decimal", "..."]` | `decimal.Decimal` |
33+
| `Double(*)`, `Float` | `["Float", n]` or `["Float", "inf/nan/-inf"]` | `float` |
34+
| `Boolean` | `["Boolean", true/false]` | `bool` |
35+
| `Path` | `["Path", "..."]` | `pathlib.Path` |
36+
| `nextflow.util.Duration(*)`, `java.time.Duration` | `["Duration", ms]` | `datetime.timedelta` |
37+
| `nextflow.util.MemoryUnit` | `["MemoryUnit", bytes]` | `MemoryUnit` |
38+
| `nextflow.util.VersionNumber` | `["VersionNumber", [major, minor, patch]]` | `VersionNumber` |
39+
40+
(*): Used as a Groovy type when deserializing result when multiple types map to the same Python type.

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

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,20 @@ from nf_python import nf
166166
throw new IllegalArgumentException("Expected a list, got: $obj")
167167
}
168168

169+
private static Integer cInteger(Object obj) {
170+
if (obj instanceof Integer) {
171+
return obj
172+
}
173+
throw new IllegalArgumentException("Expected an integer, got: $obj")
174+
}
175+
176+
private static Boolean cBoolean(Object obj) {
177+
if (obj instanceof Boolean) {
178+
return obj as Boolean
179+
}
180+
throw new IllegalArgumentException("Expected an boolean, got: $obj")
181+
}
182+
169183
private static def unpackPython(obj) {
170184
List objList = cList(obj)
171185
if (objList.size() != 2) {
@@ -186,15 +200,15 @@ from nf_python import nf
186200
}
187201
}
188202
return result
189-
case 'String': return data
190-
case 'Integer': return data
191-
case 'Decimal': return new BigDecimal(cString(data) as String)
203+
case 'String': return cString(data)
204+
case 'Integer': return cInteger(data)
205+
case 'Decimal': return new BigDecimal(cString(data))
192206
case 'Float': return unpackFloat(data)
193-
case 'Boolean': return data
207+
case 'Boolean': return cBoolean(data)
194208
case 'Null': return null
195209
case 'Path': return java.nio.file.Paths.get(cString(data))
196-
case 'Duration': return Duration.of(data as long)
197-
case 'MemoryUnit': return new MemoryUnit(data as long)
210+
case 'Duration': return Duration.of(cInteger(data))
211+
case 'MemoryUnit': return new MemoryUnit(cInteger(data))
198212
case 'VersionNumber':
199213
List dataList = cList(data)
200214
if (dataList.size() == 3) {

py/nf_python/nextflow.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -113,14 +113,15 @@ def pack_python(python_object):
113113
]
114114
elif isinstance(python_object, str):
115115
return ["String", python_object]
116+
# Bool has to come before int because bool is a subclass of int!
117+
elif isinstance(python_object, bool):
118+
return ["Boolean", python_object]
116119
elif isinstance(python_object, int):
117120
return ["Integer", python_object]
118121
elif isinstance(python_object, decimal.Decimal):
119122
return ["Decimal", str(python_object)]
120123
elif isinstance(python_object, float):
121124
return ["Float", pack_float(python_object)]
122-
elif isinstance(python_object, bool):
123-
return ["Boolean", python_object]
124125
elif python_object is None:
125126
return ["Null", None]
126127
elif isinstance(python_object, pathlib.Path):

0 commit comments

Comments
 (0)