Сообщить об ошибке.

Использование декораторов в создании приложений на Flask

Так как каждое представление во Flask - это простая функция Python, то при проектировании и создании приложений можно использовать собственные декораторы, что позволяет уменьшить количество кода функций-представлений и делать полезные вещи.

Содержание:


Декоратор проверки входа на сайт в приложениях Flask.

Представим, что есть функция-представление, которая должна отображать страницу сайта только пользователями, которые вошли в систему. Если пользователь переходит на эту страницу и не вошел в систему, то его необходимо перенаправить на страницу входа. Это хороший пример варианта использования, в котором декоратор - отличное решение.

Декоратор - это функция, которая обертывает и заменяет другую функцию. Так как исходная функция заменяется, то необходимо скопировать информацию исходной функции в новую функцию при помощи functools.wraps().

В этом примере предполагается, что страница входа называется 'login' и что текущий пользователь хранится в g.user и если никто не вошел в систему, то это значение равно None.

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:
            # дополнительно в URL передаем параметр `next`
            # что бы после авторизации перенаправить его
            # на страницу, к которой он обращался
            return redirect(url_for('login', next=request.url))
        return f(*args, **kwargs)
    return decorated_function

Чтобы применить этот декоратор к функции-представлению, его необходимо поместить как самый внутренний декоратор. При применении дополнительных декораторов всегда помните, что декоратор @app.route() является самым внешним.

@app.route('/secret_page')
@login_required
def secret_page():
    pass

Примечание. После GET запроса для страницы входа, в request.args будет существовать значение next. Это значение нужно передать при отправке запроса POST из формы входа. Это можно сделать с помощью скрытого тега <Input>, а затем получить его из request.form при входе пользователя в систему.

<input type="hidden" value="{{ request.args.get('next', '') }}"/>

Кэширующий декоратор для функций-представлений.

Представим, что есть функция-представление, которая выполняет дорогостоящие вычисления, а сгенерированные результаты необходимо кэшировать на определенное время.

Создаваемый кэширующий-декоратор будет генерировать ключ кэша из определенного префикса, в данном случае из текущего URL-адреса.

Декорированная функция-представление будет работать следующим образом:

  1. Получаем уникальный ключ кэша для текущего запроса на основе текущего пути.
  2. Получаем значение этого ключа из кэша. Если кэше что-то есть, то возвращаем это значение.
  3. В противном случае вызывается исходная функция, а возвращаемое значение сохраняется в кэше в течение указанного времени ожидания (по умолчанию 5 минут).

Для создания такого декоратора понадобится шаблон декоратора с параметрами

Пример декоратора, который кэширует функцию-представление:

from functools import wraps
from flask import request

def cached(timeout=5 * 60, key='view/{}'):
    def decorator(f):
        @wraps(f)
        def decorated_function(*args, **kwargs):
            cache_key = key.format(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

Обратите внимание, что применение такого декоратора предполагает, что экземпляр объекта кэша cache всегда доступен.

Так же рекомендуем рассмотреть использование в приложении расширения Flask-Caching, которое умеет хранить кэш в памяти, на жестком диске или в различных базах данных и легко настраивается.

Декоратор для функции render_template().

Идея такого декоратора заключается в том, что бы из функции-представления возвращать словарь со значениями контекста, передаваемый в функцию render_template(). Например при использовании декоратора @templated (его код рассматривается ниже) следующие три примера делают одно и то же:

@app.route('/')
def index():
    return render_template('index.html', value=42)

@app.route('/')
@templated('index.html')
def index():
    return dict(value=42)

@app.route('/')
@templated()
def index():
    return dict(value=42)

Если в @templated имя шаблона не передается, то декоратор будет создавать его из конечной точки endpoint, которая передается в функцию add_url_rule(). Если функция-представление возвращает словарь, то он передается в функцию отрисовки шаблона render_template(). Если функция-представление возвращает None, то в render_template() передается пустой словарь, если возвращается что-то еще, кроме словаря и None, то декоратор возвращает это значение без изменений. Таким образом, сохраняется возможность использовать функцию redirect() или возвращать простые строки.

Пример декоратора для функции render_template():

from functools import wraps
from flask import request, render_template

def templated(template=None):
    def decorator(f):
        @wraps(f)
        def decorated_function(*args, **kwargs):
            template_name = template
            # если имя шаблона не определено
            if template_name is None:
                # получаем имя шаблона из `request.endpoint`
                template_name = f"{request.endpoint.replace('.', '/')}.html"
            ctx = f(*args, **kwargs)
            if ctx is None:
                # если функция-представление ничего
                # не возвращает, то используем пустой словарь
                ctx = {}
            elif not isinstance(ctx, dict):
                # если функция-представление возвратила
                # НЕ None и НЕ словарь (например ответ 
                # или пустую строку) то просто отдаем это значение
                return ctx
            return render_template(template_name, **ctx)
        return decorated_function
    return decorator

Декоратор конечных точек endpoint.

Если предпочтительнее в приложении Flask, для большей гибкости, использовать систему маршрутизации модуля werkzeug, то необходимо сопоставить конечную точку endpoint, которая передается в Rule(), с функцией-представлением. Это возможно с помощью следующего встроенного декоратора @app.endpoint().

Пример:

from flask import Flask
from werkzeug.routing import Rule

app = Flask(__name__)
app.url_map.add(Rule('/', endpoint='index'))

@app.endpoint('index')
def my_index():
    return "Hello world"