GithubHelp home page GithubHelp logo

mahmoud / ashes Goto Github PK

View Code? Open in Web Editor NEW
55.0 5.0 9.0 499 KB

⚱️ Lightweight, self-contained templating for Python 2 and 3, a la Dust templates

Home Page: http://ashes.readthedocs.org

License: Other

Python 97.31% AGS Script 0.02% HTML 2.67%
templating python dust dustjs dust-templates ashes

ashes's Introduction

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:

  • caching/loading the template's AST
  • caching/loading the template's generated python function (as a string)
  • caching/loading the template's generated python function (as a code object)
  • caching/loading the template's generated python function (as a function)

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:

  • LOAD the Template ** Load the file
  • COMPILE the Template ** Parse the contents into an AST ** Convert the AST into a Python function string ** Compile the Python string into a code object ** Exec the code object into a function
  • RENDER the Template

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.

  • Template.to_ast/Template.from_ast. These methods of the Template class will allow templates to be cached in their native AST format. On average, this will save about 35% of Ashes overhead vs the baseline performance. Data in this format is extremely safe to cache, because it is merely pre-processed.

  • Template.to_python_string/Template.from_python_string. These methods of the Template class will allow templates to be cached as the Python functions that Ashes generates. These strings can be cached as-is. This is an efficient way to cache the data - depending on the templates this will save around 65-80 % of the overhead. Note: Data in this format is not necessarily safe to cache externally, because it will be compiled and run through exec. If your cache is compromised, arbitrary code can be executed.

  • Template.to_python_code/Template.from_python_code. These methods of the Template class will allow templates to be cached as the Python code objects that Ashes generates. Python code objects can be (de)serialized using the marshal package in the standard library. This is the most efficient way to cache the data - depending on the templates this will save around 92-94% of the overhead. Note: Data in this format is not necessarily safe to cache externally, because it will be run through exec. If your cache is compromised, arbitrary code can be executed.

  • Template.to_python_func/Template.from_python_func. This allows you to pregenerate and cache (within a process) the python functions that Ashes generates for each template. This is an unbelievably efficient way to cache the data - Ashes will only be rendering the templates, saving around 95-98% of the overhead from the baseline version.

  • ashes.python_string_to_code generates a python code object from an ashes python code string.

  • ashes.python_string_to_function generates a python function from an ashes python code string.

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 uses a different parsing mechanism than dust, so on a few rare corner cases, like comments placed within string literals, the template behavior might differ.

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

  • Accidentally passing functions in as values to a template. Dust/Ashes calls all functions referenced by the template. If the function isn't of the right signature, you may see a TypeError.

  • Leaving optimization enabled, especially when JavaScript is embedded in the page. Dust-style optimization is meant for HTML/XML and is nowhere near as smart as JavaScript/CSS minification suites. For example, a mixed-mode page (HTML/JS/CSS all in one page) may appear to work fine until the addition of a '//' single-line comment. When Dust/Ashes turns this into a single line, the page from that point on is treated as one long comment.

ashes's People

Contributors

atdt avatar djrobstep avatar jvanasco avatar mahmoud avatar markrwilliams avatar plowman avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

ashes's Issues

Add @contextDump (or equivalent debugging capabilities)

Opened on behalf of @jvanasco, based on a comment on #4:

"I'd love to see the @contextDump helper extension. debugging is possible , but a bit of a pain , without it."

I'm curious, what aspects of @contextDump are most useful for you? Just seeing the rendering context/model (whatever it's called, the dictionary of data passed in)? Or do you want to see the codegen'd code and everything, like LinkedIn's apparently supports with key="full" and so forth. Personally I feel the latter is a bit overkill, but could see some simpler functionality as convenient.

Question: any way to extend the library?

I'm very new to this library and the first question I have in mind (you may find it trivial): is there any flexible way to extend the list of features from https://github.com/mahmoud/ashes#compatibility?

More specifically, I am thinking about a way to inject external functions. If comfortable with the context, something similar to the Jinja filters. E.g.:

context.register_feature('virtual_name', feature_function_name)

And use the feature as virtual_name inside the template.

Is this currently possible or on the roadmap?

Thanks,
Mircea

Nested lists broken

Trying to template a nested list as follows:

