mahmoud / ashes

⚱️ Lightweight, self-contained templating for Python 2 and 3, a la Dust templates
http://ashes.readthedocs.org
Other
55 stars 9 forks source link
ashes dust dust-templates dustjs python templating

Ashes

Build Status

Dust templating for Python 2 and 3. Also the most convenient, portable, and powerful command-line templating utility.

A quick example:

>>> from ashes import AshesEnv
>>> ashes_env = AshesEnv()

# Register/render from source

>>> ashes_env.register_source('hello', 'Hello, {name}!')
>>> ashes_env.render('hello', {'name': 'World'})
'Hello, World!'

# Or a file-based template (note: hella templates sold separately)

>>> ashes_env2 = AshesEnv(['./templates'])
>>> ashes_env2.render('hella.html', {'names': ['Kurt', 'Alex']})
'Hella Kurt and Alex!'

There's also built-in bottle.py support, which works exactly like bottle's own template() function and view() decorator.

from ashes import ashes_bottle_template as template
from ashes import ashes_bottle_view as view

@route('/')
def hello(name='World'):
    return template('bottle_hello_template', name=name)

@route('/dec')
@view('bottle_hello_template')
def hello_dec(name='World'):
    return {'name': name}

# Use debug=True to disable template caching for easier dev
run(host='localhost', port=8080, reloader=True, debug=True)

If you've read bottle's template docs, it'll be even dead-simpler, believe it or not.

One last tip, use the keep_whitespace flag to determine whether or not to optimize away whitespace in the rendered template. It's a good idea to keep this disabled if you use JavaScript in your templated files, because occasionally a single-line comment (i.e., // ... can break your page.

ashes_env = AshesEnv(keep_whitespace=False)  # optimize away whitespace

For more general information about the dust templating language, see the Dust documentation.

Command-line interface

The ashes command-line interface serves two purposes. First, it makes it easy to experiment with and test ashes features and behavior, especially thanks to the inline "literal" options, demonstrated below.

# using ashes to pretty-print JSON
$ python ashes.py --no-filter -T '{.|ppjson}' -M '{"x": {"y": [1,2,3]}}'
{
  "x": {
      "y": [
        1,
        2,
        3,
      ]
   }
}

Secondly, thanks to the compact, single-file implementation, ashes can replace rusty sed and awk scripts, wherever Python 2.7-3.x is available. Use ashes for generating shell scripts and much more.

Templates can be files or passed at the command line. Models, the input data to the template, are passed in as JSON, either as a command line option, or through stdin, enabling piping from web requests. Several other options exist, see the help output below.

$ python ashes.py --help
Usage: ashes.py [options]

render a template using a JSON input

Options:
  --version             show program's version number and exit
  -h, --help            show this help message and exit
  --env-path=ENV_PATH   paths to search for templates, separate paths with :
  --filter=FILTER       autoescape values with this filter, defaults to 'h'
                        for HTML
  --no-filter           disables default HTML-escaping filter, overrides
                        --filter
  --trim-whitespace     removes whitespace on template load
  -m MODEL_PATH, --model=MODEL_PATH
                        path to the JSON model file, default - for stdin
  -M MODEL_LITERAL, --model-literal=MODEL_LITERAL
                        the literal string of the JSON model, overrides model
  -o OUTPUT_PATH, --output=OUTPUT_PATH
                        path to the output file, default - for stdout
  --output-encoding=OUTPUT_ENCODING
                        encoding for the output, default utf-8
  -t TEMPLATE_PATH, --template=TEMPLATE_PATH
                        path of template to render, absolute or relative to
                        env-path
  -T TEMPLATE_LITERAL, --template-literal=TEMPLATE_LITERAL
                        the literal string of the template, overrides template
  --verbose=VERBOSE     emit extra output on stderr

On systems with ashes installed, this interface is accessible through the ashes command.

$ ashes --trim-whitespace --no-filter --template script.sh.dust --model data.json --output script.sh

Installation

Ashes is implemented as a single .py file, so installation can be as easy as downloading ashes.py above and putting it in the same directory as your project.

And as always, pip install ashes. Installing the package has the added benefit of installing the ashes command.

Testimonials

Ashes is currently used in production settings at PayPal and Rackspace.

Additionally, it is part of the Clastic web framework and used in several Wikimedia-oriented projects.

If your company or project uses Ashes, feel free to submit an issue or pull request to get a link or shoutout added to this section.

Advanced Template Caching

