Copy

Copy files or entire folder structures from source to destination.

Copy


Processing

This function copies files or entire folder structures from a specified source to a destination directory. It handles single files, directories, or lists of paths and provides the list of copied, skipped, or errored items.

Inputs

source
The file(s) or folder(s) to copy. Can be a single path or a list of paths.
destination
The target directory where files or folders will be copied.

Inputs Types

Input Types
source Str, Path, List
destination Str, DirectoryPath

You can check the list of supported types here: Available Type Hints.

Outputs

copied items
A list of paths successfully copied to the destination.
skipped items
A list of paths that were not copied (e.g., due to overwrite settings).
errors
A list of error messages for paths that failed to copy.

Outputs Types

Output Types
copied items List
skipped items List
errors List

You can check the list of supported types here: Available Type Hints.

Options

The Copy brick contains some changeable options:

Overwrite
If enabled, overwrites existing files or folders in the destination. Defaults to False.
Verbose
If enabled, logs detailed information about the copy process, including errors and skipped items. Defaults to True.
import logging
import errno
import shutil
from coded_flows.types import Str, Path, DirectoryPath, Tuple, Union, List

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)


def _validate_source_input(src):
    """Validate that source input contains only strings and Path objects."""
    if isinstance(src, (str, Path)):
        return True
    elif isinstance(src, list):
        return all((isinstance(item, (str, Path)) for item in src))
    return False


def _copy_single_file(
    src_file, dst_file, result, overwrite, verbose, brick_display_name
):
    """Copy a single file and return True if successful."""
    try:
        if dst_file.exists() and (not overwrite):
            result["skipped_items"].append(str(src_file))
            verbose and logger.warning(
                f"[{brick_display_name}] '{src_file}' was skipped because it already exists."
            )
            return
        dst_file.parent.mkdir(parents=True, exist_ok=True)
        shutil.copy2(src_file, dst_file)
        result["copied_items"].append(str(dst_file))
        verbose and logger.info(
            f"[{brick_display_name}] The '{dst_file}' copy was created."
        )
    except OSError as e:
        if e.errno == errno.EROFS:
            verbose and logger.error(
                f"[{brick_display_name}]  Failed to copy file '{src_file}': File system is read-only."
            )
            result["errors"].append(f"Failed to copy file '{src_file}': {e}")
        else:
            verbose and logger.error(
                f"[{brick_display_name}]  Failed to copy file '{src_file}'."
            )
            result["errors"].append(f"Failed to copy file '{src_file}': {e}")
    except PermissionError as e:
        verbose and logger.error(
            f"[{brick_display_name}] Failed to copy file '{src_file}': Permission denied."
        )
        result["errors"].append(f"Failed to copy file '{src_file}': {e}")
    except Exception as e:
        verbose and logger.error(
            f"[{brick_display_name}] Failed to copy file '{src_file}'."
        )
        result["errors"].append(f"Failed to copy file '{src_file}': {e}")


def _copy_directory(src_dir, dst_dir, result, overwrite, verbose, brick_display_name):
    """Copy entire directory structure."""
    try:
        if dst_dir.exists() and (not overwrite):
            result["skipped_items"].append(str(src_dir))
            verbose and logger.warning(
                f"[{brick_display_name}] '{src_dir}' was skipped because it already exists."
            )
            return
        if dst_dir.exists() and overwrite:
            shutil.rmtree(dst_dir)
        shutil.copytree(src_dir, dst_dir)
        result["copied_items"].append(str(dst_dir))
        verbose and logger.info(
            f"[{brick_display_name}] The '{dst_dir}' copy was created."
        )
    except OSError as e:
        if e.errno == errno.EROFS:
            verbose and logger.error(
                f"[{brick_display_name}]  Failed to copy file '{src_dir}': File system is read-only."
            )
            result["errors"].append(f"Failed to copy file '{src_dir}': {e}")
        else:
            verbose and logger.error(
                f"[{brick_display_name}]  Failed to copy file '{src_dir}'."
            )
            result["errors"].append(f"Failed to copy file '{src_dir}': {e}")
    except PermissionError as e:
        verbose and logger.error(
            f"[{brick_display_name}] Failed to copy file '{src_dir}': Permission denied."
        )
        result["errors"].append(f"Failed to copy file '{src_dir}': {e}")
    except Exception as e:
        verbose and logger.error(
            f"[{brick_display_name}] Failed to copy file '{src_dir}'."
        )
        result["errors"].append(f"Failed to copy file '{src_dir}': {e}")


def copy_files(
    source: Union[Str, Path, List], destination: Union[Str, DirectoryPath], options=None
) -> Tuple[List, List, List]:
    brick_display_name = "Copy"
    options = options or {}
    verbose = options.get("verbose", True)
    overwrite = options.get("overwrite", True)
    result = {"copied_items": [], "skipped_items": [], "errors": []}
    if not _validate_source_input(source):
        verbose and logger.error(
            f"[{brick_display_name}] Invalid source input. Must be Str, Path, or list of Str/Path objects."
        )
        raise ValueError(
            "Invalid source input. Must be Str, Path, or list of Str/Path objects."
        )
    if isinstance(source, (str, Path)):
        source_paths = [Path(source)]
    else:
        source_paths = source
    destination_path = Path(destination)
    try:
        destination_path.mkdir(parents=True, exist_ok=True)
    except PermissionError as e:
        verbose and logger.error(
            f"[{brick_display_name}] Failed to create destination directory '{destination_path}': Permission denied."
        )
        raise PermissionError(
            f"Failed to create destination directory '{destination_path}': Permission denied."
        )
    except Exception as e:
        verbose and logger.error(
            f"[{brick_display_name}] Failed to create destination directory."
        )
        raise OSError(f"Failed to create destination directory: {e}")
    for source_item in source_paths:
        src_path = Path(source_item)
        if not src_path.exists():
            result["errors"].append(f"Source path does not exist: '{source_item}'.")
            verbose and logger.error(
                f"[{brick_display_name}] Source path does not exist: '{source_item}'."
            )
            continue
        dst_path = destination_path / src_path.name
        if src_path.is_file():
            _copy_single_file(
                src_path, dst_path, result, overwrite, verbose, brick_display_name
            )
        elif src_path.is_dir():
            _copy_directory(
                src_path, dst_path, result, overwrite, verbose, brick_display_name
            )
        else:
            verbose and logger.error(
                f"[{brick_display_name}] Unknown file type: '{source_item}'."
            )
            result["errors"].append(f"Unknown file type: '{source_item}'.")
    copied_items = result["copied_items"]
    skipped_items = result["skipped_items"]
    errors = result["errors"]
    return (copied_items, skipped_items, errors)

Brick Info

version v0.1.4
python 3.10, 3.11, 3.12, 3.13
requirements
    -