{#.}
row{~n}
{#.}
{.}{~n}
{/.}
{/.}

Given data of say: [[1,2], [3,4]]

You'd expect:

row
1
2
row
3
4

as output, but instead, this happens:

Traceback (most recent call last):
  ...
  File ".../local/lib/python2.7/site-packages/ashes.py", line 1906, in render
    return tmpl.render(model, self)
  File ".../local/lib/python2.7/site-packages/ashes.py", line 1780, in render
    self.render_chunk(chunk, Context.wrap(env, model)).end()
  File ".../local/lib/python2.7/site-packages/ashes.py", line 1787, in render_chunk
    return self.render_func(chunk, context)
  File "<string>", line 11, in render
  File "<string>", line 4, in body_0
  File ".../local/lib/python2.7/site-packages/ashes.py", line 1572, in section
    head.pop('$len', None)
TypeError: pop() takes at most 1 argument (2 given)

Bug - Ashes doesn't respect kw args in partials

in the example below, kwargs do not transfer from parent to child.

01_parent.dust

<!-- 01_parent.dust -->
<h1>01_parent</h1>
    {>"02_child.dust" animal="dog" name="bear"/}
<!-- /01_parent.dust -->

02_child.dust

<!-- 02_child.dust -->
<h1>02_child</h1>
    animal={animal}
    name={name}
<!-- /02_child.dust -->

here's the same thing in javascript using the dust library

var parent = '<!-- 01_parent_dust --><h1>01_parent</h1>{>"02_child_dust" animal="dog" name="bear"/}<!-- /01_parent_dust -->';
var child = '<!-- 02_child_dust --><h1>02_child</h1>animal={animal} name={name}<!-- /02_child_dust -->';

dust.loadSource(dust.compile(parent, '01_parent_dust'));
dust.loadSource(dust.compile(child, '02_child_dust'));

dust.render("01_parent_dust", {}, function(err, out) {
    console.log(out);
});

i'm not sure how/where to fix this.

[feature request] Make the start and end delimeters of a template token customizeable

In templates, it seems ashes hardcodes the delimiter to be opening and closing curly brackets. It think it would be a small but useful feature to have any arbitrary string as opening and closing delimiter.

I.e. if I have a template file:

Hello, {[(world)]}

I think it would be useful to do:

ashes --template mytmplt.txt -M '{ "world": "People" }' --opening-delimeter '{[(' --closing-delimiter  ')]}'

And get

Hello, People

Feature Request - Env-wide Global Variables

I've been having trouble trying to ensure some 'global' variables will always be available to templates. When using partials and context / kwargs, they are lost.

The javascript version supports this through an implementation detail -- javascript just-so-happens to have global variables, and will look there. The docs/wiki of the LinkedIn fork mentions this quite a bit.

It would be great if the AshesEnv class and/or render commands would support a globals dict.

Support partial params

It would be great to do this:

{>"tab-navs.dust" activeTab="topic"/}
{>"base-layout.dust" pageClass="page-home" noSidebar="on"/}

URL encoding error

Code:

ashesenv.register_source('url', '{q|u}')
ashesenv.render('url', {'q': u'中文'})

Output:

KeyError: u'\u4e2d'

[feature request] split out/remove bottle code

It would be great if the bottle code were isolated away (or controlled by an environment variable)

These lines are what I'm referring to.

https://github.com/mahmoud/ashes/blob/master/ashes.py#L2129-L2176

It would be nice if the bottle import could be split out to another file, or at least suppressed with an environment variable. We have bottle installed in our virtualenv for some specific services in a deployment (unrelated to an ashes process); having a dozen uswgi workers (re)start with an unnecessary 5.5MB load and memory hit is a bit annoying.

@gt/@lt bug

Code

ashesenv.render('template.dust', { 'count': 10 })

Template

{@gt key=count value=0}
expected
{:else}
unexpected
{/gt}

{@gt key=count value=1}
expected
{:else}
unexpected
{/gt}

{@gt key=count value=2}
expected
{:else}
unexpected
{/gt}

Output

expected
expected
unexpected

Howto reference python functions in templates?

Apologies if I've missed this in the docs but what are my options for calling plain old python functions directly from the templates? Is this easy/possible?

The equivalent of something like: { get_current_username() }

worrisome version numbering

i just did an update and 0.7.6 jumped to 15.0.0 on PyPi

same thing on the github tag.

there's no changelog or explanation for this numbering (which is odd).

docs update or feature request - `load_all`

AshesEnv.load_all only returns the templates; it does not register them into the environment.

BaseAshesEnv.load_all actually loads the templates. it has a kwarg do_register=True, which then runs this bit:

if do_register:
    for t in tmpls:
        self.register(t)

I'd suggest changing AshesEnv.load_all to have the same functionality OR update the docstring to note that it doesn't actually load templates into the environment -- it just fetches them.

Docs - Note any custom helpers/extensions and link to javascript versions if applicable

I was about to write an iterate helper, but then noticed you had one in the source already (!)

I couldn't find any docs for it (aside from the source code) and I'm not sure what (if any) javascript implementations it is compatible with (there are several in the "issues" section of the active dust.js linkedin fork)

It would be great if this library's docs did 2 things:

  1. noted what helpers there are
  2. referenced any javascript implementations if available (since users often choose dust to work on client and server side)

see also - #22

consumption tracking

I've been playing around with 'consumption tracking' of ashes templates for a while, but haven't figured out a good approach yet. I figured I should post here and perhaps someone else has been working on this task as well...

What I would like to accomplish: tracking what objects/assets are accessed/consumed in our templates via ashes. We render in ashes and dust, so this would allow for passing the data structure for a given template through a "sieve" when rendering any templates client-side (i.e. Ashes can potentially reduce a 100k json payload to the 15k that will be used) and also help tailor the data population.

I originally tried to do this via the data objects passed in to ashes, but that can't handle alternate logic comparisons.

I imagine I can't be alone in having tried to optimize this bit... right?

Docs-- TemplateLoaders could use some inline docs ; perhaps a base class.

It took a bit more trial & error to figure out my issues with the TemplateLoaders.

I'd suggest switching the library to use a "BaseTemplateLoader" class , which has full inline documentation for whatever methods and arguments are supported, and then having the 2 other classes inherit from it. Basically, implement the Loaders the same way the AshesEnv is implemented -- but with a bit more docs.

The 'BaseAshesEnv' could also use some more docs around load/load_all/init. .

possible feature idea - cacheable templates

i have a novel idea for a feature, but it might not be possible. i wanted to "ask first" before trying to build it.

we use the dust format for user customizable templates. they're heavily sanitized and processed after submission, but are still User Generated Content. dust.js handles client side, ashes handles server side rendering.

performance is a bit of a concern. ashes is good enough for now, but we could do quite a bit better if we were able to cache the templates somehow. A decent chunk of the time is spent compiling the templates.

Since this is for user customization, we can't recycle the AshesEnv across requests. Because of the way Ashes builds templates, neither the env not the templates can be serialized ( pickle doesn't like the render functions, probably doesn't like a lot more ).

In order to support cachable templates, I see a few opportunities:

• cache the _gen_python string. This would save about 65% of building the template ( the 100% baseline is Template._get_render_func )
• cache the AST. This would save about 45% of time loading the template, but might be easier.

My general concerns before moving forward, are that I'm not sure if this is 'legal' or not. Could there be anything in the AST / python template that is specific to the current envelope or process ?

i don't think so. looking at the output of both, they seem doable -- but I don't have experience with the various filters and other advanced Dust techniques. we just it with basic dict/string/list.

PyPi version out of date.

Looks like the newest code has not been published on PyPi. Published ashes 0.4.0 is 1149 lines long while the one in github is 1495 lines.

Hopefully this will fix a bug where the {@eq} operator isn't working correctly. :-)

Opt-in errors when a parameter is missing, or an unused parameter is passed

I noticed that missing parameters are rendered as empty strings.

>>> ashes_env.register_source('hello', 'Hello, {name}!')
<Template name='hello'>
>>> ashes_env.render('hello', {})
'Hello, !'

It would be nice if it was possible to opt-in to errors. My use case is that I want to test that I am not passing the wrong parameters for a template.

Thanks in advance, I like the library.

two questions actually

  1. Your comment: "A quick example (which will work again soon):", are you referring to ashes.load throwing this error "TypeError: load() takes exactly 2 arguments (3 given)"? At least that is the behavior I am seeing. "ashes.load('app.dust', 'tmpl')" triggers the error.

  2. Have you integrated ashes into any simple web frameworks such as bottle.py, web.py, etc? I started down the path of writing an adaptor for bottle.py extending the BaseTemplate API. No success so far but so I wanted to check with you on the question above.

FYI, bottle.py template API info: https://bottle.readthedocs.org/en/latest/api.html#templates

Thanks man!
~marc

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.