nebius/SWE-bench-extra huggingface dataset:
Repository: ResearchObject/ro-crate-py

Issue:
write-zip CLI command reinitializes the crate
The `write-zip` CLI command reinitializes the crate, so any changes like adding a workflow or test suite are lost.

Comments:

Patch:
diff --git a/rocrate/cli.py b/rocrate/cli.py
index 250a448..7d8dad7 100644
--- a/rocrate/cli.py
+++ b/rocrate/cli.py
@@ -120,7 +120,7 @@ def definition(state, suite, path, engine, engine_version):
 @click.argument('dst', type=click.Path(writable=True))
 @click.pass_obj
 def write_zip(state, dst):
-    crate = ROCrate(state.crate_dir, init=True, gen_preview=False)
+    crate = ROCrate(state.crate_dir, init=False, gen_preview=False)
     crate.write_zip(dst)
 
 


Test patch:
diff --git a/test/test_cli.py b/test/test_cli.py
index 31b701d..bcfe14f 100644
--- a/test/test_cli.py
+++ b/test/test_cli.py
@@ -21,6 +21,7 @@ from click.testing import CliRunner
 
 from rocrate.cli import cli
 from rocrate.model.metadata import TESTING_EXTRA_TERMS
+from rocrate.rocrate import ROCrate
 
 
 @pytest.mark.parametrize("gen_preview,cwd", [(False, False), (False, True), (True, False), (True, True)])
@@ -137,6 +138,9 @@ def test_cli_write_zip(test_data_dir, monkeypatch, cwd):
     crate_dir = test_data_dir / "ro-crate-galaxy-sortchangecase"
     runner = CliRunner()
     assert runner.invoke(cli, ["-c", str(crate_dir), "init"]).exit_code == 0
+    wf_path = crate_dir / "sort-and-change-case.ga"
+    args = ["-c", str(crate_dir), "add", "workflow", str(wf_path)]
+    assert runner.invoke(cli, args).exit_code == 0
 
     output_zip_path = test_data_dir / "test-zip-archive.zip"
     args = []
@@ -150,3 +154,6 @@ def test_cli_write_zip(test_data_dir, monkeypatch, cwd):
     result = runner.invoke(cli, args)
     assert result.exit_code == 0
     assert output_zip_path.is_file()
+
+    crate = ROCrate(output_zip_path)
+    assert crate.mainEntity is not None
Repository: ResearchObject/ro-crate-py

Issue:
Allow root dataset to have more than one type
Fails if root dataset has an array as a value for @type 

eg. @type = ['Dataset', 'RepositoryCollection']

Note: @ptsefton says: Can we always code defensively, please.


https://github.com/ResearchObject/ro-crate-py/blob/9c010654dce2b8cd9ca4e99dd73885439885e4fc/rocrate/rocrate.py#L154

Comments:

Patch:
diff --git a/rocrate/rocrate.py b/rocrate/rocrate.py
index 8ed15de..c0fa0b8 100644
--- a/rocrate/rocrate.py
+++ b/rocrate/rocrate.py
@@ -151,8 +151,8 @@ class ROCrate():
             root = entities[metadata["about"]["@id"]]
         except (KeyError, TypeError):
             raise ValueError("metadata descriptor does not reference the root entity")
-        if root["@type"] != "Dataset":
-            raise ValueError('root entity must be of type "Dataset"')
+        if ("Dataset" not in root["@type"] if isinstance(root["@type"], list) else root["@type"] != "Dataset"):
+            raise ValueError('root entity must have "Dataset" among its types')
         return metadata["@id"], root["@id"]
 
     def find_root_entity_id(self, entities):


Test patch:
diff --git a/test/test_read.py b/test/test_read.py
index e3f2820..6497a76 100644
--- a/test/test_read.py
+++ b/test/test_read.py
@@ -530,7 +530,7 @@ def test_find_root_bad_entities():
     # root type is not Dataset
     entities = deepcopy(orig_entities)
     entities["./"]["@type"] = "Thing"
