Repository: ResearchObject/ro-crate-py
Pull request #44 (2021-02-09 16:34:11)

Fix file write
Fixes file writing with multiple output dir levels.


Patch:
diff --git a/rocrate/model/file.py b/rocrate/model/file.py
index 36c853c..f430480 100644
--- a/rocrate/model/file.py
+++ b/rocrate/model/file.py
@@ -96,18 +96,16 @@ class File(DataEntity):
         return val
 
     def write(self, base_path):
-        out_file_path = os.path.join(base_path, self.id)
-        out_dir = pathlib.Path(os.path.dirname(out_file_path))
+        base_path = pathlib.Path(base_path)
+        out_file_path = base_path / self.id
         # check if its local or remote URI
         if isinstance(self.source, IOBase):
-            if not out_dir.exists():
-                os.mkdir(out_dir)
+            out_file_path.parent.mkdir(parents=True, exist_ok=True)
             with open(out_file_path, 'w') as out_file:
                 out_file.write(self.source.getvalue())
         else:
             if os.path.isfile(self.source):
-                if not out_dir.exists():
-                    os.mkdir(out_dir)
+                out_file_path.parent.mkdir(parents=True, exist_ok=True)
                 copy(self.source, out_file_path)
             else:
                 if self.fetch_remote:
@@ -119,12 +117,9 @@ class File(DataEntity):
                     #   self._jsonld['contentSize']
                     # this would help check if the dataset to be retrieved is
                     # in fact what was registered in the first place.
-                    try:
-                        with urllib.request.urlopen(self.source) as response, \
-                             open(out_file_path, 'wb') as out_file:
-                            shutil.copyfileobj(response, out_file)
-                    except Exception:  # requests.ConnectionError as exception:
-                        print("URI does not exists or can't be accessed")
+                    out_file_path.parent.mkdir(parents=True, exist_ok=True)
+                    with urllib.request.urlopen(self.source) as response, open(out_file_path, 'wb') as out_file:
+                        shutil.copyfileobj(response, out_file)
 
     def write_zip(self, zip_out):
         zip_out.write(self.source, self.id)


Test patch:
diff --git a/test/test_write.py b/test/test_write.py
index ec4e672..fcc6579 100644
--- a/test/test_write.py
+++ b/test/test_write.py
@@ -17,7 +17,9 @@
 
 import io
 import pytest
+import sys
 import zipfile
+from urllib.error import URLError
 
 from rocrate.model.dataset import Dataset
 from rocrate.model.person import Person
@@ -36,7 +38,7 @@ def test_file_writing(test_data_dir, tmpdir, helpers, to_zip):
     crate.creator = new_person
 
     sample_file_id = 'sample_file.txt'
-    sample_file2_id = 'subdir/sample_file2.csv'
+    sample_file2_id = 'a/b/sample_file2.csv'
     test_dir_id = 'test_add_dir/'
     data_entity_ids = [sample_file_id, sample_file2_id, test_dir_id]
     file_subdir_id = 'sample_file_subdir.txt'
@@ -96,7 +98,7 @@ def test_file_writing(test_data_dir, tmpdir, helpers, to_zip):
 def test_file_stringio(tmpdir, helpers):
     crate = ROCrate()
 
-    test_file_id = 'test_file.txt'
+    test_file_id = 'a/b/test_file.txt'
     file_content = 'This will be the content of the file'
     file_stringio = io.StringIO(file_content)
     file_returned = crate.add_file(file_stringio, test_file_id)
@@ -119,10 +121,11 @@ def test_remote_uri(tmpdir, helpers, fetch_remote):
     crate = ROCrate()
     url = ('https://raw.githubusercontent.com/ResearchObject/ro-crate-py/'
            'master/test/test-data/sample_file.txt')
-    file_returned = crate.add_file(source=url, fetch_remote=fetch_remote)
     if fetch_remote:
-        assert file_returned.id == 'sample_file.txt'
+        file_returned = crate.add_file(source=url, dest_path="a/b/sample_file.txt", fetch_remote=fetch_remote)
+        assert file_returned.id == 'a/b/sample_file.txt'
     else:
+        file_returned = crate.add_file(source=url, fetch_remote=fetch_remote)
         assert file_returned.id == url
 
     out_path = tmpdir / 'ro_crate_out'
@@ -131,6 +134,28 @@ def test_remote_uri(tmpdir, helpers, fetch_remote):
 
     metadata_path = out_path / helpers.METADATA_FILE_NAME
     assert metadata_path.exists()
-    file1 = out_path / 'sample_file.txt'
+    file1 = out_path / 'a/b/sample_file.txt'
     if fetch_remote:
         assert file1.exists()
+
+
+@pytest.mark.skipif(sys.platform.startswith("win"), reason="dir mode has no effect on Windows")
+def test_remote_uri_exceptions(tmpdir, helpers):
+    crate = ROCrate()
+    url = ('https://raw.githubusercontent.com/ResearchObject/ro-crate-py/'
+           'master/test/test-data/_do_not_create_this_.foo')
+    crate.add_file(source=url, fetch_remote=True)
+    out_path = tmpdir / 'ro_crate_out_1'
+    out_path.mkdir()
+    with pytest.raises(URLError):
+        crate.write_crate(out_path)
+
+    crate = ROCrate()
+    url = ('https://raw.githubusercontent.com/ResearchObject/ro-crate-py/'
+           'master/test/test-data/sample_file.txt')
+    crate.add_file(source=url, dest_path="a/sample_file.txt", fetch_remote=True)
+    out_path = tmpdir / 'ro_crate_out_2'
+    out_path.mkdir()
+    (out_path / "a").mkdir(mode=0o444)
+    with pytest.raises(PermissionError):
+        crate.write_crate(out_path)