|
1 | 1 | """Filesystem tool helpers and compatibility re-exports. |
2 | 2 |
|
3 | 3 | This module hosts the SHARED helper machinery for the filesystem |
4 | | -tools — spooling oversized tool results to temp files |
5 | | -(`gptel-agent--truncate-buffer` parity), git-root detection, and the |
6 | | -``natnump`` predicate — and re-exports the tool classes that live in |
7 | | -per-tool modules (`read.py`, `glob.py`, `grep.py`, `edit.py`, |
8 | | -`write.py`, `insert.py`, `mkdir.py`), so existing imports such as |
9 | | -``from .tools.filesystem import Read`` keep working. |
| 4 | +tools — atomic file replacement (``atomic_write_text``), spooling |
| 5 | +oversized tool results to temp files (`gptel-agent--truncate-buffer` |
| 6 | +parity), git-root detection, and the ``natnump`` predicate — and |
| 7 | +re-exports the tool classes that live in per-tool modules (`read.py`, |
| 8 | +`glob.py`, `grep.py`, `edit.py`, `write.py`, `insert.py`, `mkdir.py`), |
| 9 | +so existing imports such as ``from .tools.filesystem import Read`` |
| 10 | +keep working. |
10 | 11 |
|
11 | 12 | The helpers must stay defined HERE (not in a separate ``_common`` |
12 | 13 | module): tests monkey-patch ``filesystem._spool_dir`` and read |
|
31 | 32 |
|
32 | 33 | from __future__ import annotations |
33 | 34 |
|
| 35 | +import contextlib |
34 | 36 | import os |
35 | 37 | import re |
36 | 38 | import shutil # noqa: F401 (mock target for tests) |
| 39 | +import stat |
37 | 40 | import subprocess # noqa: F401 (mock target for tests) |
38 | 41 | import tempfile |
39 | 42 | import threading |
40 | 43 | import time |
| 44 | +import uuid |
41 | 45 | from collections.abc import Callable, Iterator |
42 | 46 | from pathlib import Path |
43 | 47 | from typing import TypeGuard |
@@ -232,13 +236,15 @@ def atomic_write_text(path: str, content: str) -> None: |
232 | 236 | """Replace PATH's contents with CONTENT atomically. |
233 | 237 |
|
234 | 238 | THE single write path for every tool that rewrites a file (``Edit``, |
235 | | - ``Insert``, ``Write``, and the pure-Python diff applier). It lives |
236 | | - here, in the module every tool already imports, so the four callers |
237 | | - cannot drift apart -- and because ``base`` imports nothing from |
238 | | - ``tools``, putting it here is also the only placement that avoids a |
239 | | - circular import: ``filesystem.py`` re-imports ``edit``/``write``/ |
240 | | - ``insert`` at its bottom, so a helper defined *there* would make |
241 | | - ``edit`` import a half-initialised ``filesystem``. |
| 239 | + ``Insert``, ``Write``, and the pure-Python diff applier), so the four |
| 240 | + callers cannot drift apart. It lives in this module with the other |
| 241 | + shared helpers, and the four callers import it LAZILY (inside the |
| 242 | + method that writes) rather than at module level, because this module |
| 243 | + re-imports ``edit``/``write``/``insert``/``diffapply`` at its bottom |
| 244 | + for the compatibility re-exports: a module-level import here would |
| 245 | + close the cycle on a half-initialised module (``tools/__init__`` |
| 246 | + loads ``edit`` first, whose import of this module would then ask the |
| 247 | + half-initialised ``edit`` for ``Edit``). |
242 | 248 |
|
243 | 249 | A plain ``open(path, "w")`` TRUNCATES the file before writing, so a |
244 | 250 | write that fails partway through -- ENOSPC, a quota, an I/O error, |
@@ -345,6 +351,7 @@ def atomic_write_text(path: str, content: str) -> None: |
345 | 351 | "MAX_OUTPUT", |
346 | 352 | "READ_SIZE_LIMIT", |
347 | 353 | "SPOOL_LINES", |
| 354 | + "atomic_write_text", |
348 | 355 | "cleanup_spooled_files", |
349 | 356 | "_fix_patch_headers", |
350 | 357 | "_git_glob_results", |
|
0 commit comments