An open API service indexing awesome lists of open source software.

https://github.com/polydojo/qree

Tiny but mighty Python template engine.
https://github.com/polydojo/qree

interpolation python template template-engine templating

Last synced: about 2 months ago
JSON representation

Tiny but mighty Python template engine.

Awesome Lists containing this project

README

          

Qree
====

Qree (read 'Curie') is a tiny but mighty Python templating engine, geared toward HTML. 'Qree' is short for: *Q*uote, *r*eplace, *e*xec(), *e*val().

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*](https://www.sumukhbarve.com/build-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!:**
```py
qree.renderStr("

Hello, {{: data :}}", data="World!")
# Output:

Hello, World!


```

**2. Using Expressions:**
```py
qree.renderStr("

Mr. {{: data.title() + '!' :}}", data="bond")
# Output:

Mr. Bond!


```

**3. HTML Escaping:**
```py
qree.renderStr("Mr. {{: data :}}", data=" Villain ")
# Output: Mr. <b> Villain </b>
```

**4. Without Escaping:**
```py
qree.renderStr("Mr. {{= data =}}", data=" Villain ")
# Output: Mr. Villain
```

**5. Longer Example:**
```py
qree.renderStr("""

{{: data["title"].title() :}}

{{: data["title"].title() :}}


{{: data["body"] :}}

""", data={
"title": "Lorem Ipsum",
"body": "Lorem ipsum dolor sit amet, ... elit.",
})
```
\# Output:
```html

Lorem Ipsum

Lorem Ipsum


Lorem ipsum dolor sit amet, ... elit.

```

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`):**
```py
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`):**
```py
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.

```py
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:
```py
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` :
```html

{{: data['title'] :}}

{{: data['title'] :}}


{{: data['body'] :}}

```

In `app.py`, you could have the following snippet:
```py
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:
```py
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:
```html

The TITLE Goes Here!

The TITLE Goes Here!


And the body goes here ...

```

Quick Plug
--------------
Qree built and maintained by the folks at [Polydojo, Inc.](https://www.polydojo.com/), led by [Sumukh Barve](https://www.sumukhbarve.com/). If your team is looking for a simple project management tool, please check out our latest product: [**BoardBell.com**](https://www.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`:
```html

Link 1
Link 2
Link 3

```

And similarly, `footer.html`:
```html

Link A
Link B
Link C

```

Now, you can use `header.html` and `footer.html` in `homepage.html`:
```html
@= import qree;
@= import qree;

{{: data['title'] :}}

{{= qree.renderPath("./test-views/header.html", data=None) =}}

{{: data['title'] :}}


{{: data['body'] :}}

{{= qree.renderPath("./test-views/footer.html", data=None) =}}

```

And, as before, the snippet in `app.py`:
```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:
```html

... TITLE 2 ...

Link 1
Link 2
Link 3

... TITLE 2 ...


... BODY 2 ...

Link A
Link B
Link C

```

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 `:}}`**
```py
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 `@`)**
```py
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.
```py
{ "@=": "@=",
"@{": "@{",
"@}": "@}",
"{{=": "{{=",
"=}}": "=}}",
"{{:": "{{:",
":}}": ":}}",
}
```

View Decorator
--------------------
If you're working with [Flask](https://flask.palletsprojects.com/), [Bottle](https://bottlepy.org/) or a similar WSGI framework, `qree.view` can help bind route to templates.
```py
@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:
```py
@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](https://github.com/polydojo/vilo) instead of Flask/Bottle? Great choice! Qree jives with Vilo:
```py
@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](https://github.com/polydojo/qree/blob/master/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.