Coverage for src/lektor_ng/buildfailures.py: 100%
44 statements
« prev ^ index » next coverage.py v7.15.2, created at 2026-08-08 10:40 +0000
« prev ^ index » next coverage.py v7.15.2, created at 2026-08-08 10:40 +0000
1import errno
2import hashlib
3import json
4import os
5from traceback import TracebackException
8class BuildFailure:
9 def __init__(self, data):
10 self.data = data
12 @classmethod
13 def from_exc_info(cls, artifact_name, exc_info):
14 te = TracebackException(*exc_info)
15 # NB: we have dropped werkzeug's support for Paste's __traceback_hide__
16 # frame local.
17 return cls(
18 {
19 "artifact": artifact_name,
20 "exception": "".join(te.format_exception_only()).strip(),
21 "traceback": "".join(te.format()).strip(),
22 }
23 )
25 def to_json(self):
26 return self.data
29class FailureController:
30 def __init__(self, pad, destination_path):
31 self.pad = pad
32 self.path = os.path.join(
33 os.path.abspath(os.path.join(pad.db.env.root_path, destination_path)),
34 ".lektor",
35 "failures",
36 )
38 def get_filename(self, artifact_name):
39 return os.path.join(self.path, hashlib.md5(artifact_name.encode("utf-8")).hexdigest()) + ".json"
41 def lookup_failure(self, artifact_name):
42 """Looks up a failure for the given artifact name."""
43 fn = self.get_filename(artifact_name)
44 try:
45 with open(fn, encoding="utf-8") as f:
46 return BuildFailure(json.load(f))
47 except OSError as e:
48 if e.errno != errno.ENOENT:
49 raise
50 return None
52 def clear_failure(self, artifact_name):
53 """Clears a stored failure."""
54 try:
55 os.unlink(self.get_filename(artifact_name))
56 except OSError as e:
57 if e.errno != errno.ENOENT:
58 raise
60 def store_failure(self, artifact_name, exc_info):
61 """Stores a failure from an exception info tuple."""
62 fn = self.get_filename(artifact_name)
63 try:
64 os.makedirs(os.path.dirname(fn))
65 except OSError:
66 pass
67 with open(fn, mode="w", encoding="utf-8") as f:
68 json.dump(BuildFailure.from_exc_info(artifact_name, exc_info).to_json(), f)
69 f.write("\n")