diff --git a/docs/api.rst b/docs/api.rst index e3439393..5dcdfa95 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -207,3 +207,5 @@ Template Rendering .. autofunction:: render_template .. autofunction:: render_template_string + +.. autofunction:: get_template_attribute diff --git a/docs/design.rst b/docs/design.rst new file mode 100644 index 00000000..ae1fd8d0 --- /dev/null +++ b/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. diff --git a/docs/index.rst b/docs/index.rst index 7f53ef67..06d8a4e8 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -43,6 +43,7 @@ web development. patterns/index deploying becomingbig + design Reference --------- diff --git a/flask.py b/flask.py index 0564aca3..56ce49cc 100644 --- a/flask.py +++ b/flask.py @@ -120,6 +120,27 @@ def url_for(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): """Flashes a message to the next request. In order to remove the 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): """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) + Then you still have the original application object around and + can continue to call methods on it. + :param environ: a WSGI environment :param start_response: a callable accepting a status code, a list of headers and an optional diff --git a/tests/flask_tests.py b/tests/flask_tests.py index b9edd366..bb560712 100644 --- a/tests/flask_tests.py +++ b/tests/flask_tests.py @@ -197,6 +197,12 @@ class Templating(unittest.TestCase): '
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__': unittest.main() diff --git a/tests/templates/_macro.html b/tests/templates/_macro.html new file mode 100644 index 00000000..3460ae2e --- /dev/null +++ b/tests/templates/_macro.html @@ -0,0 +1 @@ +{% macro hello(name) %}Hello {{ name }}!{% endmacro %}