# 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