-
Notifications
You must be signed in to change notification settings - Fork 0
/
sanja.py
310 lines (209 loc) · 8.89 KB
/
sanja.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
# -*- coding: utf-8 -*-
"""This module aims to make bringing Jinja templates to Sanic to be easy.
It is entirelly up to You
how You will configure your Jinja template environment.
Great freedom :-)
# First You have to configure Your Sanic app so it holds Jinja template environment instance.
app = sanic.Sanic("Some app")
sanja_conf_app(
app,
# There go normal parameters like for jijna2.Environment(),
# for example:
auto_reload=True,
loader=jinja2PrefixLoader({
'some_package': jinja_PackageLoader("some_package")}))
By default this Jinja template environment is held in:
app.config['JINJA_ENV']
so equaly well You could do:
app.config['JINJA_ENV'] = jinja2.Environment(
auto_reload=True,
loader=jinja2PrefixLoader({
'some_package': jinja_PackageLoader("some_package")}))
But if You wish to change it,
or have more then one Jinja template environment,
then just:
sanja_conf_app(
app,
jinja_template_env_name="JINJA_ENV_2",
...)
or:
app.config['JINJA_ENV_2'] = jinja2.Environment(
...)
# Then You can just use Jinja rendering and Sanic response.
To do so simply decorate your request handler,
for example:
@app.route("/some/url")
@sanja.render("some_package/some_template.html.jinja", "html")
async def some_view(request):
...
return {'jijna': "context"}
* some_view function has to return jinja "context" instance.
* In first sanja.render() parameter provide jinja template.
(In this example it has to be under
some_package/templates/some_template.html.jinja
because it is how we configured here jinja template environment,
so having any issues please refer to Jinja documentation).
* Second sanja.render() parameter coresponds to sanic.response "kind",
so if doubts plese refer to Sanic documentation.
Possible values are:
* "text"
* "html"
* "json"
* "raw"
And if You want to use other Jinja template environment,
that You have configured before,
you can do so:
@app.route("/some/url2")
@sanja.render("some_package/some_template.html.jinja", "html", jinja_template_env_name="JINJA_ENV_2")
async def some_view(request):
...
return {'jijna': "context"}
# Yo can also use it for Class-Based Views.
In decorators class variable:
class YourView(sanic.views.HTTPMethodView):
decorators = [sanja.render(...)]
...
or per http method:
class YourView(sanic.views.HTTPMethodView):
@sanja.render(...)
async def get(self, request):
...
...
# NOTE for json format.
In order to return json format,
your template should be either hand crafted proper json, e.g:
{
"some": "thing"
}
or e.g whole template can be just one variable turned into json like:
{{some_variable|tojson}}
# SENDING ADDITIONAL CONTEXT TO TEMPLATES.
## Sending template context to template.
You may find usefull to have access to template context from within template.
To enable it for all templates (generated by particular jinja env),
just provide:
update_templates_with_its_context=True
to conf\_app() function.
To enable it for particular generated template,
just provide:
update_template_with_its_context=True
to @render() decorator.
Then in template You can access all template context with:
{{get_template_context()}}
or ask for value for just some specific key:
{{get_template_contex("key")}}
(None is returned if key is not in template context).
## Sending any extra context to template.
To send e.g.: x=5 to all templates (generated by particular jinja env),
just provide:
update_jinja_env_globals_with={'x': 5}
to conf\_app() function.
To send e.g.: x=5 to particular generated template,
just provide:
update_template_globals_with={'x': 5}
to @render() decorator.
Then in template You can access it with:
{{x}}"""
from functools import wraps
from jinja2 import Environment as jinja_Environment, \
contextfunction as jinja_contextfunction
from jinja2.nativetypes import NativeEnvironment as jinja_NativeEnvironment
from sanic.response import text as sanic_response_text, \
html as sanic_response_html, \
json as sanic_response_json, \
raw as sanic_response_raw
from ujson import loads as ujson_loads
__version__ = "1.0.5"
__author__ = "tomaszdrozdz"
__author_email__ = "tomasz.drozdz.1@protonmail.com"
@jinja_contextfunction
def get_jinja_template_context(context, key=None):
context = {k: '"Is callable"' if callable(v) else v
for k, v in context.items()
if k not in ["range", "dict", "lipsum", "cycler", "joiner", "namespace"]}
if key:
return context.get(key, None)
else:
return context
def conf_app(app,
jinja_template_env_name="JINJA_ENV",
update_templates_with_its_context=False,
update_jinja_env_globals_with={},
*args, **kwargs):
"""Create Jinja template environment, and srotes it in sanic app.config.
Parameters
----------
app: sanic app instance
jinja_template_env_name: str, optional
jinja template environment instance will be held under
app.config[jinja_template_env_name].
This also coresponds to
jinja_template_env_name in render() function.
update_templates_with_its_context: bool, default False
If True then templates context can be accesed from within them
by using get_template_contex("optional: some_key").
update_jinja_env_globals_with: dict: default {}
Provide extra context for templates.
E.g when set to {'x': 5} then every template can access it with: {{x}}.
*args, **kwargs:
are just jijna2.Environment() parameters.
Returns
-------
created Jinja environment template instance."""
jinja_template_environment = jinja_Environment(*args, **kwargs)
if update_templates_with_its_context:
jinja_template_environment.globals.update({'get_template_context': get_jinja_template_context})
jinja_template_environment.globals.update(update_jinja_env_globals_with)
app.config[jinja_template_env_name] = jinja_template_environment
return jinja_template_environment
def render(template,
render_as,
update_template_with_its_context=False,
update_template_globals_with={},
jinja_template_env_name='JINJA_ENV'):
"""Decorator for Sanic request handler,
that turns it into function returning generated jinja template,
as sanic response.
Decorated function (or method) has to return jinja "context" instance.
Parameters:
-----------
template: str
jinja template name.
render_as: str
corresponds to sanic.response "kind".
Possible valuse are: "text", "html", "json", "raw".
update_template_with_its_context: bool, default False
If True then template context can be accesed from within it
by using get_template_contex("optional: some_key").
update_template_globals_with: dict: default {}
Provide extra context for template.
E.g when set to {'x': 5} then template can access it with: {{x}}.
jinja_template_env_name: str, optional
Where jinja template environment instance is held in
app.config.
This coresponds to
jinja_template_env_name in conf_app() function."""
_sanic_responses = {
'text': sanic_response_text,
'html': sanic_response_html,
'json': sanic_response_json,
'raw': sanic_response_raw }
def _decorator(to_decorate):
@wraps(to_decorate)
async def _decorated(request):
_jinja_env = request.app.config[jinja_template_env_name]
template_context = await to_decorate(request)
template_renderer = _jinja_env.get_template(template)
if update_template_with_its_context:
template_renderer.globals.update({'get_template_context': get_jinja_template_context})
template_renderer.globals.update(update_template_globals_with)
if _jinja_env.enable_async:
rendered_template = await template_renderer.render_async(template_context)
else:
rendered_template = template_renderer.render(template_context)
if render_as == "json" \
and not isinstance(_jinja_env, jinja_NativeEnvironment):
rendered_template = ujson_loads(rendered_template)
return _sanic_responses[render_as](rendered_template)
return _decorated
return _decorator