Skip to content

Capítulo 6 — Manejo de cambios incrementales

Commit: 7cddb8cfix: account for modifications, added test functions (Shiven Mian, 2 febrero 2024)

¿Qué se arregló?

Este es el primer Pull Request de la comunidad. Shiven Mian identificó un problema crítico: el índice _.aifs no manejaba correctamente los cambios en los archivos.

El problema anterior

En la implementación original de search():

python
# ANTES — Código simplificado
index = {}
if not os.path.exists(path_to_index):
    index = index_directory(path)
    # Guardaba el índice aquí
else:
    with open(path_to_index, 'r') as f:
        index = json.load(f)
    
    # Re-indexaba modificados, pero...
    for file_path, file_index in index.items():
        if os.path.getmtime(file_path) != file_index["last_modified"]:
            new_file_index = index_file(file_path)
            index[file_path] = new_file_index
    # ¿Y los archivos eliminados? ¿Y los archivos nuevos?

Tres escenarios no manejados:

  1. 📁 Archivo nuevo creado → no se indexaba hasta el próximo reinicio
  2. ✏️ Archivo modificado → se re-indexaba, pero el índice no se guardaba
  3. 🗑️ Archivo eliminado → seguía en el índice como un fantasma

La solución: index_directory() con lógica incremental

Shiven refactorizó completamente index_directory():

python
def index_directory(path, existingIndex={}, indexPath=""):
    index = existingIndex
    deletedFiles = []
    modifiedFiles = []
    writeToIndex = False
    
    # PASO 1: Detectar archivos eliminados y modificados
    for file_path, file_index in index.items():
        if not os.path.isfile(file_path):
            deletedFiles.append(file_path)
            writeToIndex = True
            continue
        
        if os.path.getmtime(file_path) != file_index["last_modified"]:
            modifiedFiles.append(file_path)
            new_file_index = index_file(file_path)
            index[file_path] = new_file_index
            writeToIndex = True
    
    # PASO 2: Eliminar los borrados del índice
    for file_path in deletedFiles:
        index.pop(file_path, None)
    
    # PASO 3: Agregar archivos nuevos
    for root, _, files in os.walk(path):
        for file in files:
            file_path = os.path.join(root, file)
            if file_path not in index or file_path in modifiedFiles:
                writeToIndex = True
                file_index = index_file(file_path)
                index[file_path] = file_index
    
    # PASO 4: Solo guardar si hubo cambios
    if writeToIndex:
        with open(indexPath, 'w') as f:
            json.dump(index, f)
    
    return index

El detalle de writeToIndex

python
writeToIndex = False
# ... detectar cambios ...
if writeToIndex:
    with open(indexPath, 'w') as f:
        json.dump(index, f)

Este flag evita escribir el archivo _.aifs si no hubo cambios. Escribir JSON al disco es costoso (sobre todo en proyectos grandes) y afecta el timestamp del directorio. Solo se escribe cuando es necesario.

last_modified como firma de cambios

python
last_modified = os.path.getmtime(file_path)

os.path.getmtime() retorna el timestamp Unix de la última modificación. Comparar timestamps es mucho más rápido que comparar hashes de contenido.

La contrapartida: si haces touch archivo.py (cambias el timestamp sin cambiar el contenido), el archivo se re-indexa innecesariamente. Para búsqueda semántica, ese trade-off está bien.

Lección del capítulo

El caché es fácil de implementar. El caché correcto — que se invalida cuando debe — es lo difícil. Los tres casos (nuevo, modificado, eliminado) deben manejarse explícitamente.


Siguiente: Cap 07 — La opción python_docstrings_only

Libro generado por El Profe 🧑‍🏫