-    with pytest.raises(ValueError, match="must be of type"):
+    with pytest.raises(ValueError, match="must have"):
         crate.find_root_entity_id(entities)
 
 
@@ -599,3 +599,30 @@ def test_find_root_multiple_entries():
     mod_entities = deepcopy(orig_entities)
     mod_entities["http://example.com/"]["@type"] = "Thing"
     check_finds_org(mod_entities)
+
+
+def test_find_root_multiple_types():
+    entities = {_["@id"]: _ for _ in [
+        {
+            "@id": "ro-crate-metadata.json",
+            "@type": "CreativeWork",
+            "about": {"@id": "./"},
+            "conformsTo": {"@id": "https://w3id.org/ro/crate/1.1"},
+        },
+        {
+            "@id": "./",
+            "@type": ["Dataset", "RepositoryCollection"],
+        },
+    ]}
+    crate = ROCrate()
+    m_id, r_id = crate.find_root_entity_id(entities)
+    assert m_id == "ro-crate-metadata.json"
+    assert r_id == "./"
+    # "Dataset" not included
+    del entities["./"]["@type"][0]
+    with pytest.raises(ValueError):
+        crate.find_root_entity_id(entities)
+    # Check we're not trying to be too clever
+    entities["./"]["@type"] = "NotADataset"
+    with pytest.raises(ValueError):
+        crate.find_root_entity_id(entities)
Repository: ResearchObject/ro-crate-py

Issue:
Remove software version defaults
E.g., [`PLANEMO_DEFAULT_VERSION = "0.74"`](https://github.com/ResearchObject/ro-crate-py/blob/81d74b3c40241235229500d30b4747a392c65a0b/rocrate/model/softwareapplication.py#L57). No info is better than arbitrary (thus likely wrong) info.

Comments:

Patch:
diff --git a/rocrate/model/softwareapplication.py b/rocrate/model/softwareapplication.py
index c7bc8a5..29d8b2b 100644
--- a/rocrate/model/softwareapplication.py
+++ b/rocrate/model/softwareapplication.py
@@ -54,7 +54,6 @@ class SoftwareApplication(ContextEntity, CreativeWork):
 
 
 PLANEMO_ID = "https://w3id.org/ro/terms/test#PlanemoEngine"
-PLANEMO_DEFAULT_VERSION = "0.74"
 
 
 def planemo(crate):
diff --git a/rocrate/rocrate.py b/rocrate/rocrate.py
index 76f9105..02c57f8 100644
--- a/rocrate/rocrate.py
+++ b/rocrate/rocrate.py
@@ -42,7 +42,7 @@ from .model.computationalworkflow import ComputationalWorkflow, WorkflowDescript
 from .model.computerlanguage import ComputerLanguage, get_lang
 from .model.testinstance import TestInstance
 from .model.testservice import TestService, get_service
-from .model.softwareapplication import SoftwareApplication, get_app, PLANEMO_DEFAULT_VERSION
+from .model.softwareapplication import SoftwareApplication, get_app
 from .model.testsuite import TestSuite
 
 from .utils import is_url, subclasses, get_norm_value, walk
@@ -509,9 +509,6 @@ class ROCrate():
             self, suite, source=None, dest_path=None, fetch_remote=False, validate_url=False, properties=None,
             engine="planemo", engine_version=None
     ):
