-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathstorage.py
More file actions
121 lines (94 loc) · 2.68 KB
/
Copy pathstorage.py
File metadata and controls
121 lines (94 loc) · 2.68 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
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
"""JSON storage for a single active application instance per data file."""
import json
import os
import tempfile
from pathlib import Path
FIELDS = {
"courses": (
"Title",
"Language",
"Description",
"Study mode",
),
"trainers": (
"First name",
"Last name",
"Subject",
),
"students": (
"First name",
"Last name",
"Birth date",
"Tuition fees",
),
"assignments": (
"Title",
"Description",
"Submission date",
"Written mark",
"Oral mark",
"Average",
"Result",
),
}
DEFAULT_PATH = (
Path(__file__).resolve().parent
/ "data"
/ "records.json"
)
def empty_data():
"""Create a separate list of records for each category."""
return {key: [] for key in FIELDS}
def validate(data):
"""Validate the structure of a saved file."""
if not isinstance(data, dict) or set(data) != set(FIELDS):
raise ValueError(
"Unexpected data format. The existing file was preserved."
)
for key, fields in FIELDS.items():
if not isinstance(data[key], list):
raise ValueError(f"Invalid collection: {key}")
for row in data[key]:
if not isinstance(row, dict):
raise ValueError(f"Invalid record in {key}")
if set(row) != set(fields):
raise ValueError(f"Invalid fields in {key}")
if any(not isinstance(value, str) for value in row.values()):
raise ValueError(f"Invalid values in {key}")
return data
def load(path=DEFAULT_PATH):
"""Load existing data or initialize empty collections."""
path = Path(path)
if not path.exists():
return empty_data()
with path.open(encoding="utf-8") as handle:
data = json.load(handle)
return validate(data)
def save(data, path=DEFAULT_PATH):
"""Write to a temporary file before replacing the destination."""
validate(data)
path = Path(path)
path.parent.mkdir(parents=True, exist_ok=True)
temporary = None
try:
with tempfile.NamedTemporaryFile(
mode="w",
encoding="utf-8",
dir=path.parent,
suffix=".tmp",
delete=False,
) as handle:
temporary = Path(handle.name)
json.dump(
data,
handle,
ensure_ascii=False,
indent=2,
allow_nan=False,
)
handle.flush()
os.fsync(handle.fileno())
os.replace(temporary, path)
finally:
if temporary is not None:
temporary.unlink(missing_ok=True)