Repository: ResearchObject/ro-crate-py
Pull request #21 (2020-10-01 16:05:16)

Galaxy Workflow RO-Crate: separate entities for main and abstract wf
In `make_workflow_rocrate`, the abstract CWL entity is being assigned to the same local variable as the main workflow entity. Since some properties are updated in subsequent code, this leads to broken entries for both workflows:

```json
        {
            "@id": "test_galaxy_wf.ga",
            "@type": "File"
        },
        {
            "@id": "abstract_wf.cwl",
            "@type": [
                "File",
                "Workflow",
                "SoftwareSourceCode"
            ],
            "programmingLanguage": {
                "@id": "https://galaxyproject.org"
            }
        }
```

This PR fixes the issue. Now the generated entries are:

```json
        {
            "@id": "test_galaxy_wf.ga",
            "@type": [
                "File",
                "Workflow",
                "SoftwareSourceCode"
            ],
            "programmingLanguage": {
                "@id": "https://galaxyproject.org"
            },
            "subjectOf": {
                "@id": "abstract_wf.cwl"
            }
        },
        {
            "@id": "abstract_wf.cwl",
            "@type": [
                "File",
                "SoftwareSourceCode",
                "Workflow"
            ]
        }
```


Patch:
diff --git a/rocrate/rocrate.py b/rocrate/rocrate.py
index 7eaa84a..d6dc65a 100644
--- a/rocrate/rocrate.py
+++ b/rocrate/rocrate.py
@@ -291,8 +291,8 @@ class ROCrate():
     def add_file(self, source, crate_path=None, fetch_remote=False,
                  properties={}, **kwargs):
         props = dict(properties)
-        props.update(kwargs) 
-        file_entity = File(self, source, crate_path, fetch_remote, properties)
+        props.update(kwargs)
+        file_entity = File(self, source=source, dest_path=crate_path, fetch_remote=fetch_remote, properties=props)
         self._add_data_entity(file_entity)
         return file_entity
 
diff --git a/rocrate/rocrate_api.py b/rocrate/rocrate_api.py
index d0cefea..ceb27bb 100644
--- a/rocrate/rocrate_api.py
+++ b/rocrate/rocrate_api.py
@@ -87,15 +87,16 @@ def make_workflow_rocrate(workflow_path, wf_type, include_files=[],
                 ) as cwl_abstract_out:
                     with redirect_stdout(cwl_abstract_out):
                         get_cwl_interface.main(['1', workflow_path])
-                wf_file = wf_crate.add_file(
+                abstract_wf_file = wf_crate.add_file(
                     cwl_abstract_out.name,
                     'abstract_wf.cwl',
                     properties={
-                        "@type": ["ComputerLanguage", "SoftwareApplication"]
+                        "@type": ["File", "SoftwareSourceCode", "Workflow"]
                     }
                 )
             finally:
                 Path(cwl_abstract_out.name).unlink()
+            wf_file["subjectOf"] = abstract_wf_file
         programming_language_entity = entity.Entity(
             wf_crate, 'https://galaxyproject.org/'
         )


Test patch:
diff --git a/test/conftest.py b/test/conftest.py
index adb2c7f..714455a 100644
--- a/test/conftest.py
+++ b/test/conftest.py
@@ -1,3 +1,4 @@
+import json
 import pathlib
 import shutil
 
@@ -6,6 +7,32 @@ import pytest
 
 THIS_DIR = pathlib.Path(__file__).absolute().parent
 TEST_DATA_NAME = 'test-data'
+METADATA_FILE_NAME = 'ro-crate-metadata.jsonld'
+WORKFLOW_TYPES = {"File", "SoftwareSourceCode", "Workflow"}
+
+
+class Helpers:
+
+    @staticmethod
+    def read_json_entities(crate_base_path):
+        metadata_path = pathlib.Path(crate_base_path) / METADATA_FILE_NAME
+        with open(metadata_path, "rt") as f:
+            json_data = json.load(f)
+        return {_["@id"]: _ for _ in json_data["@graph"]}
+
+    @staticmethod
+    def check_wf_crate(json_entities, wf_file_name):
+        assert json_entities["./"]["mainEntity"]["@id"] == wf_file_name
+        assert wf_file_name in json_entities
+        wf_entity = json_entities[wf_file_name]
+        assert isinstance(wf_entity["@type"], list)
+        assert set(wf_entity["@type"]) == WORKFLOW_TYPES
+        assert "programmingLanguage" in wf_entity
+
+
+@pytest.fixture
+def helpers():
+    return Helpers
 
 
 # pytest's default tmpdir returns a py.path object
diff --git a/test/test_api.py b/test/test_api.py
index 61c06d2..a2d69ad 100644
--- a/test/test_api.py
+++ b/test/test_api.py
@@ -2,25 +2,29 @@ from rocrate import rocrate_api as roc_api
 from rocrate.rocrate import ROCrate
 
 
-def test_galaxy_wf_crate(test_data_dir, tmpdir):
+def test_galaxy_wf_crate(test_data_dir, tmpdir, helpers):
     wf_path = test_data_dir / 'test_galaxy_wf.ga'
     wf_crate = roc_api.make_workflow_rocrate(wf_path, wf_type='Galaxy')
     assert isinstance(wf_crate, ROCrate)
     out_path = tmpdir / 'ro_crate_out'
     out_path.mkdir()
     wf_crate.write_crate(out_path)
+    json_entities = helpers.read_json_entities(out_path)
+    helpers.check_wf_crate(json_entities, wf_path.name)
 
 
-def test_cwl_wf_crate(test_data_dir, tmpdir):
+def test_cwl_wf_crate(test_data_dir, tmpdir, helpers):
     wf_path = test_data_dir / 'sample_cwl_wf.cwl'
     wf_crate = roc_api.make_workflow_rocrate(wf_path, wf_type='CWL')
     assert isinstance(wf_crate, ROCrate)
     out_path = tmpdir / 'ro_crate_out'
     out_path.mkdir()
     wf_crate.write_crate(out_path)
+    json_entities = helpers.read_json_entities(out_path)
+    helpers.check_wf_crate(json_entities, wf_path.name)
 
 
-def test_create_wf_include(test_data_dir, tmpdir):
+def test_create_wf_include(test_data_dir, tmpdir, helpers):
     wf_path = test_data_dir / 'test_galaxy_wf.ga'
     extra_file1 = test_data_dir / 'test_file_galaxy.txt'
     extra_file2 = test_data_dir / 'test_file_galaxy2.txt'
@@ -32,3 +36,5 @@ def test_create_wf_include(test_data_dir, tmpdir):
     out_path = tmpdir / 'ro_crate_out'
     out_path.mkdir()
     wf_crate.write_crate(out_path)
+    json_entities = helpers.read_json_entities(out_path)
+    helpers.check_wf_crate(json_entities, wf_path.name)