Added support for macro pulling and documented certain design decisions.
This commit is contained in:
parent
ab54924186
commit
190059c8f0
6 changed files with 187 additions and 1 deletions
|
|
@ -207,3 +207,5 @@ Template Rendering
|
||||||
.. autofunction:: render_template
|
.. autofunction:: render_template
|
||||||
|
|
||||||
.. autofunction:: render_template_string
|
.. autofunction:: render_template_string
|
||||||
|
|
||||||
|
.. autofunction:: get_template_attribute
|
||||||
|
|
|
||||||
147
docs/design.rst
Normal file
147
docs/design.rst
Normal file
|
|
@ -0,0 +1,147 @@
|
||||||
|
Design Decisions in Flask
|
||||||
|
=========================
|
||||||
|
|
||||||
|
If you are curious why Flask does certain things the way it does and not
|
||||||
|
different, this section is for you. This should give you an idea about
|
||||||
|
some of the design decisions that may appear arbitrary and surprising at
|
||||||
|
first, especially in direct comparison with other frameworks.
|
||||||
|
|
||||||
|
|
||||||
|
The Explicit Application Object
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
A Python web application based on WSGI has to have one central callable
|
||||||
|
object that implements the actual application. In Flask this is an
|
||||||
|
instance of the :class:`~flask.Flask` class. Each Flask application has
|
||||||
|
to create an instance of this class itself and pass it the name of the
|
||||||
|
module, but why can't Flask do that itself?
|
||||||
|
|
||||||
|
Without such an explicit application object the following code::
|
||||||
|
|
||||||
|
from flask import Flask
|
||||||
|
app = Flask(__name__)
|
||||||
|
|
||||||
|
@app.route('/')
|
||||||
|
def index():
|
||||||
|
return 'Hello World!'
|
||||||
|
|
||||||
|
Would look like this instead::
|
||||||
|
|
||||||
|
from hypothetical_flask import route
|
||||||
|
|
||||||
|
@route('/')
|
||||||
|
def index():
|
||||||
|
return 'Hello World!'
|
||||||
|
|
||||||
|
There are three major reasons for this. The most important one is that
|
||||||
|
implicit application objects require that there may only be one class at
|
||||||
|
the time. There are ways to fake multiple application with a single
|
||||||
|
application object, like maintaining a stack of applications, but this
|
||||||
|
causes some problems I won't outline here in detail. Now the question is:
|
||||||
|
when does a microframework need more than one application at the same
|
||||||
|
time? A good example for this is unittesting. When you want to test
|
||||||
|
something it can be very helpful to create a minimal application to test
|
||||||
|
specific behavior. When the application object is deleted everything it
|
||||||
|
allocated will be freed again.
|
||||||
|
|
||||||
|
Another thing that becomes possible with having an explicit object laying
|
||||||
|
around in your code is that you can subclass the base class
|
||||||
|
(:class:`~flask.Flask`) to alter specific behaviour. This would not be
|
||||||
|
possible without hacks if the object was created ahead of time for you
|
||||||
|
based on a class that is not exposed to you.
|
||||||
|
|
||||||
|
But there is another very important reason why Flask depends on an
|
||||||
|
explicit instanciation of that class: the package name. Whenever you
|
||||||
|
create a Flask instance you usually pass it `__name__` as package name.
|
||||||
|
Flask depends on that information to properly load resources relative
|
||||||
|
to your module. With Python's outstanding support for reflection it can
|
||||||
|
then access the package to figure out where the templates and static files
|
||||||
|
are stored (see :meth:`~flask.Flask.open_resource`). Now obviously there
|
||||||
|
are frameworks around that do not need any configuration and will still be
|
||||||
|
able to load templates relative to your application module. But they have
|
||||||
|
to use the current working directory for that, which is a very unreliable
|
||||||
|
way to determine where the application is. The current working directory
|
||||||
|
is process-wide and if you are running multiple applications in one
|
||||||
|
process (which could happen in a webserver without you knowing) the paths
|
||||||
|
will be off. Worse: many webservers do not set the working directory to
|
||||||
|
the directory of your application but to the document root which does not
|
||||||
|
have to be the same folder.
|
||||||
|
|
||||||
|
The third reason is "explicit is better than implicit". That object is
|
||||||
|
your WSGI application, you don't have to remember anything else. If you
|
||||||
|
want to apply a WSGI middleware, just wrap it and you're done (though
|
||||||
|
there are better ways to do that so that you do not lose the reference
|
||||||
|
to the application object :meth:`~flask.Flask.wsgi_app`).
|
||||||
|
|
||||||
|
One Template Engine
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
Flask decides on one template engine: Jinja2. Why doesn't Flask have a
|
||||||
|
pluggable template engine interface? You can obviously use a different
|
||||||
|
template engine, but Flask will still configure Jinja2 for you. While
|
||||||
|
that limitation that Jinja2 is *always* configured will probably go away,
|
||||||
|
the decision to bundle one template engine and use that will not.
|
||||||
|
|
||||||
|
Template engines are like programming languages and each of those engines
|
||||||
|
has a certain understandment about how things work. On the surface they
|
||||||
|
all work the same: you tell the engine to evaluate a template with a set
|
||||||
|
of variables and take the return value as string.
|
||||||
|
|
||||||
|
But that's about where similarities end. Jinja2 for example has an
|
||||||
|
extensive filter system, a certain way to do template inheritance, support
|
||||||
|
for reusable blocks (macros) that can be used from inside templates and
|
||||||
|
also from Python code, is using unicode for all operations, supports
|
||||||
|
iterative template rendering, configurable syntax and more. On the other
|
||||||
|
hand an engine like Genshi is based on XML stream evaluation, template
|
||||||
|
inheritance by taking the availability of XPath into account and more.
|
||||||
|
Mako on the other hand treats templates similar to Python modules.
|
||||||
|
|
||||||
|
When it comes to bridge a template engine with an application or framework
|
||||||
|
there is more than just rendering templates. Flask uses Jinja2's
|
||||||
|
extensive autoescaping support for instance. Also it provides ways to
|
||||||
|
access macros from Jinja2 templates.
|
||||||
|
|
||||||
|
A template abstraction layer that would not take the unique features of
|
||||||
|
the template engines away is a science on its own and a too large
|
||||||
|
undertaking for a microframework like Flask.
|
||||||
|
|
||||||
|
|
||||||
|
Micro with Dependencies
|
||||||
|
-----------------------
|
||||||
|
|
||||||
|
Why does Flask call itself a microframework and yet it depends on two
|
||||||
|
libraries (namely Werkzeug and Jinja2). Why shouldn't it? If we look
|
||||||
|
over to the Ruby side of web development there we have a protocol very
|
||||||
|
similar to WSGI. Just that it's called Rack there, but besides that it
|
||||||
|
looks very much like a WSGI rendition for Ruby. But nearly all
|
||||||
|
applications in Ruby land do not work with Rack directly, but on top of a
|
||||||
|
lirbary with the same name. This Rack library has two equivalents in
|
||||||
|
Python: WebOb (formerly Paste) and Werkzeug. Paste is still around but
|
||||||
|
from my understanding it's sortof deprecated in favour of WebOb. The
|
||||||
|
development of WebOb and Werkzeug started side by side with similar ideas
|
||||||
|
in mind: be a good implementation of WSGI for other applications to take
|
||||||
|
advantage.
|
||||||
|
|
||||||
|
Flask is a framework that takes advantage of the work already done by
|
||||||
|
Werkzeug to properly interface WSGI (which can be a complex task at
|
||||||
|
times). Thanks to recent developments in the Python package
|
||||||
|
infrastructure, packages with depencencies are no longer an issue and
|
||||||
|
there are very few reasons against having libraries that depend on others.
|
||||||
|
|
||||||
|
|
||||||
|
Thread Locals
|
||||||
|
-------------
|
||||||
|
|
||||||
|
Flask uses thread local objects (context local objects in fact, they
|
||||||
|
support greenlet contexts as well) for request, session and an extra
|
||||||
|
object you can put your own things on (:data:`~flask.g`). Why is that and
|
||||||
|
isn't that a bad idea?
|
||||||
|
|
||||||
|
Yes it is usually not such a bright idea to use thread locals. They cause
|
||||||
|
troubles for servers that are not based on the concept of threads and make
|
||||||
|
large applications harder to maintain. However Flask is just not designed
|
||||||
|
for large applications or asyncronous servers. Flask wants to make it
|
||||||
|
quick and easy to write a traditional web application.
|
||||||
|
|
||||||
|
Also see the :ref:`becomingbig` section of the documentation for some
|
||||||
|
inspiration for larger applications based on Flask.
|
||||||
|
|
@ -43,6 +43,7 @@ web development.
|
||||||
patterns/index
|
patterns/index
|
||||||
deploying
|
deploying
|
||||||
becomingbig
|
becomingbig
|
||||||
|
design
|
||||||
|
|
||||||
Reference
|
Reference
|
||||||
---------
|
---------
|
||||||
|
|
|
||||||
31
flask.py
31
flask.py
|
|
@ -120,6 +120,27 @@ def url_for(endpoint, **values):
|
||||||
return _request_ctx_stack.top.url_adapter.build(endpoint, values)
|
return _request_ctx_stack.top.url_adapter.build(endpoint, values)
|
||||||
|
|
||||||
|
|
||||||
|
def get_template_attribute(template_name, attribute):
|
||||||
|
"""Loads a macro (or variable) a template exports. This can be used to
|
||||||
|
invoke a macro from within Python code. If you for example have a
|
||||||
|
template named `_foo.html` with the following contents:
|
||||||
|
|
||||||
|
.. sourcecode:: html+jinja
|
||||||
|
|
||||||
|
{% macro hello(name) %}Hello {{ name }}!{% endmacro %}
|
||||||
|
|
||||||
|
You can access this from Python code like this::
|
||||||
|
|
||||||
|
hello = get_template_attribute('_foo.html', 'hello')
|
||||||
|
return hello('World')
|
||||||
|
|
||||||
|
:param template_name: the name of the template
|
||||||
|
:param attribute: the name of the variable of macro to acccess
|
||||||
|
"""
|
||||||
|
return getattr(current_app.jinja_env.get_template(template_name).module,
|
||||||
|
attribute)
|
||||||
|
|
||||||
|
|
||||||
def flash(message):
|
def flash(message):
|
||||||
"""Flashes a message to the next request. In order to remove the
|
"""Flashes a message to the next request. In order to remove the
|
||||||
flashed message from the session and to display it to the user,
|
flashed message from the session and to display it to the user,
|
||||||
|
|
@ -626,10 +647,18 @@ class Flask(object):
|
||||||
|
|
||||||
def wsgi_app(self, environ, start_response):
|
def wsgi_app(self, environ, start_response):
|
||||||
"""The actual WSGI application. This is not implemented in
|
"""The actual WSGI application. This is not implemented in
|
||||||
`__call__` so that middlewares can be applied:
|
`__call__` so that middlewares can be applied without losing a
|
||||||
|
reference to the class. So instead of doing this::
|
||||||
|
|
||||||
|
app = MyMiddleware(app)
|
||||||
|
|
||||||
|
It's a better idea to do this instead::
|
||||||
|
|
||||||
app.wsgi_app = MyMiddleware(app.wsgi_app)
|
app.wsgi_app = MyMiddleware(app.wsgi_app)
|
||||||
|
|
||||||
|
Then you still have the original application object around and
|
||||||
|
can continue to call methods on it.
|
||||||
|
|
||||||
:param environ: a WSGI environment
|
:param environ: a WSGI environment
|
||||||
:param start_response: a callable accepting a status code,
|
:param start_response: a callable accepting a status code,
|
||||||
a list of headers and an optional
|
a list of headers and an optional
|
||||||
|
|
|
||||||
|
|
@ -197,6 +197,12 @@ class Templating(unittest.TestCase):
|
||||||
'<p>Hello World!'
|
'<p>Hello World!'
|
||||||
]
|
]
|
||||||
|
|
||||||
|
def test_macros(self):
|
||||||
|
app = flask.Flask(__name__)
|
||||||
|
with app.test_request_context():
|
||||||
|
macro = flask.get_template_attribute('_macro.html', 'hello')
|
||||||
|
assert macro('World') == 'Hello World!'
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|
|
||||||
1
tests/templates/_macro.html
Normal file
1
tests/templates/_macro.html
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
{% macro hello(name) %}Hello {{ name }}!{% endmacro %}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue