Глава 23 · Часть IV · Реализуем SafeSort
Сканируем каталог
scan() строго читает файловую систему — обходит каталог рекурсивно и не меняет ни одного файла.
SafeSort · Часть 4 из 6Реализация
Add directory scanner
Area: FilesystemPriority: High
git switch -c feat/directory-scanner
Первый шаг SafeSort — обойти каталог и составить список файлов. Функция
scan() — одна из трёх строго читающих команд SafeSort: она
только вызывает iterdir() и stat(),
ни разу не создавая, не перемещая и не удаляя ничего на диске.
Downloads/
report.pdf
cat.jpg
archive.zip
src/safesort/scanner.py
def scan(root: Path, config: Config) -> list[FileInfo]:
root = Path(root)
excluded = config.excluded_names()
results: list[FileInfo] = []
_scan_dir(root, excluded, results)
return results
Сам обход рекурсивный: функция _scan_dir() заходит в
каждый подкаталог и вызывает себя снова:
src/safesort/scanner.py
for entry in entries:
if entry.is_symlink():
continue # символические ссылки пропускаются — см. следующую страницу
if entry.is_dir():
if entry.name in excluded:
continue # исключённые каталоги пропускаются — см. следующую страницу
_scan_dir(entry, excluded, results)
elif entry.is_file():
size = entry.stat().st_size
results.append(FileInfo(path=entry, size=size, extension=entry.suffix.lower()))
Каталог, который нельзя прочитать, не должен остановить всё сканирование
Если для какого-то подкаталога недостаточно прав доступа,
iterdir() вызывает PermissionError. Реализация _scan_dir() перехватывает эту ошибку для каждого подкаталога отдельно, записывает предупреждение в журнал и продолжает сканировать остальные каталоги — так один недоступный подкаталог не обрывает весь просмотр целиком. Позже мы рассмотрим это подробнее вместе с остальными ошибками файловой системы.Проверить сканер можно прямо сейчас — реальный вывод:
Тест-чекпойнт: требование становится регрессией
tests/test_scanner.py
def test_scan_finds_file_without_changing_it(tmp_path):
source = tmp_path / "report.pdf"
source.write_bytes(b"report")
files = scan(tmp_path, Config())
assert [item.path for item in files] == [source]
assert source.read_bytes() == b"report"
Сначала требование наблюдается на одном примере, затем тест сохраняет его как контракт. Поздняя секция тестирования расширит эту основу fixtures и edge cases.
Практика: сканируем настоящий каталог
Нужен доступ к настоящей файловой системе — выполните локально в VS Code, PyCharm или Jupyter
Практика выполняется локально