Глава 23 · Часть IV · Реализуем SafeSort

Журнал выполненных операций

Каждый apply записывает JSON-манифест того, что действительно произошло — это единственный источник данных для отмены.

SafeSort · Часть 4 из 6Реализация
GitHubIssue #7 · Project «SafeSort — первый релиз»
Record operation manifest
Area: SafetyPriority: Medium
git switch -c feat/manifest-and-undo

Каждый успешный вызов apply оставляет след — файл-манифест в формате JSON, который описывает, что именно было сделано. Без этого журнала команда undo, о которой пойдёт речь на следующей странице, не знала бы, что именно нужно отменить.

applyперемещает файлысписокперемещенийsource → destinationmanifest.jsonзаписан на диск
apply не только двигает файлы — он записывает, что именно сделал.
.safesort/history/20260824T011640800152.json
{
  "operation_id": "20260824T011640800152",
  "root": "/home/anna/Downloads",
  "timestamp": "2026-08-24T01:16:40",
  "moves": [
    {
      "source": "/home/anna/Downloads/otchet.pdf",
      "destination": "/home/anna/Downloads/Sorted/documents/otchet.pdf",
      "completed": true,
      "error": null
    }
  ]
}

Путь до манифеста — не случайный: он предсказуемо строится из корня и идентификатора операции, а сам идентификатор — временна́я метка, отформатированная так, чтобы лексикографический порядок совпадал с хронологическим:

src/safesort/manifest.py
def new_operation_id() -> str:
    return datetime.now().strftime("%Y%m%dT%H%M%S%f")

def history_dir(root: Path) -> Path:
    return Path(root) / STATE_DIRNAME / "history"
Почему именно такой формат идентификатора
Строка вида 20260824T011640800152 сортируется как текст точно в том же порядке, в каком операции происходили по времени. Это позволяет функции find_latest_manifest() находить последнюю операцию простой сортировкой имён файлов, не читая содержимое каждого манифеста.

Модели Path внутри программы нужно превратить в обычные строки, чтобы записать их в JSON — формат, у которого нет типа «путь». Это единственное место в SafeSort, где происходит такое преобразование в обе стороны.

Практика: манифест как обычный JSON
Интерактивный ноутбук прямо в браузере — Python 3.14 через Pyodide, без установки
Открыть практику →
Чекпойнт · Issue #7
git commit -m "feat: record operation manifest and add undo"
Слился как Pull Request #19. Заголовок PR показывает, что это одна ветка на две тесно связанные задачи: без манифеста Issue #8 (undo, следующая страница) попросту нечего было бы восстанавливать.
Статус в реальном Project: Done