From 3b36bef2e6165bb4dad73d17f23ee1879e99f497 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Thu, 8 Apr 2010 19:03:15 +0200 Subject: [PATCH] Improved documentation, added a contextmanager for request binding --- docs/api.rst | 154 +++++++++++++++++++++++++++++++++- docs/conf.py | 7 +- examples/minitwit/minitwit.py | 4 +- flask.py | 61 +++++++++----- 4 files changed, 198 insertions(+), 28 deletions(-) diff --git a/docs/api.rst b/docs/api.rst index 19bd68b0..5ab86e7d 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -8,8 +8,158 @@ parts where Flask depends on external libraries, we document the most important right here and provide links to the canonical documentation. -General Structure ------------------ +Application Object +------------------ .. autoclass:: Flask :members: + +Incoming Request Data +--------------------- + +.. class:: request + + To access incoming request data, you can use the global `request` + object. Flask parses incoming request data for you and gives you + access to it through that global object. Internally Flask makes + sure that you always get the correct data for the active thread if you + are in a multithreaded environment. + + The request object is an instance of a :class:`~werkzeug.Request` + subclass and provides all of the attributes Werkzeug defines. This + just shows a quick overview of the most important ones. + + .. attribute:: form + + A :class:`~werkzeug.MultiDict` with the parsed form data from `POST` + or `PUT` requests. Please keep in mind that file uploads will not + end up here, but instead in the :attr:`files` attribute. + + .. attribute:: args + + A :class:`~werkzeug.MultiDict` with the parsed contents of the query + string. (The part in the URL after the question mark). + + .. attribute:: values + + A :class:`~werkzeug.CombinedMultiDict` with the contents of both + :attr:`form` and :attr:`args`. + + .. attribute:: cookies + + A :class:`dict` with the contents of all cookies transmitted with + the request. + + .. attribute:: stream + + If the incoming form data was not encoded with a known encoding (for + example it was transmitted as JSON) the data is stored unmodified in + this stream for consumption. For example to read the incoming + request data as JSON, one can do the following:: + + json_body = simplejson.load(request.stream) + + .. attribute:: files + + A :class:`~werkzeug.MultiDict` with files uploaded as part of a + `POST` or `PUT` request. Each file is stored as + :class:`~werkzeug.FileStorage` object. It basically behaves like a + standard file object you know from Python, with the difference that + it also has a :meth:`~werkzeug.FileStorage.save` function that can + store the file on the filesystem. + + .. attribute:: method + + The current request method (``POST``, ``GET`` etc.) + + .. attribute:: path + .. attribute:: script_root + .. attribute:: url + .. attribute:: base_url + .. attribute:: url_root + + Provides different ways to look at the current URL. Imagine your + application is listening on the following URL:: + + http://www.example.com/myapplication + + And a user requests the following URL:: + + http://www.example.com/myapplication/page.html?x=y + + In this case the values of the above mentioned attributes would be + the following: + + ============= ====================================================== + `path` ``/page.html`` + `script_root` ``/myapplication`` + `url` ``http://www.example.com/myapplication/page.html`` + `base_url` ``http://www.example.com/myapplication/page.html?x=y`` + `root_url` ``http://www.example.com/myapplication/`` + ============= ====================================================== + + +Sessions +-------- + +If you have the :attr:`Flask.secret_key` set you can use sessions in Flask +applications. A session basically makes it possible to remember +information from one request to another. The way Flask does this is by +using a signed cookie. So the user can look at the session contents, but +not modify it unless he knows the secret key, so make sure to set that to +something complex and unguessable. + +To access the current session you can use the :class:`session` object: + +.. class:: session + + The session object works pretty much like an ordinary dict, with the + difference that it keeps track on modifications. + + The following attributes are interesting: + + .. attribute:: new + + `True` if the session is new, `False` otherwise. + + .. attribute:: modified + + `True` if the session object detected a modification. Be advised + that modifications on mutable structures are not picked up + automatically, in that situation you have to explicitly set the + attribute to `True` yourself. Here an example:: + + # this change is not picked up because a mutable object (here + # a list) is changed. + session['objects'].append(42) + # so mark it as modified yourself + session.modified = True + + +Useful Functions and Classes +---------------------------- + +.. autofunction:: url_for + +.. autofunction:: abort + +.. autofunction:: redirect + +.. autofunction:: escape + +.. autoclass:: Markup + :members: escape, unescape, striptags + +Message Flashing +---------------- + +.. autofunction:: flash + +.. autofunction:: get_flashed_messages + +Template Rendering +------------------ + +.. autofunction:: render_template + +.. autofunction:: render_template_string diff --git a/docs/conf.py b/docs/conf.py index 1e4fb807..85c52700 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -25,7 +25,7 @@ import sys, os # Add any Sphinx extension module names here, as strings. They can be extensions # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. -extensions = ['sphinx.ext.autodoc'] +extensions = ['sphinx.ext.autodoc', 'sphinx.ext.intersphinx'] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] @@ -234,3 +234,8 @@ latex_documents = [ # The depth of the table of contents in toc.ncx. #epub_tocdepth = 3 + +intersphinx_mapping = { + 'http://docs.python.org/dev': None, + 'http://werkzeug.pocoo.org/documentation/dev/': None +} diff --git a/examples/minitwit/minitwit.py b/examples/minitwit/minitwit.py index 264325ec..fccf969c 100644 --- a/examples/minitwit/minitwit.py +++ b/examples/minitwit/minitwit.py @@ -7,8 +7,8 @@ from hashlib import md5 from datetime import datetime from contextlib import closing from flask import Flask, request, session, url_for, redirect, \ - render_template, abort, g, flash, generate_password_hash, \ - check_password_hash + render_template, abort, g, flash +from werkzeug import check_password_hash, generate_password_hash # configuration diff --git a/flask.py b/flask.py index 20306a81..794f234a 100644 --- a/flask.py +++ b/flask.py @@ -13,25 +13,17 @@ import os import sys import pkg_resources from threading import local +from contextlib import contextmanager from jinja2 import Environment, PackageLoader -from werkzeug import Request, Response, LocalStack, LocalProxy +from werkzeug import Request, Response, LocalStack, LocalProxy, \ + create_environ, cached_property from werkzeug.routing import Map, Rule from werkzeug.exceptions import HTTPException, InternalServerError from werkzeug.contrib.securecookie import SecureCookie -# try to import the json helpers -try: - from simplejson import loads as load_json, dumps as dump_json -except ImportError: - try: - from json import loads as load_json, dumps as dump_json - except ImportError: - pass - # utilities we import from Werkzeug and Jinja2 that are unused # in the module but are exported as public interface. -from werkzeug import abort, redirect, secure_filename, cached_property, \ - html, import_string, generate_password_hash, check_password_hash +from werkzeug import abort, redirect from jinja2 import Markup, escape @@ -83,12 +75,6 @@ def url_for(endpoint, **values): return _request_ctx_stack.top.url_adapter.build(endpoint, values) -def jsonified(**values): - """Returns a json response""" - return current_app.response_class(dump_json(values), - mimetype='application/json') - - 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, @@ -113,6 +99,7 @@ def render_template(template_name, **context): """Renders a template from the template folder with the given context. """ + current_app.update_template_context(context) return current_app.jinja_env.get_template(template_name).render(context) @@ -120,6 +107,7 @@ def render_template_string(source, **context): """Renders a template from the given template source string with the given context. """ + current_app.update_template_context(context) return current_app.jinja_env.from_string(source).render(context) @@ -220,9 +208,6 @@ class Flask(object): **self.jinja_options) self.jinja_env.globals.update( url_for=url_for, - request=request, - session=session, - g=g, get_flashed_messages=get_flashed_messages ) @@ -234,6 +219,15 @@ class Flask(object): """ return PackageLoader(self.package_name) + def update_template_context(self, context): + """Update the template context with some commonly used variables. + This injects request, session and g into the template context. + """ + reqctx = _request_ctx_stack.top + context['request'] = reqctx.request + context['session'] = reqctx.session + context['g'] = reqctx.g + def run(self, host='localhost', port=5000, **options): """Runs the application on a local development server. If the :attr:`debug` flag is set the server will automatically reload @@ -443,17 +437,38 @@ class Flask(object): app.wsgi_app = MyMiddleware(app.wsgi_app) """ - _request_ctx_stack.push(_RequestContext(self, environ)) - try: + with self.request_context(environ): rv = self.preprocess_request() if rv is None: rv = self.dispatch_request() response = self.make_response(rv) response = self.process_response(response) return response(environ, start_response) + + @contextmanager + def request_context(self, environ): + """Creates a request context from the given environment and binds + it to the current context. This must be used in combination with + the `with` statement because the request is only bound to the + current context for the duration of the `with` block. + + Example usage:: + + with app.request_context(environ): + do_something_with(request) + """ + _request_ctx_stack.push(_RequestContext(self, environ)) + try: + yield finally: _request_ctx_stack.pop() + def test_request_context(self, *args, **kwargs): + """Creates a WSGI environment from the given values (see + :func:`werkzeug.create_environ` for more information). + """ + return self.request_context(create_environ(*args, **kwargs)) + def __call__(self, environ, start_response): """Shortcut for :attr:`wsgi_app`""" return self.wsgi_app(environ, start_response)