Skip to main content

Qree: Tiny but mighty Python templating.

Project description

Qree

Qree (read 'Curie') is a tiny but mighty Python templating engine, geared toward HTML. 'Qree' is short for: Quote, replace, exec(), eval().

The entire module is under 200 lines. Instead of using regular expressions or PEGs, Qree relies on Python's exec() and eval(). Thus, it supports all language features, out of the box. For more on Qree's internals, please see: Build Your Own Python Template Engine

!!! Warning: Do NOT render untrusted templates. As Qree uses eval(), rendering untrusted templates is equivalent to giving untrusted entities access to your entire systems.

Installation

pip install qree

Alternatively, just download qree.py into your project directory.

Text Interpolation

Use {{: expression :}} for HTML-escaped interpolation, or {{= expression =}} for interpolation without escaping. The latter is susceptible to XSS, so please be careful. Here are a few quick examples:

1. Hello, World!:

qree.renderStr("<h2>Hello, {{: data :}}", data="World!")
# Output: <h2>Hello, World!</h2>

2. Using Expressions:

qree.renderStr("<h2>Mr. {{: data.title() + '!' :}}", data="bond")
# Output: <h2>Mr. Bond!</h2>

3. HTML Escaping:

qree.renderStr("Mr. {{: data :}}", data="<b> Villain </b>")
# Output: Mr. &lt;b&gt; Villain &lt;/b&gt;

4. Without Escaping:

qree.renderStr("Mr. {{= data =}}", data="<b> Villain </b>")
# Output: Mr. <b> Villain </b>

5. Longer Example:

qree.renderStr("""
<!doctype html>
<html>
<head>
    <title>{{: data["title"].title() :}}</title>
</head>
<body>
    <h1>{{: data["title"].title() :}}</h1>
    <pre>{{: data["body"] :}}</pre>
</body>
</html>
""", data={
    "title": "Lorem Ipsum",
    "body": "Lorem ipsum dolor sit amet, ... elit.",
})

# Output:

<!doctype html>
<html>
<head>
    <title>Lorem Ipsum</title>
</head>
<body>
    <h1>Lorem Ipsum</h1>
    <pre>Lorem ipsum dolor sit amet, ... elit.</pre>
</body>
</html>

Python Code

Any line beginning with @= is treated as Python code. (Preceding whitespace is ignored.) You can write any code you wish, as Qree supports all language features. You can define variables, import modules, make assertions etc. For example:

Leap Year Detection (with lambda):

tplStr = """
@= isLeap = lambda n: (n % 400 == 0) or (n % 100 != 0 and n % 4 == 0)
@= isOrIsNot = "IS" if isLeap(data['year']) else "is NOT"
The year {{: data['year'] :}} {{: isOrIsNot :}} a leap year.
"""
qree.renderStr(tplStr, data={"year": 2000})
# Output: The year 2000 IS a leap year.

qree.renderStr(tplStr, data={"year": 2001})
# Output: The year 2001 is NOT a leap year.

Python Indentation

Python is an indented language. Use the special tags @{ and @} for respectively indicating indentation and de-indentation to Qree. When used, such a tag should appear by itself on a separate line, ignoring whitespace and trailing Python comments. For example:

Leap Year Detection (with def):

tplStr = """
@= def isLeap (n):
@{
    @= if n % 400 == 0: return True;
    @= if n % 100 == 0: return False;
    @= return n % 4 == 0;
@}
@= isOrIsNot = "IS" if isLeap(data['year']) else "is NOT"
The year {{: data['year'] :}} {{: isOrIsNot :}} a leap year.
"""
qree.renderStr(tplStr, data={"year": 2000})
# Output: The year 2000 IS a leap year.

qree.renderStr(tplStr, data={"year": 2001})
# Output: The year 2001 is NOT a leap year.

FizzBuzz Example
FizzBuzz is a popular programming assignment. The idea is to print consecutive numbers per line, but instead to print 'Fizz' for multiples of 3, 'Buzz' for multiples of 5, and 'FizzBuzz' for multiples of 3 and 5.

qree.renderStr("""
@= for n in range(1, data+1):
@{
    @= if n % 15 == 0:
    @{
        FizzBuzz
    @}
    @= elif n % 3 == 0:
    @{
        Fizz
    @}
    @= elif n % 5 == 0:
    @{
        Buzz
    @}
    @= else:
    @{
        {{: n :}}
    @}
@}
""", data=20)