The javascript implementation of Dust supports a form of template caching in which the templates are pre-generated into code objects and then cached. This is necessary in javascript, because the templates would otherwise be loaded and compiled on each pageview.

Most Ashes users will not need to use Template Caching, but it is supported through hooks at several distinct stages:

There are many different benefits and concerns to caching templates at these stages. To explain this, let's assume that Ashes is being used to render content that is correlated from 10 inter-dependent templates in a directory.

The standard way for rendering this would be to create a new Ashes "Loader" for the directory and render it. Ashes would then load and compile all 10 templates as needed and re-use them throughout the life of the application. This works for most situations, because the Ashes rendering Environment and Template Loader are usually created once and are persistent objects.

This is our "Baseline" rendering situation, as Ashes must perform all of the following steps -- which are the most expensive portions of templating:

The fastest part of Ashes is simply executing the template to render the content -- this is usually less than 5% of the overall work!

In certain situations, the Ashes Environment or Template Loaders can not be persistent. This will happen if we have a lot of templates in a multi-tenant application and need to constrain the size of our templating environment (like a LRU cache), or need to limit the environment/loader to a very short lifespan. In these situations, hooking into Ashes to generate or load (partially) compiled templates is necessary t o overcome bottlenecks.

A very easy way to implement this is with a custom TemplateLoader. Template Loaders are a flexible framework that can be used to precompile families of templates or even lazily preload them as needed.

If a custom loader is not used, the template must be registered with the active ashes environment:

ashesEnv = ashes.AshesEnv(loaders=(ashesLoader, ))
templateObj = ashes.Template.from_python_code(source_python_code,
                                              name='apples.dust',
                                              )
ashesEnv.register(templateObj,
                  name="apples.dust",
                  )

Recap

| method              | cacheable in process | cacheable external        | overhead |
| ------------------- | -------------------- | ------------------------- | -------- |
| baseline (standard) | -                    | -                         | 100%     |
| ast                 | Yes                  | Safe                      | 65%      |
| python string       | Yes                  | Possible Security Risk    | 20-35%   |
| python code         | Yes                  | Same Risk, must `marshal` | 6-8%     |
| python func         | Yes                  | No.                       | 3%       |

Compatibility

Ashes has full support for every feature of the original Dust.js. Here's what the test suite says about all of the examples on Dust's documentation:

  . = passed, _ = skipped, X = failed, E = exception

 Dust.js site refs   tokenize   parse    optimize  compile    render
---------------------------------------------------------------------
                path    .         .         _         .         .
               plain    .         .         _         .         .
                zero    .         .         _         .         .
           async_key    .         .         _         .         .
          sync_chunk    .         .         _         .         .
            sync_key    .         .         _         .         .
              params    .         .         _         .         .
     partial_context    .         .         _         .         .
             escaped    .         .         _         .         .
            implicit    .         .         _         .         .
              filter    .         .         _         .         .
         empty_array    .         .         _         .         .
               array    .         .         _         .         .
            partials    .         .         _         .         .
               intro    .         .         _         .         .
           recursion    .         .         _         .         .
              object    .         .         _         .         .
       force_current    .         .         _         .         .
             replace    .         .         _         .         .
          rename_key    .         .         _         .         .
             context    .         .         _         .         .
      async_iterator    .         .         _         .         .
  interpolated_param    .         .         _         .         .
            comments    .         .         _         .         .
       escape_pragma    .         .         .         .         .
       base_template    .         .         _         .         .
          else_block    .         .         _         .         .
      child_template    .         .         _         .         .
         conditional    .         .         .         .         .
---------------------------------------------------------------------
          (29 total)    29        29        2         29        29

(NOTE: Optimization is fairly straightforward, so only two of the more complex examples are tested.)

A word on functions

Of course, being a Python library, functions and callable items in the contexts intended for server-side rendering must be written in Python. However, there are three reasons this is desirable:

  1. Many context functions control some element of UI, such as a transition or delay, which would just waste cycles if executed server-side. (e.g., 'Intro' on the dust.js docs above)
  2. One-off context functions should be extremely basic, or
  3. Common, complex functions should be refactored into Helpers and registered in the rendering environment for all templates to use.

At the end of the day, though, remember that Dust is meant to be data-driven, and if one finds oneself with highly complex functions in the rendering contexts, one would do well to explore other templating solutions.

Other notes on compatibility

Ashes has been tested extensively on Python 2.7, as well as 2.6, 3.2, 3.3, and PyPy.

Things to watch out for