GeezerVisonThe World Wide Web, restored to 800 pixels of glory. | ||||||||||||||
| Home|About|Gaming cabinet|Archives|How it works|Starter sites | ||||||||||||||
GeezerVison is showing bottlepy.org — User’s Guide — Bottle 0.14-dev documentation User’s Guide¶This guide introduces you to the concepts and features of the Bottle web framework and covers basic and advanced topics alike. You can read it from start to end, or use it as a reference later on. The automatically generated API Reference may be interesting for you, too. It covers more details, but explains less than this tutorial. Solutions for the most common questions can be found in our F.A.Q. collection or on the F.A.Q. page. If you need any help, join our mailing list or visit us in our IRC channel. Installation¶Bottle does not depend on any external libraries. You can just download bottle.py into your project directory and start coding: $ wget https://bottlepy.org/bottle.py This will get you the latest development snapshot that includes all the new features. If you prefer a more stable environment, you should stick with the stable releases. These are available on PyPI and can be installed via pip (recommended) or your package manager: $ pip install --user bottle # better use a venv, see below $ sudo apt-get install python-bottle # works for debian, ubuntu, ... It is usually a better idea to create a virtualenv per project and use that to install packages: $ python3 -m venv venv # Create virtual environment $ source venv/bin/activate # Change default python to virtual one (venv)$ pip install -U bottle # Install bottle to virtual environment Hello World!¶This tutorial assumes you have Bottle either installed or copied into your project directory. Let’s start with a very basic “Hello World” example: from bottle import route, run @route('/hello') def hello(): return "Hello World!" if __name__ == '__main__': run(host='localhost', port=8080, debug=True) This is it. Run this script, visit http://localhost:8080/hello and you will see “Hello World!” in your browser. Here is how it works: The The Debug Mode is very helpful during early development, but should be switched off for public applications. Keep that in mind. This is just a demonstration of the basic concept of how applications are built with Bottle. Continue reading and you’ll see what else is possible. The Application Object¶For the sake of simplicity, most examples in this tutorial use a module-level from bottle import Bottle, run app = Bottle() @app.route('/hello') def hello(): return "Hello World!" if __name__ == '__main__': app.run(host='localhost', port=8080) The object-oriented approach is further described in the Structuring Applications section. Just keep in mind that you have a choice. Debug Mode¶During early development, the debug mode can be very helpful. # Enable debug at runtime bottle.debug(True) # or during startup run(..., debug=True) In this mode, Bottle is much more verbose and provides helpful debugging information whenever an error occurs. It also disables some optimisations that might get in your way and adds some checks that warn you about possible misconfiguration. Here is an incomplete list of things that change in debug mode:
Just make sure not to use the debug mode on a production server. Auto Reloading¶Tired of restarting the server every time you change your code? The auto reloader can do this for you. Every time you edit a module file, the reloader restarts the server process and loads the newest version of your code. run(..., debug=True, reloader=True) How it works: the main process will not start a server, but spawn a new child process using the same command line arguments used to start the main process. All module-level code is executed at least twice! Be careful. The child process will have Command Line Interface¶Bottle is not only a module, but also a command line executable that can be used to start your app instead of calling bottle --help python3 -m bottle --help ./path/to/bottle.py --help Here is a quick example: $ bottle --debug --reload mymodule Bottle v0.13-dev server starting up (using WSGIRefServer())... Listening on http://localhost:8080/ Hit Ctrl-C to quit. Changed in version 0.13: The executable script installed into (virtual) environments was named Request Routing¶In the last chapter we built a very simple web application with only a single route. Here is the routing part of the “Hello World” example again: @route('/hello') def hello(): return "Hello World!" The @route('/') @route('/hello/<name>') def greet(name='Stranger'): return template('Hello {{name}}, how are you?', name=name) This example demonstrates two things: You can bind more than one route to a single callback, and you can add wildcards to URLs and access them via keyword arguments. Dynamic Routes¶Routes that contain wildcards are called dynamic routes (as opposed to static routes) and match more than one URL at the same time. A simple wildcard consists of a name enclosed in angle brackets (e.g. Each wildcard passes the covered part of the URL as a keyword argument to the request callback. You can use them right away and implement RESTful, nice-looking and meaningful URLs with ease. Here are some other examples along with the URLs they’d match: @route('/wiki/<pagename>') # matches /wiki/Learning_Python def show_wiki_page(pagename): ... @route('/<action>/<user>') # matches /follow/defnull def user_api(action, user): ... Filters can be used to define more specific wildcards, and/or transform the covered part of the URL before it is passed to the callback. A filtered wildcard is declared as The following filters are implemented by default and more may be added:
Let’s have a look at some practical examples: @route('/object/<id:int>') def callback(id): assert isinstance(id, int) @route('/show/<name:re:[a-z]+>') def callback(name): assert name.isalpha() @route('/static/<path:path>') def callback(path): return static_file(path, ...) You can add your own filters as well. See Request Routing for details. HTTP Request Methods¶The HTTP protocol defines several request methods (sometimes referred to as “verbs”) for different tasks. GET is the default for all routes with no other method specified. These routes will match GET requests only. To handle other methods such as POST, PUT, DELETE or PATCH, add a The POST method is commonly used for HTML form submission. This example shows how to handle a login form using POST: from bottle import get, post, request # or route @get('/login') # or @route('/login') def login(): return ''' <form action="/login" method="POST"> Username: <input name="username" type="text" /> Password: <input name="password" type="password" /> <input value="Login" type="submit" /> </form> ''' @post('/login') # or @route('/login', method='POST') def do_login(): username = request.forms.username password = request.forms.password if check_login(username, password): return "<p>Your login information was correct.</p>" else: return "<p>Login failed.</p>" In this example the Special Methods: HEAD and ANY The HEAD method is used to ask for the response identical to the one that would correspond to a GET request, but without the response body. This is useful for retrieving meta-information about a resource without having to download the entire document. Bottle handles these requests automatically by falling back to the corresponding GET route and cutting off the request body, if present. You don’t have to specify any HEAD routes yourself. Additionally, the non-standard ANY method works as a low priority fallback: Routes that listen to ANY will match requests regardless of their HTTP method but only if no other more specific route is defined. This is helpful for proxy-routes that redirect requests to more specific sub-applications. To sum it up: HEAD requests fall back to GET routes and all requests fall back to ANY routes, but only if there is no matching route for the original request method. It’s as simple as that. Serving Assets¶Static files or assets such as images or CSS files are not served automatically. You have to add a route and a callback to control which files get served and where to find them. Bottle comes with a handy from bottle import static_file @route('/static/<filepath:path>') def server_static(filepath): return static_file(filepath, root='/path/to/your/static/files') Note that we used the The The File downloads Most browsers try to open downloaded files with the associated application if the MIME type is known (e.g. PDF files). If this is not what you want, you can force a download dialog and even suggest a filename to the user: @route('/download/<filename>') def download(filename): return static_file(filename, root='/path/to/static/files', download=f"download-{filename}") If the Generating content¶We already learned that route callbacks can return strings as content, but there is more: Bottle supports a bunch of other data types and even adds
The ordering of this list is significant. You may for example return a subclass of Changing the Default Encoding Bottle uses the charset parameter of the from bottle import response @route('/iso') def get_iso(): response.charset = 'ISO-8859-15' return u'This will be sent with ISO-8859-15 encoding.' @route('/latin9') def get_latin(): response.content_type = 'text/html; charset=latin9' return u'ISO-8859-15 is also known as latin9.' In some rare cases the Python encoding names differ from the names supported by the HTTP specification. Then, you have to do both: first set the Error handling¶If anything goes wrong, Bottle displays an informative but fairly plain error page. It contains a stacktrace if from bottle import error @error(404) def error404(error): return 'Nothing here, sorry' From now on, Error handlers are used only if your application returns or raises an Triggering errors with The from bottle import route, abort @route('/restricted') def restricted(): abort(401, "Sorry, access denied.") You do not need to return anything when using Other Exceptions All exceptions other than The
|
Property |
Data source |
|---|---|
Query parameters |
|
Alias for |
|
orm fields and file uploads combined |
|
orm fields |
|
File uploads or very large form fields |
|
Query parameters and form fields combined |
Introducing FormsDict
Bottle uses a special type of dictionary to store those parameters. FormsDict behaves like a normal dictionary, but has some additional features to make your life easier.
First of all, FormsDict is a subclass of MultiDict and can store more than one value per key. Only the first value is returned by default, but MultiDict.getall() can be used to get a (possibly empty) list of all values for a specific key:
for choice in request.forms.getall('multiple_choice'): do_something(choice)
Attribute-like access is also supported, returning empty strings for missing values. This simplifies code a lot whend ealing with lots of optional attributes:
name = request.query.name # may be an empty string
A word on unicode and character encodings
Unicode characters in the request path, query parameters or cookies are a bit tricky. HTTP is a very old byte-based protocol that predates unicode and lacks explicit encoding information. This is why WSGI servers have to fall back on ISO-8859-1 (aka latin1, a reversible input encoding) for those estrings. Modern browsers default to utf8, though. It’s a bit much to ask application developers to translate every single user input string to the correct encoding manually. Bottle makes this easy and just assumes utf8 for everything. All strings returned by Bottle APIs support the full range of unicode characters, as long as the webpage or HTTP client follows best practices and does not break with established standards.
The query string (as in /forum?id=1&page=5) is commonly used to transmit a small number of key/value pairs to the server. You can use the Request.query attribute (a FormsDict) to access these values and the Request.query_string attribute to get the whole string.
from bottle import route, request, response, template @route('/forum') def display_forum(): forum_id = request.query.id page = request.query.page or '1' return template('Forum ID: {{id}} (page {{page}})', id=forum_id, page=page)
Let us start from the beginning. In HTML, a typical <form> looks something like this:
<form action="/login" method="post"> Username: <input name="username" type="text" /> Password: <input name="password" type="password" /> <input value="Login" type="submit" /> </form>
The action attribute specifies the URL that will receive the form data. method defines the HTTP method to use (GET or POST). With method="get" the form values are appended to the URL and available through Request.query as described above. This is sometimes considered insecure and has other limitations, so we use method="post" here. If in doubt, use POST forms.
Form fields transmitted via POST are stored in Request.forms as a FormsDict. The server side code may look like this:
from bottle import route, request @route('/login') def login(): return ''' <form action="/login" method="post"> Username: <input name="username" type="text" /> Password: <input name="password" type="password" /> <input value="Login" type="submit" /> </form> ''' @route('/login', method='POST') def do_login(): username = request.forms.username password = request.forms.password if check_login(username, password): return "<p>Your login information was correct.</p>" else: return "<p>Login failed.</p>"
To support file uploads, we have to change the <form> tag a bit. First, we tell the browser to encode the form data in a different way by adding an enctype="multipart/form-data" attribute to the <form> tag. Then, we add <input type="file" /> tags to allow the user to select a file. Here is an example:
<form action="/upload" method="post" enctype="multipart/form-data"> Category: <input type="text" name="category" /> Select a file: <input type="file" name="upload" /> <input type="submit" value="Start upload" /> </form>
Bottle stores file uploads in Request.files as FileUpload instances, along with some metadata about the upload. Let us assume you just want to save the file to disk:
@route('/upload', method='POST') def do_upload(): category = request.forms.category upload = request.files.get('upload') name, ext = os.path.splitext(upload.filename) if ext not in ('.png','.jpg','.jpeg'): return 'File extension not allowed.' save_path = get_save_path_for_category(category) upload.save(save_path) # appends upload.filename automatically return 'OK'
FileUpload.filename contains the name of the file on the clients file system, but is cleaned up and normalized to prevent bugs caused by unsupported characters or path segments in the filename. If you need the unmodified name as sent by the client, have a look at FileUpload.raw_filename.
The FileUpload.save method is highly recommended if you want to store the file to disk. It prevents some common errors (e.g. it does not overwrite existing files unless you tell it to) and stores the file in a memory efficient way. You can access the file object directly via FileUpload.file. Just be careful.
For JavaScript and REST APIs it is very common to send application/json to the server instead of from data.
The Request.json attribute contains the parsed data structure if available, or None for empty
requests or those that did not contain application/json data. Parsing errors trigger an appropiate HTTPError.
You can access the raw body data as a file-like object via Request.body. This is a io.BytesIO buffer or a temporary file depending on the content length and Request.MEMFILE_MAX setting. In both cases the body is completely buffered before you can access the attribute. If you expect huge amounts of data and want to get direct unbuffered access to the stream, have a look at request['wsgi.input'].
Each BaseRequest instance wraps a WSGI environment dictionary which is stored in Request.environ. Most of the interesting information is also exposed through special methods or properties, but if you want to access the raw WSGI environ directly, you can do so:
@route('/my_ip') def show_ip(): ip = request.environ.get('REMOTE_ADDR') # or ip = request.get('REMOTE_ADDR') # or ip = request['REMOTE_ADDR'] return template("Your IP is: {{ip}}", ip=ip)
Bottle comes with a fast and powerful built-in template engine called SimpleTemplate. To render a template you can use the template() function or the view() decorator. All you have to do is to provide the name of the template and the variables you want to pass to the template as keyword arguments. Here’s a simple example of how to render a template:
@route('/hello') @route('/hello/<name>') def hello(name='World'): return template('hello_template', name=name)
This will load the template file hello_template.tpl and render it with the name variable set. Bottle will look for templates in the ./views/ folder or any folder specified in the bottle.TEMPLATE_PATH list.
The view() decorator allows you to return a dictionary with the template variables instead of calling template():
@route('/hello') @route('/hello/<name>') @view('hello_template') def hello(name='World'): return dict(name=name)
Syntax
The template syntax is a very thin layer around the Python language. Its main purpose is to ensure correct indentation of blocks, so you can format your template without worrying about indentation. Follow the link for a full syntax description: SimpleTemplate Here is an example template:
%if name == 'World':
<h1>Hello {{name}}!</h1>
<p>This is a test.</p>
%else:
<h1>Hello {{name.title()}}!</h1>
<p>How are you?</p>
%end
Caching
Templates are cached in memory after compilation. Modifications made to the template files will have no affect until you clear the template cache. Call bottle.TEMPLATES.clear() to do so. Caching is disabled in debug mode.
Bottle maintains a global stack of Bottle instances and uses the top of the stack as a default for some of the module-level functions and decorators. The route() decorator or run() function for example are shortcuts for Bottle.route() or Bottle.run() on the default application:
@route('/') def hello(): return 'Hello World' if __name__ == '__main__': run()
This is very convenient for small applications and saves you some typing, but also means that, as soon as your module is imported, routes are installed to the global default application. To avoid this kind of import side-effects, Bottle offers a second, more explicit way to build applications:
app = Bottle() @app.route('/') def hello(): return 'Hello World' app.run()
Separating the application object improves re-usability a lot, too. Other developers can safely import the app object from your module and use Bottle.mount() to merge applications together.
Added in version 0.13.
Starting with bottle-0.13 you can use Bottle instances as context managers:
app = Bottle() with app: # Our application object is now the default # for all shortcut functions and decorators assert my_app is default_app() @route('/') def hello(): return 'Hello World' # Also useful to capture routes defined in other modules import some_package.more_routes
Programmer code that is to be called when some external action happens. In the context of web frameworks, the mapping between URL paths and application code is often achieved by specifying a callback function for each URL.
A function returning another function, usually applied as a function transformation using the @decorator syntax. See python documentation for function definition for more about decorators.
A structure where information about all documents under the root is saved, and used for cross-referencing. The environment is pickled after the parsing stage, so that successive runs only need to read and parse new and changed documents.
A function to handle some specific event or situation. In a web framework, the application is developed by attaching a handler function as callback for each specific URL comprising the application.
The directory which, including its subdirectories, contains all source files for one Sphinx project.