def move( sources: ListOfPathsOrStrs, destination: PathOrStr = None, excludes: ListOfPathsOrStrs = None, merge: Callable[[str, str, Path], str] = None, required: bool = False, ) -> bool: """ copy file(s) at source to current directory, preserving file mode. Args: sources (ListOfPathsOrStrs): Glob pattern(s) to copy destination (PathOrStr): Destination folder for copied files excludes (ListOfPathsOrStrs): Glob pattern(s) of files to skip merge (Callable[[str, str, Path], str]): Callback function for merging files if there is an existing file. required (bool): If required and no source files are copied, throws a MissingSourceError Returns: True if any files were copied, False otherwise. """ copied = False for source in _expand_paths(sources): if destination is None: canonical_destination = _tracked_paths.relativize(source) else: canonical_destination = Path(destination) if excludes: excludes = [ _tracked_paths.relativize(e) for e in _expand_paths(excludes, source) ] else: excludes = [] if source.is_dir(): copied = copied or _copy_dir_to_existing_dir( source, canonical_destination, excludes=excludes, merge=merge) elif source not in excludes: # copy individual file if merge is not None and canonical_destination.is_file(): _merge_file(source, canonical_destination, merge) else: shutil.copy2(source, canonical_destination) copied = True if not copied: if required: raise MissingSourceError( f"No files in sources {sources} were copied. Does the source " f"contain files?") else: logger.warning( f"No files in sources {sources} were copied. Does the source " f"contain files?") return copied
def _copy_dir_to_existing_dir( source: Path, destination: Path, excludes: ListOfPathsOrStrs = None, merge: Callable[[str, str, Path], str] = None, ) -> bool: """ copies files over existing files to an existing directory this function does not copy empty directories. Returns: True if any files were copied, False otherwise. """ copied = False if not excludes: excludes = [] for root, _, files in os.walk(source): for name in files: rel_path = str(Path(root).relative_to(source)) dest_dir = destination / rel_path dest_path = dest_dir / name exclude = [ e for e in excludes if ( Path(e) == _tracked_paths.relativize(root) or Path(e) == _tracked_paths.relativize(Path(root) / name) ) ] if not exclude: os.makedirs(str(dest_dir), exist_ok=True) source_path = Path(os.path.join(root, name)) if merge is not None and dest_path.is_file(): try: _merge_file(source_path, dest_path, merge) except Exception: logger.exception( "_merge_file failed for %s, fall back to copy", source_path, ) shutil.copy2(str(source_path), str(dest_path)) else: shutil.copy2(str(source_path), str(dest_path)) copied = True return copied
def test_deep_paths(): parent = FIXTURES / "parent" deep_path = FIXTURES / "parent" / "child" / "grandchild" deep_item = deep_path / "thing.txt" _tracked_paths.add(parent) _tracked_paths.add(deep_path) assert _tracked_paths.relativize(deep_item) == Path("thing.txt")
def move( sources: ListOfPathsOrStrs, destination: PathOrStr = None, excludes: ListOfPathsOrStrs = None, merge: Callable[[str, str, Path], str] = None, ) -> bool: """ copy file(s) at source to current directory. Returns: True if any files were copied, False otherwise. """ copied = False for source in _expand_paths(sources): if destination is None: canonical_destination = _tracked_paths.relativize(source) else: canonical_destination = Path(destination) if excludes: excludes = [ _tracked_paths.relativize(e) for e in _expand_paths(excludes, source) ] else: excludes = [] if source.is_dir(): copied = copied or _copy_dir_to_existing_dir( source, canonical_destination, excludes=excludes, merge=merge ) elif source not in excludes: # copy individual file if merge is not None and canonical_destination.is_file(): _merge_file(source, canonical_destination, merge) else: shutil.copy2(source, canonical_destination) copied = True if not copied: log.warning( f"No files in sources {sources} were copied. Does the source " f"contain files?" ) return copied