diff piecrust/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 e88e330eb8dc
children 9e4c2e68a129
line wrap: on
line diff
--- a/piecrust/records.py	Wed Oct 29 08:19:58 2014 -0700
+++ b/piecrust/records.py	Sun Nov 09 14:46:23 2014 -0800
@@ -2,6 +2,7 @@
 import os.path
 import pickle
 import logging
+from piecrust import APP_VERSION
 from piecrust.events import Event
 
 
@@ -12,6 +13,12 @@
     def __init__(self):
         self.entries = []
         self.entry_added = Event()
+        self.app_version = APP_VERSION
+        self.record_version = self.__class__.RECORD_VERSION
+
+    def hasLatestVersion(self):
+        return (self.app_version == APP_VERSION and
+                self.record_version == self.__class__.RECORD_VERSION)
 
     def addEntry(self, entry):
         self.entries.append(entry)
@@ -31,9 +38,8 @@
         return odict
 
     def __setstate__(self, state):
-        for k, v in state.items():
-            setattr(self, k, v)
-        self.entry_added = Event()
+        state['entry_added'] = Event()
+        self.__dict__.update(state)
 
     @staticmethod
     def load(path):
@@ -41,3 +47,65 @@
         with open(path, 'rb') as fp:
             return pickle.load(fp)
 
+
+class TransitionalRecord(object):
+    def __init__(self, record_class, previous_path=None):
+        self._record_class = record_class
+        self.transitions = {}
+        self.incremental_count = 0
+        self.current = record_class()
+        if previous_path:
+            self.loadPrevious(previous_path)
+        else:
+            self.previous = record_class()
+        self.current.entry_added += self._onCurrentEntryAdded
+
+    def loadPrevious(self, previous_path):
+        previous_record_valid = True
+        try:
+            self.previous = self._record_class.load(previous_path)
+        except Exception as ex:
+            logger.debug("Error loading previous record: %s" % ex)
+            logger.debug("Will reset to an empty one.")
+            previous_record_valid = False
+
+        if self.previous.record_version != self._record_class.RECORD_VERSION:
+            logger.debug("Previous record has old version %d." %
+                    self.previous.record_version)
+            logger.debug("Will reset to an empty one.")
+            previous_record_valid = False
+
+        if not previous_record_valid:
+            self.previous = self._record_class()
+            return
+
+        for e in self.previous.entries:
+            key = self.getTransitionKey(e)
+            self.transitions[key] = (e, None)
+
+    def clearPrevious(self):
+        self.previous = self._record_class()
+
+    def saveCurrent(self, current_path):
+        self.current.save(current_path)
+
+    def addEntry(self, entry):
+        self.current.addEntry(entry)
+
+    def getTransitionKey(self, entry):
+        raise NotImplementedError()
+
+    def _onCurrentEntryAdded(self, entry):
+        key = self.getTransitionKey(entry)
+        te = self.transitions.get(key)
+        if te is None:
+            logger.debug("Adding new record entry: %s" % key)
+            self.transitions[key] = (None, entry)
+            return
+
+        if te[1] is not None:
+            raise Exception("A current entry already exists for: %s" %
+                    key)
+        logger.debug("Setting current record entry: %s" % key)
+        self.transitions[key] = (te[0], entry)
+