Browse Source

Added support for macro pulling and documented certain design decisions.

pull/1638/head
Armin Ronacher 15 years ago
parent
commit
190059c8f0
  1. 2
      docs/api.rst
  2. 147
      docs/design.rst
  3. 1
      docs/index.rst
  4. 31
      flask.py
  5. 6
      tests/flask_tests.py
  6. 1
      tests/templates/_macro.html

2
docs/api.rst

@ -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

@ -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.

1
docs/index.rst

@ -43,6 +43,7 @@ web development.
patterns/index patterns/index
deploying deploying
becomingbig becomingbig
design
Reference Reference
--------- ---------

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

6
tests/flask_tests.py

@ -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

@ -0,0 +1 @@
{% macro hello(name) %}Hello {{ name }}!{% endmacro %}
Loading…
Cancel
Save