oct
Loading...
Searching...
No Matches
atomic_write Namespace Reference

Functions

None _discard (Path temp_path)
None _replace_with_retry (Path source, Path target)
None atomic_write_text (Path path, str text, *, str encoding="utf-8", str|None newline=None)

Variables

int REPLACE_ATTEMPTS = 3
float REPLACE_BACKOFF_S = 0.05

Detailed Description

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)

Function Documentation

◆ _discard()

None atomic_write._discard ( Path temp_path)
protected
Remove a temp file a failed write left behind, quietly.

Never raises: the caller is already unwinding with the real error
and a cleanup failure must not replace it.

Definition at line 68 of file atomic_write.py.

Here is the caller graph for this function:

◆ _replace_with_retry()

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.

Here is the caller graph for this function:

◆ atomic_write_text()

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.

Here is the call graph for this function:

Variable Documentation

◆ REPLACE_ATTEMPTS

int atomic_write.REPLACE_ATTEMPTS = 3

Definition at line 61 of file atomic_write.py.

◆ REPLACE_BACKOFF_S

float atomic_write.REPLACE_BACKOFF_S = 0.05

Definition at line 65 of file atomic_write.py.