This is a contributed re-implementation of 'copytree' that should work with the exact same behavior on multiple platforms. When `metadata` is False, file metadata such as permissions and modification times are not copied.
(
src: Path,
dst: Path,
metadata: bool = True,
symlinks: bool = False,
ignore: Optional[Callable[[Any, list[str]], set[str]]] = None,
)
| 72 | |
| 73 | |
| 74 | def copytree( |
| 75 | src: Path, |
| 76 | dst: Path, |
| 77 | metadata: bool = True, |
| 78 | symlinks: bool = False, |
| 79 | ignore: Optional[Callable[[Any, list[str]], set[str]]] = None, |
| 80 | ) -> None: |
| 81 | """ |
| 82 | This is a contributed re-implementation of 'copytree' that |
| 83 | should work with the exact same behavior on multiple platforms. |
| 84 | |
| 85 | When `metadata` is False, file metadata such as permissions and modification |
| 86 | times are not copied. |
| 87 | """ |
| 88 | |
| 89 | def copy_file(src_path: Path, dst_path: Path, item: str) -> None: |
| 90 | s = src_path / item |
| 91 | d = dst_path / item |
| 92 | |
| 93 | if symlinks and s.is_symlink(): # pragma: no cover |
| 94 | if d.exists(): |
| 95 | d.unlink() |
| 96 | d.symlink_to(s.readlink()) |
| 97 | if metadata: |
| 98 | st = s.lstat() |
| 99 | mode = stat.S_IMODE(st.st_mode) |
| 100 | try: |
| 101 | os.chmod(str(d), mode) |
| 102 | except Exception: |
| 103 | LOG.warning(f"Unable to perform chmod on: {d}") |
| 104 | elif s.is_dir(): |
| 105 | copytree(s, d, metadata, symlinks, ignore) |
| 106 | else: |
| 107 | shutil.copy2(s, d) if metadata else shutil.copy(s, d) |
| 108 | |
| 109 | src_path = src |
| 110 | dst_path = dst |
| 111 | |
| 112 | try: |
| 113 | lst = [p.name for p in src_path.iterdir()] |
| 114 | if not dst_path.exists(): |
| 115 | dst_path.mkdir(parents=True, exist_ok=True) |
| 116 | if metadata: |
| 117 | shutil.copystat(src_path, dst_path) |
| 118 | except NotADirectoryError: # egg-link files |
| 119 | copy_file(src_path.parent, dst_path.parent, src_path.name) |
| 120 | return |
| 121 | |
| 122 | if ignore: |
| 123 | excl = ignore(src_path, lst) |
| 124 | lst = [x for x in lst if x not in excl] |
| 125 | |
| 126 | for item in lst: |
| 127 | copy_file(src_path, dst_path, item) |
| 128 | |
| 129 | |
| 130 | def parse_s3_url(url: Optional[str]) -> Tuple[str, str]: |
no test coverage detected