diff --git a/docs/patterns/caching.rst b/docs/patterns/caching.rst new file mode 100644 index 00000000..7a811b82 --- /dev/null +++ b/docs/patterns/caching.rst @@ -0,0 +1,69 @@ +.. _caching-pattern: + +Caching +======= + +When your application runs slow, throw some caches in. Well, at least +it's the easiest way to speed up things. What does a cache do? Say you +have a function that takes some time to complete but the results would +still be good enough if they were 5 minutes old. So then the idea is that +you actually put the result of that calculation into a cache for some +time. + +Flask itself does not provide caching for you, but Werkzeug, one of the +libraries it is based on, has some very basic cache support. It supports +multiple cache backends, normally you want to use a memcached server. + +Setting up a Cache +------------------ + +You create a cache object once and keep it around, similar to how +:class:`~flask.Flask` objects are created. If you are using the +development server you can create a +:class:`~werkzeug.contrib.cache.SimpleCache` object, that one is a simple +cache that keeps the item stored in the memory of the Python interpreter:: + + from werkzeug.contrib.cache import SimpleCache + cache = SimpleCache() + +If you want to use memcached, make sure to have one of the memcache modules +supported (you get them from `PyPI `_) and a +memcached server running somewhere. This is how you connect to such an +memcached server then:: + + from werkzeug.contrib.cache import MemcachedCache + cache = MemcachedCache(['127.0.0.1:11211']) + +If you are using appengine, you can connect to the appengine memcache +server easily:: + + from werkzeug.contrib.cache import GAEMemcachedCache + cache = GAEMemcachedCache() + +Using a Cache +------------- + +Now how can one use such a cache? There are two very important +operations: :meth:`~werkzeug.contrib.cache.BaseCache.get` and +:meth:`~werkzeug.contrib.cache.BaseCache.set`. This is how to use them: + +To get an item from the cache call +:meth:`~werkzeug.contrib.cache.BaseCache.get` with a string as key name. +If something is in the cache, it is returned. Otherwise that function +will return `None`:: + + rv = cache.get('my-item') + +To add items to the cache, use the :meth:`~werkzeug.contrib.cache.BaseCache.set` +method instead. The first argument is the key and the second the value +that should be set. Also a timeout can be provided after which the cache +will automatically remove item. + +Here a full example how this looks like normally:: + + def get_my_item(): + rv = cache.get('my-item') + if rv is None: + rv = calculate_value() + cache.set('my-item', rv, timeout=5 * 60) + return rv diff --git a/docs/patterns/index.rst b/docs/patterns/index.rst index b0b5fb72..513647c9 100644 --- a/docs/patterns/index.rst +++ b/docs/patterns/index.rst @@ -17,6 +17,8 @@ end of the request, the database connection is closed again. sqlite3 sqlalchemy fileuploads + caching + viewdecorators wtforms templateinheritance flashing diff --git a/docs/patterns/viewdecorators.rst b/docs/patterns/viewdecorators.rst new file mode 100644 index 00000000..22da533c --- /dev/null +++ b/docs/patterns/viewdecorators.rst @@ -0,0 +1,93 @@ +View Decorators +=============== + +Python has a really interesting feature called function decorators. This +allow some really neat things for web applications. Because each view in +Flask is a function decorators can be used to inject additional +functionality to one or more functions. The :meth:`~flask.Flask.route` +decorator is the one you probably used already. But there are use cases +for implementing your own decorator. For instance, imagine you have a +view that should only be used by people that are logged in to. If a user +goes to the site and is not logged in, he should be redirected to the +login page. This is a good example of a use case where a decorator is an +excellent solution. + +Login Required Decorator +------------------------ + +So let's implement such a decorator. A decorator is a function that +returns a function. Pretty simple actually. The only thing you have to +keep in mind when implementing something like this is to update the +`__name__`, `__module__` and some other attributes of a function. This is +often forgotten, but you don't have to do that by hand, there is a +function for that that is used like a decorator (:func:`functools.wraps`). + +This example assumes that the login page is called ``'login'`` and that +the current user is stored as `g.user` and `None` if there is no-one +logged in:: + + from functools import wraps + from flask import g, request, redirect, url_for + + def login_required(f): + @wraps(f) + def decorated_function(*args, **kwargs): + if g.user is None: + return redirect(url_for('login', next=request.url)) + return f(*args, **kwargs) + return decorated_function + +So how would you use that decorator now? Apply it as innermost decorator +to a view function. When applying further decorators, always remember +that the :meth:`~flask.Flask.route` decorator is the outermost:: + + @app.route('/secret_page') + @login_required + def secret_page(): + pass + +Caching Decorator +----------------- + +Imagine you have a view function that does an expensive calculation and +because of that you would like to cache the generated results for a +certain amount of time. A decorator would be nice for that. We're +assuming you have set up a cache like mentioned in :ref:`caching-pattern`. + +Here an example cache function. It generates the cache key from a +specific prefix (actually a format string) and the current path of the +request. Notice that we are using a function that first creates the +decorator that then decorates the function. Sounds awful? Unfortunately +it is a little bit more complex, but the code should still be +straightforward to read. + +The decorated function will then work as follows + +1. get the unique cache key for the current request base on the current + path. +2. get the value for that key from the cache. If the cache returned + something we will return that value. +3. otherwise the original function is called and the return value is + stored in the cache for the timeout provided (by default 5 minutes). + +Here the code:: + + from functools import wraps + from flask import request + + def cached(timeout=5 * 60, key='view/%s'): + def decorator(f): + @wraps(f) + def decorated_function(*args, **kwargs): + cache_key = key % request.path + rv = cache.get(cache_key) + if rv is not None: + return rv + rv = f(*args, **kwargs) + cache.set(cache_key, rv, timeout=timeout) + return rv + return decorated_function + return decorator + +Notice that this assumes an instanciated `cache` object is available, see +:ref:`caching-pattern` for more information.