diff options
author | Marcel Hellkamp <marc@gsites.de> | 2010-06-27 13:04:53 +0200 |
---|---|---|
committer | Marcel Hellkamp <marc@gsites.de> | 2010-06-27 13:04:53 +0200 |
commit | 1b2c9962865a8892de602f58fc8cb4627533ba18 (patch) | |
tree | 76ec45256c5e990f821ecf9984f2ff5c92549d2f | |
parent | fad26ff0c76dbdb3d7b9c67d8df66e39d0575df5 (diff) | |
download | bottle-1b2c9962865a8892de602f58fc8cb4627533ba18.tar.gz |
Documentation (lots of it)
-rwxr-xr-x | apidoc/api.rst | 2 | ||||
-rw-r--r-- | apidoc/changelog.rst | 6 | ||||
-rwxr-xr-x | apidoc/contact.rst | 1 | ||||
-rw-r--r-- | apidoc/development.rst | 143 | ||||
-rw-r--r-- | apidoc/faq.rst | 108 | ||||
-rwxr-xr-x | apidoc/index.rst | 17 | ||||
-rw-r--r-- | apidoc/sphinx/static/bottle.css | 7 | ||||
-rwxr-xr-x | apidoc/stpl.rst | 2 | ||||
-rwxr-xr-x | apidoc/tutorial.rst | 459 | ||||
-rw-r--r-- | docs/start.md | 1 |
10 files changed, 377 insertions, 369 deletions
diff --git a/apidoc/api.rst b/apidoc/api.rst index 4812dde..170f932 100755 --- a/apidoc/api.rst +++ b/apidoc/api.rst @@ -30,7 +30,7 @@ The module defines several functions, constants, and an exception. Routing ------------------- -Bottle maintains a stack of :class:`Bottle` instances (see :func:`app' and :class:`AppStack`) and +Bottle maintains a stack of :class:`Bottle` instances (see :func:`app` and :class:`AppStack`) and uses the top of the stack as a *default application* for some of the module-level functions and decorators. diff --git a/apidoc/changelog.rst b/apidoc/changelog.rst index 59ab7f0..e6b3201 100644 --- a/apidoc/changelog.rst +++ b/apidoc/changelog.rst @@ -34,11 +34,11 @@ These changes may break compatibility with previous versions. This is an incomplete list of new features and improved functionality. -* The :class:`Request` object got new features: :attr:`Request.body`, :attr:`Request.auth`, :attr:`Request.url`, :attr:`Request.header`. +* The :class:`Request` object got new properties: :attr:`Request.body`, :attr:`Request.auth`, :attr:`Request.url`, :attr:`Request.header`, :attr:`Request.forms`, :attr:`Request.files`. * The :meth:`Response.set_cookie` and :meth:`Request.get_cookie` methods are now able to encode and decode python objects. This is called a *secure cookie* because the encoded values are signed and protected from changes on client side. All pickle-able data structures are allowed. * The new :class:`Router` class drastically improves performance for setups with lots of dynamic routes and supports named routes (named route + dict = URL string). -* It is now possible (and recommended) to return :class:`HTTPError` and :class:`HTTPResponse` instances or other exception objects instead of raising them. -* The new function :func:`static_file` equals :func:`send_file` but returns a `HTTPResponse` or `HTTPError` instance instead of raising it. :func:`send_file` is deprecated. +* It is now possible (and recommended) to return :exc:`HTTPError` and :exc:`HTTPResponse` instances or other exception objects instead of raising them. +* The new function :func:`static_file` equals :func:`send_file` but returns a :exc:`HTTPResponse` or :exc:`HTTPError` instead of raising it. :func:`send_file` is deprecated. * New :func:`get`, :func:`post`, :func:`put` and :func:`delete` decorators. * The :class:`SimpleTemplate` engine got full unicode support. * Lots of non-critical bugfixes. diff --git a/apidoc/contact.rst b/apidoc/contact.rst index 3da1907..5e87246 100755 --- a/apidoc/contact.rst +++ b/apidoc/contact.rst @@ -15,6 +15,7 @@ Hi, I'm *Marcel Hellkamp* (aka *defnull*), author of Bottle and the guy behind t 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. .. rubric:: 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 diff --git a/apidoc/development.rst b/apidoc/development.rst index 3df1593..a22c975 100644 --- a/apidoc/development.rst +++ b/apidoc/development.rst @@ -1,39 +1,142 @@ Developer Notes ================= -This document is intended for bottle developers or people interested in bottles release strategy. If you just want to use bottle as a library, use the latest stable release from the :doc:`download` page. +This document is intended for developers and package maintainers interested in the bottle development and release workflow. If you want to contribute, you are just right! -Releases and Repository ------------------------ +Get involved +------------ -TODO +There are several ways to join the community and stay up to date. Here are some of them: -If you want to fetch a specific release from the git repository, trust the tags, not the branches. A branch may contain patches that are not released yet. The tag marks the exact commit which changed the version number. +* **Mailinglist**: Join the mailinglist by sending an email to `bottlepy@googlegroups.com <mailto:bottlepy@googlegroups.com>`_ or using the web front-end at `google groups <http://groups.google.de/group/bottlepy>`_. You don't need a Google account to use the mailing-list, but then I have to approve your subscription manually to protect the list from spam. Please be patient in that case. +* **Twitter**: Follow us on `Twitter<twitter.com/bottlepy>`_ or search for the ``#bottlepy`` tag. -Version Numbering ------------------ -The Bottle version number splits into three parts (**major.minor.revision**). These are *not* used to promote new features (features may in fact be introduced silently) but to indicate important bug-fixes and/or API changes. +Get the Sources +--------------- -.. rubric:: Revision +The bottle development repository and the issue tracker are both hosted at `github <http://github.com/defnull/bottle>`_. If you plan to contribute, it is a good idea to create an account there and fork the main repository. This way your changes and ideas are visible to other developers and can be discussed openly. Even without an account, you can clone the repository using `git <http://git-scm.com/>`_ (recommended) or just download the latest development version as a source archive. -The revision number is increased on bug-fixes and other patches that do not change the API or behaviour at all. You can safely update without editing your application code. In fact, you really should as soon as possible, because important security fixes are released this way. +* **git:** ``git clone git://github.com/defnull/bottle.git`` +* **git/http:** ``git clone http://github.com/defnull/bottle.git`` +* **svn:** ``svn checkout http://svn.github.com/defnull/bottle.git`` (not recommended) +* **Download:** Development branch as `tar archive <http://github.com/defnull/bottle/tarball/master>`_ or `zip file <http://github.com/defnull/bottle/zipball/master>`_. -.. rubric:: Minor Release +Branches and their Intention +---------------------------- -The minor release number is increased on updates that change the API or behaviour in some way. You might get some depreciation warnings any may have to tweak some configuration settings to restore the old behaviour, but in most cases these changes are designed to be backward compatible for at least one release. You should update to stay up do date, but don't have to. An exception is 0.7, which *will* break backward compatibility hard. Sorry for that. +The bottle project follows a development and release workflow similar to the one described by `Vincent Driessen in his blog <http://nvie.com/git-model>`_ (very interesting read) with some exceptions: -.. rubric:: Major Release +1) There is no ``develop`` branch. The ``master`` branch is used for development and integration. +2) Release-branches don't die. They stay alive as long as the release is still supported and host the most recent bug-fixed revision of that release. +3) Hotfix-branches don't have to start with ``hotfix-`` but should end in a release number. Example: "some_bug-0.8" -The major release number is increased on important milestones or updates that completely break backward compatibility. You probably have to work over your entire application to use a new release. These releases are very rare, through. +For a quick overview I'll describe the different branch-types and their intentions here: + +master + This is the integration, testing and development branch. All changes that are planned to be part of the next release are merged and tested here. + +release-x.y + As soon as the master branch is (almost) ready for a new release, it is branched into a new release branch. This "release candidate" is feature-frozen but may receive bug-fixes and last-minute changes until it is considered production ready and officially released. From that point on it is called a "support branch" and still receives bug-fixes, but only important ones. The revision number is increased on each push to these branches, so you can keep up with important changes. + +bugfix_name-x.y + These branches are only temporary and used to develop and share non-trivial bug-fixes for existing releases. They are merged into the corresponding release branch and delete soon after that. + +Feature branches + All other branches are feature branches. These are based on the master branch and only live as long as they are still active and not merged back into ``master``. + +.. rubric:: What does this mean for a developer? + +If you want to add a feature, create a new branch from ``master``. If you want to fix a bug, branch ``release-x.y`` for each affected release. Please use a separate branch for each feature or bug to make integration as easy as possible. Thats all. There are git workflow examples at the bottom of this page. + +Oh, and never ever change the release number. I'll do that on integration. You never know in which order I am going to pull pending requests anyway :) + +.. rubric:: What does this mean for a maintainer ? + +Watch the tags (and the mailing list) for bug-fixes and new releases. If you want to fetch a specific release from the git repository, trust the tags, not the branches. A branch may contain changes that are not released yet, but a tag marks the exact commit which changed the version number. + +.. rubric:: Patch Submission + +The best way to get your changes integrated into the main development branch is to fork the main repository at github, create a new feature-branch, apply your changes and send a pull-request. Further down this page is a small collection of git workflow examples that may guide you. Submitting git-compatible patches to the mailing list is fine too. In any case, please follow some basic rules: + + * **Documentation:** Tell us what your patch does. Comment your code. If you introduced a new feature, add to the documentation so others can learn about it. + * **Test:** Write tests to prove that your code works as expected and does not break anything. If you fixed a bug, write at least one test-case that triggers the bug. Make sure that all tests pass before you submit a patch. + * **One patch at a time:** Only fix one bug or add one feature at a time. Design your patches so that they can be applyed as a whole. Keep your patches clean, small and focused. + * **Sync with upstream:** If the ``upstream/master`` branch changed while you were working on your patch, rebase or pull to make sure that your patch still applies without conflicts. + + +Releases and Updates +-------------------- + +Bottle is released at irregular intervals and distributed through `PyPi <http://pypi.python.org/pypi/bottle>`_. Release candidates and bugfix-revisions of outdated releases are only available from the git repository mentioned above. Some Linux distributions may offer packages for outdated releases, though. + +The Bottle version number splits into three parts (**major.minor.revision**). These are *not* used to promote new features but to indicate important bug-fixes and/or API changes. Critical bugs are fixed in at least the two latest minor releases and announced in all available channels (mailinglist, twitter, github). Non-critical bugs or features are not guaranteed to be backported. This may change in the future, through. + +Major Release + The major release number is increased on important milestones or updates that completely break backward compatibility. You probably have to work over your entire application to use a new release. These releases are very rare, through. + +Minor Release + The minor release number is increased on updates that change the API or behaviour in some way. You might get some depreciation warnings any may have to tweak some configuration settings to restore the old behaviour, but in most cases these changes are designed to be backward compatible for at least one minor release. You should update to stay up do date, but don't have to. An exception is 0.8, which *will* break backward compatibility hard. (This is why 0.7 was skipped). Sorry for that. + +Revision + The revision number is increased on bug-fixes and other patches that do not change the API or behaviour. You can safely update without editing your application code. In fact, you really should as soon as possible, because important security fixes are released this way. + +Pre-Release Versions + Release candidates are marked by an ``rc`` in their revision number. These are API stable most of the time and open for testing, but not officially released yet. You should not use these for production. + + + +GIT Workflow Examples +--------------------- + +The following examples assume that you have an (free) account at `github <http://github.com>`_. This is not mandatory, but makes things a lot easier. + +First of all you have to create a fork (a personal clone) of the official repository. To do this, you simply click the "fork" button on the `bottle project page <http://github.com/defnull/bottle>`_. When the fork is done, you will be presented with a short introduction to your new repository. + +The fork you just created is hosted at github and read-able by everyone, but write-able only by you. Now you need to clone the fork locally to actually make changes to it. Make sure you use the private (read-write) URL and *not* the public (read-only) one:: + + git clone git@github.com:your_github_account/bottle.git + +Once the clone is complete your repository will have a remote named "origin" that points to your fork on github. Don’t let the name confuse you, this does not point to the original bottle repository, but to your own fork. To keep track of the official repository, add another remote named "upstream":: + + cd bottle + git remote add upstream git://github.com/defnull/bottle.git + git fetch upstream + +Note that "upstream" is a public clone URL, which is read-only. You cannot push changes directly to it. Instead, we will pull from your public repository. This is described later. + +.. rubric:: Submit a Feature + +New features are developed in separate feature-branches to make integration easy. Because they are going to be integrated into the ``master`` branch, they must be based on ``upstream/master``. To create a new feature-branch, type the following:: + + git checkout -b cool_feature upstream/master -.. rubric:: Pre-Release Versions +Now implement your feature, write tests, update the documentation, make sure that all tests pass and commit your changes:: + + git commit -a -m "Cool Feature" + +If the ``upstream/master`` branch changed in the meantime, there may be conflicts with your changes. To solve these, 'rebase' your feature-branch onto the top of the updated ``upstream/master`` branch:: + + git fetch upstream + git rebase upstream + +This is equivalent to undoing all your changes, updating your branch to the latest version and reapplying all your patches again. If you released your branch already (see next step), this is not an option because it rewrites your history. You can do a normal pull instead. Resolve any conflicts, run the tests again and commit. + +Now you are almost ready to send a pull request. But first you need to make your feature-branch public by pushing it to your github fork:: + + git push origin cool_feature + +After you’ve pushed your commit(s) you need to inform us about the new feature. One way is to send a pull-request using github. Another way would be to start a thread in the mailing-list, which is recommended. It allows other developers to see and discuss your patches and you get some feedback for free :) + +If we accept your patch, we will integrate it into the official development branch and make it part of the next release. + +.. rubric:: Fix a Bug -For each minor or major release there is a special revision number '0' which indicates a completely unstable testing and integration branch. It may break the API at any time and is the branch the developers are actively working on. You should not use these except you are a developer or want to test new features. +The workflow for bug-fixes is very similar to the one for features, but there are some differences: -At some time, this development branch may be increase to 'x.x.1rc' which indicates a release candidate. These are API stable most of the time and open for testing, but not officially released yet. +1) Branch off of the affected release branches instead of just the development branch. +2) Write at least one test-case that triggers the bug. +3) Do this for each affected branch including ``upstream/master`` if it is affected. ``git cherry-pick`` may help you reducing repetitive work. +4) Name your branch after the release it is based on to avoid confusion. Examples: ``my_bugfix-x.y`` or ``my_bugfix-dev``. -Support and Security Fixes --------------------------- -Critical bugs are fixed in at least the two newest minor releases. Non-critical bugs or features are not guaranteed to be backported at all. This may change in the future, through. diff --git a/apidoc/faq.rst b/apidoc/faq.rst index 0ec5ffd..ea471e1 100644 --- a/apidoc/faq.rst +++ b/apidoc/faq.rst @@ -1,11 +1,9 @@ .. module:: bottle -.. _beaker: http://beaker.groovie.org/ -.. _mod_python: http://www.modpython.org/ -.. _mod_wsgi: http://code.google.com/p/modwsgi/ -.. _werkzeug: http://werkzeug.pocoo.org/documentation/dev/debug.html .. _paste: http://pythonpaste.org/modules/evalexception.html .. _pylons: http://pylonshq.com/ +.. _mod_python: http://www.modpython.org/ +.. _mod_wsgi: http://code.google.com/p/modwsgi/ ========================== Frequently Asked Questions @@ -20,108 +18,6 @@ Is bottle suitable for complex applications? Bottle is a *micro* framework designed for prototyping and building small web applications and services. It stays out of your way and allows you to get things done fast, but misses some advanced features and ready-to-use solutions found in other frameworks (MVC, ORM, Form validation, scaffolding, XML-RPC). Although it *is* possible to add these features and build complex applications with Bottle, you should consider using a full-stack Web Framework like pylons_ or paste_ instead. - -How do I ...? -============= - - - - -Sessions --------------------------------------------------------------------------------- - -There is no build in support for sessions because there is no *right* way to do it (in a micro framework). 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:: - - import bottle - from beaker.middleware import SessionMiddleware - - session_opts = { - 'session.type': 'file', - 'session.cookie_expires': 300, - 'session.data_dir': './data', - 'session.auto': True - } - app = SessionMiddleware(bottle.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) - -Debugging Middleware --------------------------------------------------------------------------------- - -Bottle catches all Exceptions raised in your app code to prevent your WSGI server from crashing. If the build-in :func:`debug` mode is not enough and you need exceptions to propagate to a debugging middleware, you can turn off this behaviour:: - - import bottle - app = bottle.app() - app.catchall = False #Now most exceptions are re-raised within bottle. - myapp = DebuggingMiddleware(app) #Replace this with a middleware of your choice (see below) - bottle.run(app=myapp) - -Now, bottle only catches its own exceptions (:exc:`HTTPError`, :exc:`HTTPResponse` and :exc:`BottleException`) and your middleware can handle the rest. - -The werkzeug_ and paste_ libraries both ship with very powerfull debugging WSGI middleware. Look at :class:`werkzeug.debug.DebuggedApplication` for werkzeug_ and :class:`paste.evalexception.middleware.EvalException` for paste_. They both allow you do inspect the stack and even execute python code within the stack context, so **do not use them in production**. - - -AuthKit --------------------------------------------------------------------------------- - -TODO - -Embedding other WSGI Apps --------------------------------------------------------------------------------- - -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:: - - 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.add_header(key, value) - 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. - - -Ignore tailing slashes --------------------------------------------------------------------------------- - -For Bottle, ``/example`` and ``/example/`` are two different routes [1]_. To handle both URLs you can add two ``@route`` decorators:: - - @route('/test') - @route('/test/') - def test(): return 'Slash? no?' - -or add a WSGI middleware that strips tailing slashes from all URLs:: - - 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.app() - myapp = StripPathMiddleware(app) - bottle.run(app=appmy) - - - - - - Common Problems and Pitfalls ============================ diff --git a/apidoc/index.rst b/apidoc/index.rst index 3ddad5e..ae62f69 100755 --- a/apidoc/index.rst +++ b/apidoc/index.rst @@ -28,14 +28,6 @@ Bottle is a fast, simple and lightweight WSGI_ micro web-framework for Python_. * **Utilities:** Convenient access to form data, file uploads, cookies, headers and other HTTP related metadata. * **Server:** Build-in HTTP development server and support for paste_, fapws3_, flup_, cherrypy_ or any other WSGI_ capable HTTP server. -.. rubric:: Download and Install - -.. _download: - -.. __: http://github.com/defnull/bottle/raw/master/bottle.py - -Install the latest stable release via PyPi_ (``easy_install -U bottle``) or download `bottle.py`__ (unstable) into your project directory. There are no hard [1]_ dependencies other than the Python standard library. Bottle runs with **Python 2.5+ and 3.x** (using 2to3) - .. rubric:: Example: "Hello World" in a bottle :: @@ -48,6 +40,14 @@ Install the latest stable release via PyPi_ (``easy_install -U bottle``) or down run(host='localhost', port=8080) +.. rubric:: Download and Install + +.. _download: + +.. __: http://github.com/defnull/bottle/raw/master/bottle.py + +Install the latest stable release via PyPi_ (``easy_install -U bottle``) or download `bottle.py`__ (unstable) into your project directory. There are no hard [1]_ dependencies other than the Python standard library. Bottle runs with **Python 2.5+ and 3.x** (using 2to3) + Documentation =============== The documentation is a work in progress. If you have questions not answered here, please check the :doc:`faq`, file a ticket at bottles issue_tracker_ or send an e-mail to the `mailing list <mailto:bottlepy@googlegroups.com>`_. @@ -56,6 +56,7 @@ The documentation is a work in progress. If you have questions not answered here :maxdepth: 2 tutorial + recieps faq api stpl diff --git a/apidoc/sphinx/static/bottle.css b/apidoc/sphinx/static/bottle.css index 9afc7f9..d809b1a 100644 --- a/apidoc/sphinx/static/bottle.css +++ b/apidoc/sphinx/static/bottle.css @@ -49,3 +49,10 @@ h1, h2, h3, h4, h5, h6, h7, p.rubric { div.sphinxsidebar ul { font-size: 0.9em; } + +p.rubric { + background: #eee; + border-left: 5px solid #1C4E63; + padding-left: 5px; +} + diff --git a/apidoc/stpl.rst b/apidoc/stpl.rst index 0ab2ab8..a6991fe 100755 --- a/apidoc/stpl.rst +++ b/apidoc/stpl.rst @@ -21,7 +21,7 @@ In this document we use the :func:`template` helper in examples for the sake of >>> template('Hello {{name}}!', name='World') u'Hello World!' -Just keep in mind that compiling and rendering templates are two different actions, even if the :func:`template` helper hides that fact. Templates are usually compiled only once and cached internally, but rendered many times with differend keyword arguments. +Just keep in mind that compiling and rendering templates are two different actions, even if the :func:`template` helper hides this fact. Templates are usually compiled only once and cached internally, but rendered many times with differend keyword arguments. :class:`SimpleTemplate` Syntax ============================== diff --git a/apidoc/tutorial.rst b/apidoc/tutorial.rst index db7635b..0949d1d 100755 --- a/apidoc/tutorial.rst +++ b/apidoc/tutorial.rst @@ -24,7 +24,7 @@ Tutorial ======== -This tutorial introduces you to the concepts and features of the Bottle web framework. If you have questions not answered here, please check the :doc:`faq` page, then file a ticket at the issue_ tracker or send an e-mail to the `mailing list <mailto:bottlepy@googlegroups.com>`_. +This tutorial introduces you to the concepts and features of the Bottle web framework. If you have questions not answered here, please check the :doc:`faq` page, file a ticket at the issue_ tracker or send an e-mail to the `mailing list <mailto:bottlepy@googlegroups.com>`_. .. note:: @@ -33,8 +33,8 @@ This tutorial introduces you to the concepts and features of the Bottle web fram .. rubric:: A quick overview: * :ref:`tutorial-routing`: Web development starts with binding URLs to code. This section tells you how to do it. -* :ref:`tutorial-request`: Each client request carries a lot of information. HTTP-headers, form data and cookies to name just three. Here is how to use them. * :ref:`tutorial-output`: You have to return something to the Browser. Bottle makes it easy for you, supporting more than just plain strings. +* :ref:`tutorial-request`: Each client request carries a lot of information. HTTP-headers, form data and cookies to name just three. Here is how to use them. * :ref:`tutorial-templates`: You don't want to write HTML within your python code, do you? Template separate code from presentation. * :ref:`tutorial-debugging`: These tools and features will help you during development. * :ref:`tutorial-deployment`: Get it up and running. @@ -44,15 +44,17 @@ This tutorial introduces you to the concepts and features of the Bottle web fram Getting started =================== -Bottle has no dependencies, so all you need is Python_ (2.5 up to 3.x should work fine) and the :ref:`bottle module <download>`. Lets start with a very basic "Hello World" example:: +Bottle has no dependencies, so all you need is Python_ (2.5 up to 3.x should work fine) and the :ref:`bottle module <download>` file. Lets start with a very basic "Hello World" example:: from bottle import route, run + @route('/hello') def hello(): return "Hello World!" + run(host='localhost', port=8080) -So, whats happening here? +Whats happening here? 1. First we import some bottle components. The :func:`route` decorator and the :func:`run` function. 2. The :func:`route` :term:`decorator` is used do bind a piece of code to an URL. In this example we want to answer requests to the ``/hello`` URL. @@ -62,6 +64,22 @@ So, whats happening here? This is it. Run this script, visit http://localhost:8080/hello and you will see "Hello World!" in your Browser. Of cause this is a very simple example, but it shows the basic concept of how applications are build with bottle. Continue reading and you'll see what else is possible. +.. rubric:: The Application Object + +Several functions and decorators such as :func:`route` or :func:`run` rely on a global application object to store routes, callbacks and configuration. This makes writing small application easy, but can lead to problems in more complex scenarios. If you prefer a more explicit way to define your application and don't mind the extra typing, you can create your own concealed application object and use that instead of the global one:: + + from bottle import Bottle, run + + myapp = Bottle() + + @myapp.route('/hello') + def hello(): + return "Hello World!" + + run(app=myapp, host='localhost', port=8080) + +This tutorial uses the global-application syntax for the sake of simplicity. Just keep in mind that you have a choice. The object-oriented approach is further described in the :ref:`tutorial-appobject` section. + .. _tutorial-routing: @@ -93,13 +111,13 @@ As you can see, URLs and routes have nothing to do with actual files on the web Dynamic Routes ------------------------------------------------------------------------------ -Bottle has a special syntax to add wildcards to a route and allow a single route to match a wide range of URLs. These *dynamic routes* are often used by blogs or wikis to create nice looking and meaningful URLs such as ``/blog/2010/04/21`` or ``/wiki/Page_Title``. Let's add a ``:name`` wildcard to our last example:: +Bottle has a special syntax to add wildcards to a route and allow a single route to match a wide range of URLs. These *dynamic routes* are often used by blogs or wikis to create nice looking and meaningful URLs such as ``/archive/2010/04/21`` or ``/wiki/Page_Title``. Why? Because `cool URIs don't change <http://www.w3.org/Provider/Style/URI>`_. Let's add a ``:name`` wildcard to our last example:: @route('/hello/:name') def hello(name): return "Hello %s!" % name -This dynamic route matches ``/hello/alice`` as well as ``/hello/bob``. Each URL fragment covered by a wildcard is passed to the callback function as a keyword argument so you can use the information in your application. +This dynamic route will match ``/hello/alice`` as well as ``/hello/bob``. Each URL fragment covered by a wildcard is passed to the callback function as a keyword argument so you can use the information in your application. Normal wildcards match everything up to the next slash. You can add a regular expression to change that:: @@ -109,44 +127,44 @@ Normal wildcards match everything up to the next slash. You can add a regular ex As you can see, the keyword argument contains a string even if the wildcard is configured to only match digits. You have to explicitly cast it into an integer if you need to. - - HTTP Request Methods ------------------------------------------------------------------------------ .. __: http_method_ -The HTTP protocol defines several `request methods`__ (sometimes referred to as "verbs") for different tasks. ``GET`` is the default for all routes with no other method specified. These routes will match ``GET`` requests only. To handle other methods such as ``POST``, ``PUT`` or ``DELETE``, you can add a ``method`` keyword argument to the :func:`route` decorator or use one of the four alternative decorators: :func:`get`, :func:`post`, :func:`put` or :func:`delete`. +The HTTP protocol defines several `request methods`__ (sometimes referred to as "verbs") for different tasks. GET is the default for all routes with no other method specified. These routes will match GET requests only. To handle other methods such as POST, PUT or DELETE, you may add a ``method`` keyword argument to the :func:`route` decorator or use one of the four alternative decorators: :func:`get`, :func:`post`, :func:`put` or :func:`delete`. -The ``POST`` method is commonly used for HTML form submission. This example shows how to handle a login form using ``POST``:: +The POST method is commonly used for HTML form submission. This example shows how to handle a login form using POST:: from bottle import get, post, request - @get('/login') # or @route('/login') + #@route('/login') + @get('/login') def login_form(): return '''<form method="POST"> <input name="name" type="text" /> <input name="password" type="password" /> </from>''' - @post('/login') # or @route('/login', method='POST') + #@route('/login', method='POST') + @post('/login') def login_submit(): - name = request.POST.get('name') - password = request.POST.get('password') - if name and password and check_login(name, password): + name = request.forms.get('name') + password = request.forms.get('password') + if check_login(name, password): return "<p>Your login was correct</p>" else: - return "<p>Login failed</p>" + login_form() + return "<p>Login failed</p>" -In this example the ``/login`` URL has two callbacks assigned to it: The ``login_form()`` callback is invoked on ``GET`` requests (the user clicked on a link) and returns a HTTP form. When a user submits the form, he creates a ``POST`` request which is handled by the ``login_submit()`` callback. :attr:`Request.POST` and other ways to access request related data are described in the :ref:`tutorial-request` section. +In this example the ``/login`` URL is bound to two distinct callbacks, one for GET requests and another for POST requests. The first one displays a HTML form to the user. The second callback is invoked on a form submission and checks the login credentials the user entered into the form. The use of :attr:`Request.forms` is further described in the :ref:`tutorial-request` section. -.. note:: +.. rubric:: Automatic Fallbacks - The ``HEAD`` method is used by clients to request meta-information about a resource, but without having to download the entire document. Bottle handles these requests automatically by falling back to the corresponding ``GET`` route. You don't have to specify any ``HEAD`` routes yourself. +The special HEAD method is used to ask for the response identical to the one that would correspond to a GET request, but without the response body. This is useful for retrieving meta-information about a resource without having to download the entire document. Bottle handles these requests automatically by falling back to the corresponding GET route and cutting off the request body, if present. You don't have to specify any HEAD routes yourself. -The non-standard ``ANY`` method works as a low priority fallback in bottle. Routes that listen to ``ANY`` requests will matches requests regardless of their HTTP method but only if no other more specific route is installed. This is helpful for *proxy-routes* that redirect requests to more specific sub-applications, but you should not need to use these in normal applications. +Additionally, the non-standard ANY method works as a low priority fallback: Routes that listen to ANY will match requests regardless of their HTTP method but only if no other more specific route is defined. This is helpful for *proxy-routes* that redirect requests to more specific sub-applications. -To sum it up: ``HEAD`` requests fall back to ``GET`` routes and all requests fall back to ``ANY`` routes, if there is no matching route for the original request method. It's as simple as that. +To sum it up: HEAD requests fall back to GET routes and all requests fall back to ANY routes, but only if there is no matching route for the original request method. It's as simple as that. Routing Static Files ------------------------------------------------------------------------------ @@ -158,258 +176,274 @@ Static files such as images or css files are not served automatically. You have def server_static(filename): return static_file(filename, root='/path/to/your/static/files') -The :func:`static_file` function is a helper to serve files in a save and convenient way (see :ref:`tutorial-static-files`). This example is limited to files directly within the ``/path/to/your/static/files`` directory because the ``:filename`` wildcard won't match a filename with a slash in it. To serve files in subdirectories too, we can loosen the wildcard a bit:: +The :func:`static_file` function is a helper to serve files in a save and convenient way (see :ref:`tutorial-static-files`). This example is limited to files directly within the ``/path/to/your/static/files`` directory because the ``:filename`` wildcard won't match a path with a slash in it. To serve files in subdirectories too, we can loosen the wildcard a bit:: @route('/static/:path#.+#') def server_static(path): return static_file(path, root='/path/to/your/static/files') +Be carefull when specifying a relative root-path such as ``root='./static/files'``. The working directory (``./``) and the project directory are not always the same. -.. _tutorial-request: +Error Pages +------------------------------------------------------------------------------ -Request and Response Objects -============================================================================== +If anything goes wrong, Bottle displays an informative but fairly booring error page. You can override the default error pages using the :func:`error` decorator. It works similar to the :func:`route` decorator, but expects an HTTP status code instead of a route:: -Bottle defines a global ``request`` object to provide access to HTTP related meta-data such as cookies, headers and POST form data. This object is updated on each client request and always contains information about the *current* request (as long as you access it from within a callback function). It is thread-local and save to use multi-threaded environments, too. + @error(404) + def error404(error): + return 'Nothing here, sorry' -The global ``response`` object follows the same principle. It is also bound to the current request circle and stores the meta-data you want to send back to the browser. +The ``error`` parameter passed to the error handler is an instance of :exc:`HTTPError`. -The full API and feature list of these objects is documented in the API section (see :class:`Request` and :class:`Response`), but the most common use cases and features are covered here. -Accessing Request Data ------------------------------------------------------------------------------- -HTTP defines many ways to transmit data to the server. The :class:`Request` object provides several attributes to access raw or parsed versions of these informations. Most of the parsing and data processing happens on demand, so you won't see any overhead if you don't use the attributes. +.. _tutorial-output: -.. rubric:: HTTP Header +Generating content +============================================================================== -Header are stored in :attr:`Request.header` using a :class:`HeaderDict` object. This is basically a dictionary with case-insensitive keys:: +In pure WSGI, the range of types you may return from your application is very limited. Applications must return an iterable yielding byte strings. You may return a string (because strings are iterable) but this causes most servers to transmit your content char by char. Unicode strings are not allowed at all. This is not very practical. - from bottle import route, request - @route('/is_ajax') - def is_ajax(): - if request.header.get('X-Requested-With') == 'XMLHttpRequest': - return 'This is an AJAX request' - else: - return 'This is a normal request' +Bottle is much more flexible and supports a wide range of types. It even adds a ``Content-Length`` header if possible and encodes unicode automatically, so you don't have to. What follows is a list of data types you may return from your application callbacks and a short description of how these are handled by the framework: -.. rubric:: Cookies +Dictionaries + As I have already mentioned above, Python dictionaries (or subclasses thereof) are automatically transformed into JSON strings and returned to the browser with the ``Content-Type`` header set to ``application/json``. This makes it easy to implement json-bases APIs. Data formats other than json are supported too. See the :ref:`tutorial-output-filter` to learn more. -Cookies are stored in :attr:`Request.COOKIES` as a normal dictionary. The :meth:`Request.get_cookie` method allows access to :ref:`tutorial-secure-cookies` as described in a separate section. This example shows a simple cookie-based view counter:: +Empty Strings, ``False``, ``None`` or other non-true values: + These produce an empty output with ``Content-Length`` header set to 0. - from bottle import route, request, response - @route('/counter') - def counter(): - count = int( request.COOKIES.get('counter', '0') ) + 1 - count += 1 - response.set_cookie('counter', str(count)) - return 'You visited this page %d times' % count +Unicode strings + Unicode strings (or iterables yielding unicode strings) are automatically encoded with the codec specified in the ``Content-Type`` header (utf8 by default) and then treated as normal byte strings (see below). +Byte strings + Bottle returns strings as a whole (instead of iterating over each char) and adds a ``Content-Length`` header based on the string length. Lists of byte strings are joined first. Other iterables yielding byte strings are not joined because they may grow to big to fit into memory. The ``Content-Length`` header is not set in this case. -.. rubric:: Query Strings +Instances of :exc:`HTTPError` or :exc:`HTTPResponse` + Returning these has the same effect than raising them as an exception. In case of an :exc:`HTTPError`, the error handler are applied. See :ref:`tutorial-errorhandling` for details. -The query string (as in ``/forum?id=1&page=5``) is commonly used to transmit a small number of key/value pairs to the server. You can use the :attr:`Request.GET` attribute to access these values and the :attr:`Request.query_string` attribute to get the whole string. +File objects + Everything that has a ``.read()`` method is treated as a file or file-like object and passed to the ``wsgi.file_wrapper`` callable defined by the WSGI server framework. Some WSGI server implementations can make use of optimized system calls (sendfile) to transmit files more efficiently. In other cases this just iterates over chuncks that fit into memory. Optional headers such as ``Content-Length`` or ``Content-Type`` are *not* set automatically. Use :func:`send_file` if possible. See :ref:`tutorial-static-files` for details. -The :attr:`Request.GET` attribute uses a :class:`MultiDict` to store the query values. Multiple values per key are saved, but the standard dict access methods will only return a single value. Use the :meth:`MultiDict.getall` method do receive a (possibly empty) list of all values for a specific key. +Iterables and generators + You are allowed to use ``yield`` within your callbacks or return an iterable, as long as the iterable yields byte strings, unicode strings, :exc:`HTTPError` or :exc:`HTTPResponse` instances. Nested iterables are not supported, sorry. Please note that the HTTP status code and the headers are sent to the browser as soon as the iterable yields its first non-empty value. Changing these later has no effect. + +The ordering of this list is significant. You may for example return a subclass of :class:`str` with a ``read()`` method. It is still treated as a string instead of a file, because strings are handled first. -:: +.. rubric:: Changing the Default Encoding - from bottle import route, request, response - @route('/forum') - def display_forum(): - forum_id = request.GET.get('id') - page = request.GET.get('page', 1) - return 'Forum ID: %d (page %d)' % (forum_id, page) +Bottle uses the `charset` parameter of the ``Content-Type`` header to decide how to encode unicode strings. This header defaults to ``text/html; charset=UTF8`` and can be changed using the :attr:`Response.content_type` attribute or by setting the :attr:`Response.charset` attribute directly. (The :class:`Response` object is described in the section: :ref:`tutorial-response`):: -.. rubric:: POST Form Data and File Uploads + 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.' -The request body of POST requests may contain form data encoded in various formats. The :attr:`Request.body` attribute holds a file object with the raw body data, but you usually don't need to parse it yourself. Use the :attr:`Request.forms` attribute (a :class:`MultiDict`) to access normal POST form fields. File uploads are stored separately in :attr:`Request.files` as :class:`cgi.FieldStorage` objects. The :attr:`Request.POST` attribute combines both attributes in a single :class:`MultiDict`. + @route('/latin9') + def get_latin(): + response.content_type = 'text/html; charset=latin9' + return u'ISO-8859-15 is also known as latin9.' -As with the :attr:`Request.GET` attribute, multiple values per key are possible. Use the :meth:`MultiDict.getall` method do get all values for a specific key. +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 :attr:`Response.content_type` header (which is sent to the client unchanged) and then set the :attr:`Response.charset` attribute (which is used to decode unicode). -Here is an example for a simple file upload form: +.. _tutorial-static-files: -.. code-block:: html +Static Files +-------------------------------------------------------------------------------- - <form action="/upload" method="post" enctype="multipart/form-data"> - <input type="text" name="name" /> - <input type="file" name="data" /> - </form> +You can directly return file objects, but :func:`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. :: - from bottle import route, request - @route('/upload', method='POST') - def do_upload(): - name = request.forms.get('name') - data = request.files.get('data') - if name and data: - raw = data.read() # This is dangerous for big files - filename = data.filename - return "Hello %s! Your uploaded %s (%d bytes)." % (name, filename, len(raw)) - return "You missed a field." + 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_static(filename): + return static_file(filename, root='/path/to/static/files') -.. rubric:: WSGI environment +You can raise the return value of :func:`static_file` as an exception if you really need to. -The :class:`Request` object stores the WSGI environment dictionary in :attr:`Request.environ` and allows dict-like access to its values. See the `WSGI specification`_ for details. +.. rubric:: Forced Download -:: +Most browsers try to open downloaded files if the MIME type is known and assigned to an application (e.g. PDF files). If this is not what you want, you can force a download-dialog and even suggest a filename to the user:: - @route('/my_ip') - def show_ip(): - ip = request.environ.get('REMOTE_ADDR') - # or ip = request.get('REMOTE_ADDR') - # or ip = request['REMOTE_ADDR'] - return "Your IP is: %s" % ip + @route('/download/:filename') + def download(filename): + return static_file(filename, root='/path/to/static/files', download=filename) +If the ``download`` parameter is just ``True``, the original filename is used. +.. _tutorial-error: +HTTP Errors and Redirects +-------------------------------------------------------------------------------- +The :func:`abort` function is a shortcut for generating HTTP error pages. -The Response Object -------------------------------------------------------------------------------- +:: -TODO + from bottle import route, 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. :func:`redirect` does that for you:: -.. _tutorial-secure-cookies: + from bottle import redirect + @route('/wrong/url') + def wrong(): + redirect("/right/url") +You may provide a different HTTP status code as a second parameter. -Secure Cookies -------------------------------------------------------------------------------- +.. note:: + Both functions will interrupt your callback code by raising an :exc:`HTTPError` exception. -TODO +.. rubric:: Other Exceptions -.. _tutorial-output: +All exceptions other than :exc:`HTTPResponse` or :exc:`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``. -Generating content -============================================================================== -The `WSGI specification`_ expects an iterable list of byte strings to be returned by 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. 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. +.. _tutorial-response: +The :class:`Response` Object +-------------------------------------------------------------------------------- +Response meta-data such as the HTTP status code, response header and cookies are stored in an object called :data:`response` up to the point where they are transmitted to the browser. You can manipulate these meta-data directly or use the predefined helper methods to do so. The full API and feature list is described in the API section (see :class:`Response`), but the most common use cases and features are covered here, too. +.. rubric:: Status Code -Strings and Unicode ------------------------------------------------------------------------------- +The `HTTP status code <http_code>`_ controls the behaviour of the browser and defaults to ``200 OK``. In most scenarios you won't need to set the :attr:`Response.status` attribute manually, but use the :func:`abort` helper or return an :exc:`HTTPResponse` instance with the appropriate status code. Any integer is allowed but only the codes defined by the `HTTP specification <http_code>`_ will have an effect other than confusing the browser and breaking standards. + +.. rubric:: Response Header -Returning strings (bytes) is not a problem. Unicode however needs to be encoded 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 iterables yielding unicode. +Add values to the :attr:`Response.headers` dictionary to add or change response headers. Note that the keys are case-insensitive. :: + + @route('/wiki/:page') + def wiki(page): + response.headers['Content-Language'] = 'en' + return get_wiki_page(page) - @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' +.. _tutorial-secure-cookies: -You can change the encoding by setting :attr:`Response.content_type` to a value containing a ``charset=...`` parameter or by changing :attr:`Response.charset` directly. (The :class:`Response` object is described in the section: :ref:`tutorial-request`) +Cookies +------------------------------------------------------------------------------- -:: +TODO - 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.' +.. rubric:: Secure Cookies - @route('/latin9') - def get_latin(): - response.content_type = 'text/html; charset=latin9' - return u'ISO-8859-15 is also known as latin9.' +TODO -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 :attr:`Response.content_type` header (which is sent to the client unchanged) and then set the :attr:`Response.charset` attribute (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. -:: - @route('/file') - def get_file(): - return open('some/file.txt','r') +.. _tutorial-request: +Accessing Request Data +============================================================================== -JSON --------------------------------------------------------------------------------- +Bottle provides access to HTTP related meta-data such as cookies, headers and POST form data through a global ``request`` object. This object always contains information about the *current* request, as long as it is accessed from within a callback function. This works even in multi-threaded environments where multiple requests are handled at the same time. For details on how a global object can be thread-save, see :doc:`contextlocal`. -Even dictionaries are allowed. They are converted to json_ 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``. +.. note:: + Bottle stores most of the parsed HTTP meta-data in :class:`MultiDict` instances. These behave like normal dictionaries but are able to store multiple values per key. The standard dictionary access methods will only return a single value. Use the :meth:`MultiDict.getall` method do receive a (possibly empty) list of all values for a specific key. The :class:`HeaderDict` class inherits from :class:`MultiDict` and additionally uses case insensitive keys. -:: +The full API and feature list is described in the API section (see :class:`Request`), but the most common use cases and features are covered here, too. - @route('/api/status') - def api_status(): - return {'status':'online', 'servertime':time.time()} +.. rubric:: HTTP Header +Header are stored in :attr:`Request.header`. The attribute is an instance of :class:`HeaderDict` which is basically a dictionary with case-insensitive keys:: -.. _tutorial-static-files: + from bottle import route, request + @route('/is_ajax') + def is_ajax(): + if request.header.get('X-Requested-With') == 'XMLHttpRequest': + return 'This is an AJAX request' + else: + return 'This is a normal request' -Static Files --------------------------------------------------------------------------------- +.. rubric:: Cookies -You can directly return file objects, but :func:`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. +Cookies are stored in :attr:`Request.COOKIES` as a normal dictionary. The :meth:`Request.get_cookie` method allows access to :ref:`tutorial-secure-cookies` as described in a separate section. This example shows a simple cookie-based view counter:: -:: + from bottle import route, request, response + @route('/counter') + def counter(): + count = int( request.COOKIES.get('counter', '0') ) + 1 + count += 1 + response.set_cookie('counter', str(count)) + return 'You visited this page %d times' % count - 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_static(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. +.. rubric:: Query Strings +The query string (as in ``/forum?id=1&page=5``) is commonly used to transmit a small number of key/value pairs to the server. You can use the :attr:`Request.GET` dictionary to access these values and the :attr:`Request.query_string` attribute to get the whole string. +:: -HTTPError, HTTPResponse and Redirects --------------------------------------------------------------------------------- + from bottle import route, request, response + @route('/forum') + def display_forum(): + forum_id = request.GET.get('id') + page = request.GET.get('page', '1') + return 'Forum ID: %s (page %s)' % (forum_id, page) -The ``abort(code[, message])`` function is used to generate [HTTP error pages][http_code]. -:: +.. rubric:: POST Form Data and File Uploads - from bottle import route, redirect, abort - @route('/restricted') - def restricted(): - abort(401, "Sorry, access denied.") +The request body of POST and PUT requests may contain form data encoded in various formats. Use the :attr:`Request.forms` attribute (a :class:`MultiDict`) to access normal POST form fields. File uploads are stored separately in :attr:`Request.files` as :class:`cgi.FieldStorage` instances. The :attr:`Request.body` attribute holds a file object with the raw body data. -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. +Here is an example for a simple file upload form: + +.. code-block:: html + + <form action="/upload" method="post" enctype="multipart/form-data"> + <input type="text" name="name" /> + <input type="file" name="data" /> + </form> :: - from bottle import redirect - @route('/wrong/url') - def wrong(): - redirect("/right/url") + from bottle import route, request + @route('/upload', method='POST') + def do_upload(): + name = request.forms.get('name') + data = request.files.get('data') + if name and data: + raw = data.file.read() # This is dangerous for big files + filename = data.filename + return "Hello %s! Your uploaded %s (%d bytes)." % (name, filename, len(raw)) + return "You missed a field." -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. +.. rubric:: WSGI environment + +The :class:`Request` object stores the WSGI environment dictionary in :attr:`Request.environ` and allows dict-like access to its values. See the `WSGI specification`_ for details. :: - from bottle import HTTPError - @route('/denied') - def denied(): - return HTTPError(401, 'Access denied!') + @route('/my_ip') + def show_ip(): + ip = request.environ.get('REMOTE_ADDR') + # or ip = request.get('REMOTE_ADDR') + # or ip = request['REMOTE_ADDR'] + return "Your IP is: %s" % ip + + -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``. @@ -421,84 +455,49 @@ All exceptions other than ``HTTPResponse`` or ``HTTPError`` will result in a ``5 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. - -:: +Bottle comes with a fast and powerful build-in template engine called :doc:`stpl`. To render a template you can use the :func:`template` function or the :func:`view` decorator. All you have to do is to provide the name of the template and the variables you want to pass to the template as keyword arguments. Here’s a simple example of how to render a template:: + @route('/hello') @route('/hello/:name') - def hello(name): - 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. - -.. highlight:: html+django - -The ``hello_template.tpl`` file could look like this:: - - <h1>Hello {{username}}</h1> - <p>How are you?</p> + def hello(name='World'): + return template('hello_template', name=name) +This will load the template file ``hello_template.tpl`` and render it with the ``name`` variable set. Bottle will look for templates in the ``./views/`` folder or any folder specified in the ``bottle.TEMPLATE_PATH`` list. +The :func:`view` decorator allows you to return a dictionary with the template variables instead of calling :func:`template`:: -Template search path --------------------------------------------------------------------------------- + @route('/hello') + @route('/hello/:name') + @view('hello_template') + def hello(name='World'): + return dict(name=name) -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']``. +.. rubric:: Syntax +.. highlight:: html+django +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. Follow the link for a full syntax description: :doc:`stpl` -Template caching --------------------------------------------------------------------------------- +Here is an example template:: -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. + %if name == 'World': + <h1>Hello {{name}}!</h1> + <p>This is a test.</p> + %else: + <h1>Hello {{name.title()}}!</h1> + <p>How are you?</p> + %end +.. rubric:: Caching +Templates are cached in memory after compilation. Modifications made to the template files will have no affect until you clear the template cache. Call ``bottle.TEMPLATES.clear()`` to do so. Caching is disabled in debug mode. -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:: - - %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 +.. highlight:: python .. _tutorial-debugging: diff --git a/docs/start.md b/docs/start.md index b09bdfa..09c08e0 100644 --- a/docs/start.md +++ b/docs/start.md @@ -160,6 +160,7 @@ Bottle does **not** include (yet): * [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 |