1
0
mirror of https://github.com/jbranchaud/til synced 2026-07-04 08:38:23 +00:00

Add Make Secure Temp File For Atomic Write as a Python TIL

This commit is contained in:
jbranchaud
2026-06-22 17:26:14 -05:00
parent 03c11f9042
commit 20bbdb6d55
2 changed files with 50 additions and 1 deletions
+2 -1
View File
@@ -10,7 +10,7 @@ working across different projects via [VisualMode](https://www.visualmode.dev/).
For a steady stream of TILs, [sign up for my newsletter](https://visualmode.kit.com/newsletter).
_1798 TILs and counting..._
_1799 TILs and counting..._
See some of the other learning resources I work on:
@@ -1079,6 +1079,7 @@ If you've learned something here, support my efforts writing daily TILs by
- [Load A File Into The Python REPL](python/load-a-file-into-the-python-repl.md)
- [Look Inside Pytest tmp_path](python/look-inside-pytest-tmp-path.md)
- [Make Dataclass Sortable By Specific Field](python/make-dataclass-sortable-by-specific-field.md)
- [Make Secure Temp File For Atomic Write](python/make-secure-temp-file-for-atomic-write.md)
- [Override The Boolean Context Of A Class](python/override-the-boolean-context-of-a-class.md)
- [Parse Relative Time To datetime Object](python/parse-relative-time-to-datetime-object.md)
- [Reclassify Certain Packages As Dev Dependencies](python/reclassify-certain-packages-as-dev-dependencies.md)
@@ -0,0 +1,48 @@
# Make Secure Temp File For Atomic Write
Two types of failure modes that can occur while writing to a shared file on the
file system are 1) a corrupted file due to a crash mid-write and 2) another
process reading a partial file mid-write.
One way I've handled this in [`py-vmt`](https://github.com/jbranchaud/py-vmt) is
to perform the write operations on a secure temp file and then use the OS-level
atomic `rename` operation. I do this by [creating a
`contextmanager`](https://docs.python.org/3/library/contextlib.html#contextlib.contextmanager)
that uses
[`tempfile.mkstemp`](https://docs.python.org/3/library/tempfile.html#tempfile.mkstemp)
and [`os.replace`](https://docs.python.org/3/library/os.html#os.replace).
Here is what the `contextmanager` looks like:
```python
from contextlib import contextmanager
from pathlib import Path
import os, tempfile
@contextmanager
def atomic_write(path: Path):
# write to a tmp file in the same directory, then atomically swap it
fd, temp_file_path = tempfile.mkstemp(dir=path.parent, suffix=".tmp")
try:
with os.fdopen(fd, "w") as file:
yield file
os.replace(temp_file_path, path)
except BaseException:
os.unlink(temp_file_path)
raise
```
This explicitly creates a secure temp file in the same directory as the given
path with `.tmp` as the suffix. I then open the file descriptor using the
`os.fdopen` context manager (which will manage closing the file descriptor for
me). The `@contextmanager` decorator plus the `yield file` are what allow this
to be used as a `with` block. Once any file operations are done, then I use
`os.replace` to atomically swap out the original file with the temp file.
Here is how I use it to write updates to JSON data files:
```python
def write_active_session(self, session: Session) -> None:
with atomic_write(self.active_session_file) as file:
json.dump(session.marshal(), file)
```