Purpose
-------
One atomic text writer for the artefacts ``oct`` rewrites in place
(Plan 278 P1, TEST-12). A committed file is replaced by a file that was
written beside it and then moved onto it, so a write that dies part-way
costs the new document rather than the old one.
The defect this replaces: ``Path.write_text`` opens the target with
``w``, which truncates it before the first byte lands. During plan 276
a RAM-disk write fault hit exactly there and left the committed
``parallel_manifest.json`` at 0 bytes -- a tracked file destroyed by a
transient device fault, with nothing on disk to recover it from.
Responsibilities
----------------
- :func:`atomic_write_text` -- write text to a temp file beside the
target, flush it to the device, then replace the target with it.
Diagnostics
-----------
Domain: OCT-CORE
Levels:
L1 -- a write or replace that could not be completed
L2 -- lifecycle (the replace landed)
L3 -- semantic details (the target and its byte count)
L4 -- deep tracing
Contracts
---------
- The TARGET is never opened for writing. On any failure it is left
exactly as it was, the temp file is removed, and the exception is
re-raised unchanged -- callers keep the error they would have got.
- The bytes are the bytes: for the same ``text``, ``encoding`` and
``newline`` the file is byte-identical to what ``Path.write_text``
would have produced, so adopting this helper changes no document.
- The temp file is created in the TARGET's own directory, because
``os.replace`` is atomic only within one filesystem.
- stdlib plus the ``_dbg`` shim only; imports nothing of ``oct``'s
beyond diagnostics, so any module may call it.
Dependencies
------------
- oct.core.diagnostics (the ``_dbg`` shim)
| None atomic_write._replace_with_retry |
( |
Path | source, |
|
|
Path | target ) |
|
protected |
Replace *target* with *source*, tolerating a read-only target.
Windows refuses ``os.replace`` with ``PermissionError`` when the
target carries the read-only attribute or is briefly held open by
another reader (antivirus, an indexer, a cleanup pass). This host
creates the first case routinely: ``G:\\PythonApps`` is a Google
Drive mirror root and the mirror sets the read-only bit on synced
files (ENV-03).
The shape is lifted from
``oc_orchestrator/work_queue_file.py::_windows_safe_replace``
(plan 258 P2), which is the same recovery for the same hazard;
``oct`` cannot import ``oc_orchestrator``, so the credit is here
rather than a shared import. Atomicity holds on every attempt: a
replace either happened or did not.
Definition at line 83 of file atomic_write.py.
| None atomic_write.atomic_write_text |
( |
Path | path, |
|
|
str | text, |
|
|
* | , |
|
|
str | encoding = "utf-8", |
|
|
str | None | newline = None ) |
Write *text* to *path* without ever truncating *path*.
``encoding`` and ``newline`` mean what they mean to
:meth:`pathlib.Path.write_text`, and produce the same bytes: pass
``newline="\\n"`` for a file whose line endings are pinned, and
leave it ``None`` for the platform translation that the default
gives.
Parameters
----------
path
The file to end up with *text* in it. Its parent directory is
created when absent.
text
The whole document. This helper has no append mode: a partial
document is what it exists to prevent.
encoding
The text encoding, ``"utf-8"`` by default.
newline
The newline translation, ``None`` (platform) by default.
Raises
------
OSError
Whatever the write, the flush or the replace raised, unchanged.
The target is untouched and no temp file is left behind.
Definition at line 117 of file atomic_write.py.