-        if engine_version is None:
-            # FIXME: this should be engine-specific
-            engine_version = PLANEMO_DEFAULT_VERSION
         suite = self.__validate_suite(suite)
         definition = self.add(
             TestDefinition(self, source=source, dest_path=dest_path, fetch_remote=fetch_remote,
@@ -523,7 +520,8 @@ class ROCrate():
             engine = get_app(self, engine)
             self.add(engine)
         definition.engine = engine
-        definition.engineVersion = engine_version
+        if engine_version is not None:
+            definition.engineVersion = engine_version
         suite.definition = definition
         self.metadata.extra_terms.update(TESTING_EXTRA_TERMS)
         return definition


Test patch:
diff --git a/test/test_test_metadata.py b/test/test_test_metadata.py
index 219baa4..6ba584d 100644
--- a/test/test_test_metadata.py
+++ b/test/test_test_metadata.py
@@ -243,6 +243,8 @@ def test_add_test_definition(test_data_dir, engine, engine_version):
     assert crate.dereference(PLANEMO) is d.engine
     if engine_version:
         assert d.engineVersion == engine_version
+    else:
+        assert "engineVersion" not in d
 
 
 def test_test_suites_prop(test_data_dir):
Repository: ResearchObject/ro-crate-py

Issue:
hasPart property on root dataset is assumed to be an array
Loading this dataset causes an error as the hasPart value is not an array but the code assumes it will be :

```json
{
    "@id": "./",
    "@type": "Dataset",
    "hasPart":  {"@id": "spam/foo.txt"} 
},
{
    "@id": "spam/foo.txt",
    "@type": "File"
}
```

This line is the culprit: https://github.com/ResearchObject/ro-crate-py/blob/dc3f75b0d0390dedbf9a830b6325b76dba468f1f/rocrate/rocrate.py#L145C1-L146C1

 parts = root_entity.pop('hasPart', [])

I added this to my copy  make it work but not sure if you have a general purpose way of doing this in this library:

        if not isinstance(parts, list):
            parts = [parts]



Comments:

Patch:
diff --git a/rocrate/rocrate.py b/rocrate/rocrate.py
index 57bcd39..5d5a98d 100644
--- a/rocrate/rocrate.py
+++ b/rocrate/rocrate.py
@@ -142,7 +142,7 @@ class ROCrate():
 
         root_entity = entities.pop(root_id)
         assert root_id == root_entity.pop('@id')
-        parts = root_entity.pop('hasPart', [])
+        parts = as_list(root_entity.pop('hasPart', []))
         self.add(RootDataset(self, root_id, properties=root_entity))
         preview_entity = entities.pop(Preview.BASENAME, None)
         if preview_entity and not gen_preview:
@@ -167,7 +167,7 @@ class ROCrate():
                 else:
                     instance = cls(self, source / id_, id_, properties=entity)
             self.add(instance)
-            self.__add_parts(entity.get("hasPart", []), entities, source)
+            self.__add_parts(as_list(entity.get("hasPart", [])), entities, source)
 
     def __read_contextual_entities(self, entities):
         type_map = {_.__name__: _ for _ in subclasses(ContextEntity)}


Test patch:
diff --git a/test/test_read.py b/test/test_read.py
index 9aa3e96..43bf328 100644
--- a/test/test_read.py
+++ b/test/test_read.py
@@ -499,17 +499,17 @@ def test_indirect_data_entity(tmpdir):
             {
                 "@id": "./",
                 "@type": "Dataset",
-                "hasPart": [{"@id": "d1"}]
+                "hasPart": {"@id": "d1"}
             },
             {
                 "@id": "d1",
                 "@type": "Dataset",
-                "hasPart": [{"@id": "d1/d2"}]
+                "hasPart": {"@id": "d1/d2"}
             },
             {
                 "@id": "d1/d2",
                 "@type": "Dataset",
-                "hasPart": [{"@id": "d1/d2/f1"}]
+                "hasPart": {"@id": "d1/d2/f1"}
             },
             {
                 "@id": "d1/d2/f1",
Repository: ResearchObject/ro-crate-py

Issue:
BUG: If Root Data Entity has an arcp:// ID the arcp:// part gets stripped off it
Here's some code to reproduce the error:

```python
from rocrate.rocrate import ROCrate
import json
import os
test_crate = {
  "@context": [
    "https://w3id.org/ro/crate/1.1/context",
    {
      "@vocab": "http://schema.org/"
    }
  ],
  "@graph": [
    {
      "@id": "ro-crate-metadata.json",
      "@type": "CreativeWork",
      "conformsTo": [
        {
          "@id": "https://w3id.org/ro/crate/1.2"
        }
      ],
      "about": {
        "@id": "arcp://name,corpus-of-oz-early-english"
      }
     
      
    },
    {"@id": "arcp://name,corpus-of-oz-early-english", "@type": "Dataset"}
  ]}
os.makedirs("test_crate", exist_ok=True)
f = open("test_crate/ro-crate-metadata.json","w")
f.write(json.dumps(test_crate, indent=2))
f.close()
crate = ROCrate("test_crate")
print("ID of root dataset", crate.root_dataset.id) # name,corpus-of-oz-early-english 

print("ID of original", test_crate["@graph"][1]["@id"]) # arcp://name,corpus-of-oz-early-english
print("same?", crate.root_dataset.id == test_crate["@graph"][1]["@id"])

```

Comments:

Patch:
diff --git a/rocrate/utils.py b/rocrate/utils.py
index 18aa6f2..434fb41 100644
--- a/rocrate/utils.py
+++ b/rocrate/utils.py
@@ -33,7 +33,7 @@ def is_url(string):
     parts = urlsplit(string)
     if os.name == "nt" and len(parts.scheme) == 1:
         return False
-    return all((parts.scheme, parts.path))
+    return bool(parts.scheme)
 
 
 def iso_now():


Test patch:
diff --git a/test/test_utils.py b/test/test_utils.py
index 7b0607b..20a8504 100644
--- a/test/test_utils.py
+++ b/test/test_utils.py
@@ -18,7 +18,7 @@
 
 import pytest
 
-from rocrate.utils import subclasses, get_norm_value
+from rocrate.utils import subclasses, get_norm_value, is_url
 
 
 class Pet:
@@ -53,3 +53,12 @@ def test_get_norm_value():
     assert get_norm_value({"@id": "#xyz"}, "name") == []
     with pytest.raises(ValueError):
         get_norm_value({"@id": "#xyz", "name": [["foo"]]}, "name")
+
+
+def test_is_url():
+    assert is_url("http://example.com/index.html")
+    assert is_url("http://example.com/")
+    assert is_url("http://example.com")
+    assert not is_url("/etc/")
+    assert not is_url("/etc")
+    assert not is_url("/")
Repository: ResearchObject/ro-crate-py

Issue:
Entity id should not be modifiable
It's used to index the entity in the crate's `__entity_map`, so changing it leads to inconsistencies:

```pycon
>>> from rocrate.rocrate import ROCrate
>>> crate = ROCrate()
>>> d = crate.add_dataset("FOO")
>>> crate._ROCrate__entity_map
{..., 'arcp://uuid,2f145cc1-20be-4cd7-ac86-d6d4a08cdcf9/FOO': <FOO/ Dataset>}
>>> d.id = "foo"
>>> crate._ROCrate__entity_map
{..., 'arcp://uuid,2f145cc1-20be-4cd7-ac86-d6d4a08cdcf9/FOO': <foo Dataset>}
>>> crate.dereference("foo")
>>> crate.dereference("FOO")
<foo Dataset>

```

Comments:

Patch:
diff --git a/rocrate/model/entity.py b/rocrate/model/entity.py
index 41bf09b..fa26218 100644
--- a/rocrate/model/entity.py
+++ b/rocrate/model/entity.py
@@ -30,9 +30,9 @@ class Entity(MutableMapping):
     def __init__(self, crate, identifier=None, properties=None):
         self.crate = crate
         if identifier:
-            self.id = self.format_id(identifier)
+            self.__id = self.format_id(identifier)
         else:
-            self.id = f"#{uuid.uuid4()}"
+            self.__id = f"#{uuid.uuid4()}"
         if properties:
             empty = self._empty()
             empty.update(properties)
@@ -40,6 +40,10 @@ class Entity(MutableMapping):
         else:
             self._jsonld = self._empty()
 
+    @property
+    def id(self):
+        return self.__id
+
     # Format the given ID with rules appropriate for this type.
     # For example, Dataset (directory) data entities SHOULD end with /
     def format_id(self, identifier):


Test patch:
diff --git a/test/test_model.py b/test/test_model.py
index e7b5343..9f00a48 100644
--- a/test/test_model.py
+++ b/test/test_model.py
@@ -505,3 +505,10 @@ def test_add_no_duplicates(test_data_dir, tmpdir):
     assert ret["name"] == "Jim"
     assert ret in crate.get_entities()
     assert crate.contextual_entities == [jim]
+
+
+def test_immutable_id():
+    crate = ROCrate()
+    p = crate.add(Person(crate, "#foo"))
+    with pytest.raises(AttributeError):
+        p.id = "#bar"
Repository: ResearchObject/ro-crate-py

Issue:
Add contentUrl property to files when it's known
E.g., when a remote file is added with `fetch_remote=True`. See https://github.com/ResearchObject/ro-crate/pull/189#issuecomment-1227697348.

Comments:

Patch:
diff --git a/rocrate/model/file.py b/rocrate/model/file.py
index 9251aeb..a5d1c43 100644
--- a/rocrate/model/file.py
+++ b/rocrate/model/file.py
@@ -59,6 +59,7 @@ class File(FileOrDir):
                     if self.fetch_remote:
                         out_file_path.parent.mkdir(parents=True, exist_ok=True)
                         urllib.request.urlretrieve(response.url, out_file_path)
+                        self._jsonld['contentUrl'] = str(self.source)
         elif self.source is None:
             # Allows to record a File entity whose @id does not exist, see #73
             warnings.warn(f"No source for {self.id}")


Test patch:
diff --git a/test/test_write.py b/test/test_write.py
index 10f0ec3..6fb57b6 100644
--- a/test/test_write.py
+++ b/test/test_write.py
@@ -150,6 +150,7 @@ def test_remote_uri(tmpdir, helpers, fetch_remote, validate_url, to_zip):
     if fetch_remote:
         out_file = out_crate.dereference(file_.id)
         assert (out_path / relpath).is_file()
+        assert out_file["contentUrl"] == url
     else:
         out_file = out_crate.dereference(url)
         assert not (out_path / relpath).exists()
Repository: ResearchObject/ro-crate-py

Issue:
write_zip should not include the new archive in itself
If one calls `write_zip` with an output path that is within the RO-crate directory, the new zip file ends up inside itself.  As `write_zip` walks the directory tree it'll find the partially written zip file and copy it into the archive.

Comments:

Patch:
diff --git a/rocrate/rocrate.py b/rocrate/rocrate.py
index 5c3969d..a968f10 100644
--- a/rocrate/rocrate.py
+++ b/rocrate/rocrate.py
@@ -510,6 +510,8 @@ class ROCrate():
                 root = Path(root)
                 for name in files:
                     source = root / name
+                    if source.samefile(out_file_path):
+                        continue
                     dest = source.relative_to(top)
                     if not self.dereference(str(dest)):
                         zf.write(str(source), str(dest))


Test patch:
diff --git a/test/test_write.py b/test/test_write.py
index 63fdc85..c83797c 100644
--- a/test/test_write.py
+++ b/test/test_write.py
@@ -215,3 +215,17 @@ def test_no_parts(tmpdir, helpers):
     json_entities = helpers.read_json_entities(out_path)
     helpers.check_crate(json_entities)
     assert "hasPart" not in json_entities["./"]
+
+
+def test_no_zip_in_zip(test_data_dir, tmpdir):
+    crate_dir = test_data_dir / 'ro-crate-galaxy-sortchangecase'
+    crate = ROCrate(crate_dir)
+
+    zip_name = 'ro_crate_out.crate.zip'
+    zip_path = crate_dir / zip_name  # within the crate dir
+    crate.write_zip(zip_path)
+    out_path = tmpdir / 'ro_crate_out'
+    with zipfile.ZipFile(zip_path, "r") as zf:
+        zf.extractall(out_path)
+
+    assert not (out_path / zip_name).exists()