# Output:

        1
        2
        Fizz
        4
        Buzz
        Fizz
        7
        8
        Fizz
        Buzz
        11
        Fizz
        13
        14
        FizzBuzz
        16
        17
        Fizz
        19
        Buzz

The data Variable

By default, data passed via the data parameter is available in the template as the data variable. However, if you'd like to change the variable name, you may do so via the variable parameter. For example:

qree.renderStr("Hi {{: name :}}!", data="Jim", variable="name")
# Output: Hi Jim!

Template Files

It's always convenient to store templates using separate files. To work with files, use qree.renderPath(tplPath, data, ...) instead of qree.renderStr(tplStr, data, ...).

Let's say you have the following directory structure:

- app.py
- qree.py
- views/
        - homepage.html

Here's homepage.html :

<doctype html>
<html>
<head><title>{{: data['title'] :}}</title></head>
<body>
    <h2>{{: data['title'] :}}</h2>
    <pre>{{: data['body'] :}}</pre>
</body>
<html>

In app.py, you could have the following snippet:

def serve_homepage ():
    return qree.renderPath("./views/homepage.html", data={
        "title": "The TITLE Goes Here!",
        "body": "And the body goes here ...",
    });

Which would be equivalent to:

def serve_homepage ():
    with open("./views/homepage.html", "r") as f:
        return qree.renderStr(f.read(), data={
            "title": "The TITLE Goes Here!",
            "body": "And the body goes here ...",
        });

In either case, the output would be:

<doctype html>
<html>
<head><title>The TITLE Goes Here!</title></head>
<body>
<h2>The TITLE Goes Here!</h2>
<pre>And the body goes here ...</pre>
</body>
<html>

Quick Plug

Qree built and maintained by the folks at Polydojo, Inc., led by Sumukh Barve. If your team is looking for a simple project management tool, please check out our latest product: BoardBell.com.

Template Nesting

Since templates can include any Python code, you can call qree.renderPath() from within a template! Consider the following directory structure:

- app.py
- qree.py
- views/
        - header.html
        - homepage.html
        - footer.html

With header.html:

<header class="header">
    <a href="/link1">Link 1</a>
    <a href="/link2">Link 2</a>
    <a href="/link3">Link 3</a>
</header>

And similarly, footer.html:

<footer class="footer">
    <a href="/linkA">Link A</a>
    <a href="/linkB">Link B</a>
    <a href="/linkC">Link C</a>
</footer>

Now, you can use header.html and footer.html in homepage.html:

@= import qree;
@= import qree;
<doctype html>
<html>
<head><title>{{: data['title'] :}}</title></head>
<body>
{{= qree.renderPath("./test-views/header.html", data=None) =}}
<h2>{{: data['title'] :}}</h2>
<pre>{{: data['body'] :}}</pre>
{{= qree.renderPath("./test-views/footer.html", data=None) =}}
</body>
<html>

And, as before, the snippet in app.py:

def serve_homepage ():
    return qree.renderPath("./views/homepage.html", data={
        "title": "The TITLE Goes Here!",
        "body": "And the body goes here ...",
    });

The output is:

<doctype html>
<html>
<head><title>... TITLE 2 ...</title></head>
<body>
<header class="header">
    <a href="/link1">Link 1</a>
    <a href="/link2">Link 2</a>
    <a href="/link3">Link 3</a>
</header>

<h2>... TITLE 2 ...</h2>
<pre>... BODY 2 ...</pre>
<footer class="footer">
    <a href="/linkA">Link A</a>
    <a href="/linkB">Link B</a>
    <a href="/linkC">Link C</a>
</footer>

</body>
<html>

In the above example, we explicitly passed data=None to each nested template. We could've passed any value. We could've even ignored the data parameter, as it defaults to None anyway.

Custom Tags (via tagMap)

Default tags like {{:, :}}, @=, etc. can each be customized via the tagMap parameter. Using tagMap, just supply your desired tag as the value against the default tag as key. A few examples follow:

1. [[: Square :]] Brackets Instead Of {{: Braces :}}

