Last active
January 3, 2016 05:39
-
-
Save aliles/8417271 to your computer and use it in GitHub Desktop.
Technical proof-of-concept for using Jinja2 as a template language for API endpoints.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
"""Proof of concept for adding external actions to Jinja2 templates | |
Demonstrates how control may be passed out of Jinja2 where additional | |
processing could occur and a value be passed back. Targetted use case is using | |
Jinja2 as a templating language for API endpoints. | |
""" | |
import os | |
import random | |
import string | |
import sys | |
import jinja2 | |
import jinja2.ext | |
import requests | |
# Example of Jinja2 template code. | |
SOURCE = """{% set email = '[email protected]' %} | |
{% set url = 'http://haveibeenpwned.com/api/breachedaccount/' + email|urlencode %} | |
{% http get url into result as json %} | |
{% for site in result %} | |
{{ site }} | |
{% endfor %} | |
""" | |
class HTTPExtension(jinja2.ext.Extension): | |
"""HTTP method extension for Jinja2 templates. | |
{% http METHOD URL into NAME as TYPE %} | |
Defers control out of template to the calling scope for exection of HTTP | |
request. A Mutable object is also passed out to allow external scope to | |
pass result back into template context. | |
""" | |
tags = set(['http']) | |
mutable = type('Mutable', (object,), {'__slots__': ('value',)}) | |
def __init__(self, environment): | |
super(HTTPExtension, self).__init__(environment) | |
def parse(self, parser): | |
proxy = "_" + "".join(random.choice(string.letters) for i in range(16)) | |
lineno = parser.stream.next().lineno | |
method = parser.stream.expect('name').value | |
url = parser.parse_expression() | |
_ = parser.stream.expect('name:into') | |
target = parser.stream.next().value | |
_ = parser.stream.expect('name:as') | |
content = parser.stream.expect('name').value | |
return [ | |
# Creates new mutable proxy object inside template context. | |
jinja2.nodes.Assign( | |
jinja2.nodes.Name(proxy, 'store'), | |
jinja2.nodes.Call( | |
self.attr('mutable', lineno=lineno), | |
[], [], None, None)).set_lineno(lineno), | |
# Pass control out to calling scope. | |
jinja2.nodes.CallBlock( | |
self.call_method('action', [ | |
jinja2.nodes.Name(proxy, 'load'), | |
jinja2.nodes.Const(method), | |
url, | |
jinja2.nodes.Const(content) | |
]), [], [], []).set_lineno(lineno), | |
# Retrives value and assigns it to final result target name. | |
jinja2.nodes.Assign( | |
jinja2.nodes.Name(target, 'store'), | |
jinja2.nodes.Getattr( | |
jinja2.nodes.Name(proxy, 'load'), 'value', 'load') | |
).set_lineno(lineno) | |
] | |
def action(self, proxy, method, url, content, caller): | |
def closure(): | |
request = getattr(requests, method) | |
response = request(url) | |
data = getattr(response, content) | |
if callable(data): | |
data = data() | |
proxy.value = data | |
return closure | |
if __name__ == '__main__': | |
# Create Environment, with the HTTP extension, and load sample template | |
# code. | |
env = jinja2.Environment(trim_blocks=True, lstrip_blocks=True, | |
extensions=[HTTPExtension]) | |
tmpl = env.from_string(SOURCE) | |
# Iteratively process the template in blocks using the template generator. | |
# If the generator returns a callable, call it. Otherwise, print the | |
# object to standard out. | |
for event in tmpl.generate(): | |
if callable(event): | |
event() | |
else: | |
sys.stdout.write(event) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment