diff options
author | Marcel Hellkamp <marc@gsites.de> | 2010-06-27 15:47:49 +0200 |
---|---|---|
committer | Marcel Hellkamp <marc@gsites.de> | 2010-06-27 15:47:49 +0200 |
commit | 41a0194a5fee514f80b4addb59c6e8e79676e87e (patch) | |
tree | 40acbc95c528a2a7a4ec16d38e921c16bd1ebc50 /docs | |
parent | 17ebead6e2adb2a474b28c12135543662fe58f60 (diff) | |
download | bottle-41a0194a5fee514f80b4addb59c6e8e79676e87e.tar.gz |
Moved the homepage sources into a separate repository
See: http://github.com/defnull/bottle.paws.de
Diffstat (limited to 'docs')
-rw-r--r-- | docs/2009-12-18_jQuery_Plugin_Selector.md | 22 | ||||
-rw-r--r-- | docs/2009-12-19_Comparing_HelloWorld_Performance.md | 62 | ||||
-rw-r--r-- | docs/contact.md | 23 | ||||
-rw-r--r-- | docs/docs.md | 677 | ||||
-rw-r--r-- | docs/docs_0_7.md | 621 | ||||
-rw-r--r-- | docs/faq.md | 127 | ||||
-rw-r--r-- | docs/logos.md | 31 | ||||
-rw-r--r-- | docs/python3.md | 3 | ||||
-rw-r--r-- | docs/release.md | 34 | ||||
-rw-r--r-- | docs/start.md | 201 | ||||
-rw-r--r-- | docs/tutorial.md | 633 |
11 files changed, 0 insertions, 2434 deletions
diff --git a/docs/2009-12-18_jQuery_Plugin_Selector.md b/docs/2009-12-18_jQuery_Plugin_Selector.md deleted file mode 100644 index af97282..0000000 --- a/docs/2009-12-18_jQuery_Plugin_Selector.md +++ /dev/null @@ -1,22 +0,0 @@ -# Selector: jQuery plugin - -I just wrote my first [jQuery](http://jquery.com/) plugin. It allows a user to visually select an area on the screen. Check out the demo: - -<script src="http://github.com/defnull/Lucullus/raw/master/lucullus/data/static_files/js/jquery.selector.js" type="text/javascript"></script> -<p> - <button id='selector_test'>Start Demo</button> - <span id='selector_position' style='font-weight:bold; padding-left: 15px;'>Klick it! I know you want to!</span> -</p> -<script type="text/javascript"> - /*<![CDATA[*/ - var update = function(sa) { - $('#selector_position') - .text('x:'+sa.area[0]+' y:'+sa.area[1]+' width:'+(sa.area[2]-sa.area[0])+'px height:'+(sa.area[3]-sa.area[1]+'px')) - } - $('#selector_test').bind('click', function() { - jQuery.selectArea(update, {onchange: update}); - }); - /*]]>*/ -</script> - -You can get the script [here](http://github.com/defnull/Lucullus/blob/master/lucullus/data/static_files/js/jquery.selector.js). diff --git a/docs/2009-12-19_Comparing_HelloWorld_Performance.md b/docs/2009-12-19_Comparing_HelloWorld_Performance.md deleted file mode 100644 index b2fc9df..0000000 --- a/docs/2009-12-19_Comparing_HelloWorld_Performance.md +++ /dev/null @@ -1,62 +0,0 @@ -# Comparing "Hello World" Performance - -I was curious and tested a "Hello World!" app with [Paste](http://pythonpaste.org/), [Fapws3](http://github.com/william-os4y/fapws3), [CherryPy](http://www.cherrypy.org/), [WSGIRef](http://www.wsgi.org/wsgi/) and the brand new [Tornado](http://www.tornadoweb.org/) adapter. - - #!Python - @bottle.route('/') - def index(): - return 'Hello World' - bottle.run(server=...) - -Some of you will now say "These tests are not representative. Real apps are much more complex." and you are right. This benchmark will not tell you how fast your real app can go, but it can tell you how much overhead you get with each of the available server adapters. - -I used [ApacheBench](http://en.wikipedia.org/wiki/ApacheBench) on an old single core AMD3000+ over a GBit network connection. Each server adapter was tested with 10000 requests and the "concurrent request" settings (-c) set to 1, 10, 100 and 1000. Here are the results: - -This chart shows simultaneous connections (x-axis) and mean requests per sec (y-axis) - -<div id="ChartContainer" style="width:600px; height:400px;"></div> - -As you can see, fapws3 performs *great*! But to be fair: fapws3 is the only tested server implemented in C. Tornado is the fastest pure python server in this test. - -<script src="http://www.highcharts.com/js/highcharts.js" type="text/javascript"></script> -<!--[if IE]> -<script src="http://www.highcharts.com/js/excanvas-compressed.js" type="text/javascript"></script> -<![endif]--> - -<script type="text/javascript"> - $(document).ready(function() { -var chart = new Highcharts.Chart({ - chart: { renderTo: 'ChartContainer', margin: [60, 150, 60, 60] }, - title: { text: 'Requests per Second' }, - subtitle: { text: 'Depending on Number of Simultaneous Connections' }, - xAxis: { - title: { text: 'Connections' }, - categories: [1, 10, 100, 1000], - }, - yAxis: { title: { text: 'Requests per Second' }, }, - tooltip: { - formatter: function() { - return '<b>'+ this.series.name +'</b><br/>'+ - this.x +': '+ this.y +'req/s'; - } - }, - legend: { - layout: 'vertical', - style: { - left: 'auto', - bottom: 'auto', - right: '10px', - top: '100px' - } - }, - series: [ - {name: 'Fapws3', data: [1707.94, 2058.70, 2070.98, 1960.06]}, - {name: 'Tornado', data: [1143.59, 1498.65, 1466.11, 1413.46]}, - {name: 'CherryPy', data: [1091.10, 1146.08, 1227.02, 731.12]}, - {name: 'WSGIRef', data: [608.66, 739.33, 611.85, 680.95]}, - {name: 'Paste', data: [518.35, 588.38, 570.65, 553.82]}, - ] -}); -}) -</script> - diff --git a/docs/contact.md b/docs/contact.md deleted file mode 100644 index eb264a4..0000000 --- a/docs/contact.md +++ /dev/null @@ -1,23 +0,0 @@ -# About Me -<img src="/myface_small.png" style="float: left; margin: 5px; border: 1px solid grey;" /> -Hi, I'm <i>Marcel Hellkamp</i> (aka <i>defnull</i>), author of Bottle and the guy behind this blog and website. I'm 25 years old and studying computer science at the Georg-August-University in Göttingen, Germany. Python is my favourite language, but I also code in ruby and JavaScript a lot. Watch me on [twitter](http://twitter.com/bottlepy) or visit my profile at [GitHub](http://twitter.com/bottlepy) to get in contact. A [mailinglist](http://groups.google.de/group/bottlepy) is open for Bottle related questions, too. - -<div style='clear:both'></div> - -## About Bottle -This is my first open source project so far. It started and a small experiment but soon got so much positive feedback I decided to make something real out of it. Here it is. - -## Impressum und Kontaktdaten -(This is required by [German law](http://bundesrecht.juris.de/tmg/__5.html)) - -Die Nutzung der folgenden Kontaktdaten ist ausschließlich für die -Kontaktaufnahme mit dem Betreiber dieser Webseite bei rechtlichen -Problemen vorgesehen. Insbesondere die Nutzung zu Werbe- oder ähnlichen -Zwecken ist ausdrücklich untersagt. - - * **Betreiber**: Marcel Hellkamp - * **Ort**: D - 37075 Göttingen - * **Strasse**: Theodor-Heuss Strasse 13 - * **Telefon**: +49 (0) 551 2509854 - * **E-Mail**: <img src='/email.png' alt='domain: paws.de, user: bottle' style="vertical-align:text-bottom;" /> - diff --git a/docs/docs.md b/docs/docs.md deleted file mode 100644 index 9903ab6..0000000 --- a/docs/docs.md +++ /dev/null @@ -1,677 +0,0 @@ -[TOC] - - [apache]: http://www.apache.org/ - [cherrypy]: http://www.cherrypy.org/ - [decorator]: http://docs.python.org/glossary.html#term-decorator - [fapws3]: http://github.com/william-os4y/fapws3 - [flup]: http://trac.saddi.com/flup - [http_code]: http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html - [http_method]: http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html - [mako]: http://www.makotemplates.org/ - [mod_wsgi]: http://code.google.com/p/modwsgi/ - [paste]: http://pythonpaste.org/ - [wsgi]: http://www.wsgi.org/wsgi/ - -# Bottle Documentation - -__This document is a work in progress__ and intended to be a tutorial, howto and an api documentation at the same time. If you have questions not answered here, -please check the [F.A.Q.](/page/faq) or file a ticket at bottles [issue tracker](http://github.com/defnull/bottle/issues). - -This documentation describes the features of the **0.6.4 Release** and is not updated anymore. The 0.7 docs can be found [here](/docs/) - - -## "Hello World" in a Bottle - -Lets start with a very basic example: Hello World - - #!Python - from bottle import route, run - @route('/hello') - def hello(): - return "Hello World!" - run() # This starts the HTTP server - -Run this script, visit <http://localhost:8080/hello> and you will see "Hello World!" in your Browser. So, what happened here? - - 1. First we imported some bottle components. The `route()` decorator and the `run()` function. - 2. The `route()` [decorator][] is used do bind a piece of code to an URL. In this example we want to answer requests to the `/hello` URL. - 3. This function will be called every time someone hits the `/hello` URL on the web server. It is called a __handler function__ or __callback__. - 4. The return value of a handler function will be sent back to the Browser. - 5. Now it is time to start the actual HTTP server. The default is a development server running on *localhost* port *8080* and serving requests until you hit __Ctrl-C__ - - - - -# Routing - -Routes are used to map URLs to __callbacks__ that generate the content for the specific URL. Bottle has a `route()` decorator to do that. You can add any number of routes to a callback. - - #!Python - from bottle import route - @route('/') - @route('/index.html') - def index(): - return "<a href='/hello'>Go to Hello World page</a>" - - @route('/hello') - def hello(): - return "Hello World!" - -As you can see, URLs and routes have nothing to do with actual files on the web server. Routes are unique names for your callbacks, nothing more and nothing less. Requests to URLs not matching any routes are answered with a 404 HTTP error. Exceptions within your handler callbacks will cause a 500 error. - - - - - -## Request Methods - -The `route()` decorator has an optional keyword argument `method` which defaults to `method='GET'`, so only GET requests get answered. -Possible values are `POST`, `PUT`, `DELETE`, `HEAD` or any other [HTTP request method][http_method] you want to listen to. - - #!Python - from bottle import route, request - @route('/form/submit', method='POST') - def form_submit(): - form_data = request.POST - do_something_with(form_data) - return "Done" - -In this example we used `request.POST` to access POST form data. This is described [here](#get-and-post-values) - - - - -## Dynamic Routes - -Static routes are fine, but URLs may carry information as well. Let's add a `:name` placeholder to our route. - - #!Python - from bottle import route - @route('/hello/:name') - def hello(name): - return "Hello %s!" % name - -This dynamic route matches `/hello/alice` as well as `/hello/bob`. In fact, the `:name` part of the route matches everything but a slash (`/`), so any name is possible. `/hello/bob/and/alice` or `/hellobob` won't match. - -Each part of the URL covered by a placeholder is provided as a keyword parameter to your handler callback. - - - - -### Regular Expressions - -The default placeholder matches everything up to the next slash. To change that, you can add some regular expression: - - #!Python - from bottle import route - @route('/get_object/:id#[0-9]+#') - def get(id): - return "Object ID: %d" % int(id) - -or even use full featured regular expressions with named groups: - - #!Python - from bottle import route - @route('/get_object/(?P<id>[0-9]+)') - def get(id): - return "Object ID: %d" % int(id) - -As you can see, URL parameters remain strings, even if they are -configured to only match digits. You have to explicitly cast them into -the type you need. - - - - -## The @validate() decorator - -Bottle offers a handy decorator called `validate()` to check and manipulate URL parameters. -It takes callables (function or class objects) as keyword arguments and filters every URL parameter -through the corresponding callable before they are passed to your request handler. - - #!Python - from bottle import route, validate - # /test/validate/1/2.3/4,5,6,7 - @route('/test/validate/:i/:f/:csv') - @validate(i=int, f=float, csv=lambda x: map(int, x.split(','))) - def validate_test(i, f, csv): - return "Int: %d, Float:%f, List:%s" % (i, f, repr(csv)) - -You may raise `ValueError` in your custom callable if a parameter -does not validate. - - - - -# Generating content - -TODO - - - -## Output Casting - -The [WSGI specification][wsgi] expects an iterable list of byte strings to be returned from your application and can't handle file objects, unicode, dictionaries or exceptions. - - #!Python - from bottle import route - @route('/wsgi') - def wsgi(): - return ['WSGI','wants a','list of','strings'] - -Bottle automatically tries to convert anything to a WSGI supported type, so you -don't have to. The following examples will work with Bottle, but won't work with -pure WSGI. - -### Strings and Unicode - -Returning strings (bytes) is not a problem. Unicode however needs to be encoded into a byte stream before -the webserver can send it to the client. The default encoding is utf-8, so if that fits your needs, you can -simply return unicode or unicode iterables. - - #!Python - from bottle import route, response - @route('/string') - def get_string(): - return 'Bottle converts strings to iterables' - - @route('/unicode') - def get_unicode(): - return u'Unicode is encoded with UTF-8 by default' - -You can change Bottles default encoding by setting `response.content_type` to a value -containing a `charset=...` parameter or by changing `response.charset` directly. - - #!Python - from bottle import route, 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 `response.content_type` header (which is sent to the client -unchanged) and then set the `response.charset` option (which is used to decode unicode). - - -### File Objects and Streams - -Bottle wrapps everything that has a `read()` method (file objects) with the `wsgi.file_wrapper` -provided by your WSGI server implementation. This wrapper should use highly optimised system calls for -your operating system (`sendfile` on UNIX) to transfer the file. - - #!Python - @route('/file') - def get_file(): - return open('some/file.txt','r') - - - -### JSON - -Even dictionaries are allowed. They are converted to -[json](http://de.wikipedia.org/wiki/JavaScript_Object_Notation) and returned -with `Content-Type` header set to `application/json`. To disable this feature (and pass dicts to your -middleware) you can set `bottle.default_app().autojson` to `False`. - - #!Python - @route('/api/status') - def api_status(): - return {'status':'online', 'servertime':time.time()} - - - - -### Static Files - -You can directly return file objects, but `bottle.send_file()` is the recommended way to serve static files. -It automatically guesses a mime-type, adds a `Last-Modified` header, restricts paths to a `root` directory -for security reasons and generates appropriate error pages (401 on permission errors, 404 on missing files). It -even supports the `If-Modified-Since` header and eventually generates a `304 Not modified` response. -You can pass a custom mimetype to disable mimetype guessing. - - #!Python - from bottle import send_file - - @route('/static/:filename') - def static_file(filename): - send_file(filename, root='/path/to/static/files') - - @route('/images/:filename#.*\.png#') - def static_image(filename): - send_file(filename, root='/path/to/image/files', mimetype='image/png') - - - - -## HTTP Errors and Redirects - -The `bottle.abort(code[, message])` function is used to generate [HTTP error pages][http_code]. - - #!Python - from bottle import route, redirect, abort - @route('/restricted') - def restricted(): - abort(401, "Sorry, access denied.") - -To redirect a client to a different URL, you can send a `307 Temporary Redirect` response -with the `Location` header set to the new URL. `bottle.redirect(url[, code])` does that for you. -You may provide a different HTTP status code as a second parameter. - - #!Python - from bottle import route, redirect, abort - @route('/wrong/url') - def wrong(): - redirect("/right/url") - -Both functions interrupt your handler code (by throwing a `bottle.HTTPError` exception) so you don't -have to return anything. - -All unhandled exceptions other than `bottle.HTTPError` will result in a `500 Internal Server Error` -response, so they won't crash your WSGI server. - - - -# HTTP Stuff - -TODO - - -## Cookies - -Bottle stores cookies sent by the client in a dictionary called `request.COOKIES`. To create new cookies, the method `response.set_cookie(name, value[, **params])` is used. It accepts additional parameters as long as they are valid cookie attributes supported by [SimpleCookie](http://docs.python.org/library/cookie.html#morsel-objects). - - #!Python - from bottle import response - response.set_cookie('key','value', path='/', domain='example.com', secure=True, expires=+500, ...) - -To set the `max-age` attribute use the `max_age` name. - - - -## GET and POST values - -Query strings and/or POST form submissions are parsed into dictionaries and made -available as `bottle.request.GET` and `bottle.request.POST`. Multiple values per -key are possible, so each each value of these dictionaries may contain a string -or a list of strings. - - - #!html - <form action="/search" method="post"> - <input type="text" name="query" /> - <input type="submit" /> - </form> - - - #!Python - from bottle import route, request - @route('/search', method='POST') - def do_search(): - query = request.POST.get('query', '').strip() - if not query: - return "You didn't supply a search query." - else: - return 'You searched for %s.' % query - -## File Uploads - -Bottle handles file uploads similar to normal POST form data. -Instead of strings, you will get file-like objects. These objects -have two primary attributes: `file` is a file object that can be -used to read it, and `value`, which will read the file and return -it as a string. - - #!html - <form action="/upload" method="post" enctype="multipart/form-data"> - <input name="datafile" type="file" /> - </form> - - #!Python - from bottle import route, request - @route('/upload', method='POST') - def do_upload(): - datafile = request.POST.get('datafile') - return datafile.file.read() - - - - - -# Templates - -Bottle uses its own little template engine by default. You can use a template by -calling `template(template_name, **template_arguments)` and returning -the result. - - #!Python - @route('/hello/:name') - def hello(name): - return template('hello_template', username=name) - -The `@view` decorator is another option: - - #!Python - @route('/hello/:name') - @view('hello_template') - def hello(name): - return dict(username=name) - -Both examples load the template `hello_template.tpl` with the `username` variable set to the URL `:name` part and return the result as a string. - -A simple `hello_template.tpl` file looks this: - - #!html - <h1>Hello {{username}}</h1> - <p>How are you?</p> - - - - -## Template search path - -The list `bottle.TEMPLATE_PATH` is used to map template names to actual -file names. By default, this list contains `['./', './views/']`. - - - - -## Template caching - -Templates are cached in memory after compilation. Modifications made to -the template file will have no affect until you clear the template -cache. Call `bottle.TEMPLATES.clear()` to do so. - - - - -## Template Syntax - -An updated and more detailed documentation is available [here](/api/stpl.html). - -The template syntax is a very thin layer around the Python language. It's main purpose is to ensure correct indention of blocks, so you can format your template without worrying about indentions. It does not prevent your template code from doing bad stuff, so **never ever** execute template code from untrusted sources. - -Here is how it works: - - * Lines starting with `%` are interpreted as python code. You can indent these lines but you don't have to. The template engine handles the correct indention of python blocks. - * A line starting with `%end` closes a python block opened by `%if ...`, `%for ...` or other block statements. Explicitly closing of blocks is required. - * Every other line is just returned as text. - * `{{...}}` within a text line is replaced by the result of the included python statement. This is used to include template variables. - * The two statements `%include` and `%rebase` have special meanings - -Here is a simple example, printing a HTML-list of names. - - #!html - <ul> - % for name in names: - <li>{{name}}</li> - % end - </ul> - - #!python - import template - print template('mylist', names=['Marc','Susan','Alice','Bob']) - -You can include other template using the `%include` statement followed by a template name and an optional argument list. The include-line is replaced by the rendered result of the named sub-template. - - #!html - <h1>{{title}}</h1> - - #!html - %include header_template title='Hello World' - <p> - Hello World! - </p> - -The `%rebase` statement is the inverse of `%include` and is used to render a template into a surrounding base-template. This is similar to the 'Inheritance' feature found in most other template engines. The base-template can access all the parameters specified by the `%rebase` statement and use an empty `%include` statement to include the text returned by the rebased template. - - #!html - <h1>{{title}}</h1> - <p> - %include - </p> - - #!html - %rebase paragraph_template title='Hello World' - hello world! - -And a last thing: You can add `\\` to the end of a text line preceding a line of python code to suppress the line break. - - #!html - List: \\ - %for i in range(5): - {{i}} - <br /> - - #!html - List: 1 2 3 4 5 <br /> - -Thats all. - - - - -# Key/Value Databases - -<div style="color:darkred">Warning: The included key/value database is depreciated since 0.6.4.</div> Please switch to a [real](http://code.google.com/p/redis/) [key](http://couchdb.apache.org/) [value](http://www.mongodb.org/) [database](http://docs.python.org/library/anydbm.html). - -# Using WSGI and Middleware - -A call to `bottle.default_app()` returns your WSGI application. After applying as many WSGI middleware modules as you like, you can tell -`bottle.run()` to use your wrapped application, instead of the default one. - - #!Python - from bottle import default_app, run - app = default_app() - newapp = YourMiddleware(app) - run(app=newapp) - - - - -## How default_app() works - -Bottle creates a single instance of `bottle.Bottle()` and uses it as a default for most of the module-level decorators and the `bottle.run()` routine. -`bottle.default_app()` returns (or changes) this default. You may, however, create your own instances of `bottle.Bottle()`. - - #!Python - from bottle import Bottle, run - mybottle = Bottle() - @mybottle.route('/') - def index(): - return 'default_app' - run(app=mybottle) - - - - -# Development -Bottle has two features that may be helpfull during development. - -## Debug Mode - -In debug mode, bottle is much more verbose and tries to help you finding -bugs. You should never use debug mode in production environments. - - #!Python - import bottle - bottle.debug(True) - -This does the following: - - * Exceptions will print a stacktrace - * Error pages will contain that stacktrace - * Templates will not be cached. - - - - -## Auto Reloading - -During development, you have to restart the server a lot to test your -recent changes. 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. - - #!Python - from bottle import run - run(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 -carefull. - -The child process will have `os.environ['BOTTLE_CHILD']` set to `true` -and start as a normal non-reloading app server. As soon as any of the -loaded modules changes, the child process is terminated and respawned by -the main process. Changes in template files will not trigger a reload. -Please use debug mode to deactivate template caching. - -The reloading depends on the ability to stop the child process. If you are -running on Windows or any other operating system not supporting -`signal.SIGINT` (which raises `KeyboardInterrupt` in Python), -`signal.SIGTERM` is used to kill the child. Note that exit handlers and -finally clauses, etc., are not executed after a `SIGTERM`. - - - - -# Deployment - -Bottle uses the build-in `wsgiref.SimpleServer` by default. This non-threading -HTTP server is perfectly fine for development and early production, -but may become a performance bottleneck when server load increases. - -There are three ways to eliminate this bottleneck: - - * Use a multi-threaded server adapter - * Spread the load between multiple bottle instances - * Do both - - - - -## Multi-Threaded Server - -The easiest way to increase performance is to install a multi-threaded and -WSGI-capable HTTP server like [Paste][paste], [flup][flup], [cherrypy][cherrypy] -or [fapws3][fapws3] and use the corresponding bottle server-adapter. - - #!Python - from bottle import PasteServer, FlupServer, FapwsServer, CherryPyServer - bottle.run(server=PasteServer) # Example - -If bottle is missing an adapter for your favorite server or you want to tweak -the server settings, you may want to manually set up your HTTP server and use -`bottle.default_app()` to access your WSGI application. - - #!Python - def run_custom_paste_server(self, host, port): - myapp = bottle.default_app() - from paste import httpserver - httpserver.serve(myapp, host=host, port=port) - - - - -## Multiple Server Processes - -A single Python process can only utilise one CPU at a time, even if -there are more CPU cores available. The trick is to balance the load -between multiple independent Python processes to utilise all of your -CPU cores. - -Instead of a single Bottle application server, you start one instances -of your server for each CPU core available using different local port -(localhost:8080, 8081, 8082, ...). Then a high performance load -balancer acts as a reverse proxy and forwards each new requests to -a random Bottle processes, spreading the load between all available -backed server instances. This way you can use all of your CPU cores and -even spread out the load between different physical servers. - -But there are a few drawbacks: - - * You can't easily share data between multiple Python processes. - * It takes a lot of memory to run several copies of Python and Bottle -at the same time. - -One of the fastest load balancer available is [pound](http://www.apsis.ch/pound/) but most common web servers have a proxy-module that can do the work just fine. - -I'll add examples for [lighttpd](http://www.lighttpd.net/) and -[Apache](http://www.apache.org/) web servers soon. - - - - -## Apache mod_wsgi - -Instead of running your own HTTP server from within Bottle, you can -attach Bottle applications to an [Apache server][apache] using -[mod_wsgi][] and Bottles WSGI interface. - -All you need is an `app.wsgi` file that provides an -`application` object. This object is used by mod_wsgi to start your -application and should be a WSGI conform Python callable. - - #!Python - # File: /var/www/yourapp/app.wsgi - - # Change working directory so relative paths (and template lookup) work again - os.chdir(os.path.dirname(__file__)) - - import bottle - # ... add or import your bottle app code here ... - # Do NOT use bottle.run() with mod_wsgi - application = bottle.default_app() - -The Apache configuration may look like this: - - #!ApacheConf - <VirtualHost *> - ServerName example.com - - WSGIDaemonProcess yourapp user=www-data group=www-data processes=1 threads=5 - WSGIScriptAlias / /var/www/yourapp/app.wsgi - - <Directory /var/www/yourapp> - WSGIProcessGroup yourapp - WSGIApplicationGroup %{GLOBAL} - Order deny,allow - Allow from all - </Directory> - </VirtualHost> - - - - -## Google AppEngine - -I didn't test this myself but several Bottle users reported that this -works just fine. - - #!Python - import bottle - from google.appengine.ext.webapp import util - # ... add or import your bottle app code here ... - # Do NOT use bottle.run() with AppEngine - util.run_wsgi_app(bottle.default_app()) - - - - -## Good old CGI - -CGI is slow as hell, but it works. - - #!Python - import bottle - # ... add or import your bottle app code here ... - bottle.run(server=bottle.CGIServer) - - diff --git a/docs/docs_0_7.md b/docs/docs_0_7.md deleted file mode 100644 index e28debf..0000000 --- a/docs/docs_0_7.md +++ /dev/null @@ -1,621 +0,0 @@ -[TOC] - - [apache]: http://www.apache.org/ - [cherrypy]: http://www.cherrypy.org/ - [decorator]: http://docs.python.org/glossary.html#term-decorator - [fapws3]: http://github.com/william-os4y/fapws3 - [flup]: http://trac.saddi.com/flup - [http_code]: http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html - [http_method]: http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html - [mako]: http://www.makotemplates.org/ - [mod_wsgi]: http://code.google.com/p/modwsgi/ - [paste]: http://pythonpaste.org/ - [wsgi]: http://www.wsgi.org/wsgi/ - -# Bottle Documentation - -__This document is a work in progress__ and intended to be a tutorial, howto and an api documentation at the same time. If you have questions not answered here, -please check the [F.A.Q.](/page/faq) or file a ticket at bottles [issue tracker](http://github.com/defnull/bottle/issues). - -This documentation describes the features of the **0.7 Release** - - -## "Hello World" in a Bottle - -Lets start with a very basic example: Hello World - - #!Python - from bottle import route, run - @route('/hello') - def hello(): - return "Hello World!" - run() # This starts the HTTP server - -Run this script, visit <http://localhost:8080/hello> and you will see "Hello World!" in your Browser. So, what happened here? - - 1. First we imported some bottle components. The `route()` decorator and the `run()` function. - 2. The `route()` [decorator][] is used to bind a piece of code to an URL. In this example we want to answer requests to the `/hello` URL. - 3. This function will be called every time someone hits the `/hello` URL on the web server. It is called a __handler function__ or __callback__. - 4. The return value of a handler function will be sent back to the Browser. - 5. Now it is time to start the actual HTTP server. The default is a development server running on *localhost* port *8080* and serving requests until you hit __Ctrl-C__ - - - - -# Routing - -Routes are used to map an URL to a callback function that generate the content for that specific URL. Bottle has a `route()` decorator to do that. You can add any number of routes to a callback. - - #!Python - from bottle import route - @route('/') - @route('/index.html') - def index(): - return "<a href='/hello'>Go to Hello World page</a>" - - @route('/hello') - def hello(): - return "Hello World!" - -As you can see, URLs and routes have nothing to do with actual files on the web server. Routes are unique names for your callbacks, nothing more and nothing less. Requests to URLs not matching any routes are answered with a 404 HTTP error page. Exceptions within your handler callbacks will cause a 500 error. - - - - - -## HTTP Request Methods - -The `route()` decorator has an optional keyword argument `method` which defaults to `method='GET'`; only GET requests get answered by that route. -Possible values are `POST`, `PUT`, `DELETE`, `HEAD`, or any other [HTTP request method][http_method] you want to listen to. Also `ANY` which will be used as fallback for any method. As an alternative, you can use the `@get()`, `@post()`, `@put()` and `@delete()` aliases. - - #!Python - from bottle import post, request - @post('/form/submit') - def form_submit(): - form_data = request.POST # (*) - do_something_with(form_data) - return "Done" - -\* In this example we used [request.POST](#working-with-http-requests) to access POST form data. - -Note that `HEAD` requests will fall back to `GET` routes and all requests will fall back to `ANY` routes, if there is no matching route for the original request method. - - - - - - -## Dynamic Routes - -Static routes are fine, but URLs may carry information as well. Let's add a `:name` placeholder to our route. - - #!Python - from bottle import route - @route('/hello/:name') - def hello(name): - return "Hello %s!" % name - -This dynamic route matches `/hello/alice` as well as `/hello/bob`. In fact, the `:name` part will match everything but a slash (`/`), so any name is possible. `/hello/bob/and/alice` or `/hellobob` won't match. Each part of the URL covered by a placeholder is provided as a keyword argument to your handler callback. - -A normal placeholder matches everything up to the next slash. To change that, you can add a regular expression pattern: - - #!Python - from bottle import route - @route('/get_object/:id#[0-9]+#') - def get(id): - return "Object ID: %d" % int(id) - -As you can see, URL parameters remain strings, even if they are configured to only match digits. You have to explicitly cast them into the type you need. - - - - -## The @validate() decorator - -Bottle offers a handy decorator called `validate()` to check and manipulate URL parameters. It takes callables (function or class objects) as keyword arguments and filters every URL parameter through the corresponding callable before they are passed to your request handler. - - #!Python - from bottle import route, validate - # /test/validate/1/2.3/4,5,6,7 - @route('/test/validate/:i/:f/:csv') - @validate(i=int, f=float, csv=lambda x: map(int, x.split(','))) - def validate_test(i, f, csv): - return "Int: %d, Float:%f, List:%s" % (i, f, repr(csv)) - -You may raise `ValueError` in your custom callable if a parameter does not validate. - - - - -# Generating content - -The [WSGI specification][wsgi] expects an iterable list of byte strings to be returned from your application and can't handle unicode, dictionaries or exceptions. File objects will be handled as iterables in *pure* WSGI, with no conditional caching or `Content-Length` calculation. - - #!Python - @route('/wsgi') - def wsgi(): - return ['WSGI','wants a','list of','strings'] - -Bottle automatically tries to convert anything to a WSGI supported type, so you -don't have to. The following examples will work with Bottle, but won't work with -pure WSGI. - -## Strings and Unicode - -Returning strings (bytes) is not a problem. Unicode however needs to be encoded into a byte stream before -the webserver can send it to the client. The default encoding is utf-8. If that fits your needs, you can -simply return unicode or unicode iterables. - - #!Python - @route('/string') - def get_string(): - return 'Bottle converts strings to iterables' - - @route('/unicode') - def get_unicode(): - return u'Unicode is encoded with UTF-8 by default' - -You can change Bottles default encoding by setting `response.content_type` to a value containing a `charset=...` parameter or by changing `response.charset` directly. - - #!Python - 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 `response.content_type` header (which is sent to the client unchanged) and then set the `response.charset` option (which is used to decode unicode). - -## File Objects and Streams - -Bottle passes everything that has a `read()` method (file objects) to the `wsgi.file_wrapper` provided by your WSGI server implementation. This wrapper should use optimised system calls (`sendfile` on UNIX) to transfer the file contents. - - #!Python - @route('/file') - def get_file(): - return open('some/file.txt','r') - -## JSON - -Even dictionaries are allowed. They are converted to [json](http://de.wikipedia.org/wiki/JavaScript_Object_Notation) and returned with the `Content-Type` header set to `application/json`. To disable this feature (and pass dicts to your middleware) you can set `bottle.app().autojson` to `False`. - - #!Python - @route('/api/status') - def api_status(): - return {'status':'online', 'servertime':time.time()} - -## Static Files - -You can directly return file objects, but `static_file()` is the recommended way to serve static files. It automatically guesses a mime-type, adds a `Last-Modified` header, restricts paths to a `root` directory for security reasons and generates appropriate error responses (401 on permission errors, 404 on missing files). It even supports the `If-Modified-Since` header and eventually generates a `304 Not modified` response. You can pass a custom mimetype to disable mimetype guessing. - - #!Python - from bottle import static_file - - @route('/images/:filename#.*\.png#') - def send_image(filename): - return static_file(filename, root='/path/to/image/files', mimetype='image/png') - - @route('/static/:filename') - def send_file(filename): - return static_file(filename, root='/path/to/static/files') - -You can raise the return value of `static_file()` as an exception if you really need to. The raised `HTTPResponse` exception is handled by the Bottle framework. - -## HTTPError, HTTPResponse and Redirects - -The `abort(code[, message])` function is used to generate [HTTP error pages][http_code]. - - #!Python - from bottle import route, redirect, abort - @route('/restricted') - def restricted(): - abort(401, "Sorry, access denied.") - -To redirect a client to a different URL, you can send a `303 See Other` response with the `Location` header set to the new URL. `redirect(url[, code])` does that for you. You may provide a different HTTP status code as a second parameter. - - #!Python - from bottle import redirect - @route('/wrong/url') - def wrong(): - redirect("/right/url") - -Both functions interrupt your handler code by raising a `HTTPError` exception. - -You can return `HTTPError` exceptions instead of raising them. This is faster than raising and capturing Exceptions, but does exactly the same. - - #!Python - from bottle import HTTPError - - @route('/denied') - def denied(): - return HTTPError(401, 'Access denied!') - -## Exceptions - -All exceptions other than `HTTPResponse` or `HTTPError` will result in a `500 Internal Server Error` response, so they won't crash your WSGI server. You can turn off this behaviour to handle exceptions in your middleware by setting `bottle.app().catchall` to `False`. - -# Working with HTTP Requests - -Bottle parses the HTTP request data into a thread-save `request` object and provides some useful tools and methods to access this data. Most of the parsing happens on demand, so you won't see any overhead if you don't need the result. Here is a short summary: - - * `request[key]`: A shortcut for `request.environ[key]` - * `request.environ`: WSGI environment dictionary. Use this with care. - * `request.app`: Currently used Bottle instance (same as `bottle.app()`) - * `request.method`: HTTP request-method (GET,POST,PUT,DELETE,...). - * `request.query_string`: HTTP query-string (http://host/path?query_string) - * `request.path`: Path string that matched the current route. - * `request.fullpath`: Full path including the `SCRIPT_NAME` part. - * `request.url`: The full URL as requested by the client (including `http(s)://` and hostname) - * `request.input_length` The Content-Length header (if present) as an integer. - * `request.header`: HTTP header dictionary. - * `request.GET`: The parsed content of `request.query_string` as a dict. Each value may be a string or a list of strings. - * `request.POST`: A dict containing parsed form data. Supports URL- and multipart-encoded form data. Each value may be a string, a file or a list of strings or files. - * `request.COOKIES`: The cookie data as a dict. - * `request.params`: A dict containing both, `request.GET` and `request.POST` data. - * `request.body`: The HTTP body of the request as a buffer object. - * `request.auth`: HTTP authorisation data as a named tuple. (experimental) - * `request.get_cookie(key[, default])`: Returns a specific cookie and decodes secure cookies. (experimental) - - -## Cookies - -Bottle stores cookies sent by the client in a dictionary called `request.COOKIES`. To create new cookies, the method `response.set_cookie(name, value[, **params])` is used. It accepts additional parameters as long as they are valid cookie attributes supported by [SimpleCookie](http://docs.python.org/library/cookie.html#morsel-objects). - - #!Python - from bottle import response - response.set_cookie('key','value', path='/', domain='example.com', secure=True, expires=+500, ...) - -To set the `max-age` attribute use the `max_age` name. - -TODO: It is possible to store python objects and lists in cookies. This produces signed cookies, which are pickled and unpickled automatically. - -## GET and POST values - -Query strings and/or POST form submissions are parsed into dictionaries and made -available as `request.GET` and `request.POST`. Multiple values per -key are possible, so each value of these dictionaries may contain a string -or a list of strings. - -You can use `.getall(key)` to get all values, or `.get(key[, default])` if you expect only one value. `getall` returns a list, `get` returns a string. - - #!Python - from bottle import route, request - @route('/search', method='POST') - def do_search(): - query = request.POST.get('query', '').strip() - if not query: - return "You didn't supply a search query." - else: - return 'You searched for %s.' % query - - -## File Uploads - -Bottle handles file uploads similar to normal POST form data. -Instead of strings, you will get file-like objects. These objects -have two primary attributes: `file` is a file object that can be -used to read it, and `value`, which will read the file and return -it as a string. - - #!Python - from bottle import route, request - @route('/upload', method='POST') - def do_upload(): - datafile = request.POST.get('datafile') - return datafile.file.read() - -Here is an example HTML Form for file uploads - - #!html - <form action="/upload" method="post" enctype="multipart/form-data"> - <input name="datafile" type="file" /> - </form> - - - -# Templates - -Bottle uses its own little template engine by default. You can use a template by -calling `template(template_name, **template_arguments)` and returning -the result. - - #!Python - @route('/hello/:name') - def hello(name): - return template('hello_template', username=name) - -This will load the template `hello_template.tpl` with the `username` variable set to the URL `:name` part and return the result as a string. - -The `hello_template.tpl` file could look like this: - - #!html - <h1>Hello {{username}}</h1> - <p>How are you?</p> - - - - -## Template search path - -The list `bottle.TEMPLATE_PATH` is used to map template names to actual -file names. By default, this list contains `['./%s.tpl', './views/%s.tpl']`. - - - - -## Template caching - -Templates are cached in memory after compilation. Modifications made to -the template file will have no affect until you clear the template -cache. Call `bottle.TEMPLATES.clear()` to do so. - - - - -## Template Syntax - -The template syntax is a very thin layer around the Python language. -It's main purpose is to ensure correct indention of blocks, so you -can format your template without worrying about indentions. Here is the -complete syntax description: - - * `%...` starts a line of python code. You don't have to worry about indentions. Bottle handles that for you. - * `%end` closes a Python block opened by `%if ...`, `%for ...` or other block statements. Explicitly closing of blocks is required. - * `{{...}}` prints the result of the included python statement. - * `%include template_name optional_arguments` allows you to include other templates. - * Every other line is returned as text. - -Example: - - #!html - %header = 'Test Template' - %items = [1,2,3,'fly'] - %include http_header title=header, use_js=['jquery.js', 'default.js'] - <h1>{{header.title()}}</h1> - <ul> - %for item in items: - <li> - %if isinstance(item, int): - Zahl: {{item}} - %else: - %try: - Other type: ({{type(item).__name__}}) {{repr(item)}} - %except: - Error: Item has no string representation. - %end try-block (yes, you may add comments here) - %end - </li> - %end - </ul> - %include http_footer - - - - -# Key/Value Databases - -<div style="color:darkred">Warning: The included key/value database is depreciated.</div> Please switch to a [real](http://code.google.com/p/redis/) [key](http://couchdb.apache.org/) [value](http://www.mongodb.org/) [database](http://docs.python.org/library/anydbm.html). - - - - -# Using WSGI and Middleware - -A call to `bottle.default_app()` returns your WSGI application. After applying as many WSGI middleware modules as you like, you can tell -`bottle.run()` to use your wrapped application, instead of the default one. - - #!Python - from bottle import default_app, run - app = default_app() - newapp = YourMiddleware(app) - run(app=newapp) - - - - -## How default_app() works - -Bottle creates a single instance of `bottle.Bottle()` and uses it as a default for most of the module-level decorators and the `bottle.run()` routine. -`bottle.default_app()` returns (or changes) this default. You may, however, create your own instances of `bottle.Bottle()`. - - #!Python - from bottle import Bottle, run - mybottle = Bottle() - @mybottle.route('/') - def index(): - return 'default_app' - run(app=mybottle) - - - - -# Development -Bottle has two features that may be helpfull during development. - -## Debug Mode - -In debug mode, bottle is much more verbose and tries to help you finding -bugs. You should never use debug mode in production environments. - - #!Python - import bottle - bottle.debug(True) - -This does the following: - - * Exceptions will print a stacktrace - * Error pages will contain that stacktrace - * Templates will not be cached. - - - - -## Auto Reloading - -During development, you have to restart the server a lot to test your -recent changes. 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. - - #!Python - from bottle import run - run(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 -carefull. - -The child process will have `os.environ['BOTTLE_CHILD']` set to `true` -and start as a normal non-reloading app server. As soon as any of the -loaded modules changes, the child process is terminated and respawned by -the main process. Changes in template files will not trigger a reload. -Please use debug mode to deactivate template caching. - -The reloading depends on the ability to stop the child process. If you are -running on Windows or any other operating system not supporting -`signal.SIGINT` (which raises `KeyboardInterrupt` in Python), -`signal.SIGTERM` is used to kill the child. Note that exit handlers and -finally clauses, etc., are not executed after a `SIGTERM`. - - - - -# Deployment - -Bottle uses the build-in `wsgiref.SimpleServer` by default. This non-threading -HTTP server is perfectly fine for development and early production, -but may become a performance bottleneck when server load increases. - -There are three ways to eliminate this bottleneck: - - * Use a multi-threaded server adapter - * Spread the load between multiple bottle instances - * Do both - - - - -## Multi-Threaded Server - -The easiest way to increase performance is to install a multi-threaded and -WSGI-capable HTTP server like [Paste][paste], [flup][flup], [cherrypy][cherrypy] -or [fapws3][fapws3] and use the corresponding bottle server-adapter. - - #!Python - from bottle import PasteServer, FlupServer, FapwsServer, CherryPyServer - bottle.run(server=PasteServer) # Example - -If bottle is missing an adapter for your favorite server or you want to tweak -the server settings, you may want to manually set up your HTTP server and use -`bottle.default_app()` to access your WSGI application. - - #!Python - def run_custom_paste_server(self, host, port): - myapp = bottle.default_app() - from paste import httpserver - httpserver.serve(myapp, host=host, port=port) - - - - -## Multiple Server Processes - -A single Python process can only utilise one CPU at a time, even if -there are more CPU cores available. The trick is to balance the load -between multiple independent Python processes to utilise all of your -CPU cores. - -Instead of a single Bottle application server, you start one instances -of your server for each CPU core available using different local port -(localhost:8080, 8081, 8082, ...). Then a high performance load -balancer acts as a reverse proxy and forwards each new requests to -a random Bottle processes, spreading the load between all available -backed server instances. This way you can use all of your CPU cores and -even spread out the load between different physical servers. - -But there are a few drawbacks: - - * You can't easily share data between multiple Python processes. - * It takes a lot of memory to run several copies of Python and Bottle -at the same time. - -One of the fastest load balancer available is [pound](http://www.apsis.ch/pound/) but most common web servers have a proxy-module that can do the work just fine. - -I'll add examples for [lighttpd](http://www.lighttpd.net/) and -[Apache](http://www.apache.org/) web servers soon. - - - - -## Apache mod_wsgi - -Instead of running your own HTTP server from within Bottle, you can -attach Bottle applications to an [Apache server][apache] using -[mod_wsgi][] and Bottles WSGI interface. - -All you need is an `app.wsgi` file that provides an -`application` object. This object is used by mod_wsgi to start your -application and should be a WSGI conform Python callable. - - #!Python - # File: /var/www/yourapp/app.wsgi - - # Change working directory so relative paths (and template lookup) work again - os.chdir(os.path.dirname(__file__)) - - import bottle - # ... add or import your bottle app code here ... - # Do NOT use bottle.run() with mod_wsgi - application = bottle.default_app() - -The Apache configuration may look like this: - - #!ApacheConf - <VirtualHost *> - ServerName example.com - - WSGIDaemonProcess yourapp user=www-data group=www-data processes=1 threads=5 - WSGIScriptAlias / /var/www/yourapp/app.wsgi - - <Directory /var/www/yourapp> - WSGIProcessGroup yourapp - WSGIApplicationGroup %{GLOBAL} - Order deny,allow - Allow from all - </Directory> - </VirtualHost> - - - - -## Google AppEngine - -I didn't test this myself but several Bottle users reported that this -works just fine. - - #!Python - import bottle - from google.appengine.ext.webapp import util - # ... add or import your bottle app code here ... - # Do NOT use bottle.run() with AppEngine - util.run_wsgi_app(bottle.default_app()) - - - - -## Good old CGI - -CGI is slow as hell, but it works. - - #!Python - import bottle - # ... add or import your bottle app code here ... - bottle.run(server=bottle.CGIServer) - - diff --git a/docs/faq.md b/docs/faq.md deleted file mode 100644 index 00a4a55..0000000 --- a/docs/faq.md +++ /dev/null @@ -1,127 +0,0 @@ -[TOC] - -# Frequently Asked Questions - -[beaker]: http://beaker.groovie.org/ - -## How to implement sessions? - -There is no build in support for sessions because there is no *right* -way to do it. Depending on requirements and environment you could use [beaker][] -middleware with a fitting backend or implement it yourself. - -Here is an example for beaker sessions with a file-based backend. - - #!Python - import bottle - from beaker.middleware import SessionMiddleware - - app = bottle.default_app() - session_opts = { - 'session.type': 'file', - 'session.cookie_expires': 300, - 'session.data_dir': './data', - 'session.auto': True - } - app = SessionMiddleware(app, session_opts) - - @bottle.route('/test') - def test(): - s = bottle.request.environ.get('beaker.session') - s['test'] = s.get('test',0) + 1 - s.save() - return 'Test counter: %d' % s['test'] - - bottle.run(app=app) - - - -## How to use a debugging middleware? - -Bottle catches all Exceptions raised in your app code, so your WSGI server won't crash. If you need exceptions to propagate to a debugging middleware, you can turn off this behaviour. - - #!Python - import bottle - app = bottle.default_app() # or bottle.app() since 0.7 - app.catchall = False - myapp = DebuggingMiddleware(app) - bottle.run(app=myapp) - -Now, bottle only catches its own exceptions (`HTTPError`, `HTTPResponse` and `BottleException`) and your middleware can handle the rest. - - - - -## How to call a WSGI app from within bottle - -This is not the recommend way (you should use a middleware in front of bottle to do this) but you can call other WSGI applications from within your bottle app and let bottle act as a pseudo-middleware. Here is an example: - - #!Python - from bottle import request, response, route - subproject = SomeWSGIApplication() - - @route('/subproject/:subpath#.*#', method='ALL') - def call_wsgi(subpath): - new_environ = request.environ.copy() - new_environ['SCRIPT_NAME'] = new_environ.get('SCRIPT_NAME','') + '/subproject' - new_environ['PATH_INFO'] = '/' + subpath - def start_response(status, headerlist): - response.status = int(status.split()[0]) - for key, value in headerlist: - response.header.append(key, value) # or .add_header() with bottle < 0.7 - return app(new_environ, start_response) - -Again, this is not the recommend way to implement subprojects. It is only here because many people asked for this and to show how bottle maps to WSGI. - -## How to ignore tailing slashes? - -Bottle does not ignore tailing slashes by default. -To handle URLs like `/example` and `/example/` the same, -you could add two `@route` decorators - - #!Python - @route('/test') - @route('/test/') - def test(): pass - -or use regular expressions in dynamic routes - - #!Python - @route('/test/?') - def test(): pass - -or add a WSGI middleware to strips tailing '/' from URLs - - #!Python - class StripPathMiddleware(object): - def __init__(self, app): - self.app = app - def __call__(self, e, h): - e['PATH_INFO'] = e['PATH_INFO'].rstrip('/') - return self.app(e,h) - - app = bottle.default_app() - app = StripPathMiddleware(app) - bottle.run(app=app) - - - - - - -## mod_python and "Template Not Found"? - -Bottle searches in "./" and "./views/" for templates. In a mod_python -environment, the working directory ('.') depends on your Apache settings. You -should add an absolute path to the template search path - - #!Python - bottle.TEMPLATE_PATH.insert(0,'/absolut/path/to/templates/') - -or change the working directory - - #!Python - os.chdir(os.path.dirname(__file__)) - -so bottle searches the right paths. - diff --git a/docs/logos.md b/docs/logos.md deleted file mode 100644 index b484a09..0000000 --- a/docs/logos.md +++ /dev/null @@ -1,31 +0,0 @@ -Bottle Logo -=========== - -The official color is: - -<img src="/logo_130.png" /> - -But ... - -<div style="text-align: center"> -<img src="/logo_10.png" alt='color logo 1' width='10%' /> -<img src="/logo_20.png" alt='color logo 2' width='10%' /> -<img src="/logo_30.png" alt='color logo 3' width='10%' /> -<img src="/logo_40.png" alt='color logo 4' width='10%' /> -<img src="/logo_50.png" alt='color logo 5' width='10%' /> -<img src="/logo_60.png" alt='color logo 6' width='10%' /> -<img src="/logo_70.png" alt='color logo 7' width='10%' /> -<img src="/logo_80.png" alt='color logo 8' width='10%' /> -<img src="/logo_90.png" alt='color logo 9' width='10%' /> -<img src="/logo_100.png" alt='color logo 10' width='10%' /> -<img src="/logo_110.png" alt='color logo 11' width='10%' /> -<img src="/logo_120.png" alt='color logo 12' width='10%' /> -<img src="/logo_130.png" alt='color logo 14' width='10%' /> -<img src="/logo_140.png" alt='color logo 14' width='10%' /> -<img src="/logo_150.png" alt='color logo 15' width='10%' /> -<img src="/logo_160.png" alt='color logo 16' width='10%' /> -<img src="/logo_170.png" alt='color logo 17' width='10%' /> -<img src="/logo_180.png" alt='color logo 18' width='10%' /> -<img src="/logo_190.png" alt='color logo 19' width='10%' /> -<img src="/logo_200.png" alt='color logo 20' width='10%' /> -</div> diff --git a/docs/python3.md b/docs/python3.md deleted file mode 100644 index e53f696..0000000 --- a/docs/python3.md +++ /dev/null @@ -1,3 +0,0 @@ -# Support for Python 3 - -Yes, Bottle runs with Python 3 (using 2to3) but Python 3.x is different enough from any Python 2.x release that the WSGI specification (PEP 333) no longer completely makes sense. There is no way to implement a 100% Python 3 compatible WSGI Framework until there is a new WSGI specification. diff --git a/docs/release.md b/docs/release.md deleted file mode 100644 index 072b89e..0000000 --- a/docs/release.md +++ /dev/null @@ -1,34 +0,0 @@ -# Bottle Releases - -This is a small guide to Bottle releases. It is a work-in-progress document, so please don't mind the loose ends. - -## Version Numbering and Releases - -Bottles version number breaks into three parts: Major release, minor release and revision. Major releases are very rare and only happen on significant jumps in functionality. Minor releases introduce new functionality and may break compatibility with previous releases in some places, but are mostly API compatible. Revisions may fix bugs, improve performance or introduce minor new features, but (hopefully) never break compatibility. - -It is save and recommended to update to new revisions. You should consider updating to new releases as well, because I don't have the time and energy to support old releases. - -## Development - -The 'master' branch at [Gitub](http://github.com/defnull/bottle) always contains the latest release candidate. New features and bug fixes are developed and tested in separate branches or forks until they are merged into [bottle/master](http://github.com/defnull/bottle). You can use 'master' for testing. It should work most of the time. - -## Release notes - -This are randomly ordered lists of changes that affect the API or behaviour of Bottle. Bug fixes and optimisations are not documented here, please see the [commit log](http://github.com/defnull/bottle/commits/) if you are interested in those. - -### Release 0.7 - - * `default_app()` renamed to `app()` - * `bottle.db` removed. It was marked deprecated since 0.6.4 - * New request method `get_cookie()`. - * `request.get_cookie()` and `response.set_cookie()` are now able to handle pickle-able objects. These are reliable (signed), but not encrypted. - * Route syntax and behaviour changed: - * `:name` still matches everything up to the next slash. `:name#regexp#` matches a regular expression. `:#regexp#` does an anonymous match. A backslash can be used to escape the `:` character. - * All other regular expression metacharacters are escaped. - * It is now possible to name routes `@route(..., name='routename')` and generate URLs `url('routename', param1='value1', ...)`. - * Exceptions - * The `BreakTheBottle` exception is gone. Use `HTTPResponse(text[, status=200])` instead. - * It is now possible (and recommended) to return `HTTPError` and `HTTPResponse` instances instead of raising them. - * The new function `static_file()` equals `send_file()` but returns a `HTTPResponse` or `HTTPError` instance instead of raising it. Please use `return static_file()` instead of `send_file()` whenever possible. - * SimpleTemplate now encodes unicode variables - * **More notes to come. This page is a work in progress...** diff --git a/docs/start.md b/docs/start.md deleted file mode 100644 index 09c08e0..0000000 --- a/docs/start.md +++ /dev/null @@ -1,201 +0,0 @@ -Bottle: Python Web Framework -==================== - -<div style="float: right; padding: 0px 0px 2em 2em"><img src="/bottle-logo.png" alt="Botle Logo" /></div> - -Bottle is a fast and simple [WSGI][] web-framework for [Python][py] packed into a single file with no external dependencies. - -### Core Features - - * **Routes:** Mapping URLs to code with a simple but powerful pattern syntax. - * **Templates:** Fast build-in template engine and support for [mako][], [jinja2][] and [cheetah][] templates. - * **Server:** Build-in HTTP development server and support for [paste][], [fapws3][], [flup][], [cherrypy][] or any other [WSGI][] capable server. - * **No dependencies:** All in a single file and no dependencies other than the Python standard library. - - [mako]: http://www.makotemplates.org/ - [cheetah]: http://www.cheetahtemplate.org/ - [jinja2]: http://jinja.pocoo.org/2/ - [paste]: http://pythonpaste.org/ - [fapws3]: http://github.com/william-os4y/fapws3 - [flup]: http://trac.saddi.com/flup - [cherrypy]: http://www.cherrypy.org/ - [WSGI]: http://www.wsgi.org/wsgi/ - [py]: http://python.org/ - [bottle-dl]: http://github.com/defnull/bottle/raw/master/bottle.py - -### Download / Install - -You can install the latest stable release with `easy_install -U bottle` or just download the newest [bottle.py][bottle-dl] and place it in your project directory. There are no (hard) dependencies other than the Python standard library. Bottle runs with **Python 2.5+ and 3.x** (using 2to3) - -## Features and Examples - -No installation or configuration required. No dependencies other than the Python standard library. Just get a copy of [bottle.py][bottle-dl], place it into your project directory and start coding. - - #!Python - from bottle import route, run - - @route('/') - def index(): - return 'Hello World!' - - run(host='localhost', port=8080) - -That's all. Run your code and visit [http://localhost:8080/](/localhost.png) - -### Routes - -Use the `@route()` decorator to bind URLs to your handler functions. Named parameters may be used to produce nice looking URLs. - - #!Python - @route('/hello/:name') - def hello(name): - return 'Hello, %s' % name - -### Templates - -Bottle includes a simple and lightning fast template engine called *SimpleTemplate*. Just return a dictionary filled with template variables and pass a template name to the `@view` decorator. - - #!Python - @route('/hello/template/:names') - @view('hello') - def template_hello(names): - names = names.split(',') - return dict(title='Hello World', names=names) - -And here is the template in "./views/hello.tpl": - - #!html - <html> - <head> - <title>{{title}}</title> - </head> - <body> - %for name in names: - <p>Hello, <strong>{{name}}</strong></p> - %end - </body> - </html> - -Bottle makes it easy to switch to other template engines. [mako][], [jinja2][] and [cheetah][] are supported. - - #!Python - from bottle import mako_view as view - -### Static Files, Redirects and HTTP Errors - -Use these handy helpers for regular tasks. - - #!Python - from bottle import send_file, redirect, abort - - @route('/static/:filename') - def static_file(filename): - send_file(filename, root='/path/to/static/files') - - @route('/wrong/url') - def wrong(): - redirect("/right/url") - - @route('/restricted') - def restricted(): - abort(401, "Sorry, access denied.") - -### POST, GET, Header and Cookies - -As easy as using a `dict()` - - #!Python - from bottle import request, response - - @route('/hello/cookie') - def cookie(): - name = request.COOKIES.get('name', 'Stranger') - response.header['Content-Type'] = 'text/plain' - return 'Hello, %s' % name - - @route('/hello/cookie', method='POST') - def set_cookie(): - if 'name' in request.POST: - name = request.POST['name'] - response.COOKIES['name'] = name - return 'OK' - - -### HTTP Server - -Bottle has a HTTP Server build in but also supports [cherrypy][], -[flup][], [paste][] and [fapws3][] as alternatives. - - #!Python - from bottle import PasteServer - run(server=PasteServer) - - - -### Non-Features and Known Bugs - -Bottle does **not** include (yet): - - * Models and ORMs: Choose your own (SQLAlchemy, Elixir) - * HTML-Helper, Session, Identification and Authentication: Do it yourself - * Scaffolding: No, sorry - - -## Voices - -[Kaelin](http://bitbucket.org/kaelin), 2009-10-22, [PyPi Comment](http://pypi.python.org/pypi/bottle): - -> Bottle rocks! The fastest path I've found between idea and implementation for simple Web applications. - -[Seth](http://blog.curiasolutions.com/about/) in his [blog](http://blog.curiasolutions.com/2009/09/the-great-web-development-shootout/) [posts](http://blog.curiasolutions.com/2009/10/the-great-web-technology-shootout-round-3-better-faster-and-shinier/) about common web framework performance: - -> As you can see, there was practically no difference in speed between Bottle and pure WSGI in a basic “hello world” test. Even with the addition of Mako and SQLAlchemy, Bottle performed significantly faster than a bare Pylons or Django setup. On a side note, adding a sample template using Bottle’s default templating package didn’t seem to change these numbers at all. - -## Projects using Bottle - - * [whatismyencoding.com](http://whatismyencoding.com/) guesses the encoding of an URL or string. - * [nagios4iphone](http://damien.degois.info/projects/nagios4iphone/) A Nagios interface for iPhone without touching anything on your nagios servers. - * [flugzeit-rechner.de](http://www.flugzeit-rechner.de/) runs on Bottle and Jinja2. - * [Cuttlefish](http://bitbucket.org/kaelin/cuttlefish/) A browser-based search tool for quickly `grep`ing source code. - * [Torque](http://github.com/jreid42/torque) A multiuser collaborative interface for torrenting. - * [Message in a Bottle](http://github.com/kennyshen/MIAB) A simple community messaging app using Bottle and Cassandra. - * [ResBottle](http://github.com/tnm/redweb) A [Redis](http://code.google.com/p/redis/) web interface. - * [Choofnik](http://www.choofnik.com/) A WYSIWYG localization tool for web sites. - -## Thanks to - -In chronological order of their last contribution (DESC). - - * [Jochen Schnelle](http://github.com/noisefloor) for his great [bottle tutorial](/page/tutorial) - * [Damien Degois](http://github.com/babs) for his `If-Modified-Since` support in `send_file()` and his excellent bug reports - * [Stefan Matthias Aust](http://github.com/sma) for his contribution to `SimpleTemplate` and `Jinja2Template` - * [DauerBaustelle](http://github.com/dauerbaustelle) for his ideas - * [smallfish](http://pynotes.appspot.com/) for his chinese translation of the bottle documentation - * [Johannes Schönberger](http://www.python-forum.de/user-6026.html) for his auto reloading code - * [clutchski](http://github.com/clutchski) for his `CGIAdapter` and CGI support - * huanguan1978 for his windows `send_file()` bug report and patch - * The [German Python Community](http://www.python-forum.de/topic-19451.html) for their support and motivation - - -## Licence (MIT) - - Copyright (c) 2009, Marcel Hellkamp. - - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in - all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - THE SOFTWARE. - diff --git a/docs/tutorial.md b/docs/tutorial.md deleted file mode 100644 index f8de925..0000000 --- a/docs/tutorial.md +++ /dev/null @@ -1,633 +0,0 @@ -[TOC] - - [sqlite_win]: http://www.sqlite.org/download.html - [pysqlite]: http://pypi.python.org/pypi/pysqlite/ - [py_db_api]: http://www.python.org/dev/peps/pep-0249/ - [decorator]: http://docs.python.org/glossary.html#term-decorator - [python]: http://www.python.org - [sqlite]: http://www.sqlite.org - [bottle]: http://bottle.paws.org - [bottle_doc]: http://github.com/defnull/bottle/blob/master/docs/docs.md - [wsgiref]: http://docs.python.org/library/wsgiref.html#module-wsgiref.simple_server - [cherrypy]: http://www.cherrypy.org/ - [fapws3]: http://github.com/william-os4y/fapws3 - [flup]: http://trac.saddi.com/flup - [paste]: http://pythonpaste.org/ - [apache]: http://www.apache.org - [mod_wsgi]: http://code.google.com/p/modwsgi/ - [json]: http://www.json.org - - -# Tutorial -This tutorial should give a brief introduction into the [Bottle WSGI Framework][bottle]. The main goal is to be able, after reading through this tutorial, to create a project using Bottle. Within this document, not all abilities will be shown, but at least the main and important ones like routing, utilizing the Bottle template abilities to format output and handling GET / POST parameters. - -To understand the content here, it is not necessary to have a basic knowledge of WSGI, as Bottle tries to keep WSGI away from the user anyway. You should have a fair understanding of the [Python][python] programming language. Furthermore, the example used in the tutorial retrieves and stores data in a SQL databse, so a basic idea about SQL helps, but is not a must to understand the concepts of Bottle. Right here, [SQLite][sqlite] is used. The output of Bottle send to the browser is formated in some examples by the help of HTML. Thus, a basic idea about the common HTML tags does help as well. - -For the sake of introducing Bottle, the Python code "in between" is kept short, in order to keep the focus. Also all code within the tutorial is working fine, but you may not necessarily use it "in the wild", e.g. on a public web server. In order to do so, you may add e.g. more error handling, protect the database with a password, test and escape the input etc. - -## Goals -At the end of this tutorial, we will have a simple, web-based ToDo list. The list contains a text (with max 100 characters) and a status (0 for closed, 1 for open) for each item. Through the web-based user interface, open items can be view and edited and new items can be added. - -During development, all pages will be available on "localhost" only, but later on it will be show how to adapt the application for a "real" server, including how to use with Apache's mod_wsgi. - -Bottle will do the routing and format the output, by the help of templates. The items of the list will be stored inside a SQLite database. Reading and writing from / the database will be done by Python code. - -We will end up with an application with the following pages and functionality: - - * start page `http://localhost:8080/todo` - * adding new items to the list: `http://localhost:8080/new` - * page for editing items: `http://localhost:8080/edit/:no` - * validating data assigned by dynamic routes with the @validate decorator - * catching errors - -## Before We Start... - -### Install Bottle - -Assuming that you have a fairly new installation of Python (version 2.5 or higher), you only need to install Bottle in addition to that. Bottle has no other dependencies than Python itself. - -You can either manually install Bottle or use Python's easy_install: `easy_install bottle` - -### Further Software Necessities - -As we use SQLite3 as a database, make sure it is installed. On Linux systems, most distributions have SQLite3 installed by default. SQLite is available for [Windows and MacOS X][sqlite_win] as well. - -Furthermore, you need [Pysqlite][pysqlite], the Python modules to access SQLite databases. Again, many Linux distributions have the module (often called "python-sqlite3") pre-installed, otherwise just install manually or via `easy_install pysqlite`. - -*Note*: Many older systems have SQLite2 pre-installed. All examples will work fine with this version, too. You just need to import the corresponding Python module named "sqlite" instead of "sqlite3", as used in the examples below. - - -### Create An SQL Database - -First, we need to create the database we use later on. To do so, run SQLite with the command `sqlite3 todo.db`. This will create an empty data base called "todo.db" and you will see the SQLite prompt, which may look like this: `sqlite>`. Right here, input the following commands: - - #!sql - CREATE TABLE todo (id INTEGER PRIMARY KEY, task char(100) NOT NULL, status bool NOT NULL); - INSERT INTO todo (task,status) VALUES ('Read A-byte-of-python to get a good introduction into Python',0); - INSERT INTO todo (task,status) VALUES ('Visit the Python website',1); - INSERT INTO todo (task,status) VALUES ('Test various editors for and check the syntax highlighting',1); - INSERT INTO todo (task,status) VALUES ('Choose your favorite WSGI-Framework',0); - -The first line generates a tables called "todo" with the three columns "id", "task", and "status". "id" is a unique id for each row, which is used later on to reference the rows. The column "task" holds the text which describes the task, it can be max 100 characters long. Finally, the column "status" is used to mark a task as open (value 1) or closed (value 0). - -Alternatively, we can create the database using the sqlite version embedded in python since version 2.5. In this way there is no need to install the full sqlite package. Just execute `python` and enter: - - #!pycon - >>> import sqlite3 - >>> con=sqlite3.connect("todo.db") - >>> sql = """ - ... CREATE TABLE todo (id integer PRIMARY KEY, task char(100) NOT NULL, status boolean NOT NULL); - ... INSERT INTO "todo" VALUES(1,'Read A-byte-of-python to get a good introduction into Python',0); - ... INSERT INTO "todo" VALUES(2,'Visit the Python website',1); - ... INSERT INTO "todo" VALUES(3,'Test various editors for and check the syntax highlighting',1); - ... INSERT INTO "todo" VALUES(4,'Choose your favorite WSGI-Framework',0); - ... """ - >>> con.executescript(sql) - - -## Using Bottle for a web-based ToDo list - -Now it is time to introduce Bottle in order to create a web-based application. But first, we need to look into a basic concept of Bottle: routes. - -### Understanding routes -Basically, each page visible in the browser is dynamically generate when the page address is called. Thus, there is no static content. That is exactly what is called a "route" within Bottle: a certain address on the server. So, for example, when the page `http://localhost:8080/todo` is called from the browser, Bottle "grabs" the call and checks if there is any (Python) function defined for the route "todo". If so, Bottle will execute the corresponding Python code and return its result. - -### First Step - Showing All Open Items -So, after understanding the concept of routes, let's create the first one. The goal is to see all open items from the ToDo list: - - #!Python - import sqlite3 - from bottle import route, run - - @route('/todo') - def todo_list(): - conn = sqlite3.connect('todo.db') - c = conn.cursor() - c.execute("SELECT id, task FROM todo WHERE status LIKE '1'") - result = c.fetchall() - return str(result) - - run() - -Save the code a "todo.py", preferable in the same directory as the file "todo.db". Otherwise, you need to add the path to "todo.db" in the `sqlite3.connect()` statement. - -Let's have a look what we just did: We imported the necessary module "sqlite3" to access to SQLite database and from Bottle we imported "route" and "run". The `run()` statement simply starts the web server included in Bottle. By default, the web server serves the pages on localhost and port 8080. Furthermore, we imported "route", which is the function responsible for Bottle's routing. As you can see, we defined one function, "todo_list()", with a few lines of code reading from the database. The important point is the [decorator statement][decorator] `@route('/todo')` right before the `def todo_list()` statement. By doing this, we bind this function to the route "/todo", so every time the browsers calls `http://localhost:8080/todo`, Bottle returns the result of the function "todo_list()". That is how routing within bottle works. - -Actually you can bind more than one route to a function. So the following code - - #!Python - ... - @route('/todo') - @route('/my_todo_list') - def todo_list(): - ... - -will work fine, too. What will not work is to bind one route to more than one function. - -What you will see in the browser is what is returned, thus the value given by the `return` statement. In this example, we need to convert "result" in to a string by `str()`, as Bottle expects a string or a list of strings from the return statement. But here, the result of the database query is a list of tuples, which is the standard defined by the [Python DB API][py_db_api]. - -Now, after understanding the little script above, it is time to execute it and watch the result yourself. Remember that on Linux- / Unix-based systems the file "todo.py" need to be executable first. Then, just run `python todo.py` and call the page `http://localhost:8080/todo` in your browser. In case you made no mistake writing the script, the output should look like this: - - #!Python - [(2, u'Visit the Python website'), (3, u'Test various editors for and check the syntax highlighting')] - -If so - congratulations! You are now a successful user of Bottle. In case it did not work and you need to make some changes to the script, remember to stop Bottle serving the page, otherwise the revised version will not be loaded. - -Actually, the output is not really exciting nor nice to read. It is the raw result returned from the SQL-Query. - -So, in the next step we format the output in a nicer way. But before we do that, we make our life easier. - -### Debugging and Auto-Reload -Maybe you already experienced the Bottle sends a short error message to the browser in case something within the script is wrong, e.g. the connection to the database is not working. For debugging purposes it is quite helpful to get more details. This can be easily achieved by adding the following statement to the script: - - #!Python - from bottle import run, route, debug - ... - #add this at the very end: - debug(True) - run() - -By enabling "debug", you will get a full stacktrace of the Python interpreter, which usually contains useful information for finding bugs. Furthermore, templates (see below) are not cached, thus changes to template will take effect without stopping the server. - -**Note** that `debug(True)` is supposed to be used for development only, it should *not* be used in productive environments. - -A further quite nice feature is auto-reloading, which is enabled by modifying the `run()` statement to - - #!Python - run(reloader=True) - -This will automatically detect changes to the script and reload the new version once it is called again, without the need to stop and start the server. - -Again, the feature is mainly supposed to be used while development, not on productive systems. - -### Bottle Template To Format The Output -Now let's have a look to cast the output of the script into a proper format. - -Actually Bottle expects to receive a string or a list of strings from a function and returns them by the help of the build-in server to the browser. Bottle does not bother about the content of the string itself, so it can be text formated with HTML markup, too. - -Bottle brings its own easy-to-use template engine with it. Templates are stored as separate files having a ".tpl" extension. The template can be called then from within a function. Templates can contain any type of text (which will be most likely HTML-markup mixed with Python statements). Furthermore, templates can take arguments, e.g. the result set of a database query, which will be then formated nicely within the template. - -Right here, we are going to cast the result of our query showing the open ToDo items into a simple table with two columns: the first column will contain the ID of the item, the second column the text. The result set is, as seen above, a list of tuples, each tuple contains one set of results. - -To include the template into our example, just add the following lines: - - #!Python - from bottle import from bottle import route, run, debug, template - ... - result = c.fetchall() - c.close() - output = template('make_table', rows=result) - return output - ... - -So we do here two things: First, we import "template" from Bottle in order to be able to use templates. Second, we assign the output of the template "make_table" to the variable "output", which is then returned. In addition to calling the template, we assign "result", which we received from the database query, to the variable "rows", which is later on used within the template. If necessary, you can assign more than one variable / value to a template. - -Templates always return a list of strings, thus there is no need to convert anything. Of course, we can save one line of code by writing `return template('make_table', rows=result)`, which gives exactly the same result as above. - -Now it is time to write the corresponding template, which looks like this: - - #!html - %#template to generate a HTML table from a list of tuples (or list of lists, or tuple of tuples or ...) - <p>The open items are as follows:</p> - <table border="1"> - %for row in rows: - <tr> - %for r in row: - <td>{{r}}</td> - %end - </tr> - %end - </table> - -Save the code as "make_table.tpl" in the same directory where "todo.py" is stored. - -Let's have a look at the code: Every line starting with % is interpreted as Python code. Please note that, of course, only valid Python statements are allowed, otherwise the template will raise an exception, just as any other Python code. The other lines are plain HTML-markup. - -As you can see, we use Python's "for"-statement two times, in order to go through "rows". As seen above, "rows" is a variable which holds the result of the database query, so it is a list of tuples. The first "for"-statement accesses the tuples within the list, the second one the items within the tuple, which are put each into a cell of the table. Important is the fact that you need additionally close all "for", "if", "while" etc. statements with `%end`, otherwise the output may not be what you expect. - -If you need to access a variable within a non-Python code line inside the template, you need to put it into double curly braces. This tells the template to insert the actual value of the variable right in place. - -Run the script again and look at the output. Still not really nice, but at least better readable than the list of tuples. Of course, you can spice-up the very simple HTML-markup above, e.g. by using in-line styles to get a better looking output. - -### Using GET And POST values -As we can review all open items properly, we move to the next step, which is adding new items to the ToDo list. The new item should be received from a regular HTML-based form, which sends its data by the GET-method. - -To do so, we first add a new route to our script and tell the route that it should get GET-data: - - #!Python - from bottle import route, run, debug, template, request - ... - return template('make_table', rows=result) - ... - - @route('/new', method='GET') - def new_item(): - - new = request.GET.get('task', '').strip() - - conn = sqlite3.connect('todo.db') - c = conn.cursor() - - query = "INSERT INTO todo (task,status) VALUES (?,1)" - c.execute(query, (new,)) - new_id = c.lastrowid - conn.commit() - c.close() - - return '<p>The new task was inserted into the database, the ID is %s</p>' % new_id - -To access GET (or POST) data, we need to import "request" from Bottle. To assign the actual data to a variable, we use the statement `request.GET.get('task','').strip()` statement, where "task" is the name of the GET-data we want to access. That's all. If your GET-data has more than one variable, multiple `request.GET.get()` statements can be used and assigned to other variables. - -The rest of this piece of code is just processing of the gained data: writing to the database, retrieve the corresponding id from the database and generate the output. - -But where do we get the GET-data from? Well, we can use a static HTML page holding the form. Or, what we do right now, is to use a template which is output when the route "/new" is called without GET-data. - -The code need to be extended to: - - #!Python - ... - @route('/new', method='GET') - def new_item(): - - if request.GET.get('save','').strip(): - - new = request.GET.get('task', '').strip() - conn = sqlite3.connect('todo.db') - c = conn.cursor() - - query = "INSERT INTO todo (task,status) VALUES (?,1)" - c.execute(query, (new,)) - new_id = c.lastrowid - conn.commit() - c.close() - - return '<p>The new task was inserted into the database, the ID is %s</p>' %new_id - - else: - return template('new_task.tpl') - ... - -"new_task.tpl" looks like this: - - #!html - <p>Add a new task to the ToDo list:</p> - <form action="/new" method="GET"> - <input type="text" size="100" maxlength="100" name="task"> - <input type="submit" name="save" value="save"> - </form> - -That's all. As you can see, the template is plain HTML this time. - -Now we are able to extend our to do list. - -By the way, if you prefer to use POST-data: This works exactly the same why, just use `request.POST.get()` instead. - -### Editing Existing Items -The last point to do is to enable editing of existing items. - -By using the routes we know so far only it is possible, but may be quite tricky. But Bottle knows something called "dynamic routes", which makes this task quite easy. - -The basic statement for a dynamic route looks like this: - - #!Python - @route('/myroute/:something') - -The key point here is the colon. This tells Bottle to accept for ":something" any string up to the next slash. Furthermore, the value of "something" will be passed to the function assigned to that route, so the data can be processed within the function. - -For our ToDo list, we will create a route `@route('/edit/:no)`, where "no" is the id of the item to edit. - -The code looks like this: - - #!Python - @route('/edit/:no', method='GET') - def edit_item(no): - - if request.GET.get('save','').strip(): - edit = request.GET.get('task','').strip() - status = request.GET.get('status','').strip() - - if status == 'open': - status = 1 - else: - status = 0 - - conn = sqlite3.connect('todo.db') - c = conn.cursor() - query = "UPDATE todo SET task = ?, status = ? WHERE id LIKE ?" - c.execute(query, (edit,status,no)) - conn.commit() - - return '<p>The item number %s was successfully updated</p>' %no - - else: - conn = sqlite3.connect('todo.db') - c = conn.cursor() - query = "SELECT task, status FROM todo WHERE id LIKE ?" - c.execute(query, (no,)) - cur_data = c.fetchone() - - return template('edit_task', old = cur_data, no = no) - -It is basically pretty much the same what we already did above when adding new items, like using "GET"-data etc. The main addition here is using the dynamic route ":no", which here passes the number to the corresponding function. As you can see, "no" is used within the function to access the right row of data within the database. - -The template "edit_task.tpl" called within the function looks like this: - - #!html - %#template for editing a task - %#the template expects to receive a value for "no" as well a "old", the text of the selected ToDo item - <p>Edit the task with ID = {{no}}</p> - <form action="/edit/{{no}}" method="get"> - <input type="text" name="task" value="{{old[0]}}" size="100" maxlength="100"> - <select name="status"> - <option>open</option> - <option>closed</option> - </select> - <br/> - <input type="submit" name="save" value="save"> - </form> - -Again, this template is a mix of Python statements and HTML, as already explained above. - -A last word on dynamic routes: you can even use a regular expression for a dynamic route. But this topic is not discussed further here. - -### Validating dynamic routes -Using dynamic routes is fine, but for many cases it makes sense to validate the dynamic part of the route. For example, we expect a integer number in our route for editing above. But if a float, characters or so are received, the Python interpreter throws an exception, which is not what we want. - -For those cases, Bottle offers the `@validate` decorator, which validates the "input" prior to passing it to the function. In order to apply the validator, extend the code as follows: - - #!Python - from bottle import route, run, debug, template, request, validate - - ... - - @route('/edit/:no', method='GET') - @validate(no=int) - def edit_item(no): - - ... - -At first, we imported "validate" from the Bottle framework, than we apply the @validate-decorator. Right here, we validate if "no" is an integer. Basically, the validation works with all types of data like floats, lists etc. - -Save the code and call the page again using a "403 forbidden" value for ":no", e.g. a float. You will receive not an exception, but a "403 - Forbidden" error, saying that a integer was expected. - -### Caching Errors -The next step may is to catch the error with Bottle itself, to keep away any type of error message from the user of your application. To do that, Bottle has an "error-route", which can be a assigned to a HTML-error. - -In our case, we want to catch a 403 error. The code is as follows: - - #!Python - from bottle import route, run, debug, template, request, validate, error - - ... - - @error(403) - def mistake(code): - return 'The parameter you passed has the wrong format!' - -So, at first we need to import "error" from Bottle and define a route by `error(403)`, which catches all "403 forbidden" errors. The function "mistake" is assigned to that. Please note that `error()` always passed the error-code to the function - even if you do not need it. Thus, the function always needs to accept one argument, otherwise it will not work. - -Again, you can assign more than one error-route to a function, or catch various errors with one function each. So this code: - - #!Python - @error(404) - @error(403) - def mistake(code): - return 'There is something wrong!' - -works fine, the following one as well: - - #!Python - @error(403) - def mistake403(code): - return 'The parameter you passed has the wrong format!' - - @error(404) - def mistake404(code): - return 'Sorry, this page does not exist!' - -### Summary -After going through all the sections above, you should have a brief understanding how the Bottle WSGI framework works. Furthermore you have all the knowledge necessary to use Bottle for you applications. - -The following chapter give a short introduction how to adapt Bottle for larger projects. Furthermore, we will show how to operate Bottle with web servers which performs better on a higher load / more web traffic than the one we used so far. - -## Server Setup -So far, we used the standard server used by Bottle, which is the [WSGI reference Server][wsgiref] shipped along with Python. Although this server is perfectly suitable for development purposes, it is not really suitable for larger applications. But before we have a look at the alternatives, let's have a look how to tweak the setting of the standard server first - -### Running Bottle on a different port and IP -As a standard, Bottle does serve the pages on the IP-adress 127.0.0.1, also known as "localhost", and on port "8080". To modify there setting is pretty simple, as additional parameters can be passed to Bottle's `run()` function to change the port and the address. - -To change the port, just add `port=portnumber` to the run command. So, for example - - #!Python - run(port=80) - -would make Bottle listen to port 80. - -To change the IP-address where Bottle is listing / serving can be change by - - #!Python - run(host='123.45.67.89') - -Of course, both parameters can be combined, like: - - #!Python - run(port=80, host='123.45.67.89') - -The `port` and `host` parameter can also be applied when Bottle is running with a different server, as shown in the following section - -### Running Bottle with a different server -As said above, the standard server is perfectly suitable for development, personal use or a small group of people only using your application based on Bottle. For larger task, the standard server may become a Bottle neck, as it is single-threaded, thus it can only serve on request at a time. - -But Bottle has already various adapters to multi-threaded server on board, which perform better on higher load. Bottle supports [cherryPy][cherrypy], [fapws3][fapws3], [flup][flup] and [Paste][paste]. - -If you want to run for example Bottle with the Paste server, use the following code: - - #!Python - from bottle import PasteServer - ... - run(server=PasterServer) - -This works exactly the same way with `FlupServer`, `CherryPyServer` and `FapwsServer`. - -### Running Bottle on Apache with mod_wsgi -Maybe you already have an [Apache web server][apache] or you want to run a Bottle-based application large scale - than it is time to think about Apache with [mod_wsgi][mod_wsgi]. - -We assume that your Apache server is up and running and mod_wsgi is working fine as well. On a lot of Linux distributions, mod_wsgi can be installed via the package management easily. - -Bottle brings a adapter for mod_wsgi with it, so serving your application is an easy task. - -In the following example, we assume that you want to make your application "ToDO list" accessible through "http://www.mypage.com/todo" and your code, templates and SQLite database is stored in the path "var/www/todo". - -At first, we need to import "defautl_app" from Bottle in our little script: - - #!Python - from bottle import route, run, debug, template, request, validate, error, default_app - -When you run your application via mod_wsgi, it is imperative to remove the `run()` statement from you code, otherwise it won't work here. - -After that, create a file called "adapter.wsgi" with the following content: - - #!Python - import sys - sys.path = ['/var/www/todo/'] + sys.path - - import todo - import os - - os.chdir(os.path.dirname(__file__)) - - application = default_app() - -and save it in the same path, "/var/www/todo". Actually the name of the file can be anything, as long as the extensions is ".wsgi". The name is only used to reference the file from your virtual host. - -Finally, we need to add a virtual host to the Apache configuration, which looks like this: - - #!ApacheConf - <VirtualHost *> - ServerName mypage.com - - WSGIDaemonProcess todo user=www-data group=www-data processes=1 threads=5 - WSGIScriptAlias / /var/www/todo/adapter.wsgi - - <Directory /var/www/todo> - WSGIProcessGroup todo - WSGIApplicationGroup %{GLOBAL} - Order deny,allow - Allow from all - </Directory> - </VirtualHost> - -After restarting the server, your the ToDo list should be accessible at "http://www.mypage.com/todo" - -## Final words -Now we are at the end of this introduction and tutorial to Bottle. We learned about the basic concepts of Bottle and wrote a first application using the Bottle framework. In addition to that, we saw how to adapt Bottle for large task and server Bottle through a Apache web server with mod_wsgi. - -As said in the introduction, this tutorial is not showing all shades and possibilities of Bottle. What we skipped here is e.g. using regular expressions on dynamic routes, returning [JSON data][json], how to serve static files and receive File Objects and Streams. Furthermore, we did not show how templates can be called from within another template. For an introduction into those points, please refer to the full [Bottle documentation][bottle_doc]. - -## Complete example listing -As above the ToDo list example was developed piece by piece, here is the complete listing: - -Main code for the application `todo.py`: - - #!Python - import os, sqlite3 - from bottle import route, run, debug, template, request, validate, error - - # only needed when you run Bottle on mod_wsgi - from bottle import default_app - - @route('/todo') - def todo_list(): - - conn = sqlite3.connect('todo.db') - c = conn.cursor() - c.execute("SELECT id, task FROM todo WHERE status LIKE '1';") - result = c.fetchall() - c.close() - - output = template('make_table', rows=result) - return output - - @route('/new', method='GET') - def new_item(): - - if request.GET.get('save','').strip(): - - new = request.GET.get('task', '').strip() - conn = sqlite3.connect('todo.db') - c = conn.cursor() - - query = "INSERT INTO todo (task,status) VALUES (?,1)" - c.execute(query, (new,)) - new_id = c.lastrowid - conn.commit() - c.close() - - return '<p>The new task was inserted into the database, the ID is %s</p>' %new_id - - else: - return template('new_task.tpl') - - @route('/edit/:no', method='GET') - @validate(no=int) - def edit_item(no): - - if request.GET.get('save','').strip(): - edit = request.GET.get('task','').strip() - status = request.GET.get('status','').strip() - - if status == 'open': - status = 1 - else: - status = 0 - - conn = sqlite3.connect('todo.db') - c = conn.cursor() - query = "UPDATE todo SET task = ?, status = ? WHERE id LIKE ?" - c.execute(query, (edit,status,no)) - conn.commit() - - return '<p>The item number %s was successfully updated</p>' %no - - else: - conn = sqlite3.connect('todo.db') - c = conn.cursor() - query = "SELECT task FROM todo WHERE id LIKE ?" - c.execute(query, (no,)) - cur_data = c.fetchone() - print cur_data - - return template('edit_task', old = cur_data, no = no) - - - @error(403) - def mistake403(code): - return 'There is a mistake in your url!' - - @error(404) - def mistake404(code): - return 'Sorry, this page does not exist!' - - - debug(True) - - def main(): - run(reloader=True) - - if __name__ == "__main__": - # Interactive mode - main() - else: - # Mod WSGI launch - os.chdir(os.path.dirname(__file__)) - application = default_app() - - #remember to remove reloader=True and debug(True) when you move your application from development to a productive environment. - -Template `edit_task.tpl`: - - #!html - %#template for editing a task - %#the template expects to receive a value for "no" as well a "old", the text of the selected ToDo item - <p>Edit the task with ID = {{no}}</p> - <form action="/edit/{{no}}" method="get"> - <input type="text" name="task" value="{{old[0]}}" size="100" maxlength="100"> - <select name="status"> - <option>open</option> - <option>closed</option> - </select> - <br/> - <input type="submit" name="save" value="save"> - </form> - -Template `new_task.tpl`: - - #!html - %#template for the form for a new task - <p>Add a new task to the ToDo list:</p> - <form action="/new" method="GET"> - <input type="text" size="100" maxlenght="100" name="task"> - <input type="submit" name="save" value="save"> - </form> - - |