summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMarcel Hellkamp <marc@gsites.de>2011-05-12 21:24:49 +0200
committerMarcel Hellkamp <marc@gsites.de>2011-05-12 21:31:58 +0200
commit8eae50917e7041a695c1025855696a148fb3ad3e (patch)
tree40252b46f1bc6a8117c6b5fd3c313d6bad42612f
parente82f43583044f192dd7ba681bb624d93a8d203d8 (diff)
downloadbottle-8eae50917e7041a695c1025855696a148fb3ad3e.tar.gz
Sync with master (mostly docs and plugins).
-rwxr-xr-xbottle.py12
-rw-r--r--[-rwxr-xr-x]docs/async.rst8
-rw-r--r--docs/conf.py3
-rw-r--r--[-rwxr-xr-x]docs/index.rst3
-rw-r--r--docs/plugins/index.rst38
-rw-r--r--docs/plugins/sqlite.rst1
-rw-r--r--docs/plugins/werkzeug.rst1
-rwxr-xr-xdocs/recipes.rst24
-rwxr-xr-xdocs/tutorial.rst9
-rw-r--r--plugins/sqlite/README.rst87
-rw-r--r--plugins/sqlite/setup.py2
-rw-r--r--plugins/werkzeug/README.rst80
-rw-r--r--plugins/werkzeug/bottle_werkzeug.py38
13 files changed, 254 insertions, 52 deletions
diff --git a/bottle.py b/bottle.py
index 5638890..0fd6b02 100755
--- a/bottle.py
+++ b/bottle.py
@@ -2155,15 +2155,11 @@ class MakoTemplate(BaseTemplate):
from mako.lookup import TemplateLookup
options.update({'input_encoding':self.encoding})
options.setdefault('format_exceptions', bool(DEBUG))
- #TODO: This is a hack... https://github.com/defnull/bottle/issues#issue/8
- mylookup = TemplateLookup(directories=['.']+self.lookup, **options)
+ lookup = TemplateLookup(directories=self.lookup, **options)
if self.source:
- self.tpl = Template(self.source, lookup=mylookup, **options)
- else: #mako cannot guess extentions. We can, but only at top level...
- name = self.name
- if not os.path.splitext(name)[1]:
- name += os.path.splitext(self.filename)[1]
- self.tpl = mylookup.get_template(name)
+ self.tpl = Template(self.source, lookup=lookup, **options)
+ else:
+ self.tpl = Template(uri=self.name, filename=self.filename, lookup=lookup, **options)
def render(self, *args, **kwargs):
for dictarg in args: kwargs.update(dictarg)
diff --git a/docs/async.rst b/docs/async.rst
index 234a3ea..522e63e 100755..100644
--- a/docs/async.rst
+++ b/docs/async.rst
@@ -40,7 +40,13 @@ The first line is important. It causes gevent to monkey-patch most of Python's b
If you run this script and point your browser to ``http://localhost:8080/stream``, you should see `START`, `MIDDLE`, and `END` show up one by one (rather than waiting 8 seconds to see them all at once). It works exactly as with normal threads, but now your server can handle thousands of concurrent requests without any problems.
-Just a hint: Some browsers buffer a certain amount of data before they start to render a page. You might need to yield more than a few bytes to see an effect in the browser.
+.. note::
+
+ Some browsers buffer a certain amount of data before they start rendering a
+ page. You might need to yield more than a few bytes to see an effect in
+ these browsers. Additionally, many browsers have a limit of one concurrent
+ connection per URL. If this is the case, you can use a second browser or a
+ benchmark tool (e.g. `ab` or `httperf`) to measure performance.
Event Callbacks
---------------
diff --git a/docs/conf.py b/docs/conf.py
index 1ac2cca..e944454 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -203,7 +203,8 @@ latex_logo = "_static/logo_nav.png"
# Example configuration for intersphinx: refer to the Python standard library.
-intersphinx_mapping = {'http://docs.python.org/': None}
+intersphinx_mapping = {'python': ('http://docs.python.org/', None),
+ 'werkzeug': ('http://werkzeug.pocoo.org/docs/', None)}
autodoc_member_order = 'bysource'
diff --git a/docs/index.rst b/docs/index.rst
index 93881b5..8e1ee6d 100755..100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -57,9 +57,10 @@ Start here if you want to learn how to use the bottle framework for web developm
tutorial
stpl
api
+ plugins/index
-Knowlage Base
+Knowledge Base
==============
A collection of articles, guides and HOWTOs.
diff --git a/docs/plugins/index.rst b/docs/plugins/index.rst
index 45caf3b..1e5265f 100644
--- a/docs/plugins/index.rst
+++ b/docs/plugins/index.rst
@@ -9,32 +9,12 @@ The plugin API is extremely flexible and most plugins are designed to be portabl
Have a look at :ref:`plugins` for general questions about plugins (installation, usage). If you plan to develop a new plugin, the :doc:`/plugindev` may help you.
-SQLite Plugin
-----------------------
-
-* **Author:** Marcel Hellkamp
-* **License:** MIT
-* **Installation:** Included
-* **Documentation:** :doc:`/plugins/sqlite`
-* **Description:** Provides an sqlite database connection handle to callbacks that request it.
-
-
-Werkzeug Plugin
-----------------------
-
-* **Author:** Marcel Hellkamp
-* **License:** MIT
-* **Installation:** ``pip install bottle-werkzeug``
-* **Documentation:** :doc:`/plugins/werkzeug`
-* **Description:** Integrates the "werkzeug" library (alternative request and response objects, advanced debugging middleware and more).
-
-
-Profile Plugin
-----------------------
-
-* **Author:** Marcel Hellkamp
-* **License:** MIT
-* **Installation:** ``pip install bottle-profile``
-* **Documentation:** :doc:`/plugins/profile`
-* **Description:** This plugin collects profiling data and displays it in the browser.
-
+* :doc:`sqlite`: Adds support for `SQLite` databases.
+* :doc:`werkzeug`: Integrates the `werkzeug` library (alternative request and response objects, advanced debugging middleware and more).
+* :doc:`profile`: This plugin collects profiling data and displays it in the browser.
+
+.. toctree::
+ :glob:
+ :hidden:
+
+ /plugins/*
diff --git a/docs/plugins/sqlite.rst b/docs/plugins/sqlite.rst
new file mode 100644
index 0000000..46ad496
--- /dev/null
+++ b/docs/plugins/sqlite.rst
@@ -0,0 +1 @@
+.. include:: ../../plugins/sqlite/README.rst \ No newline at end of file
diff --git a/docs/plugins/werkzeug.rst b/docs/plugins/werkzeug.rst
new file mode 100644
index 0000000..8647dd8
--- /dev/null
+++ b/docs/plugins/werkzeug.rst
@@ -0,0 +1 @@
+.. include:: ../../plugins/werkzeug/README.rst \ No newline at end of file
diff --git a/docs/recipes.rst b/docs/recipes.rst
index 5b250fb..f06784b 100755
--- a/docs/recipes.rst
+++ b/docs/recipes.rst
@@ -7,6 +7,9 @@
.. _paste: http://pythonpaste.org/modules/evalexception.html
.. _pylons: http://pylonshq.com/
.. _gevent: http://www.gevent.org/
+.. _compression: https://github.com/defnull/bottle/issues/92
+.. _GzipFilter: http://www.cherrypy.org/wiki/GzipFilter
+.. _cherrypy: http://www.cherrypy.org
Recipes
=============
@@ -129,3 +132,24 @@ Several "push" mechanisms like XHR multipart need the ability to write response
If you browse to ``http://localhost:8080/stream``, you should see 'START', 'MIDDLE', and 'END' show up one at a time (rather than waiting 8 seconds to see them all at once).
+Gzip Compression in Bottle
+--------------------------
+
+.. note::
+ For a detailed discussion, see compression_
+
+A common feature request is for Bottle to support Gzip compression, which speeds up sites by compressing static resources (like CSS and JS files) during a request.
+
+Supporting Gzip compression is not a straightforward proposition, due to a number of corner cases that crop up frequently. A proper Gzip implementation must:
+
+* Compress on the fly and be fast doing so.
+* Do not compress for browsers that don't support it.
+* Do not compress files that are compressed already (images, videos).
+* Do not compress dynamic files.
+* Support two differed compression algorithms (gzip and deflate).
+* Cache compressed files that don't change often.
+* De-validate the cache if one of the files changed anyway.
+* Make sure the cache does not get to big.
+* Do not cache small files because a disk seek would take longer than on-the-fly compression.
+
+Because of these requirements, it is the reccomendation of the Bottle project that Gzip compression is best handled by the WSGI server Bottle runs on top of. WSGI servers such as cherrypy_ provide a GzipFilter_ middleware that can be used to accomplish this.
diff --git a/docs/tutorial.rst b/docs/tutorial.rst
index a828343..672f036 100755
--- a/docs/tutorial.rst
+++ b/docs/tutorial.rst
@@ -202,16 +202,21 @@ The :func:`static_file` function is a helper to serve files in a safe and conven
Be careful when specifying a relative root-path such as ``root='./static/files'``. The working directory (``./``) and the project directory are not always the same.
+
+.. _tutorial-errorhandling:
+
Error Pages
------------------------------------------------------------------------------
-If anything goes wrong, Bottle displays an informative but fairly boring 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::
+If anything goes wrong, Bottle displays an informative but fairly boring error page. You can override the default for a specific HTTP status code with the :func:`error` decorator::
@error(404)
def error404(error):
return 'Nothing here, sorry'
-The ``error`` parameter passed to the error handler is an instance of :exc:`HTTPError`.
+From now on, `404 File not Found` errors will display a custom error page to the user. The only parameter passed to the error-handler is an instance of :exc:`HTTPError`. Apart from that, an error-handler is quite similar to a regular request callback. You can read from :data:`request`, write to :data:`response` and return any supported data-type except for :exc:`HTTPError` instances.
+
+Error handlers are used only if your application returns or raises an :exc:`HTTPError` exception (:func:`abort` does just that). Changing :attr:`Request.status` or returning :exc:`HTTPResponse` won't trigger the error handler.
diff --git a/plugins/sqlite/README.rst b/plugins/sqlite/README.rst
new file mode 100644
index 0000000..31f6756
--- /dev/null
+++ b/plugins/sqlite/README.rst
@@ -0,0 +1,87 @@
+=====================
+Bottle-SQLite
+=====================
+
+SQLite is a self-contained SQL database engine that runs locally and does not
+require any additional server software or setup. The sqlite3 module is part of the
+Python standard library and already installed on most systems. It it very useful
+for prototyping database-driven applications that are later ported to larger
+databases such as PostgreSQL or MySQL.
+
+This plugin simplifies the use of sqlite databases in your Bottle applications.
+Once installed, all you have to do is to add a ``db`` keyword argument
+(configurable) to route callbacks that need a database connection.
+
+Installation
+===============
+
+Install with one of the following commands::
+
+ $ pip install bottle-sqlite
+ $ easy_install bottle-sqlite
+
+or download the latest version from github::
+
+ $ git clone git://github.com/defnull/bottle.git
+ $ cd bottle/plugins/sqlite
+ $ python setup.py install
+
+Usage
+===============
+
+Once installed to an application, the plugin passes an open
+:class:`sqlite3.Connection` instance to all routes that require a ``db`` keyword
+argument::
+
+ import bottle
+
+ app = bottle.Bottle()
+ plugin = bottle.ext.sqlite.Plugin(dbfile='/tmp/test.db')
+ app.install(plugin)
+
+ @app.route('/show/:item')
+ def show(item, db):
+ row = db.execute('SELECT * from items where name=?', item).fetchone()
+ if row:
+ return template('showitem', page=row)
+ return HTTPError(404, "Page not found")
+
+Routes that do not expect a ``db`` keyword argument are not affected.
+
+The connection handle is configured so that :class:`sqlite3.Row` objects can be
+accessed both by index (like tuples) and case-insensitively by name. At the end of
+the request cycle, outstanding transactions are committed and the connection is
+closed automatically. If an error occurs, any changes to the database since the
+last commit are rolled back to keep the database in a consistent state.
+
+Configuration
+=============
+
+The following configuration options exist for the plugin class:
+
+* **dbfile**: Database filename (default: in-memory database).
+* **keyword**: The keyword argument name that triggers the plugin (default: 'db').
+* **autocommit**: Whether or not to commit outstanding transactions at the end of the request cycle (default: True).
+* **dictrows**: Whether or not to support dict-like access to row objects (default: True).
+
+You can override each of these values on a per-route basis::
+
+ @app.route('/cache/:item', sqlite={'dbfile': ':memory:'})
+ def cache(item, db):
+ ...
+
+or install two plugins with different ``keyword`` settings to the same application::
+
+ app = bottle.Bottle()
+ test_db = bottle.ext.sqlite.Plugin(dbfile='/tmp/test.db')
+ cache_db = bottle.ext.sqlite.Plugin(dbfile=':memory:', keyword='cache')
+ app.install(test_db)
+ app.install(cache_db)
+
+ @app.route('/show/:item')
+ def show(item, db):
+ ...
+
+ @app.route('/cache/:item')
+ def cache(item, cache):
+ ...
diff --git a/plugins/sqlite/setup.py b/plugins/sqlite/setup.py
index a889c88..6b4af7b 100644
--- a/plugins/sqlite/setup.py
+++ b/plugins/sqlite/setup.py
@@ -19,7 +19,7 @@ exec(source)
setup(
name = 'bottle-sqlite',
version = __version__,
- url = 'http://bottlepy.org/docs/dev/plugin/sqlite/',
+ url = 'http://bottlepy.org/docs/dev/plugins/sqlite.html',
description = 'SQLite3 integration for Bottle.',
long_description = __doc__,
author = 'Marcel Hellkamp',
diff --git a/plugins/werkzeug/README.rst b/plugins/werkzeug/README.rst
new file mode 100644
index 0000000..51eb090
--- /dev/null
+++ b/plugins/werkzeug/README.rst
@@ -0,0 +1,80 @@
+=====================
+Bottle-Werkzeug
+=====================
+
+`Werkzeug <http://werkzeug.pocoo.org/>`_ is a powerfull WSGI utility library for
+Python. It includes an interactive debugger and feature-packed request and response
+objects.
+
+This plugin integrates :class:`werkzeug.wrappers.Request` and
+:class:`werkzeug.wrappers.Response` as an alternative to the built-in
+implementations, adds support for :mod:`werkzeug.exceptions` and replaces the
+default error page with an interactive debugger.
+
+
+
+Installation
+===============
+
+Install with one of the following commands::
+
+ $ pip install bottle-werkzeug
+ $ easy_install bottle-werkzeug
+
+or download the latest version from github::
+
+ $ git clone git://github.com/defnull/bottle.git
+ $ cd bottle/plugins/werkzeug
+ $ python setup.py install
+
+
+
+Usage
+===============
+
+Once installed to an application, this plugin adds support for
+:class:`werkzeug.wrappers.Response`, all kinds of :mod:`werkzeug.exceptions` and
+provides a thread-local instance of :class:`werkzeug.wrappers.Request` that is
+updated with each request. The plugin instance itself doubles as a werkzeug
+module object, so you don't have to import werkzeug in your application. Here
+is an example::
+
+ import bottle
+
+ app = bottle.Bottle()
+ werkzeug = bottle.ext.werkzeug.Plugin()
+ app.install(werkzeug)
+
+ req = werkzueg.request # For the lazy.
+
+ @app.route('/hello/:name')
+ def say_hello(name):
+ greet = {'en':'Hello', 'de':'Hallo', 'fr':'Bonjour'}
+ language = req.accept_languages.best_match(greet.keys())
+ if language:
+ return werkzeug.Response('%s %s!' % (greet[language], name))
+ else:
+ raise werkzeug.exceptions.NotAcceptable()
+
+
+
+Using the Debugger
+====================
+
+This plugin replaces the default error page with an advanced debugger. If you
+have the `evalex` feature enabled, you will get an interactive console that
+allows you to inspect the error context in the browser. Please read
+`Debugging Applications with werkzeug <werkzeug:debug>`_ before you enable this
+feature.
+
+
+
+Configuration
+=============
+
+The following configuration options exist for the plugin class:
+
+* **evalex**: Enable the exception evaluation feature (interactive debugging). This requires a non-forking server and is a security risk. Please read `Debugging Applications with werkzeug <werkzeug:debug>`_. (default: False)
+* **request_class**: Defaults to :class:`werkzeug.wrappers.Request`
+* **debugger_class**: Defaults to a subclass of :class:`werkzeug.debug.DebuggedApplication` which obeys the :data:`bottle.DEBUG` setting.
+
diff --git a/plugins/werkzeug/bottle_werkzeug.py b/plugins/werkzeug/bottle_werkzeug.py
index ce97962..4de2afc 100644
--- a/plugins/werkzeug/bottle_werkzeug.py
+++ b/plugins/werkzeug/bottle_werkzeug.py
@@ -1,6 +1,6 @@
"""
This plugin adds support for :class:`werkzeug.Response`, all kinds of
-:exc:`werkzeug.HTTPException` and provides a thread-local instance of
+:exc:`werkzeug.exceptions` and provides a thread-local instance of
:class:`werkzeug.Request`. It basically turns Bottle into Flask.
The plugin instance doubles as a werkzeug module object, so you don't need to
@@ -11,16 +11,17 @@ For werkzeug library documentation, see: http://werkzeug.pocoo.org/
Example::
import bottle
- from bottle.ext.werkzeug import WerkzeugPlugin
app = bottle.Bottle()
- werkzeug = app.install(WerkzeugPlugin())
- wrequest = werkzueg.request # For the lazy.
+ werkzeug = bottle.ext.werkzeug.Plugin()
+ app.install(werkzeug)
+
+ req = werkzueg.request # For the lazy.
@app.route('/hello/:name')
def say_hello(name):
greet = {'en':'Hello', 'de':'Hallo', 'fr':'Bonjour'}
- language = wrequest.accept_languages.best_match(greet.keys())
+ language = req.accept_languages.best_match(greet.keys())
if language:
return werkzeug.Response('%s %s!' % (greet[language], name))
else:
@@ -39,6 +40,17 @@ import werkzeug
from werkzeug import *
import bottle
+
+class WerkzeugDebugger(DebuggedApplication):
+ """ A subclass of :class:`werkzeug.debug.DebuggedApplication` that obeys the
+ :data:`bottle.DEBUG` setting. """
+
+ def __call__(self, environ, start_response):
+ if bottle.DEBUG:
+ return DebuggedApplication.__call__(self, environ, start_response)
+ return self.app(environ, start_response)
+
+
class WerkzeugPlugin(object):
""" This plugin adds support for :class:`werkzeug.Response`, all kinds of
:module:`werkzeug.exceptions` and provides a thread-local instance of
@@ -46,18 +58,23 @@ class WerkzeugPlugin(object):
name = 'werkzeug'
- def __init__(self, request_class=werkzeug.Request, **config):
- self.request_factory = request_class
- self.config = config
+ def __init__(self, evalex=False, request_class=werkzeug.Request,
+ debugger_class=WerkzeugDebugger):
+ self.request_class = request_class
+ self.debugger_class = debugger_class
+ self.evalex=evalex
self.app = None
def setup(self, app):
self.app = app
+ if self.debugger_class:
+ app.wsgi = self.debugger_class(app.wsgi, evalex=self.evalex)
+ app.catchall = False
def apply(self, callback, context):
def wrapper(*a, **ka):
environ = bottle.request.environ
- bottle.local.werkzueg_request = self.request_factory(environ, **self.config)
+ bottle.local.werkzueg_request = self.request_class(environ)
try:
rv = callback(*a, **ka)
except werkzeug.exceptions.HTTPException, e:
@@ -76,3 +93,6 @@ class WerkzeugPlugin(object):
def __getattr__(self, name):
''' Convenient access to werkzeug module contents. '''
return getattr(werkzeug, name)
+
+
+Plugin = WerkzeugPlugin