From 4aa76212d1be9b17a57249549c2a73fb10b6cda8 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Fri, 9 Apr 2010 01:32:39 +0200 Subject: [PATCH] Updated documentation. Starting to look pretty good --- docs/api.rst | 20 +++ docs/index.rst | 2 + docs/installation.rst | 6 + docs/quickstart.rst | 312 ++++++++++++++++++++++++++++++++++ examples/minitwit/minitwit.py | 6 +- flask.py | 17 +- 6 files changed, 359 insertions(+), 4 deletions(-) create mode 100644 docs/installation.rst create mode 100644 docs/quickstart.rst diff --git a/docs/api.rst b/docs/api.rst index 5ab86e7d..6c763393 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -68,6 +68,10 @@ Incoming Request Data it also has a :meth:`~werkzeug.FileStorage.save` function that can store the file on the filesystem. + .. attribute:: environ + + The underlying WSGI environment. + .. attribute:: method The current request method (``POST``, ``GET`` etc.) @@ -136,6 +140,22 @@ To access the current session you can use the :class:`session` object: session.modified = True +Application Globals +------------------- + +To share data that is valid for one request only from one function to +another, a global variable is not good enough because it would break in +threaded environments. Flask provides you with a special object that +ensures it is only valid for the active request and that will return +different values for each request. In a nutshell: it does the right +thing, like it does for :class:`request` and :class:`session`. + +.. data:: g + + Just store on this whatever you want. For example a database + connection or the user that is currently logged in. + + Useful Functions and Classes ---------------------------- diff --git a/docs/index.rst b/docs/index.rst index 217ced04..5e5d8622 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -8,4 +8,6 @@ Contents: .. toctree:: :maxdepth: 2 + installation + quickstart api diff --git a/docs/installation.rst b/docs/installation.rst new file mode 100644 index 00000000..8b0804d2 --- /dev/null +++ b/docs/installation.rst @@ -0,0 +1,6 @@ +.. _installation: + +Installation +============ + +Blafasel, add me diff --git a/docs/quickstart.rst b/docs/quickstart.rst new file mode 100644 index 00000000..0bfd7df7 --- /dev/null +++ b/docs/quickstart.rst @@ -0,0 +1,312 @@ +Quickstart +========== + +Eager to get started? This page gives a good introduction in how to gets +started with Flask. This assumes you already have Flask installed. If +you do not, head over to the :ref:`installation` section. + + +A Minimal Application +--------------------- + +A minimal Flask application looks something like that:: + + from flask import Flask + app = Flask(__name__) + + @app.route('/') + def hello_world(): + return "Hello World!" + + if __name__ == '__main__': + app.run() + +If you now start that application with your Python interpreter and head +over to `http://localhost:5000/ `_, you should see +your hello world application. + +So what did that code do? + +1. first we imported the :class:`~flask.Flask` class. An instance of this + class will be our WSGI application. +2. next we create an instance of it. We pass it the name of the module / + package. This is needed so that Flask knows where it should look for + templates, static files and so on. +3. Then we use the :meth:`~flask.Flask.route` decorator to tell Flask + what URL should trigger our function. +4. The function then has a name which is also used to generate URLs to + that particular function, and returns the message we want to display in + the user's browser. +5. Finally we use the :meth:`~flask.Flask.run` function to run the + local server with our application. The ``if __name__ == '__main__':`` + makes sure the server only runs if the script is executed directly from + the Python interpreter and not used as imported module. + + +Routing +------- + +As you have seen above, the :meth:`~flask.Flask.route` decorator is used +to bind a function to a URL. But there is more to it! You can make +certain parts of the URL dynamic and attach multiple rules to a function. + +Here some examples:: + + @app.route('/') + def index(): + return 'Index Page' + + @app.route('/hello') + def hello(): + return 'Hello World' + + +Variable Rules +`````````````` + +Modern web applications have beautiful URLs. This helps people remember +the URLs which is especially handy for applications that are used from +mobile devices with slower network connections. If the user can directly +go to the desired page without having to hit the index page it is more +likely he will like the page and come back next time. + +To add variable parts to a URL you can mark these special sections as +````. Such a part is then passed as keyword argument to +your function. Optionally a converter can be specifed by specifying a +rule with ````. Here some nice examples:: + + @app.route('/user/') + def show_user_profile(username): + # show the user profile for that user + pass + + @app.route('/post/') + def show_post(post_id): + # show the post with the given id, the id is an integer + pass + +The following converters exist: + +=========== =========================================== +`int` accepts integers +`float` like `int` but for floating point values +`path` like the default but also accepts slashes +=========== =========================================== + + +HTTP Methods +```````````` + +HTTP knows different methods to access URLs. By default a route only +answers to ``GET`` requests, but that can be changed by providing the +`methods` argument to the :meth:`~flask.Flask.route` decorator. Here some +examples:: + + @app.route('/login', methods=['GET', 'POST']) + def login(): + if request.method == 'POST': + do_the_login() + else: + show_the_login_form() + +If ``GET`` is present, ``HEAD`` will be added automatically for you. You +don't have to deal with that. It will also make sure that ``HEAD`` +requests are handled like the RFC demands, so you can completely ignore +that part of the HTTP specification. + + +Accessing Request Data +---------------------- + +For web applications it's crucial to react to the data a client sent to +the server. In Flask this information is provided by the global +:class:`~flask.request` object. If you have some experience with Python +you might be wondering how that object can be global and how Flask +manages to still be threadsafe. The answer are context locals: + +Context Locals +`````````````` + +.. admonition:: Insider Information + + If you want to understand how that works and how you can implement + tests with context locals, read this section, otherwise just skip it. + +Certain objects in Flask are global objects, but not just a standard +global object, but actually a proxy to an object that is local to a +specific context. What a mouthful. But that is actually quite easy to +understand. + +Imagine the context being the handling thread. A request comes in and the +webserver decides to spawn a new thread (or something else, the +underlying object is capable of dealing with other concurrency systems +than threads as well). When Flask starts its internal request handling it +figures out that the current thread is the active context and binds the +current application and the WSGI environments to that context (thread). +It does that in an intelligent way that one application can invoke another +application without breaking. + +So what does this mean to you? Basically you can completely ignore that +this is the case unless you are unittesting or something different. You +will notice that code that depends on a request object will suddenly break +because there is no request object. The solution is creating a request +object yourself and binding it to the context. The easiest solution for +unittesting is by using the :meth:`~flask.Flask.test_request_context` +context manager. In combination with the `with` statement it will bind a +test request so that you can interact with it. Here an example:: + + from flask import request + + with app.test_request_context('/hello', method='POST'): + # now you can do something with the request until the + # end of the with block, such as basic assertions: + assert request.path == '/hello' + assert request.method == 'POST' + +The other possibility is passing a whole WSGI environment to the +:meth:`~flask.Flask.request_context` method:: + + from flask import request + + with app.request_context(environ): + assert request.method == 'POST' + +The Request Object +`````````````````` + +The request object is documented in the API section and we will not cover +it here in detail (see :class:`~flask.request`), but just mention some of +the most common operations. First of all you have to import it from the +the `flask` module:: + + from flask import request + +The current request method is available by using the +:attr:`~flask.request.method` attribute. To access form data (data +transmitted in a `POST` or `PUT` request) you can use the +:attr:`~flask.request.form` attribute. Here a full example of the two +attributes mentioned above:: + + @app.route('/login', method=['POST', 'GET']) + def login(): + error = None + if request.method == 'POST': + if valid_login(request.form['username'], + request.form['password']): + return log_the_user_in(request.form['username']) + else: + error = 'Invalid username/password' + # this is executed if the request method was GET or the + # credentials were invalid + +What happens if the key does not exist in the `form` attribute? In that +case a special :exc:`KeyError` is raised. You can catch it like a +standard :exc:`KeyError` but if you don't do that, a HTTP 400 Bad Request +error page is shown instead. So for many situations you don't have to +deal with that problem. + +To access parameters submitted in the URL (``?key=value``) you can use the +:attr:`~flask.request.args` attribute:: + + searchword = request.args.get('q', '') + +We recommend accessing URL parameters with `get` or by catching the +`KeyError` because users might change the URL and presenting them a 400 +bad request page in that case is a bit user unfriendly. + +For a full list of methods and attribtues on that object, head over to the +:class:`~flask.request` documentation. + + +File Uploads +```````````` + +Obviously you can handle uploaded files with Flask just as easy. Just +make sure not to forget to set the ``enctype="multipart/form-data"`` +attribtue on your HTML form, otherwise the browser will not transmit your +files at all. + +Uploaded files are stored in memory or at a temporary location on the +filesystem. You can access those files by looking at the +:attr:`~flask.request.files` attribute on the request object. Each +uploaded file is stored in that dictionary. It behaves just like a +standard Python :class:`file` object, but it also has a +:meth:`~werkzeug.FileStorage.save` method that allows you to store that +file on the filesystem of the server. Here a simple example how that +works:: + + from flask import request + + @app.route('/upload', methods=['GET', 'POST']) + def upload_file(): + if request.method == 'POST': + f = request.files['the_file'] + f.save('/var/www/uploads/uploaded_file.txt') + ... + +If you want to know how the file was named on the client before it was +uploaded to your application, you can access the +:attr:`~werkzeug.FileStorage.filename` attribute. However please keep in +mind that this value can be forged so never ever trust that value. If you +want to use the filename of the client to store the file on the server, +pass it through the :func:`~werkzeug.secure_filename` function that +Werkzeug provides for you:: + + from flask import request + from werkzeug import secure_filename + + @app.route('/upload', methods=['GET', 'POST']) + def upload_file(): + if request.method == 'POST': + f= request.files['the_file'] + f.save('/var/www/uploads/' + secure_filename(f.filename)) + ... + +Cookies +``````` + +To access cookies you can use the :attr:`~flask.request.cookies` +attribute. Again this is a dictionary with all the cookies the client +transmits. If you want to use sessions, do not use the cookies directly +but instead use the :ref:`sessions` in Flask that add some security on top +of cookies for you. + +.. _sessions: + +Sessions +-------- + +Besides the request object there is also a second object called +:class:`~flask.session` that allows you to store information specific to a +user from one request to the next. This is implemented on top of cookies +for you and signes the cookies cryptographically. What this means is that +the user could look at the contents of your cookie but not modify it, +unless he knows the secret key used for signing. + +In order to use sessions you have to set a secret key. Here is how +sessions work:: + + from flask import session, redirect, url_for, escape + + @app.route('/') + def index(): + if 'username' in session: + return 'Logged in as %s' % escape(session['username']) + return 'You are not logged in' + + @app.route('/login', methods=['GET', 'POST']) + def login(): + if request.method == 'POST': + session['username'] = request.form['username'] + return redirect(url_for('index')) + return ''' +
+

