Mercurial > piecrust2
comparison piecrust/baking/records.py @ 120:133845647083
Better error management and removal support in baking/processing.
* Baker and processor pipeline now store errors in their records.
* They also support deleting output files that are no longer valid.
* The basic transitional record class implements more boilerplate code.
* The processor pipeline is run from the `bake` command directly.
* New unit tests.
* Unit test mocking now mocks `os.remove` too.
author | Ludovic Chabant <ludovic@chabant.com> |
---|---|
date | Sun, 09 Nov 2014 14:46:23 -0800 |
parents | 0445a2232de7 |
children | 1f4c3dae1fe8 |
comparison
equal
deleted
inserted
replaced
119:0811f92cbdc7 | 120:133845647083 |
---|---|
1 import os.path | 1 import os.path |
2 import logging | 2 import logging |
3 from piecrust import APP_VERSION | |
4 from piecrust.sources.base import PageSource | 3 from piecrust.sources.base import PageSource |
5 from piecrust.records import Record | 4 from piecrust.records import Record, TransitionalRecord |
6 | 5 |
7 | 6 |
8 logger = logging.getLogger(__name__) | 7 logger = logging.getLogger(__name__) |
9 | |
10 | |
11 RECORD_VERSION = 6 | |
12 | 8 |
13 | 9 |
14 def _get_transition_key(source_name, rel_path, taxonomy_name=None, | 10 def _get_transition_key(source_name, rel_path, taxonomy_name=None, |
15 taxonomy_term=None): | 11 taxonomy_term=None): |
16 key = '%s:%s' % (source_name, rel_path) | 12 key = '%s:%s' % (source_name, rel_path) |
22 key += taxonomy_term | 18 key += taxonomy_term |
23 return key | 19 return key |
24 | 20 |
25 | 21 |
26 class BakeRecord(Record): | 22 class BakeRecord(Record): |
23 RECORD_VERSION = 8 | |
24 | |
27 def __init__(self): | 25 def __init__(self): |
28 super(BakeRecord, self).__init__() | 26 super(BakeRecord, self).__init__() |
29 self.out_dir = None | 27 self.out_dir = None |
30 self.bake_time = None | 28 self.bake_time = None |
31 self.app_version = APP_VERSION | |
32 self.record_version = RECORD_VERSION | |
33 | |
34 def hasLatestVersion(self): | |
35 return (self.app_version == APP_VERSION and | |
36 self.record_version == RECORD_VERSION) | |
37 | |
38 def __setstate__(self, state): | |
39 state.setdefault('app_version', -1) | |
40 state.setdefault('record_version', -1) | |
41 super(BakeRecord, self).__setstate__(state) | |
42 | 29 |
43 | 30 |
44 FLAG_NONE = 0 | 31 FLAG_NONE = 0 |
45 FLAG_SOURCE_MODIFIED = 2**0 | 32 FLAG_SOURCE_MODIFIED = 2**0 |
46 FLAG_OVERRIDEN = 2**1 | 33 FLAG_OVERRIDEN = 2**1 |
55 self.taxonomy_term = taxonomy_term | 42 self.taxonomy_term = taxonomy_term |
56 self.path_mtime = os.path.getmtime(factory.path) | 43 self.path_mtime = os.path.getmtime(factory.path) |
57 | 44 |
58 self.flags = FLAG_NONE | 45 self.flags = FLAG_NONE |
59 self.config = None | 46 self.config = None |
47 self.errors = [] | |
60 self.out_uris = [] | 48 self.out_uris = [] |
61 self.out_paths = [] | 49 self.out_paths = [] |
62 self.used_source_names = set() | 50 self.used_source_names = set() |
63 self.used_taxonomy_terms = set() | 51 self.used_taxonomy_terms = set() |
64 | 52 |
65 @property | 53 @property |
66 def was_baked(self): | 54 def was_baked(self): |
67 return len(self.out_paths) > 0 | 55 return len(self.out_paths) > 0 or len(self.errors) > 0 |
56 | |
57 @property | |
58 def was_baked_successfully(self): | |
59 return len(self.out_paths) > 0 and len(self.errors) == 0 | |
68 | 60 |
69 @property | 61 @property |
70 def num_subs(self): | 62 def num_subs(self): |
71 return len(self.out_paths) | 63 return len(self.out_paths) |
72 | |
73 @property | |
74 def transition_key(self): | |
75 return _get_transition_key(self.source_name, self.rel_path, | |
76 self.taxonomy_name, self.taxonomy_term) | |
77 | 64 |
78 def __getstate__(self): | 65 def __getstate__(self): |
79 state = self.__dict__.copy() | 66 state = self.__dict__.copy() |
80 del state['path_mtime'] | 67 del state['path_mtime'] |
81 return state | 68 return state |
82 | 69 |
83 | 70 |
84 class TransitionalBakeRecord(object): | 71 class TransitionalBakeRecord(TransitionalRecord): |
85 DELETION_MISSING = 1 | |
86 DELETION_CHANGED = 2 | |
87 | |
88 def __init__(self, previous_path=None): | 72 def __init__(self, previous_path=None): |
89 self.previous = BakeRecord() | 73 super(TransitionalBakeRecord, self).__init__(BakeRecord, |
90 self.current = BakeRecord() | 74 previous_path) |
91 self.transitions = {} | |
92 self.incremental_count = 0 | |
93 if previous_path: | |
94 self.loadPrevious(previous_path) | |
95 self.current.entry_added += self._onCurrentEntryAdded | |
96 | |
97 def loadPrevious(self, previous_path): | |
98 try: | |
99 self.previous = BakeRecord.load(previous_path) | |
100 except Exception as ex: | |
101 logger.debug("Error loading previous record: %s" % ex) | |
102 logger.debug("Will reset to an empty one.") | |
103 self.previous = BakeRecord() | |
104 return | |
105 | |
106 for e in self.previous.entries: | |
107 self.transitions[e.transition_key] = (e, None) | |
108 | |
109 def clearPrevious(self): | |
110 self.previous = BakeRecord() | |
111 | |
112 def saveCurrent(self, current_path): | |
113 self.current.save(current_path) | |
114 | 75 |
115 def addEntry(self, entry): | 76 def addEntry(self, entry): |
116 if (self.previous.bake_time and | 77 if (self.previous.bake_time and |
117 entry.path_mtime >= self.previous.bake_time): | 78 entry.path_mtime >= self.previous.bake_time): |
118 entry.flags |= FLAG_SOURCE_MODIFIED | 79 entry.flags |= FLAG_SOURCE_MODIFIED |
119 self.current.addEntry(entry) | 80 super(TransitionalBakeRecord, self).addEntry(entry) |
81 | |
82 def getTransitionKey(self, entry): | |
83 return _get_transition_key(entry.source_name, entry.rel_path, | |
84 entry.taxonomy_name, entry.taxonomy_term) | |
120 | 85 |
121 def getOverrideEntry(self, factory, uri): | 86 def getOverrideEntry(self, factory, uri): |
122 for pair in self.transitions.values(): | 87 for pair in self.transitions.values(): |
123 prev = pair[0] | 88 prev = pair[0] |
124 cur = pair[1] | 89 cur = pair[1] |
146 def getCurrentEntries(self, source_name): | 111 def getCurrentEntries(self, source_name): |
147 return [e for e in self.current.entries | 112 return [e for e in self.current.entries |
148 if e.source_name == source_name] | 113 if e.source_name == source_name] |
149 | 114 |
150 def collapseRecords(self): | 115 def collapseRecords(self): |
151 for pair in self.transitions.values(): | 116 for prev, cur in self.transitions.values(): |
152 prev = pair[0] | |
153 cur = pair[1] | |
154 | |
155 if prev and cur and not cur.was_baked: | 117 if prev and cur and not cur.was_baked: |
156 # This page wasn't baked, so the information from last | 118 # This page wasn't baked, so the information from last |
157 # time is still valid (we didn't get any information | 119 # time is still valid (we didn't get any information |
158 # since we didn't bake). | 120 # since we didn't bake). |
159 cur.flags = prev.flags | 121 cur.flags = prev.flags |
160 if prev.config: | 122 if prev.config: |
161 cur.config = prev.config.copy() | 123 cur.config = prev.config.copy() |
162 cur.out_uris = list(prev.out_uris) | 124 cur.out_uris = list(prev.out_uris) |
163 cur.out_paths = list(prev.out_paths) | 125 cur.out_paths = list(prev.out_paths) |
126 cur.errors = list(prev.errors) | |
164 cur.used_source_names = set(prev.used_source_names) | 127 cur.used_source_names = set(prev.used_source_names) |
165 cur.used_taxonomy_terms = set(prev.used_taxonomy_terms) | 128 cur.used_taxonomy_terms = set(prev.used_taxonomy_terms) |
166 | 129 |
167 def _onCurrentEntryAdded(self, entry): | 130 def getDeletions(self): |
168 key = entry.transition_key | 131 for prev, cur in self.transitions.values(): |
169 te = self.transitions.get(key) | 132 if prev and not cur: |
170 if te is None: | 133 for p in prev.out_paths: |
171 logger.debug("Adding new record entry: %s" % key) | 134 yield (p, 'previous source file was removed') |
172 self.transitions[key] = (None, entry) | 135 elif prev and cur and cur.was_baked_successfully: |
173 return | 136 diff = set(prev.out_paths) - set(cur.out_paths) |
137 for p in diff: | |
138 yield (p, 'source file changed outputs') | |
174 | 139 |
175 if te[1] is not None: | |
176 raise Exception("A current entry already exists for: %s" % | |
177 key) | |
178 logger.debug("Setting current record entry: %s" % key) | |
179 self.transitions[key] = (te[0], entry) | |
180 |