Miniset¶
A Jinja2 template processor for interacting with an SQL engine.
(Forked from apache/superset and sripathikrishnan/jinjasql)
Requirements¶
- Python 3.9+
Installation¶
pip install mini-set
Basic Usage¶
First, import JinjaTemplateProcessor class from miniset and create a processor.
from miniset import JinjaTemplateProcessor
p = JinjaTemplateProcessor()
Next, call prepare_query method with a Jinja2 template and keyword arguments. Keyword arguments are passed to a Jinja2 context.
The method returns:
queryis the generated SQL query. Variables are replaced by%s.bind_paramsis a list of parameters corresponding to the%s.
query, bind_params = p.prepare_query(
"SELECT * FROM {{ table | sql_safe }} WHERE name = {{ name }} OR project_id IN {{ project_ids | where_in }}",
name="foo",
project_ids=[1, 2],
table="projects",
)
query
SELECT * FROM projects WHERE name = %s OR project_id IN (%s,%s)
bind_params
["foo", 1, 2]
Multiple Param Styles¶
A placeholder for a bind param can be specified in multiple ways.
format: where name =%s. This is the default.qmark: where name =?.numeric: where name =:1and last_name =:2.named: where name =:nameand last_name =:last_name.pyformat: where name =%(name)sand last_name =%(last_name)s.asyncpg: where name =$1and last_name =$2.
Note
You need to escape % by %% if you want to use % literal in your query with format. (e.g., column LIKE '%%blah%%')
Note
See Working Along With SQLAlchemy to use this library with SQLAlchemy v2.
You can pass the optional constructor argument param_style to control the style of query parameter.
p = JinjaTemplateProcessor(param_style="named")
In case of named and pyformat, prepare_query returns dict instead of list.
p = JinjaTemplateProcessor(param_style="named")
query, bind_params = p.prepare_query(
"SELECT * FROM {{ table | sql_safe }} WHERE name = {{ name }} OR project_id IN {{ project_ids | where_in }}",
name="foo",
project_ids=[1, 2],
table="projects",
)
query
SELECT * FROM projects WHERE name = :name_1 OR project_id IN (:where_in_2,:where_in_3)
bind_params
{"name_1": "foo", "where_in_2": 1, "where_in_3": 2}
Built-in Filters¶
Miniset provides the following built-in Jinja2 filters.
where_in¶
where_in filter builds a parenthesis list suitable for an IN expression.
SELECT * FROM projects WHERE project_id IN {{ project_ids | where_in }}
For example,
query, bind_params = p.prepare_query(
"SELECT * FROM projects WHERE project_id IN {{ project_ids | where_in }}",
project_ids=[1, 2, 3],
)
query
SELECT * FROM projects WHERE project_id IN (%s,%s,%s)
bind_params
[1, 2, 3]
sql_safe¶
Table and columns names are usually not allowed in bind params.
In such case, you can use sql_safe filter.
SELECT {{ column | sql_safe }} FROM {{ table | sql_safe }}
For example,
query, bind_params = p.prepare_query(
"SELECT {{ column | sql_safe }} FROM {{ table | sql_safe }}",
column="id",
table="projects",
)
query
SELECT id FROM projects
Warning
You have a responsibility to ensure that there is no SQL injection if you use sql_safe filter.
identifier¶
identifier filter quotes a value to make it a named object.
SELECT * from {{ table | identifier }}
You can use identifier_quote_character constructor argument to control the quote character for the identifier. (Defaults to ")
p = JinjaTemplateProcessor(identifier_quote_character="`")
query, bind_params = p.prepare_query(
"SELECT * FROM {{ table | identifier }}",
table="projects",
)
query
SELECT * FROM `projects`
Working Along With SQLAlchemy v2¶
You cannot use format and pyformat param styles with SQLAlchemy v2 because of this change.
Alternatively, you can use named, qmark, numeric and asyncpg.
named
p = JinjaTemplateProcessor(param_style="named")
query, bind_params = p.prepare_query(
"SELECT * FROM hero WHERE id = {{ id }}",
id=1,
)
with engine.connect() as conn:
res = conn.execute(text(query), bind_params)
others (qmark, numeric and asyncpg)
p = JinjaTemplateProcessor(param_style="qmark")
query, bind_params = p.prepare_query(
"SELECT * FROM hero WHERE id = {{ id }}",
id=1,
)
with engine.connect() as conn:
res = conn.exec_driver_sql(query, tuple(bind_params))