+

+

+ ''' + + @app.route('/logout') + def logout(): + # remove the username from the session if its there + session.pop('username', None) diff --git a/examples/minitwit/minitwit.py b/examples/minitwit/minitwit.py index fccf969c..ebb304a7 100644 --- a/examples/minitwit/minitwit.py +++ b/examples/minitwit/minitwit.py @@ -148,7 +148,7 @@ def unfollow_user(username): return redirect(url_for('user_timeline', username=username)) -@app.route('/add_message') +@app.route('/add_message', methods=['POST']) def add_message(): if 'user_id' not in session: abort(401) @@ -161,7 +161,7 @@ def add_message(): return redirect(url_for('timeline')) -@app.route('/login') +@app.route('/login', methods=['GET', 'POST']) def login(): if 'user_id' in session: return redirect(url_for('timeline')) @@ -181,7 +181,7 @@ def login(): return render_template('login.html', error=error) -@app.route('/register') +@app.route('/register', methods=['GET', 'POST']) def register(): if 'user_id' in session: return redirect(url_for('timeline')) diff --git a/flask.py b/flask.py index 794f234a..9ea9db5c 100644 --- a/flask.py +++ b/flask.py @@ -326,11 +326,25 @@ class Flask(object): def show_post(post_id): pass + An important detail to keep in mind is how Flask deals with trailing + slashes. The idea is to keep each URL unique so the following rules + apply: + + 1. If a rule ends with a slash and is requested without a slash + by the user, the user is automatically redirected to the same + page with a trailing slash attached. + 2. If a rule does not end with a trailing slash and the user request + the page with a trailing slash, a 404 not found is raised. + + This is consistent with how web servers deal with static files. This + also makes it possible to use relative link targets safely. + The :meth:`route` decorator accepts a couple of other arguments as well: :param methods: a list of methods this rule should be limited - to (``GET``, ``POST`` etc.) + to (``GET``, ``POST`` etc.). By default a rule + just listens for ``GET`` (and implicitly ``HEAD``). :param subdomain: specifies the rule for the subdoain in case subdomain matching is in use. :param strict_slashes: can be used to disable the strict slashes @@ -339,6 +353,7 @@ class Flask(object): def decorator(f): if 'endpoint' not in options: options['endpoint'] = f.__name__ + options.setdefault('methods', ('GET',)) self.url_map.add(Rule(rule, **options)) self.view_functions[options['endpoint']] = f return f