# HG changeset patch # User cmlenz # Date 1207694041 0 # Node ID b5bd8c109209f49fe3fd5d4d1ce122ee915023a3 # Parent fc6d9d2a35275f6dbda1a157a50cf9ddc1661270 Enable pickling of `Template` and `Code` objects. diff --git a/genshi/template/base.py b/genshi/template/base.py --- a/genshi/template/base.py +++ b/genshi/template/base.py @@ -363,10 +363,7 @@ self.loader = loader self.lookup = lookup self.allow_exec = allow_exec - - self.filters = [self._flatten, self._eval, self._exec] - if loader: - self.filters.append(self._include) + self._init_filters() if isinstance(source, basestring): source = StringIO(source) @@ -377,9 +374,23 @@ except ParseError, e: raise TemplateSyntaxError(e.msg, self.filepath, e.lineno, e.offset) + def __getstate__(self): + state = self.__dict__.copy() + state['filters'] = [] + return state + + def __setstate__(self, state): + self.__dict__ = state + self._init_filters() + def __repr__(self): return '<%s "%s">' % (self.__class__.__name__, self.filename) + def _init_filters(self): + self.filters = [self._flatten, self._eval, self._exec] + if self.loader: + self.filters.append(self._include) + def _parse(self, source, encoding): """Parse the template. diff --git a/genshi/template/eval.py b/genshi/template/eval.py --- a/genshi/template/eval.py +++ b/genshi/template/eval.py @@ -75,6 +75,21 @@ lookup = {'lenient': LenientLookup, 'strict': StrictLookup}[lookup] self._globals = lookup.globals + def __getstate__(self): + state = {'source': self.source, 'ast': self.ast, + 'lookup': self._globals.im_self} + c = self.code + state['code'] = (c.co_nlocals, c.co_stacksize, c.co_flags, c.co_code, + c.co_consts, c.co_names, c.co_varnames, c.co_filename, + c.co_name, c.co_firstlineno, c.co_lnotab, (), ()) + return state + + def __setstate__(self, state): + self.source = state['source'] + self.ast = state['ast'] + self.code = new.code(0, *state['code']) + self._globals = state['lookup'].globals + def __eq__(self, other): return (type(other) == type(self)) and (self.code == other.code) diff --git a/genshi/template/markup.py b/genshi/template/markup.py --- a/genshi/template/markup.py +++ b/genshi/template/markup.py @@ -60,16 +60,13 @@ serializer = 'xml' _number_conv = Markup - def __init__(self, source, filepath=None, filename=None, loader=None, - encoding=None, lookup='strict', allow_exec=True): - Template.__init__(self, source, filepath=filepath, filename=filename, - loader=loader, encoding=encoding, lookup=lookup, - allow_exec=allow_exec) + def _init_filters(self): + Template._init_filters(self) # Make sure the include filter comes after the match filter - if loader: + if self.loader: self.filters.remove(self._include) self.filters += [self._match] - if loader: + if self.loader: self.filters.append(self._include) def _parse(self, source, encoding): diff --git a/genshi/template/tests/eval.py b/genshi/template/tests/eval.py --- a/genshi/template/tests/eval.py +++ b/genshi/template/tests/eval.py @@ -12,6 +12,8 @@ # history and logs, available at http://genshi.edgewall.org/log/. import doctest +import pickle +from StringIO import StringIO import sys import unittest @@ -32,6 +34,14 @@ self.assertEqual(hash(expr), hash(Expression('x,y'))) self.assertNotEqual(hash(expr), hash(Expression('y, x'))) + def test_pickle(self): + expr = Expression('1 < 2') + buf = StringIO() + pickle.dump(expr, buf, 2) + buf.seek(0) + unpickled = pickle.load(buf) + assert unpickled.evaluate({}) is True + def test_name_lookup(self): self.assertEqual('bar', Expression('foo').evaluate({'foo': 'bar'})) self.assertEqual(id, Expression('id').evaluate({})) @@ -443,6 +453,16 @@ class SuiteTestCase(unittest.TestCase): + def test_pickle(self): + suite = Suite('foo = 42') + buf = StringIO() + pickle.dump(suite, buf, 2) + buf.seek(0) + unpickled = pickle.load(buf) + data = {} + unpickled.execute(data) + self.assertEqual(42, data['foo']) + def test_internal_shadowing(self): # The context itself is stored in the global execution scope of a suite # It used to get stored under the name 'data', which meant the diff --git a/genshi/template/tests/markup.py b/genshi/template/tests/markup.py --- a/genshi/template/tests/markup.py +++ b/genshi/template/tests/markup.py @@ -13,6 +13,7 @@ import doctest import os +import pickle import shutil from StringIO import StringIO import sys @@ -39,6 +40,15 @@ tmpl = MarkupTemplate(stream) self.assertEqual(' 42 42', str(tmpl.generate(var=42))) + def test_pickle(self): + stream = XML('$var') + tmpl = MarkupTemplate(stream) + buf = StringIO() + pickle.dump(tmpl, buf, 2) + buf.seek(0) + unpickled = pickle.load(buf) + self.assertEqual('42', str(unpickled.generate(var=42))) + def test_interpolate_mixed3(self): tmpl = MarkupTemplate(' ${var} $var') self.assertEqual(' 42 42', str(tmpl.generate(var=42))) diff --git a/genshi/tests/core.py b/genshi/tests/core.py --- a/genshi/tests/core.py +++ b/genshi/tests/core.py @@ -21,7 +21,7 @@ import unittest from genshi import core -from genshi.core import Markup, Namespace, QName, escape, unescape +from genshi.core import Markup, Attrs, Namespace, QName, escape, unescape from genshi.input import XML, ParseError @@ -164,6 +164,18 @@ self.assertEquals("", repr(pickle.load(buf))) +class AttrsTestCase(unittest.TestCase): + + def test_pickle(self): + attrs = Attrs([("attr1", "foo"), ("attr2", "bar")]) + buf = StringIO() + pickle.dump(attrs, buf, 2) + buf.seek(0) + unpickled = pickle.load(buf) + self.assertEquals("Attrs([('attr1', 'foo'), ('attr2', 'bar')])", + repr(unpickled)) + + class NamespaceTestCase(unittest.TestCase): def test_pickle(self): @@ -206,6 +218,7 @@ suite.addTest(unittest.makeSuite(StreamTestCase, 'test')) suite.addTest(unittest.makeSuite(MarkupTestCase, 'test')) suite.addTest(unittest.makeSuite(NamespaceTestCase, 'test')) + suite.addTest(unittest.makeSuite(AttrsTestCase, 'test')) suite.addTest(unittest.makeSuite(QNameTestCase, 'test')) suite.addTest(doctest.DocTestSuite(core)) return suite