Curso de Python / Automatizar copias de archivos con vista previa y verificación

Una automatización de archivos debe separar la vista previa de los cambios de su aplicación. Este ejercicio copia archivos de texto a una carpeta nueva, rechaza destinos existentes y comprueba que sus bytes coinciden, conservando los originales.

Preparar una carpeta de prácticas y definir el alcance

Trabaja en una carpeta vacía que puedas dedicar al ejercicio. El programa toma archivos *.txt de copy-demo/in y los copia a copy-demo/out. No recorre subcarpetas y excluye entradas que sean enlaces simbólicos. No realiza movimientos ni eliminaciones.

Guarda este primer código como setup_copy.py y ejecuta python setup_copy.py una sola vez. Es código Python, no una orden para pegar directamente en el intérprete de comandos.

from pathlib import Path
source = Path("copy-demo/in")
source.mkdir(parents=True, exist_ok=True)
with (source / "note.txt").open("x", encoding="utf-8") as f:
    f.write("demo\n")
print("sample ready")

Al mostrar sample ready habrá creado el archivo de muestra. El modo x rechaza un archivo existente: si repites la preparación, inspecciona el resultado anterior en lugar de sobrescribirlo. Esta escritura pertenece a la preparación, no a la vista previa de la herramienta.

Revisar las copias sin aplicarlas

Guarda el siguiente programa completo como safe_copy.py, junto al archivo de preparación. Ejecútalo sin argumentos mediante python safe_copy.py.

import argparse
import hashlib
import shutil
from pathlib import Path
parser = argparse.ArgumentParser()
parser.add_argument("--apply", action="store_true")
args = parser.parse_args()
source, target = Path("copy-demo/in"), Path("copy-demo/out")
if not source.is_dir():
    parser.error("prepare copy-demo/in first")
files = sorted(p for p in source.glob("*.txt") if p.is_file() and not p.is_symlink())
for src in files:
    dst = target / src.name
    if dst.exists() or dst.is_symlink():
        raise FileExistsError(dst)
    print("copy", src.name)
    if args.apply:
        target.mkdir(parents=True, exist_ok=True)
        with src.open("rb") as incoming, dst.open("xb") as outgoing:
            shutil.copyfileobj(incoming, outgoing)
        if hashlib.sha256(src.read_bytes()).digest() != hashlib.sha256(dst.read_bytes()).digest():
            raise RuntimeError("copy verification failed")
print("applied" if args.apply else "dry-run")
copy note.txt
dry-run

La ejecución predeterminada muestra el archivo seleccionado y termina en dry-run. No crea la carpeta de salida. La comprobación de destinos detecta colisiones antes de copiar cada archivo, y la apertura exclusiva protege además frente a un destino que aparezca entre la comprobación y la apertura.

Aplicar y comprobar el resultado

Después de revisar los nombres y el alcance, ejecuta:

python safe_copy.py --apply

La primera aplicación muestra copy note.txt y applied. Comprueba que existe copy-demo/out/note.txt y que el original sigue en la entrada. El resumen SHA-256 compara el contenido de ambos archivos; la referencia de shutil describe las operaciones de copia utilizadas.

Una segunda aplicación debe producir FileExistsError y conservar el resultado anterior. Si una copia falla a mitad de camino, puede quedar un destino parcial. Inspecciona su ruta resuelta y su contenido antes de decidir si corresponde retirarlo y repetir la operación. La gestión de excepciones debe conservar esta diferencia entre éxito y fallo.

Reconocer los límites antes de ampliar la herramienta

El cálculo del resumen lee cada archivo pequeño completo en memoria. Para archivos grandes conviene procesar bloques. La igualdad de bytes no verifica permisos, todos los metadatos ni todos los comportamientos de enlaces simbólicos. Tampoco protege contra un usuario no confiable que reemplace directorios padre durante la ejecución.

Antes de añadir borrados o cambios de nombre, diseña inventarios, copias de seguridad y recuperación. Para limpiar el ejercicio, verifica primero que los destinos absolutos resueltos permanecen dentro de la carpeta desechable copy-demo. Las reglas de pathlib y directorio de trabajo ayudan a mantener ese alcance explícito.