Для рекурсивного копирования директорий в Python с использованием модуля shutil, применяется функция shutil.copytree().
Функция copytree() принимает как минимум два аргумента:
src: Путь к исходной директории, которую необходимо скопировать.dst: Путь к целевой директории, куда будет скопирована исходная директория. Если целевая директория не существует, она будет создана. Если существует, и она пустая, файлы из исходной будут скопированы в нее. Если в целевой директории есть другие файлы/папки, копирование завершится с ошибкой (по умолчанию).Пример использования:
import shutil
import os
source_dir = '/путь/к/исходной/директории'
destination_dir = '/путь/к/целевой/директории'
try:
    shutil.copytree(source_dir, destination_dir)
    print(f"Директория '{source_dir}' успешно скопирована в '{destination_dir}'")
except FileExistsError:
    print(f"Ошибка: Директория '{destination_dir}' уже существует.")
except shutil.Error as errors:
    print(f"Ошибка при копировании:")
    for error in errors.errors:
        src, dst, msg = error
        print(f"  Не удалось скопировать '{src}' в '{dst}': {msg}")
except Exception as e:
    print(f"Произошла непредвиденная ошибка: {e}")
Важные замечания:
copytree() не будет перезаписывать существующие файлы или директории в целевой директории.  Если целевая директория существует и не пуста, возникнет исключение FileExistsError.ignore, который принимает функцию, возвращающую список игнорируемых элементов.  Это позволяет гибко настраивать процесс копирования.  Аргумент dirs_exist_ok=True, доступный начиная с Python 3.8, позволяет избежать ошибки FileExistsError при существовании целевой директории, объединяя содержимое исходной с целевой.copytree() выбрасывает исключение shutil.Error, которое содержит список кортежей с информацией об ошибках. Необходимо предусмотреть обработку этого исключения для обеспечения стабильной работы программы.copytree() пытается скопировать метаданные файлов (например, права доступа, время последнего изменения).  Это поведение можно контролировать с помощью других аргументов функции.Пример с обработкой ошибок и игнорированием:
import shutil
import os
def ignore_patterns(patterns):
    def _ignore_patterns(path, names):
        return set(shutil.ignore_patterns(*patterns)(path, names))
    return _ignore_patterns
source_dir = '/путь/к/исходной/директории'
destination_dir = '/путь/к/целевой/директории'
try:
    shutil.copytree(source_dir, destination_dir, ignore=ignore_patterns('*.pyc', 'tmp*'), dirs_exist_ok=True)
    print(f"Директория '{source_dir}' успешно скопирована в '{destination_dir}'")
except shutil.Error as errors:
    print(f"Ошибка при копировании:")
    for error in errors.errors:
        src, dst, msg = error
        print(f"  Не удалось скопировать '{src}' в '{dst}': {msg}")
except Exception as e:
    print(f"Произошла непредвиденная ошибка: {e}")
В этом примере игнорируются все файлы с расширением .pyc и все файлы/директории, начинающиеся с tmp. Также, используется dirs_exist_ok=True, чтобы избежать FileExistsError, если целевая директория уже существует.