diff --git a/CHANGES b/CHANGES index a8455fdf9bcfc86668431065eb87c7029b3253da..388c9b7d623750f149a4bd91dc986fbf38c4b908 100644 --- a/CHANGES +++ b/CHANGES @@ -37,6 +37,12 @@ script, allows passing of kw to the template from the command line. [ticket:178] +- [feature] Added module_writer argument to Template, + TemplateLookup, allows a callable to be passed which + takes over the writing of the template's module source + file, so that special environment-specific steps + can be taken. [ticket:181] + - [bug] The exception message in the html_error_template is now escaped with the HTML filter. [ticket:142] diff --git a/mako/lookup.py b/mako/lookup.py index ef75ade22c5a2b9dd85b9fe284ababcda4e7b244..f6f811b9bee54e36e81d3bdf1d5bdd82afa89343 100644 --- a/mako/lookup.py +++ b/mako/lookup.py @@ -160,6 +160,7 @@ class TemplateLookup(TemplateCollection): cache_url=None, modulename_callable=None, + module_writer=None, default_filters=None, buffer_filters=(), strict_undefined=False, @@ -195,6 +196,7 @@ class TemplateLookup(TemplateCollection): 'encoding_errors':encoding_errors, 'input_encoding':input_encoding, 'module_directory':module_directory, + 'module_writer':module_writer, 'cache_args':cache_args, 'cache_enabled':cache_enabled, 'default_filters':default_filters, diff --git a/mako/template.py b/mako/template.py index 5d30c139f4af76c03a62603be809871fa3c15209..dcfc9f1f8f8f7ad6eac303c05ddfcaedb502c3d2 100644 --- a/mako/template.py +++ b/mako/template.py @@ -110,6 +110,39 @@ class Template(object): :param module_filename: Overrides the filename of the generated Python module file. For advanced usage only. + :param module_writer: A callable which overrides how the Python + module is written entirely. The callable is passed the + encoded source content of the module and the destination + path to be written to. The default behavior of module writing + uses a tempfile in conjunction with a file move in order + to make the operation atomic. So a user-defined module + writing function that mimics the default behavior would be:: + + import tempfile + import os + import shutil + + def module_writer(source, outputpath): + (dest, name) = \\ + tempfile.mkstemp( + dir=os.path.dirname(outputpath) + ) + + os.write(dest, source) + os.close(dest) + shutil.move(name, outputpath) + + from mako.template import Template + mytemplate = Template( + file="index.html", + module_directory="/path/to/modules", + module_writer=module_writer + ) + + The function is provided for unusual configurations where + certain platform-specific permissions or other special + steps are needed. + :param output_encoding: The encoding to use when :meth:`.render` is called. See :ref:`usage_unicode` as well as :ref:`unicode_toplevel`. @@ -154,6 +187,7 @@ class Template(object): module_filename=None, input_encoding=None, disable_unicode=False, + module_writer=None, bytestring_passthrough=False, default_filters=None, buffer_filters=(), @@ -188,6 +222,7 @@ class Template(object): self.disable_unicode = disable_unicode self.bytestring_passthrough = bytestring_passthrough or disable_unicode self.strict_undefined = strict_undefined + self.module_writer = module_writer if util.py3k and disable_unicode: raise exceptions.UnsupportedError( @@ -276,7 +311,8 @@ class Template(object): self, open(filename, 'rb').read(), filename, - path) + path, + self.module_writer) module = imp.load_source(self.module_id, path, open(path, 'rb')) del sys.modules[self.module_id] if module._magic_number != codegen.MAGIC_NUMBER: @@ -284,7 +320,8 @@ class Template(object): self, open(filename, 'rb').read(), filename, - path) + path, + self.module_writer) module = imp.load_source(self.module_id, path, open(path, 'rb')) del sys.modules[self.module_id] ModuleInfo(module, path, self, filename, None, None) @@ -543,7 +580,7 @@ def _compile_text(template, text, filename): exec code in module.__dict__, module.__dict__ return (source, module) -def _compile_module_file(template, text, filename, outputpath): +def _compile_module_file(template, text, filename, outputpath, module_writer): identifier = template.module_id lexer = Lexer(text, filename, @@ -563,17 +600,20 @@ def _compile_module_file(template, text, filename, outputpath): disable_unicode=template.disable_unicode, strict_undefined=template.strict_undefined) - # make tempfiles in the same location as the ultimate - # location. this ensures they're on the same filesystem, - # avoiding synchronization issues. - (dest, name) = tempfile.mkstemp(dir=os.path.dirname(outputpath)) - if isinstance(source, unicode): source = source.encode(lexer.encoding or 'ascii') - - os.write(dest, source) - os.close(dest) - shutil.move(name, outputpath) + + if module_writer: + module_writer(source, outputpath) + else: + # make tempfiles in the same location as the ultimate + # location. this ensures they're on the same filesystem, + # avoiding synchronization issues. + (dest, name) = tempfile.mkstemp(dir=os.path.dirname(outputpath)) + + os.write(dest, source) + os.close(dest) + shutil.move(name, outputpath) def _get_module_info_from_callable(callable_): return _get_module_info(callable_.func_globals['__name__']) diff --git a/test/test_template.py b/test/test_template.py index ddab16e4e07339629f0bd8120072baff597e17bc..769af02dfc4f50b762e607afc8b0444715484f24 100644 --- a/test/test_template.py +++ b/test/test_template.py @@ -841,6 +841,10 @@ for utf8 in (True, False): del _do_test class ModuleDirTest(TemplateTest): + def tearDown(self): + import shutil + shutil.rmtree(module_base, True) + def test_basic(self): t = self._file_template("modtest.html") t2 = self._file_template('subdir/modtest.html') @@ -874,6 +878,22 @@ class ModuleDirTest(TemplateTest): os.path.join(module_base, 'subdir', 'foo', 'modtest.html.py') ) + def test_custom_writer(self): + canary = [] + def write_module(source, outputpath): + with open(outputpath, 'wb') as f: + canary.append(outputpath) + f.write(source) + lookup = TemplateLookup(template_base, module_writer=write_module, + module_directory=module_base) + t = lookup.get_template('/modtest.html') + t2 = lookup.get_template('/subdir/modtest.html') + eq_( + canary, + [os.path.join(module_base, "modtest.html.py"), + os.path.join(module_base, "subdir/modtest.html.py")] + ) + class FilenameToURITest(TemplateTest): def test_windows_paths(self): """test that windows filenames are handled appropriately by Template.""" @@ -954,6 +974,7 @@ class FilenameToURITest(TemplateTest): t = Template("test", uri="foo/bar/../../foo.html") eq_(t.uri, "foo/bar/../../foo.html") + class ModuleTemplateTest(TemplateTest): def test_module_roundtrip(self): lookup = TemplateLookup()