Repository: nipype/pydra

Issue:
review what should be included checksum
for example I'm removing bindings and input that have output_file_template

Discussion:

Patch:
diff --git a/pydra/engine/helpers.py b/pydra/engine/helpers.py
index 1a441f1d..3cd9c65a 100644
--- a/pydra/engine/helpers.py
+++ b/pydra/engine/helpers.py
@@ -10,7 +10,7 @@ from hashlib import sha256
 import subprocess as sp
 
 from .specs import Runtime, File, attr_fields
-from .helpers_file import is_existing_file, hash_file, copyfile, is_existing_file
+from .helpers_file import hash_file, copyfile, is_existing_file
 
 
 def ensure_list(obj, tuple2list=False):
@@ -444,12 +444,14 @@ def hash_function(obj):
 
 def hash_value(value, tp=None, metadata=None):
     """calculating hash or returning values recursively"""
+    if metadata is None:
+        metadata = {}
     if isinstance(value, (tuple, list)):
         return [hash_value(el, tp, metadata) for el in value]
     elif isinstance(value, dict):
         dict_hash = {k: hash_value(v, tp, metadata) for (k, v) in value.items()}
         # returning a sorted object
-        return sorted(dict_hash.items(), key=lambda x: x[0])
+        return [list(el) for el in sorted(dict_hash.items(), key=lambda x: x[0])]
     else:  # not a container
         if (
             (tp is File or "pydra.engine.specs.File" in str(tp))
@@ -457,8 +459,6 @@ def hash_value(value, tp=None, metadata=None):
             and "container_path" not in metadata
         ):
             return hash_file(value)
-        elif isinstance(value, tuple):
-            return list(value)
         else:
             return value
 


Test patch:
diff --git a/pydra/engine/tests/test_helpers.py b/pydra/engine/tests/test_helpers.py
index 862f6b1d..50437354 100644
--- a/pydra/engine/tests/test_helpers.py
+++ b/pydra/engine/tests/test_helpers.py
@@ -4,17 +4,18 @@ import pytest
 import cloudpickle as cp
 
 from .utils import multiply
-from .. import helpers
+from ..helpers import hash_value, hash_function, save, create_pyscript
 from .. import helpers_file
+from ..specs import File
 
 
 def test_save(tmpdir):
     outdir = Path(tmpdir)
     with pytest.raises(ValueError):
-        helpers.save(tmpdir)
+        save(tmpdir)
     foo = multiply(name="mult", x=1, y=2)
     # save task
-    helpers.save(outdir, task=foo)
+    save(outdir, task=foo)
     del foo
     # load saved task
     task_pkl = outdir / "_task.pklz"
@@ -24,7 +25,7 @@ def test_save(tmpdir):
     # execute task and save result
     res = foo()
     assert res.output.out == 2
-    helpers.save(outdir, result=res)
+    save(outdir, result=res)
     del res
     # load saved result
     res_pkl = outdir / "_result.pklz"
@@ -35,10 +36,10 @@ def test_save(tmpdir):
 def test_create_pyscript(tmpdir):
     outdir = Path(tmpdir)
     with pytest.raises(Exception):
-        helpers.create_pyscript(outdir, "checksum")
+        create_pyscript(outdir, "checksum")
     foo = multiply(name="mult", x=1, y=2)
-    helpers.save(outdir, task=foo)
-    pyscript = helpers.create_pyscript(outdir, foo.checksum)
+    save(outdir, task=foo)
+    pyscript = create_pyscript(outdir, foo.checksum)
     assert pyscript.exists()
 
 
@@ -50,3 +51,76 @@ def test_hash_file(tmpdir):
         helpers_file.hash_file(outdir / "test.file")
         == "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08"
     )
+
+
+def test_hashfun_float():
+    import math
+
+    pi_50 = 3.14159265358979323846264338327950288419716939937510
+    pi_15 = 3.141592653589793
+    pi_10 = 3.1415926536
+    # comparing for x that have the same x.as_integer_ratio()
+    assert (
+        math.pi.as_integer_ratio()
+        == pi_50.as_integer_ratio()
+        == pi_15.as_integer_ratio()
+    )
+    assert hash_function(math.pi) == hash_function(pi_15) == hash_function(pi_50)
+    # comparing for x that have different x.as_integer_ratio()
+    assert math.pi.as_integer_ratio() != pi_10.as_integer_ratio()
+    assert hash_function(math.pi) != hash_function(pi_10)
+
+
+def test_hash_value_dict():
+    dict1 = {"a": 10, "b": 5}
+    dict2 = {"b": 5, "a": 10}
+    assert (
+        hash_value(dict1)
+        == hash_value(dict2)
+        == [["a", hash_value(10)], ["b", hash_value(5)]]
+        == [["a", 10], ["b", 5]]
+    )
+
+
+def test_hash_value_list_tpl():
+    lst = [2, 5.6, "ala"]
+    tpl = (2, 5.6, "ala")
+    assert hash_value(lst) == [hash_value(2), hash_value(5.6), hash_value("ala")] == lst
+    assert hash_value(lst) == hash_value(tpl)
+
+
+def test_hash_value_list_dict():
+    lst = [2, {"a": "ala", "b": 1}]
+    hash_value(lst)
+    assert (
+        hash_value(lst)
+        == [hash_value(2), hash_value([["a", "ala"], ["b", 1]])]
+        == [2, [["a", "ala"], ["b", 1]]]
+    )
+
+
+def test_hash_value_files(tmpdir):
+    file_1 = tmpdir.join("file_1.txt")
+    file_2 = tmpdir.join("file_2.txt")
+    with open(file_1, "w") as f:
+        f.write("hello")
+    with open(file_2, "w") as f:
+        f.write("hello")
+
+    assert hash_value(file_1, tp=File) == hash_value(file_2, tp=File)
+    assert hash_value(file_1, tp=str) != hash_value(file_2, tp=str)
+    assert hash_value(file_1) != hash_value(file_2)
+
+
+def test_hash_value_files_list(tmpdir):
+    file_1 = tmpdir.join("file_1.txt")
+    file_2 = tmpdir.join("file_2.txt")
+    with open(file_1, "w") as f:
+        f.write("hello")
+    with open(file_2, "w") as f:
+        f.write("hi")
+
+    assert hash_value([file_1, file_2], tp=File) == [
+        hash_value(file_1, tp=File),
+        hash_value(file_2, tp=File),
+    ]
diff --git a/pydra/engine/tests/test_specs.py b/pydra/engine/tests/test_specs.py
index 67ea41ea..da98ff88 100644
--- a/pydra/engine/tests/test_specs.py
+++ b/pydra/engine/tests/test_specs.py
@@ -289,7 +289,7 @@ def test_input_file_hash_4(tmpdir):
 
     # checking specific hash value
     hash1 = inputs(in_file=[{"file": file, "int": 3}]).hash
-    assert hash1 == "0148961a66106e15e55de3ab7e8c49e6227b6db7cdc614dcbae20406feb2548a"
+    assert hash1 == "e0555e78a40a02611674b0f48da97cdd28eee7e9885ecc17392b560c14826f06"
 
     # the same file, but int field changes
     hash1a = inputs(in_file=[{"file": file, "int": 5}]).hash