mirror of
https://github.com/PR0M3TH3AN/SeedPass.git
synced 2026-11-03 03:12:07 +00:00
Add atomic write utility and tests
This commit is contained in:
1 parent
2294656f36
commit
032caed3d0
8 files changed
+123
-24
No files matched your search
@@ -35,6 +35,7 @@ try:
|
||||
clear_and_print_fingerprint,
|
||||
clear_header_with_notification,
|
||||
)
|
||||
from .atomic_write import atomic_write
|
||||
|
||||
if logger.isEnabledFor(logging.DEBUG):
|
||||
logger.info("Modules imported successfully.")
|
||||
@@ -68,4 +69,5 @@ __all__ = [
|
||||
"clear_and_print_fingerprint",
|
||||
"clear_header_with_notification",
|
||||
"pause",
|
||||
"atomic_write",
|
||||
]
|
||||
@@ -0,0 +1,62 @@
|
||||
"""Utility helpers for performing atomic file writes.
|
||||
|
||||
This module provides a small helper function :func:`atomic_write` which
|
||||
implements a simple pattern for writing files atomically. Data is written to a
|
||||
temporary file in the same directory, flushed and synced to disk, and then
|
||||
``os.replace`` is used to atomically move the temporary file into place.
|
||||
|
||||
The function accepts a callable ``write_func`` that receives the temporary file
|
||||
object. This keeps the helper flexible enough to support both text and binary
|
||||
writes and allows callers to perform complex serialisation steps (e.g. JSON
|
||||
dumping) without exposing a partially written file to other processes.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
from typing import Callable, Any, IO
|
||||
|
||||
|
||||
def atomic_write(
|
||||
path: str | Path,
|
||||
write_func: Callable[[IO[Any]], None],
|
||||
*,
|
||||
mode: str = "w",
|
||||
**open_kwargs: Any,
|
||||
) -> None:
|
||||
"""Write to ``path`` atomically using ``write_func``.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
path:
|
||||
Destination file path.
|
||||
write_func:
|
||||
Callable that receives an open file object and performs the actual
|
||||
write. The callable should not close the file.
|
||||
mode:
|
||||
File mode used when opening the temporary file. Defaults to ``"w"``.
|
||||
**open_kwargs:
|
||||
Additional keyword arguments passed to :func:`os.fdopen`.
|
||||
"""
|
||||
|
||||
dest = Path(path)
|
||||
dest.parent.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
fd, tmp_path = tempfile.mkstemp(dir=str(dest.parent))
|
||||
try:
|
||||
with os.fdopen(fd, mode, **open_kwargs) as tmp_file:
|
||||
write_func(tmp_file)
|
||||
tmp_file.flush()
|
||||
os.fsync(tmp_file.fileno())
|
||||
os.replace(tmp_path, dest)
|
||||
except Exception:
|
||||
try:
|
||||
os.unlink(tmp_path)
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
raise
|
||||
|
||||
|
||||
__all__ = ["atomic_write"]
|
||||
@@ -21,6 +21,7 @@ from typing import Optional, Any
|
||||
from termcolor import colored
|
||||
|
||||
from constants import APP_DIR, SCRIPT_CHECKSUM_FILE
|
||||
from utils.atomic_write import atomic_write
|
||||
|
||||
# Instantiate the logger
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -121,8 +122,7 @@ def update_checksum(content: str, checksum_file_path: str) -> bool:
|
||||
hasher = hashlib.sha256()
|
||||
hasher.update(content.encode("utf-8"))
|
||||
new_checksum = hasher.hexdigest()
|
||||
with open(checksum_file_path, "w") as f:
|
||||
f.write(new_checksum)
|
||||
atomic_write(checksum_file_path, lambda f: f.write(new_checksum))
|
||||
logging.debug(f"Updated checksum for '{checksum_file_path}' to: {new_checksum}")
|
||||
return True
|
||||
except Exception as e:
|
||||
@@ -179,8 +179,7 @@ def initialize_checksum(file_path: str, checksum_file_path: str) -> bool:
|
||||
return False
|
||||
|
||||
try:
|
||||
with open(checksum_file_path, "w") as f:
|
||||
f.write(checksum)
|
||||
atomic_write(checksum_file_path, lambda f: f.write(checksum))
|
||||
logging.debug(
|
||||
f"Initialized checksum file '{checksum_file_path}' with checksum: {checksum}"
|
||||
)
|
||||
@@ -206,8 +205,7 @@ def update_checksum_file(file_path: str, checksum_file_path: str) -> bool:
|
||||
if checksum is None:
|
||||
return False
|
||||
try:
|
||||
with open(checksum_file_path, "w") as f:
|
||||
f.write(checksum)
|
||||
atomic_write(checksum_file_path, lambda f: f.write(checksum))
|
||||
logging.debug(
|
||||
f"Updated checksum for '{file_path}' to '{checksum}' at '{checksum_file_path}'."
|
||||
)
|
||||
|
||||
@@ -9,6 +9,7 @@ from typing import List, Optional
|
||||
|
||||
import shutil # Ensure shutil is imported if used within the class
|
||||
|
||||
from utils.atomic_write import atomic_write
|
||||
from utils.fingerprint import generate_fingerprint
|
||||
|
||||
# Instantiate the logger
|
||||
@@ -92,16 +93,15 @@ class FingerprintManager:
|
||||
Saves the current list of fingerprints to the fingerprints.json file.
|
||||
"""
|
||||
try:
|
||||
with open(self.fingerprints_file, "w") as f:
|
||||
json.dump(
|
||||
{
|
||||
"fingerprints": self.fingerprints,
|
||||
"last_used": self.current_fingerprint,
|
||||
"names": self.names,
|
||||
},
|
||||
f,
|
||||
indent=4,
|
||||
)
|
||||
data = {
|
||||
"fingerprints": self.fingerprints,
|
||||
"last_used": self.current_fingerprint,
|
||||
"names": self.names,
|
||||
}
|
||||
atomic_write(
|
||||
self.fingerprints_file,
|
||||
lambda f: json.dump(data, f, indent=4),
|
||||
)
|
||||
logger.debug(
|
||||
f"Fingerprints saved: {self.fingerprints} (last used: {self.current_fingerprint})"
|
||||
)
|
||||
|
||||
Reference in new issue
Block a user