Path.copy() amplía el flujo orientado a objetos de pathlib para copiar archivos y directorios. En lugar de convertir rutas en cadenas y alternar entre varias APIs, una aplicación puede mantener navegación, validación y copia alrededor de objetos Path. Esto resulta útil en backups, sistemas de build, instaladores, migraciones, pruebas y automatizaciones.
Esta guía se centra en problemas reales: conflictos de destino, enlaces simbólicos, metadatos, permisos, fallos parciales, compatibilidad, rendimiento y verificación. Una llamada breve no elimina los riesgos del sistema de archivos; simplemente expresa mejor la intención.
Qué hace Path.copy
La ruta de origen es el objeto actual y el destino se entrega al método. Si el origen es un archivo, se replica su contenido. Si es un directorio, la operación puede copiar el árbol completo según las opciones disponibles y la versión de Python. El camino devuelto puede emplearse para registros y validaciones posteriores.
Antes de copiar, define el contrato: si se puede reemplazar un destino existente, si se siguen enlaces, qué metadatos deben conservarse y cómo se recuperará el sistema ante un fallo intermedio.
Ejemplo básico
from pathlib import Path
origen = Path("datos/informe.csv")
destino = Path("backup/informe.csv")
destino.parent.mkdir(parents=True, exist_ok=True)
resultado = origen.copy(destino)
print(resultado)
Crea explícitamente el directorio padre. Así el comportamiento es fácil de revisar. Conserva la ruta devuelta cuando debas comprobar tamaño, permisos o checksum.
Copia de directorios
Una copia recursiva normalmente no es una transacción atómica. Si un elemento falla, parte del árbol de destino puede quedar creado. Para flujos sensibles, copia primero a un directorio temporal, verifica el contenido y renómbralo al final.
from pathlib import Path
import tempfile
origen = Path("sitio")
destino = Path("dist/sitio")
with tempfile.TemporaryDirectory(dir=destino.parent) as temp:
preparado = Path(temp) / "sitio"
origen.copy(preparado)
if destino.exists():
raise FileExistsError(destino)
preparado.replace(destino)
Este patrón reduce la posibilidad de presentar como válido un directorio incompleto. Comprueba las reglas del sistema cuando el temporal y el destino están en volúmenes distintos.
Política de sobrescritura
No supongas que un destino existente será reemplazado. Lee la documentación de la versión desplegada y define una política explícita. Un backup debería generar nombres versionados; un build reproducible puede eliminar su propia salida anterior; los documentos del usuario no deben sobrescribirse silenciosamente.
def copiar_sin_reemplazar(origen: Path, destino: Path) -> Path:
if destino.exists():
raise FileExistsError(f"El destino existe: {destino}")
destino.parent.mkdir(parents=True, exist_ok=True)
return origen.copy(destino)
En entornos concurrentes existe una carrera entre comprobar y copiar. Usa directorios de preparación únicos, bloqueos o nombres aleatorios cuando una colisión sea importante.
Enlaces simbólicos
Los enlaces simbólicos exigen una decisión de seguridad. Seguir un enlace copia el objetivo; preservarlo copia la referencia. Con entradas no confiables, seguir enlaces puede salir del árbol esperado y exponer archivos sensibles. Resuelve rutas con cuidado y exige una raíz permitida.
raiz = Path("entrada").resolve()
origen = Path("entrada/item").resolve()
if origen != raiz and raiz not in origen.parents:
raise ValueError("El origen sale de la raíz permitida")
La comprobación ayuda, pero no elimina todas las condiciones de carrera. Un servicio privilegiado no debería procesar árboles mutables controlados por atacantes sin protecciones del sistema operativo.
Metadatos y permisos
Contenido, timestamps, bits de modo, propietario, ACL y atributos extendidos son aspectos diferentes. Una copia puede preservar algunos y perder otros, especialmente entre sistemas de archivos. Si la fidelidad es importante, prueba en cada plataforma y valida los atributos exactos.
Aplica privilegios mínimos en el destino. Los archivos de configuración y credenciales no deben quedar legibles por todos debido a permisos heredados. Ajusta el modo después de copiar y evita registrar datos confidenciales.
Manejo de errores
Captura excepciones específicas como FileNotFoundError, PermissionError, FileExistsError y OSError. Registra origen, destino y etapa, ocultando rutas sensibles en mensajes para usuarios.
try:
origen.copy(destino)
except FileNotFoundError:
print("El origen no existe")
except PermissionError:
print("Permiso denegado")
except OSError as error:
print(f"Fallo del sistema de archivos: {error}")
En lotes, decide si un error detiene todo. Una herramienta robusta separa éxitos y fallos y conserva datos suficientes para reintentar con seguridad.
Rendimiento
La velocidad depende de latencia, tamaño, caché, red y soporte del sistema operativo. No leas todo el archivo en memoria solo para duplicarlo. Mide cargas reales, comprueba espacio libre y limita concurrencia para no saturar el almacenamiento.
Los destinos remotos requieren más cuidado. Reintenta solo fallos transitorios, aplica espera progresiva y detecta resultados parciales. Los reintentos ciegos pueden crear duplicados o inconsistencias.
Verificación
Para archivos comunes, existencia y tamaño pueden bastar. Para artefactos críticos, compara hashes criptográficos. El hash añade I/O, pero detecta corrupción o copias incompletas.
import hashlib
def sha256(ruta: Path) -> str:
digest = hashlib.sha256()
with ruta.open("rb") as archivo:
for bloque in iter(lambda: archivo.read(1024 * 1024), b""):
digest.update(bloque)
return digest.hexdigest()
En árboles, compara rutas relativas esperadas y calcula hashes de todos o de los archivos críticos. Guarda los resultados en logs estructurados.
Compatibilidad de versiones
Confirma la versión mínima antes de depender de Path.copy. Una biblioteca compatible con versiones antiguas puede centralizar un fallback con shutil.
def copiar_ruta(origen: Path, destino: Path) -> Path:
if hasattr(origen, "copy"):
return origen.copy(destino)
import shutil
if origen.is_dir():
return Path(shutil.copytree(origen, destino))
return Path(shutil.copy2(origen, destino))
Centralizar la compatibilidad evita condicionales dispersos y concentra la política de enlaces, sobrescritura, metadatos y registros.
Estrategia de pruebas
Prueba un archivo pequeño, archivo vacío, directorio anidado, destino existente, falta de permisos, enlaces simbólicos, nombres Unicode, rutas largas, disco lleno e interrupción durante una copia recursiva. Usa tempfile.TemporaryDirectory para aislar pruebas y ejecuta integración en todos los sistemas compatibles.
Diseño práctico
Un servicio reutilizable debe validar el origen, exigir una raíz permitida, crear padres, definir sobrescritura, controlar enlaces, preparar árboles en temporal, comprobar capacidad, verificar salidas importantes y devolver un resultado estructurado. Nunca aceptes rutas arbitrarias de usuarios sin normalización y reglas.
Consulta también pathlib en Python, shutil en Python, archivos en Python y manejo de excepciones. Revisa la documentación oficial de pathlib y la documentación de shutil.
Conclusión
Path.copy integra la copia con el estilo moderno de pathlib. Su interfaz concisa debe acompañarse de reglas explícitas de sobrescritura, tratamiento de enlaces, expectativas de metadatos, preparación segura de directorios, verificación y pruebas de compatibilidad. Con esos controles, simplifica flujos sin ocultar riesgos operativos.