qree.renderStr(
    tplStr="Hello, [[: data.title().rstrip('!') + '!' :]]",
    data="world",
    tagMap = {
        "{{:": "[[:",
        ":}}": ":]]",
        "{{=": "[[=",   # <-- Not directly used in this example.
        "=}}": "=]]",   # <---^
})
# Output: Hello, World!

2. Percentage Sign For Code Blocks (% vs @)

tplStr = """
%= isLeap = lambda n: (n % 400 == 0) or (n % 100 != 0 and n % 4 == 0)
%= isOrIsNot = "IS" if isLeap(data['year']) else "is NOT"
The year {{: data['year'] :}} {{: isOrIsNot :}} a leap year.
"""
qree.renderStr(tplStr, data={"year": 2020}, tagMap = {
    "@=": "%=",
    "@{": "%{",   # <-- Not directly used in this example.
    "@}": "%}",   # <--^
})
# Output: The year 2020 IS a leap year.

Default tagMap: The default values for each of the tags is as specified in the dict below.

{   "@=": "@=",
    "@{": "@{",
    "@}": "@}",
    "{{=": "{{=", 
    "=}}": "=}}",
    "{{:": "{{:",
    ":}}": ":}}",
}

View Decorator

If you're working with Flask, Bottle or a similar WSGI framework, qree.view can help bind route to templates.

@app.route("/user-list")
@qree.view("./views/user-list.html", variable="userList")
def serve_userList ():
    userList = yourLogicHere();
    return userList;

The above is identical to the following:

@app.route("/user-list")
def serve_user_list_page ():
    userList = yourLogicHere();
    return qree.renderPath("./views/user-list.html",
        data=userList, variable="userList",
    );

Vilo:
Using Vilo instead of Flask/Bottle? Great choice! Qree jives with Vilo:

@app.route("GET", "/user-list")
@qree.view("./views/user-list.html", variable="userList")
def serve_userList (req, res):
    userList = yourLogicHere(req);
    return userList;

Custom Tags:
Like with qree.renderPath(.) and qree.renderStr(.), you can use custom tags with qree.view(.) by passing tagMap.

Testing & Contributing

Install pytest via pip install -U pytest. Run tests with:

pytest

If you encounter a bug, please open an issue on GitHub; but if you find a security vulnerability, please email security@polydojo.com instead.

If you'd like to see a new feature or contribute code, please open a GitHub issue. We'd love to hear from you! Suggestions and code contributions will always be appreciated, big and small.

Licensing

Copyright (c) 2020 Polydojo, Inc.

Software Licensing:
The software is released "AS IS" under the MIT license, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED. Kindly see LICENSE.txt for more details.

No Trademark Rights:
The above software licensing terms do not grant any right in the trademarks, service marks, brand names or logos of Polydojo, Inc.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

qree-0.0.4.tar.gz (8.9 kB view details)

Uploaded Source

Built Distribution

qree-0.0.4-py2.py3-none-any.whl (8.1 kB view details)

Uploaded Python 2 Python 3

File details

Details for the file qree-0.0.4.tar.gz.

File metadata

  • Download URL: qree-0.0.4.tar.gz
  • Upload date:
  • Size: 8.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: python-requests/2.24.0

File hashes

Hashes for qree-0.0.4.tar.gz
Algorithm Hash digest
SHA256 b3bf9b47baa217aabc3a357d24659357bd385426031cd065037613a4a975d453
MD5 fcfa5fc523a16822095ece2c04629d1b
BLAKE2b-256 55428ea078430c1fb3a5e58879908fd7127fba30b3a5644f04adbfa4bcf75392

See more details on using hashes here.

File details

Details for the file qree-0.0.4-py2.py3-none-any.whl.

File metadata

  • Download URL: qree-0.0.4-py2.py3-none-any.whl
  • Upload date:
  • Size: 8.1 kB
  • Tags: Python 2, Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: python-requests/2.24.0

File hashes

Hashes for qree-0.0.4-py2.py3-none-any.whl
Algorithm Hash digest
SHA256 d2e3fc8487959f628c6e3c0440248a780ec7a5fe7d0f3ec5f98cc539f7a27903
MD5 346ec7ba6f10ccf1878397743d299ddb
BLAKE2b-256 cf2b1ca104d9515c80829f1e53667d0b9704d1b729c9e79e4d685290afba2d75

See more details on using hashes here.

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page