diff --git a/cloudpathlib/cloudpath.py b/cloudpathlib/cloudpath.py index 3838ddc4..98c6d55d 100644 --- a/cloudpathlib/cloudpath.py +++ b/cloudpathlib/cloudpath.py @@ -785,6 +785,7 @@ def copytree( ], collections.abc.Iterable, ] = None, + dirs_exist_ok: bool = False, ) -> Union[Path, "CloudPath"]: """Copy self to a directory, if self is a directory.""" if not self.is_dir(): @@ -798,7 +799,11 @@ def copytree( if destination.exists() and destination.is_file(): raise CloudPathFileExistsError( - "Destination path {destination} of copytree must be a directory." + f"Destination path {destination} of copytree must be a directory." + ) + if destination.exists() and not dirs_exist_ok: + raise CloudPathFileExistsError( + f"Destination directory {destination} already exists and dirs_exist_ok is False." ) contents = list(self.iterdir()) @@ -808,7 +813,7 @@ def copytree( else: ignored_names = set() - destination.mkdir(parents=True, exist_ok=True) + destination.mkdir(parents=True, exist_ok=dirs_exist_ok) for subpath in contents: if subpath.name in ignored_names: @@ -821,6 +826,7 @@ def copytree( subpath.copytree( destination / subpath.name, force_overwrite_to_cloud=force_overwrite_to_cloud, + dirs_exist_ok=dirs_exist_ok, ignore=ignore, ) diff --git a/tests/test_cloudpath_upload_copy.py b/tests/test_cloudpath_upload_copy.py index 16c12b49..e0558571 100644 --- a/tests/test_cloudpath_upload_copy.py +++ b/tests/test_cloudpath_upload_copy.py @@ -214,14 +214,18 @@ def test_copytree(rig, tmpdir): # cloud dir to local dir that exists p = rig.create_cloud_path("dir_1") local_out = Path(tmpdir.mkdir("copytree_from_cloud")) - p.copytree(local_out) + p.copytree(local_out, dirs_exist_ok=True) assert assert_mirrored(p, local_out) # str version of path local_out = Path(tmpdir.mkdir("copytree_to_str_path")) - p.copytree(str(local_out)) + p.copytree(str(local_out), dirs_exist_ok=True) assert assert_mirrored(p, local_out) + # test dirs_exist_ok + with pytest.raises(CloudPathFileExistsError): + p.copytree(str(local_out), dirs_exist_ok=False) + # cloud dir to local dir that does not exist local_out = local_out / "new_folder" p.copytree(local_out) @@ -235,14 +239,18 @@ def test_copytree(rig, tmpdir): # cloud dir to cloud dir that exists p2 = rig.create_cloud_path("new_dir2") (p2 / "existing_file.txt").write_text("asdf") # ensures p2 exists - p.copytree(p2) + p.copytree(p2, dirs_exist_ok=True) assert assert_mirrored(p2, p, check_no_extra=False) + # test dirs_exist_ok + with pytest.raises(CloudPathFileExistsError): + p.copytree(p2, dirs_exist_ok=False) + (p / "new_file.txt").write_text("hello!") # add file so we can assert mirror with pytest.raises(OverwriteNewerCloudError): - p.copytree(p2) + p.copytree(p2, dirs_exist_ok=True) - p.copytree(p2, force_overwrite_to_cloud=True) + p.copytree(p2, force_overwrite_to_cloud=True, dirs_exist_ok=True) assert assert_mirrored(p2, p, check_no_extra=False) # additional files that will be ignored using the ignore argument