-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathtext_file_functions.py
More file actions
82 lines (65 loc) · 2.62 KB
/
text_file_functions.py
File metadata and controls
82 lines (65 loc) · 2.62 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
"""
Functions for reading and writing text files
"""
import json
import logging
import os
import tempfile
from pathlib import Path
logger = logging.getLogger(__name__)
def read_text_file(file_path: Path, as_list: bool = False) -> str | list[str] | None:
"""
Reads a text file as a single string or a list of lines.
Args:
file_path: Path to the file.
as_list: If True, returns a list of strings (lines). If False, one string.
"""
if not file_path.exists():
logger.warning("File not found: %s", file_path)
return None
try:
if as_list:
# .read_text().splitlines() is cleaner than .readlines()
# as it handles different OS line endings automatically
data = file_path.read_text(encoding='utf-8').splitlines()
else:
data = file_path.read_text(encoding='utf-8')
logger.info("Successfully read text from %s", file_path)
return data
except Exception as e:
logger.error("Unexpected error reading %s: %s", file_path, e)
return None
def write_text_file(file_path: Path, data: str | list[str]) -> bool:
"""
Writes a string or a list of strings to a text file atomically.
"""
file_path = Path(file_path).absolute()
if not file_path.parent.exists():
file_path.parent.mkdir(parents=True, exist_ok=True)
logger.debug("Created %s", json.dumps(str(file_path.parent.as_posix())))
temp_file_path: Path | None = None
try:
with tempfile.NamedTemporaryFile(mode='w', dir=str(file_path.parent), encoding='utf-8', suffix=".tmp", delete=False) as tf:
temp_file_path = Path(tf.name)
logger.info("Starting atomic text write to %s", file_path)
if isinstance(data, list):
# Add newlines if they aren't already there to ensure
# list items don't all end up on one line
tf.writelines(line if line.endswith('\n') else f"{line}\n" for line in data)
else:
tf.write(data)
tf.flush()
os.fsync(tf.fileno())
temp_file_path.replace(file_path)
logger.info("Successfully saved text to %s", file_path)
return True
except (KeyboardInterrupt, SystemExit):
logger.error("Write interrupted for %s. Cleaning up.", file_path)
if temp_file_path and temp_file_path.exists():
temp_file_path.unlink()
raise
except Exception as e:
logger.error("Failed to write text to %s: %s", file_path, e)
if temp_file_path and temp_file_path.exists():
temp_file_path.unlink()
return False