import flask
import chameleon_rs.flask as chameleon
app = flask.Flask(__name__)
chameleon.configure(template_folder=template_folder, auto_reload=dev_mode)
@app.get('/')
@chameleon.template('home/index.pt')
def index():
return {'message': 'Hello', 'videos': ['intro', 'partials']}Flask and Quart
chameleon_rs.flask renders templates from Flask and Quart views. It has chameleon-flask’s API, so moving an app over is an import change, or no change at all with chameleon_rs.compat.install().
import chameleon_rs doesn’t import Flask. Only import chameleon_rs.flask does.
A first view
The view returns the template’s model, and @template renders it into a flask.Response:
client = app.test_client()
print(client.get('/').text)<h1>Hello</h1>
<ul><li><a href="/videos/intro">intro</a></li>
<li><a href="/videos/partials">partials</a></li></ul>
configure() here is the same call as chameleon_rs.configure(), so it also tells render_partial where templates live.
Decorator order
@template goes below the route decorator. Written above it, Flask registers the bare view, the template never renders and the route quietly returns the view’s dict as JSON. Give the app to configure() and it checks before its first request (Quart: when it starts serving):
chameleon.configure(template_folder=template_folder, app=app)
@chameleon.template('home/index.pt') # wrong: above the route
@app.get('/about')
def about():
return {'message': 'About', 'videos': []}
# The first request raises chameleon_rs.web.DecoratorOrderError:
# @template must be placed below the route decorator (@app.route, @app.get,
# @blueprint.route, ...) on about() at app.py:12 (route GET /about): ...Flask has no startup hook, so the check runs on the first request. Blueprints are included. chameleon.check_views(app) runs the same check on demand, for example in a test. Once the check passes, the app runs without it.
One pattern can’t be told from the mistake: a function routed raw on this app whose @template wrapper is used somewhere else (called from another view, or routed on another app). For such an app, pass check_order=False to configure() and keep the check in a test with chameleon.check_views(app, ignore=[that_function]).
What a view can return
- A model: a
dict, any other mapping, a dataclass instance (its fields, shallowly) or a Pydantic model (anything with a callablemodel_dump()). - A response: a
flask.Response, a Quart response or a werkzeug response passes through unchanged. Use it for redirects.
Anything else raises chameleon_rs.flask.FlaskChameleonException.
The @template decorator
@chameleon.template('home/index.pt') # a template path
@chameleon.template('feed.pt', content_type='application/xml') # or mimetype=
@chameleon.template('home/created.pt', status_code=201)
@chameleon.template # no argumentsA text type without a charset gets ; charset=utf-8 (the default is text/html; charset=utf-8), as Flask’s own render_template responses do. chameleon-flask sent the bare type.
With no path, it renders {module}/{function}.html, where {module} is the last part of the view’s module name. If there’s no .html file in the template folder, it uses {module}/{function}.pt. It checks once, when the view is decorated.
It works on sync views, Flask’s async views and Quart views alike.
Error pages
Inside a decorated view, not_found() and error() stop the view and render an error page:
app = flask.Flask(__name__)
@app.get('/videos/<name>')
@chameleon.template('home/index.pt')
def video(name):
if name != 'intro':
chameleon.not_found(path=flask.request.path) # renders errors/404.pt
return {'message': name, 'videos': [name]}
response = app.test_client().get('/videos/missing')
print(response.status_code, response.text)404 <h1>Not found: /videos/missing</h1>
not_found(template_file='errors/404.pt', **data)renders the page with status 404.error(template_file, status_code=500, **data)renders any error page.generic_error(template_file, status_code, template_data)is fastapi-chameleon’s name for error().
They raise chameleon_rs.web.NotFound and chameleon_rs.web.ErrorPage, which the decorator turns into the response.
The names given to @template, render(), response(), not_found() / error() and render_partial() are looked up as the original libraries look them up, which allows .. and absolute paths. Write them in your code; never build one from a request (a path or query parameter, a header). chameleon_rs.starlette.Templates, the Jinja drop-in, refuses names that reach outside its folder, as Jinja does.
Rendering without the decorator
html = chameleon.render('emails/welcome.pt', user=user) # a str
resp = chameleon.response('feed.pt', content_type='application/xml', items=items)render() and response() work anywhere, such as in a view that builds its own response or in a background job. Both raise FlaskChameleonException if nothing has been configured.
One configuration per app
Pass app= to give one Flask or Quart app its own template folder. It’s stored in app.extensions['chameleon_rs'] and used while that app handles a request. Other apps, and code outside a request, use the process-wide configuration. It also turns on the decorator-order check for that app.
chameleon.configure(template_folder='admin/templates', app=admin_app)Quart
The same module works with Quart, with no extra install:
import quart
import chameleon_rs.flask as chameleon
app = quart.Quart(__name__)
chameleon.configure(template_folder=template_folder)
@app.get('/')
@chameleon.template('home/index.pt')
async def index():
videos = await load_videos()
return {'message': 'Hello', 'videos': videos}An async view’s page renders with render_async: a model value that is awaitable (a coroutine, task or future) is awaited where the template uses it, without blocking the event loop. With no awaitable values the output is the same as render()’s.
Coming from chameleon-flask
| chameleon-flask | chameleon-rs |
|---|---|
global_init(folder, auto_reload=False, cache_init=True) |
Still works. It configures the views only, as before, and ignores a second call. configure() sets views and partials together, and every call applies. |
@template, render, response, not_found |
Same. not_found also takes the template as template_file=, as well as four04template_file=. |
| — | error(), any status code. |
FlaskChameleonException, FlaskChameleonNotFoundException |
Same, now subclasses of chameleon_rs.web.TemplateError and NotFound. except chameleon_rs.web.TemplateError catches them all. |
Views return a dict |
Also mappings, dataclasses and Pydantic models. |