Spec-Zone.ru › Bottle 0.11

Учебник

В этом учебнике вы познакомитесь с концепциями и возможностями веб-фреймворка Bottle, включая базовые и продвинутые темы. Вы можете прочитать его от начала до конца или использовать в качестве справочника позже. Возможно, вам также будет интересна автоматически сгенерированная Ссылка на API. Она содержит больше деталей, но объясняет меньше, чем этот учебник. Решения наиболее распространенных вопросов можно найти в нашем сборнике Рецептов или на странице Часто задаваемых вопросов. Если вам нужна помощь, присоединяйтесь к нашей почтовой рассылке или посетите нас в нашем канале IRC.

Установка

Bottle не зависит от каких-либо внешних библиотек. Вы можете просто загрузить bottle.py в каталог своего проекта и начать программирование:

$ wget http://bottlepy.org/bottle.py

Это даст вам последний снимок разработки, который включает все новые функции. Если вы предпочитаете более стабильную среду, вам следует придерживаться стабильных релизов. Они доступны на PyPi и могут быть установлены с помощью pip (рекомендуется), easy_install или вашего менеджера пакетов:

$ sudo pip install bottle              # recommended
$ sudo easy_install bottle             # alternative without pip
$ sudo apt-get install python-bottle   # works for debian, ubuntu, ...

В любом случае, для запуска приложений bottle вам понадобится Python 2.5 или более поздняя версия (включая 3.x). Если у вас нет разрешений на установку пакетов по всему системному каталогу или вы просто не хотите этого делать, сначала создайте virtualenv:

$ virtualenv develop              # Create virtual environment
$ source develop/bin/activate     # Change default python to virtual one
(develop)$ pip install -U bottle  # Install bottle to virtual environment

Или, если virtualenv не установлен на вашей системе:

$ wget https://raw.github.com/pypa/virtualenv/master/virtualenv.py
$ python virtualenv.py develop    # Create virtual environment
$ source develop/bin/activate     # Change default python to virtual one
(develop)$ pip install -U bottle  # Install bottle to virtual environment

Быстрый старт: «Hello World»

В этом учебнике предполагается, что у вас есть Bottle, либо установленная, либо скопированная в каталог вашего проекта. Давайте начнём с очень простого примера «Hello World»:

from bottle import route, run

@route('/hello')
def hello():
    return "Hello World!"

run(host='localhost', port=8080, debug=True)

Вот и всё. Запустите этот скрипт, посетите http://localhost:8080/hello, и вы увидите «Hello World!» в своём браузере. Вот как это работает:

Декоратор route() связывает фрагмент кода с путём URL. В данном случае мы связываем URL /hello с функцией hello(). Это называется route (отсюда и название декоратора) и является наиболее важной концепцией этого фреймворка. Вы можете определять столько маршрутов, сколько хотите. Всякий раз, когда браузер запрашивает URL, вызывается соответствующая функция, а её возвращаемое значение отправляется обратно в браузер. Всё так просто.

Вызов run() в последней строке запускает встроенный сервер разработки. Он работает на порту localhost 8080 и обрабатывает запросы до тех пор, пока вы не нажмёте Control-c. Позже вы можете поменять серверный бэкэнд, но пока нам нужен только сервер разработки. Он не требует никакой настройки и является невероятно простым способом запуска вашего приложения для локальных тестов.

Режим отладки Debug Mode очень полезен на ранних этапах разработки, но должен быть выключен для публичных приложений. Имейте это в виду.

Конечно, это очень простой пример, но он показывает основную концепцию построения приложений с помощью Bottle. Продолжайте чтение, и вы увидите, что ещё возможно.

Default Application

Для простоты большинство примеров в этом учебнике используют декоратор route() на уровне модуля для определения маршрутов. Это добавляет маршруты в глобальное «приложение по умолчанию», экземпляр Bottle, который автоматически создаётся при первом вызове route(). Несколько других декораторов и функций на уровне модуля относятся к этому приложению по умолчанию, но если вы предпочитаете объектно-ориентированный подход и не возражаете против дополнительных наборов текста, вы можете создать отдельный объект приложения и использовать его вместо глобального:

from bottle import Bottle, run, template

app = Bottle()

@app.route('/hello')
def hello():
    return "Hello World!"

run(app, host='localhost', port=8080)

Объектно-ориентированный подход подробно описан в разделе Приложения по умолчанию. Просто помните, что у вас есть выбор.

Маршрутизация запросов

В предыдущей главе мы создали очень простое веб-приложение только с одним маршрутом. Вот часть маршрутизации примера «Hello World» ещё раз:

@route('/hello')
def hello():
    return "Hello World!"

Декоратор route() связывает путь URL с функцией обратного вызова и добавляет новый маршрут в приложение по умолчанию. Однако приложение всего с одним маршрутом довольно скучно. Давайте добавим ещё несколько:

@route('/')
@route('/hello/<name>')
def greet(name='Stranger'):
    return template('Hello {{name}}, how are you?', name=name)

Этот пример демонстрирует два момента: вы можете связать более одного маршрута с одним обратным вызовом, и вы можете добавить подстановочные знаки в URL и получить доступ к ним через ключевые аргументы.

Динамические маршруты

Маршруты, содержащие подстановочные знаки, называются dynamic routes (в отличие от static routes) и соответствуют более чем одному URL одновременно. Простой подстановочный знак состоит из имени в угловых скобках (например, <name>) и принимает один или несколько символов до следующего слэша (/). Например, маршрут /hello/<name> принимает запросы для /hello/alice и /hello/bob, но не для /hello, /hello/ или /hello/mr/smith.

Каждый подстановочный знак передаёт соответствующую часть URL в качестве ключевого аргумента обратному вызову запроса. Вы можете сразу их использовать и легко реализовать RESTful, привлекательные и содержательные URL-адреса. Вот ещё несколько примеров вместе с URL-адресами, которые они бы соответствовали:

@route('/wiki/<pagename>')            # matches /wiki/Learning_Python
def show_wiki_page(pagename):
    ...

@route('/<action>/<user>')            # matches /follow/defnull
def user_api(action, user):
    ...

Введено в версии 0.10.

Фильтры используются для определения более конкретных подстановочных знаков и/или преобразования соответствующей части URL перед передачей её обратному вызову. Отфильтрованный подстановочный знак объявляется как <name:filter> или <name:filter:config>. Синтаксис необязательной части конфигурации зависит от используемого фильтра.

По умолчанию реализованы следующие фильтры, и могут быть добавлены и другие:

  • :int соответствует только (знаковым) цифрам и преобразует значение в целое число.
  • :float аналогично :int, но для десятичных чисел.
  • :path соответствует всем символам, включая символ слэша, не жадно, и может использоваться для соответствия более чем одному сегменту пути.
  • :re позволяет указать пользовательское регулярное выражение в поле конфигурации. Соответствующее значение не изменяется.

Давайте посмотрим на некоторые практические примеры:

@route('/object/<id:int>')
def callback(id):
    assert isinstance(id, int)

@route('/show/<name:re:[a-z]+>')
def callback(name):
    assert name.isalpha()

@route('/static/<path:path>')
def callback(path):
    return static_file(path, ...)

Вы также можете добавлять свои собственные фильтры. Подробности см. в Routing.

Изменено в версии 0.10.

Новый синтаксис правил был введён в Bottle 0.10 для упрощения некоторых распространённых случаев использования, но старый синтаксис всё ещё работает, и вы можете найти много примеров кода, которые всё ещё его используют. Различия лучше всего описаны на примерах:

Старый синтаксис Новый синтаксис
:name <name>
:name#regexp# <name:re:regexp>
:#regexp# <:re:regexp>
:## <:re>

Постарайтесь избегать старого синтаксиса в будущих проектах, если это возможно. Он сейчас не устарел, но в конечном итоге им перестанут пользоваться.

Методы HTTP-запросов

Протокол HTTP определяет несколько методов запросов (иногда их называют «глаголами») для различных задач. GET является значением по умолчанию для всех маршрутов без указания другого метода. Эти маршруты будут соответствовать только запросам GET. Для обработки других методов, таких как POST, PUT или DELETE, добавьте ключевой аргумент method к декоратору route() или используйте один из четырёх альтернативных декораторов: get(), post(), put() или delete().

Метод POST обычно используется для отправки HTML-форм. Этот пример показывает, как обработать форму входа в систему с помощью POST:

from bottle import get, post, request

@get('/login') # or @route('/login')
def login_form():
    return '''<form method="POST" action="/login">
                <input name="name"     type="text" />
                <input name="password" type="password" />
                <input type="submit" />
              </form>'''

@post('/login') # or @route('/login', method='POST')
def login_submit():
    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>"

В этом примере URL /login связан с двумя различными функциями обратного вызова, одной для запросов GET и другой для запросов POST. Первая отображает пользователю HTML-форму. Вторая функция обратного вызова вызывается при отправке формы и проверяет данные входа пользователя, введённые в форму. Использование Request.forms подробно описано в разделе Данные запроса.

Специальные методы: HEAD и ANY

Метод HEAD используется для запроса ответа, идентичного тому, который соответствовал бы запросу GET, но без тела ответа. Это полезно для получения метаданных о ресурсе, не загружая весь документ целиком. Bottle автоматически обрабатывает эти запросы, обращаясь к соответствующему маршруту GET и отрезая тело запроса, если оно присутствует. Вам не нужно указывать какие-либо маршруты HEAD самостоятельно.

Кроме того, нестандартный метод ANY работает как резервный вариант низкого приоритета: Маршруты, которые слушают ANY, будут соответствовать запросам независимо от их HTTP-метода, но только если другой более конкретный маршрут не определён. Это полезно для _маршрутов-прокси_, которые перенаправляют запросы в более конкретные подприложения.

Подводя итог: запросы HEAD обращаются к маршрутам GET, а все запросы обращаются к маршрутам ANY, но только если для исходного метода запроса не определён соответствующий маршрут. Это всё.

Маршрутизация статических файлов

Статические файлы, такие как изображения или CSS-файлы, не обслуживаются автоматически. Вы должны добавить маршрут и функцию обратного вызова, чтобы контролировать, какие файлы обслуживаются и где они находятся:

from bottle import static_file
@route('/static/<filename>')
def server_static(filename):
    return static_file(filename, root='/path/to/your/static/files')

Функция static_file() служит для безопасного и удобного предоставления файлов (см. Статические файлы). Этот пример ограничен файлами, расположенными непосредственно в каталоге /path/to/your/static/files, потому что подстановка <filename> не будет соответствовать пути с косой чертой. Для обслуживания файлов в подкаталогах измените подстановку на использование фильтра path:

@route('/static/<filepath:path>')
def server_static(filepath):
    return static_file(filepath, root='/path/to/your/static/files')

Будьте внимательны при указании относительного корневого пути, такого как root='./static/files'. Рабочая директория (./) и директория проекта не всегда совпадают.

Страницы ошибок

Если что-то пойдёт не так, Bottle отобразит информативную, но довольно простую страницу ошибки. Вы можете переопределить стандартное поведение для определённого кода HTTP-статуса с помощью декоратора error():

from bottle import error
@error(404)
def error404(error):
    return 'Nothing here, sorry'

Отныне, ошибки 404 File not Found будут отображать пользователю пользовательскую страницу ошибки. Единственный параметр, передаваемый обработчику ошибок, — это экземпляр HTTPError. Помимо этого, обработчик ошибок весьма похож на обычный обратный вызов запроса. Вы можете читать из request, записывать в response и возвращать любой поддерживаемый тип данных, кроме экземпляров HTTPError.

Обработчики ошибок используются только в том случае, если ваше приложение возвращает или вызывает исключение HTTPError (abort() делает именно это). Изменение Request.status или возвращение HTTPResponse не запустит обработчик ошибок.

Генерация содержимого

В чистом WSGI набор типов, которые вы можете вернуть из своего приложения, очень ограничен. Приложения должны возвращать итерируемый объект, генерирующий байтовые строки. Вы можете вернуть строку (потому что строки являются итерируемыми), но это заставляет большинство серверов передавать ваше содержимое символ за символом. Строки Unicode вообще не разрешены. Это не очень практично.

Bottle намного гибче и поддерживает широкий спектр типов. Он даже добавляет заголовок Content-Length если это возможно и автоматически кодирует unicode, так что вам не нужно. Далее следует список типов данных, которые вы можете вернуть из обратных вызовов приложения, и краткое описание того, как они обрабатываются фреймворком:

Словари
Как упоминалось выше, словари Python (или подклассы) автоматически преобразуются в строки JSON и возвращаются браузеру с установленным заголовком Content-Type со значением application/json. Это упрощает реализацию JSON-базированных API. Поддерживаются и другие форматы данных, кроме JSON. Чтобы узнать больше, см. tutorial-output-filter.
Empty Strings, False, None or other non-true values:
Эти значения генерируют пустой вывод с заголовком Content-Length, установленным в 0.
Строки Unicode
Строки Unicode (или итерируемые объекты, генерирующие строки Unicode) автоматически кодируются с кодеком, указанным в заголовке Content-Type (utf8 по умолчанию), а затем обрабатываются как обычные байтовые строки (см. ниже).
Байтовые строки
Bottle возвращает строки целиком (вместо итерации по каждому символу) и добавляет заголовок Content-Length на основе длины строки. Список байтовых строк объединяется сначала. Другие итерируемые объекты, генерирующие байтовые строки, не объединяются, потому что они могут стать слишком большими, чтобы поместиться в памяти. В этом случае заголовок Content-Length не устанавливается.
Instances of HTTPError or HTTPResponse
Возврат этих значений имеет тот же эффект, что и при их повышении в качестве исключения. В случае с HTTPError применяется обработчик ошибок. Подробности см. в разделе Страницы ошибок.
Объекты файлов
Всё, что имеет метод .read(), обрабатывается как объект файла или похожий на файл и передаётся вызываемому объекту wsgi.file_wrapper, определённому фреймворком WSGI-сервера. Некоторые реализации WSGI-серверов могут использовать оптимизированные системные вызовы (sendfile) для более эффективной передачи файлов. В других случаях происходит просто итерация по частям, которые помещаются в память. Дополнительные заголовки, такие как Content-Length или Content-Type, не устанавливаются автоматически. Если возможно, используйте send_file(). Подробности см. в разделе Статические файлы.
Итерируемые объекты и генераторы
Вы можете использовать yield в своих обратных вызовах или возвращать итерируемый объект, если только итерируемый объект генерирует байтовые строки, строки Unicode, экземпляры HTTPError или HTTPResponse. Вложенные итерируемые объекты не поддерживаются, к сожалению. Обратите внимание, что код HTTP-статуса и заголовки отправляются в браузер как только итерируемый объект сгенерирует первое ненулевое значение. Изменение этих значений позже не оказывает влияния.

Порядок в этом списке важен. Например, вы можете вернуть подкласс str с методом read(). Он всё равно будет обрабатываться как строка, а не как файл, так как строки обрабатываются в первую очередь.

Изменение кодировки по умолчанию

Bottle использует параметр charset заголовка Content-Type для определения способа кодирования строк Unicode. Этот заголовок по умолчанию равен text/html; charset=UTF8 и может быть изменён с помощью атрибута Response.content_type или путём непосредственного задания атрибута Response.charset. (Объект Response описан в разделе Объект ответа.)

from bottle import response
@route('/iso')
def get_iso():
    response.charset = 'ISO-8859-15'
    return u'This will be sent with ISO-8859-15 encoding.'

@route('/latin9')
def get_latin():
    response.content_type = 'text/html; charset=latin9'
    return u'ISO-8859-15 is also known as latin9.'

В некоторых редких случаях имена кодировок Python отличаются от имён, поддерживаемых спецификацией HTTP. Тогда вам нужно сделать оба действия: сначала установить заголовок Response.content_type (который отправляется клиенту без изменений), а затем установить атрибут Response.charset (который используется для кодирования Unicode).

Статические файлы

Вы можете напрямую возвращать объекты файлов, но static_file() — это рекомендуемый способ предоставления статических файлов. Он автоматически определяет MIME-тип, добавляет заголовок Last-Modified, ограничивает пути каталогом root по соображениям безопасности, генерирует соответствующие ответы об ошибках (401 при ошибках доступа, 404 при отсутствии файлов). Он даже поддерживает заголовок If-Modified-Since и в конечном итоге генерирует ответ 304 Not Modified. Вы можете передать пользовательский MIME-тип, чтобы отключить определение.

from bottle import static_file
@route('/images/<filename:re:.*\.png>#')
def send_image(filename):
    return static_file(filename, root='/path/to/image/files', mimetype='image/png')

@route('/static/<filename:path>')
def send_static(filename):
    return static_file(filename, root='/path/to/static/files')

Вы можете вызвать исключение, возвращаемое static_file(), если вам это действительно нужно.

Принудительное скачивание

Большинство браузеров пытаются открыть загруженные файлы, если MIME-тип известен и назначен приложению (например, PDF-файлы). Если это не то, что вы хотите, вы можете принудительно открыть диалог скачивания и даже предложить пользователю имя файла:

@route('/download/<filename:path>')
def download(filename):
    return static_file(filename, root='/path/to/static/files', download=filename)

Если параметр download просто True, используется исходное имя файла.

HTTP-ошибки и перенаправления

Функция abort() — это сокращение для генерации страниц HTTP-ошибок.

from bottle import route, abort
@route('/restricted')
def restricted():
    abort(401, "Sorry, access denied.")

Чтобы перенаправить клиента на другой URL, вы можете отправить ответ 303 See Other с заголовком Location , установленным на новый URL. redirect() делает это за вас:

from bottle import redirect
@route('/wrong/url')
def wrong():
    redirect("/right/url")

Вы можете указать другой код HTTP-статуса в качестве второго параметра.

Примечание

Обе функции прервут ваш код обратного вызова, вызвав исключение HTTPError.

Другие исключения

Все исключения, кроме HTTPResponse или HTTPError, приведут к ответу 500 Internal Server Error, поэтому они не повредят ваш WSGI-сервер. Вы можете отключить это поведение, чтобы обрабатывать исключения в своём промежуточном ПО, установив bottle.app().catchall в False.

Объект Response

Метаданные ответа, такие как код HTTP-статуса, заголовки ответа и куки, хранятся в объекте, называемом response, до момента передачи их в браузер. Вы можете непосредственно манипулировать этими метаданными или использовать предопределённые вспомогательные методы для этого. Полный API и список функций описаны в разделе API (см. Response), но здесь также рассматриваются наиболее распространённые случаи использования и функции.

Код статуса

Код HTTP-статуса управляет поведением браузера и по умолчанию равен 200 OK. В большинстве сценариев вам не нужно вручную устанавливать атрибут Response.status, а вместо этого используйте вспомогательную функцию abort() или верните экземпляр HTTPResponse с соответствующим кодом статуса. Допускается любое целое число, но коды, не определённые в спецификации HTTP, могут только ввести браузер в заблуждение и нарушить стандарты.

Заголовок ответа

Заголовки ответа, такие как Cache-Control или Location, определяются через метод Response.set_header(). Этот метод принимает два параметра: имя заголовка и значение. Часть имени регистронезависима:

@route('/wiki/<page>')
def wiki(page):
    response.set_header('Content-Language', 'en')
    ...

Большинство заголовков уникальны, то есть в ответ клиенту отправляется только один заголовок с определённым именем. Однако некоторые специальные заголовки могут появляться в ответе несколько раз. Чтобы добавить дополнительный заголовок, используйте Response.add_header() вместо Response.set_header():

response.set_header('Set-Cookie', 'name=value')
response.add_header('Set-Cookie', 'name2=value2')

Обратите внимание, что это всего лишь пример. Если вы хотите работать с куки, прочитайте дальше.

Куки

Куки — это именованный фрагмент текста, хранящийся в профиле браузера пользователя. Вы можете получить доступ к ранее определённым кукам через Request.get_cookie() и установить новые куки с помощью Response.set_cookie():

@route('/hello')
def hello_again():
    if request.get_cookie("visited"):
        return "Welcome back! Nice to see you again"
    else:
        response.set_cookie("visited", "yes")
        return "Hello there! Nice to meet you"

Метод Response.set_cookie() принимает несколько дополнительных ключевых аргументов, которые управляют временем жизни и поведением куки. Некоторые из наиболее распространённых настроек описаны здесь:

  • max_age: Максимальное время жизни в секундах. (по умолчанию: None)
  • expires: Объект datetime или временная метка UNIX. (по умолчанию: None)
  • domain: Домен, которому разрешено читать куки. (по умолчанию: текущий домен)
  • path: Ограничить куки заданным путём (по умолчанию: /)
  • secure: Ограничить куки соединениями HTTPS (по умолчанию: выключено).
  • httponly: Предотвратить чтение этой куки клиентской стороной JavaScript (по умолчанию: выключено, требуется Python 2.6 или новее).

Если ни expires ни max_age не установлены, куки истекают в конце сессии браузера или как только окно браузера закрывается. Есть ещё несколько нюансов, которые следует учитывать при работе с куками:

  • В большинстве браузеров куки ограничены 4 КБ текста.
  • Некоторые пользователи настраивают свои браузеры так, чтобы не принимать куки вообще. Большинство поисковых систем также игнорируют куки. Убедитесь, что ваше приложение по-прежнему работает без куки.
  • Куки хранятся на стороне клиента и не шифруются. Пользователь может прочитать всё, что хранится в куках. Хуже того, злоумышленник может украсть куки пользователя через уязвимости XSS на вашей стороне. Известно, что некоторые вирусы также считывают куки браузера. Поэтому никогда не храните конфиденциальную информацию в куках.
  • Куки легко подделываются злонамеренными клиентами. Не доверяйте кукам.

Подписанные куки

Как упоминалось выше, куки легко подделываются злонамеренными клиентами. Bottle может криптографически подписать ваши куки, чтобы предотвратить такие манипуляции. Всё, что вам нужно сделать, это указать ключ подписи через ключевой аргумент secret при чтении или записи куки и хранить этот ключ в секрете. В результате, Request.get_cookie() вернёт None если куки не подписаны или ключи подписи не совпадают:

@route('/login')
def login():
    username = request.forms.get('username')
    password = request.forms.get('password')
    if check_user_credentials(username, password):
        response.set_cookie("account", username, secret='some-secret-key')
        return "Welcome %s! You are now logged in." % username
    else:
        return "Login failed."

@route('/restricted')
def restricted_area():
    username = request.get_cookie("account", secret='some-secret-key')
    if username:
        return "Hello %s. Welcome back." % username
    else:
        return "You are not logged in. Access denied."

Кроме того, Bottle автоматически сериализует и десериализует данные, хранящиеся в подписанных куках. Это позволяет хранить в куках любые сериализуемые объекты (не только строки), при условии, что размер сериализованных данных не превышает 4 КБ.

Предупреждение

Подписанные куки не зашифрованы (клиент всё ещё может видеть содержимое) и не защищены от копирования (клиент может восстановить старые куки). Основная цель — обеспечить безопасность сериализации и десериализации и предотвратить манипуляции, а не хранить секретную информацию на стороне клиента.

Данные запроса

Bottle предоставляет доступ к метаданным, связанным с HTTP, таким как куки, заголовки и данные формы POST, через глобальный объект request. Этот объект всегда содержит информацию о текущем запросе, если к нему обращаются из функции обратного вызова. Это работает даже в многопоточных средах, где одновременно обрабатываются несколько запросов. Подробности о том, как глобальный объект может быть потокобезопасным, см. в contextlocal.

Примечание

Bottle хранит большую часть проанализированных метаданных HTTP в экземплярах FormsDict. Они ведут себя как обычные словари, но имеют некоторые дополнительные функции: все значения в словаре доступны как атрибуты. Эти виртуальные атрибуты всегда возвращают строку unicode, даже если значение отсутствует. В этом случае строка пустая.

FormsDict является подклассом MultiDict и может хранить более одного значения на ключ. Стандартные методы доступа к словарям возвращают только одно значение, но метод MultiDict.getall() возвращает (возможно, пустой) список всех значений для определённого ключа.

Полный список API и функций описан в разделе API (см. Request), но здесь также освещаются наиболее распространённые случаи использования и функции.

Куки

Куки хранятся в BaseRequest.cookies как FormsDict. Метод BaseRequest.get_cookie() позволяет получить доступ к подписанным кукам, как описано в отдельном разделе. Этот пример демонстрирует простую подсчётчик просмотров на основе куки:

from bottle import route, request, response
@route('/counter')
def counter():
    count = int( request.cookies.get('counter', '0') )
    count += 1
    response.set_cookie('counter', str(count))
    return 'You visited this page %d times' % count

HTTP-заголовки

Все HTTP-заголовки, отправленные клиентом (например, Referer, Agent или Accept-Language), хранятся в WSGIHeaderDict и доступны через BaseRequest.headers. WSGIHeaderDict — это по существу словарь с ключами, не чувствительными к регистру:

from bottle import route, request
@route('/is_ajax')
def is_ajax():
    if request.headers.get('X-Requested-With') == 'XMLHttpRequest':
        return 'This is an AJAX request'
    else:
        return 'This is a normal request'

Переменные запроса

Строка запроса (как в /forum?id=1&page=5) обычно используется для передачи небольшого количества пар ключ-значение на сервер. Вы можете использовать BaseRequest.query (a FormsDict) для доступа к этим значениям и атрибут BaseRequest.query_string для получения всей строки.

from bottle import route, request, response
@route('/forum')
def display_forum():
    forum_id = request.query.id
    page = request.query.page or '1'
    return 'Forum ID: %s (page %s)' % (forum_id, page)

Данные формы POST и загрузки файлов

Тело запроса для POST и PUT запросов может содержать данные формы, закодированные в различных форматах. Словарь BaseRequest.forms содержит проанализированные текстовые поля формы, BaseRequest.files хранит загрузки файлов, а BaseRequest.POST объединяет оба словаря в один. Все три являются экземплярами FormsDict и создаются по мере необходимости. Загрузки файлов сохраняются как специальные cgi.FieldStorage объекты вместе с некоторыми метаданными. Наконец, вы можете получить доступ к исходным данным тела как к объекту типа «файл» через BaseRequest.body.

Вот пример простой формы загрузки файла:

<form action="/upload" method="post" enctype="multipart/form-data">
  <input type="text" name="name" />
  <input type="file" name="data" />
</form>
from bottle import route, request
@route('/upload', method='POST')
def do_upload():
    name = request.forms.name
    data = request.files.data
    if name and data and data.file:
        raw = data.file.read() # This is dangerous for big files
        filename = data.filename
        return "Hello %s! You uploaded %s (%d bytes)." % (name, filename, len(raw))
    return "You missed a field."

Проблемы с кодировкой Unicode

В Python 2 все ключи и значения являются строками байтов. Если вам нужна строка unicode, вы можете вызвать FormsDict.getunicode() или получить значения через доступ к атрибутам. Оба метода пытаются декодировать строку (по умолчанию: utf8) и возвращают пустую строку, если это не удаётся. Нет необходимости перехватывать UnicodeError:

>>> request.query['city']
'G\xc3\xb6ttingen'  # A utf8 byte string
>>> request.query.city
u'Göttingen'        # The same string as unicode

В Python 3 все строки являются строками unicode, но HTTP — это байтовый протокол. Сервер должен как-то декодировать строки байтов перед передачей их приложению. Для большей безопасности WSGI рекомендует использовать ISO-8859-1 (также известный как latin1), обратимый кодек с одним байтом, который можно перекодировать с использованием другой кодировки позже. Bottle делает это для FormsDict.getunicode() и доступа к атрибутам, но не для методов доступа к словарям. Эти методы возвращают неизменённые значения, предоставленные реализацией сервера, что, вероятно, не то, что вам нужно.

>>> request.query['city']
'Göttingen' # An utf8 string provisionally decoded as ISO-8859-1 by the server
>>> request.query.city
'Göttingen'  # The same string correctly re-encoded as utf8 by bottle

Если вам нужен весь словарь с правильно декодированными значениями (например, для WTForms), вы можете вызвать FormsDict.decode(), чтобы получить скопированную и перекодированную версию.

WSGISреда окружения

Каждый экземпляр BaseRequest оборачивает словарь WSGI окружения. Исходный хранится в BaseRequest.environ, но сам объект запроса тоже ведет себя как словарь. Большая часть интересной информации доступна через специальные методы или атрибуты, но если вам нужно напрямую получить доступ к переменным среды WSGI, вы можете это сделать:

@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

Шаблоны

Bottle поставляется с быстрым и мощным встроенным движком шаблонов под названием SimpleTemplate Engine. Для рендеринга шаблона можно использовать функцию template() или декоратор view(). Всё, что вам нужно сделать, это указать имя шаблона и переменные, которые вы хотите передать шаблону, в качестве ключевых аргументов. Вот простой пример рендеринга шаблона:

@route('/hello')
@route('/hello/<name>')
def hello(name='World'):
    return template('hello_template', name=name)

Это загрузит файл шаблона hello_template.tpl и рендерит его с установленной переменной name. Bottle будет искать шаблоны в папке ./views/ или любой папке, указанной в списке bottle.TEMPLATE_PATH.

Декоратор view() позволяет вам возвращать словарь с переменными шаблона вместо вызова template():

@route('/hello')
@route('/hello/<name>')
@view('hello_template')
def hello(name='World'):
    return dict(name=name)

Синтаксис

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

Вот пример шаблона:

%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

Кэширование

Шаблоны кэшируются в памяти после компиляции. Изменения, внесенные в файлы шаблонов, не будут иметь эффекта, пока вы не очистите кэш шаблонов. Для этого вызовите bottle.TEMPLATES.clear(). Кэширование отключено в отладочном режиме.

Плагины

Новое в версии 0.9.

Основные возможности Bottle охватывают большинство распространенных случаев использования, но как микрофреймворк, он имеет свои ограничения. Здесь на помощь приходят «Плагины». Плагины добавляют недостающие функциональные возможности в фреймворк, интегрируют сторонние библиотеки или просто автоматизируют некоторые повторяющиеся задачи.

У нас есть растущий Список доступных плагинов, и большинство из них разработаны для портативности и повторного использования в разных приложениях. Высока вероятность, что ваша проблема уже решена, и готовый к использованию плагин существует. Если нет, руководство по разработке плагинов Руководство по разработке плагинов может помочь вам.

Эффекты и API плагинов многообразны и зависят от конкретного плагина. Например, плагин SQLitePlugin обнаруживает обратные вызовы, требующие ключевого аргумента db, и создаёт новый объект подключения к базе данных каждый раз, когда вызывается обратный вызов. Это делает очень удобным использование базы данных:

from bottle import route, install, template
from bottle_sqlite import SQLitePlugin

install(SQLitePlugin(dbfile='/tmp/test.db'))

@route('/show/<post_id:int>')
def show(db, post_id):
    c = db.execute('SELECT title, content FROM posts WHERE id = ?', (post_id,))
    row = c.fetchone()
    return template('show_post', title=row['title'], text=row['content'])

@route('/contact')
def contact_page():
    ''' This callback does not need a db connection. Because the 'db'
        keyword argument is missing, the sqlite plugin ignores this callback
        completely. '''
    return template('contact')

Другие плагины могут заполнить защищённый от многопоточности объект local, изменить детали объекта request, отфильтровать данные, возвращаемые обратным вызовом, или вообще обойти обратный вызов. Например, плагин «auth» может проверить наличие валидной сессии и вернуть страницу входа вместо вызова исходного обратного вызова. То, что произойдёт, зависит от плагина.

Установка на уровне приложения

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

Давайте рассмотрим пример плагина SQLitePlugin. Он влияет только на обратные вызовы маршрутов, которым нужно подключение к базе данных. Другие маршруты остаются без изменений. Благодаря этому мы можем установить плагин на уровне приложения без дополнительной нагрузки.

Для установки плагина просто вызовите install() с плагином в качестве первого аргумента:

from bottle_sqlite import SQLitePlugin
install(SQLitePlugin(dbfile='/tmp/test.db'))

Плагин ещё не применён к обратным вызовам маршрутов. Это откладывается, чтобы убедиться, что ни один маршрут не пропущен. Вы можете установить плагины сначала и добавить маршруты позже, если хотите. Порядок установленных плагинов важен. Если плагин требует подключения к базе данных, сначала нужно установить плагин базы данных.

Удаление плагинов

Вы можете использовать имя, класс или экземпляр для uninstall() ранее установленного плагина:

sqlite_plugin = SQLitePlugin(dbfile='/tmp/test.db')
install(sqlite_plugin)

uninstall(sqlite_plugin) # uninstall a specific plugin
uninstall(SQLitePlugin)  # uninstall all plugins of that type
uninstall('sqlite')      # uninstall all plugins with that name
uninstall(True)          # uninstall all plugins at once

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

Примечание

Функции на уровне модуля install() и uninstall() влияют на Приложение по умолчанию. Для управления плагинами для определенного приложения используйте соответствующие методы объекта приложения Bottle.

Установка для конкретных маршрутов

Параметр apply декоратора route() пригодится, если вы хотите установить плагины только для небольшого числа маршрутов:

sqlite_plugin = SQLitePlugin(dbfile='/tmp/test.db')

@route('/create', apply=[sqlite_plugin])
def create(db):
    db.execute('INSERT INTO ...')

Исключение плагинов

Возможно, вам захочется явно отключить плагин для ряда маршрутов. Декоратор route() имеет параметр skip для этой цели:

sqlite_plugin = SQLitePlugin(dbfile='/tmp/test.db')
install(sqlite_plugin)

@route('/open/<db>', skip=[sqlite_plugin])
def open_db(db):
    # The 'db' keyword argument is not touched by the plugin this time.
    if db in ('test', 'test2'):
        # The plugin handle can be used for runtime configuration, too.
        sqlite_plugin.dbfile = '/tmp/%s.db' % db
        return "Database File switched to: /tmp/%s.db" % db
    abort(404, "No such database.")

Параметр skip принимает одно значение или список значений. Вы можете использовать имя, класс или экземпляр для идентификации плагина, который нужно пропустить. Установите skip=True , чтобы пропустить все плагины сразу.

Плагины и подприложения

Большинство плагинов специфичны для приложения, к которому они были установлены. Соответственно, они не должны влиять на подприложения, подключенные с помощью Bottle.mount(). Вот пример:

root = Bottle()
root.mount('/blog', apps.blog)

@root.route('/contact', template='contact')
def contact():
    return {'email': 'contact@example.com'}

root.install(plugins.WTForms())

Всякий раз, когда вы подключаете приложение, Bottle создаёт прокси-маршрут в основном приложении, который перенаправляет все запросы к подприложению. Плагины по умолчанию отключены для этого типа прокси-маршрута. В результате наш (вымышленный) плагин WTForms влияет на маршрут /contact, но не влияет на маршруты подприложения /blog.

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

root.mount('/blog', apps.blog, skip=None)

Но есть проблема: плагин видит всё подприложение как один маршрут, а именно упомянутый выше прокси-маршрут. Чтобы повлиять на каждый отдельный маршрут подприложения, необходимо явно установить плагин в подключённое приложение.

Разработка

Итак, вы освоили основы и хотите написать своё приложение? Вот несколько советов, которые могут помочь вам быть более продуктивными.

Приложение по умолчанию

Bottle поддерживает глобальный стек экземпляров Bottle и использует верхнюю часть стека в качестве значения по умолчанию для некоторых функций и декораторов на уровне модуля. Например, декоратор route() — это сокращение для вызова Bottle.route() в приложении по умолчанию:

@route('/')
def hello():
    return 'Hello World'

Это очень удобно для небольших приложений и экономит написание кода, но также означает, что как только ваш модуль импортирован, маршруты устанавливаются в глобальном приложении. Чтобы избежать подобных побочных эффектов импорта, Bottle предлагает второй, более явный способ создания приложений:

app = Bottle()

@app.route('/')
def hello():
    return 'Hello World'

Отделение объекта приложения значительно улучшает повторное использование. Другие разработчики могут безопасно импортировать объект app из вашего модуля и использовать Bottle.mount() для объединения приложений.

В качестве альтернативы, вы можете использовать стек приложения для изоляции ваших маршрутов, сохраняя при этом удобные сокращения:

default_app.push()

@route('/')
def hello():
    return 'Hello World'

app = default_app.pop()

Как app(), так и default_app() являются экземплярами AppStack и реализуют интерфейс стека. Вы можете добавлять и удалять приложения из стека по мере необходимости. Это также полезно, если вы хотите импортировать сторонний модуль, который не предлагает отдельный объект приложения:

default_app.push()

import some.module

app = default_app.pop()

Отладочный режим

На ранних этапах разработки отладочный режим может быть очень полезен.

bottle.debug(True)

В этом режиме Bottle более подробен и предоставляет полезную информацию об отладке всякий раз, когда возникает ошибка. Он также отключает некоторые оптимизации, которые могут помешать вам, и добавляет проверки, которые предупреждают вас о возможной неправильной настройке.

Вот неполный список изменений в отладочном режиме:

  • Страница ошибки по умолчанию отображает трассировку стека.
  • Шаблоны не кэшируются.
  • Плагины применяются немедленно.

Просто убедитесь, что отладочный режим не используется на сервере в рабочей среде.

Автоматическая перезагрузка

Во время разработки вам часто приходится перезапускать сервер для тестирования последних изменений. Автоматическая перезагрузка может сделать это за вас. Каждый раз, когда вы изменяете файл модуля, загрузчик перезапускает процесс сервера и загружает новую версию вашего кода.

from bottle import run
run(reloader=True)

Как это работает: основной процесс не запускает сервер, а порождает новый дочерний процесс с теми же аргументами командной строки, которые использовались для запуска основного процесса. Весь код на уровне модуля выполняется как минимум дважды! Будьте осторожны.

Дочерний процесс будет иметь os.environ['BOTTLE_CHILD'] , установленное в True, и запустится как обычный сервер приложения без перезагрузки. Как только любой из загруженных модулей изменится, дочерний процесс будет завершён и перезапущен основным процессом. Изменения в файлах шаблонов не будут вызывать перезагрузку. Используйте отладочный режим для отключения кэширования шаблонов.

Перезагрузка зависит от возможности остановить дочерний процесс. Если вы работаете на Windows или любой другой операционной системе, не поддерживающей signal.SIGINT (что вызывает KeyboardInterrupt в Python), используется signal.SIGTERM для завершения дочернего процесса. Обратите внимание, что обработчики выхода и блоки finally и т. д. не выполняются после SIGTERM.

Интерфейс командной строки

Начиная с версии 0.10, вы можете использовать bottle как утилиту командной строки:

$ python -m bottle

Usage: bottle.py [options] package.module:app

Options:
  -h, --help            show this help message and exit
  --version             show version number.
  -b ADDRESS, --bind=ADDRESS
                        bind socket to ADDRESS.
  -s SERVER, --server=SERVER
                        use SERVER as backend.
  -p PLUGIN, --plugin=PLUGIN
                        install additional plugin/s.
  --debug               start server in debug mode.
  --reload              auto-reload on file changes.

Поле ADDRESS принимает IP-адрес или пару IP:PORT и по умолчанию равно localhost:8080. Другие параметры должны быть понятны.

Плагины и приложения указываются с помощью выражений импорта. Эти выражения состоят из пути импорта (например, package.module) и выражения, которое должно быть вычислено в пространстве имён этого модуля, разделённых двоеточием. См. load() для получения подробностей. Вот несколько примеров:

# Grab the 'app' object from the 'myapp.controller' module and
# start a paste server on port 80 on all interfaces.
python -m bottle -server paste -bind 0.0.0.0:80 myapp.controller:app

# Start a self-reloading development server and serve the global
# default application. The routes are defined in 'test.py'
python -m bottle --debug --reload test

# Install a custom debug plugin with some parameters
python -m bottle --debug --reload --plugin 'utils:DebugPlugin(exc=True)'' test

# Serve an application that is created with 'myapp.controller.make_app()'
# on demand.
python -m bottle 'myapp.controller:make_app()''

Развёртывание

Bottle по умолчанию работает на встроенном сервере wsgiref WSGIServer. Этот не-многопоточный HTTP-сервер прекрасно подходит для разработки и начальной стадии производства, но может стать узким местом в производительности при увеличении нагрузки на сервер.

Самый простой способ повышения производительности — установить многопоточную библиотеку сервера, например, paste или cherrypy, и указать Bottle использовать её вместо однопоточного сервера:

bottle.run(server='paste')

Это и многие другие варианты развертывания описаны в отдельной статье: Развертывание

Словарь терминов

callback
Код программиста, который должен вызываться при возникновении внешнего действия. В контексте веб-фреймворков отображение URL-путей к коду приложения часто реализуется путем задания функции обратного вызова для каждого URL.
decorator
Функция, возвращающая другую функцию, обычно применяемая как преобразование функции с помощью синтаксиса @decorator. Подробнее о декораторах см. в документации Python по определению функций.
environ
Структура, в которой хранится информация обо всех документах под корнем, используемая для перекрестных ссылок. Окружение сериализуется после этапа разбора, так что последующие запуски нуждаются только в чтении и разборе новых и измененных документов.
handler function
Функция для обработки определённого события или ситуации. В веб-фреймворке приложение разрабатывается путем прикрепления функции обработчика как функции обратного вызова для каждого конкретного URL, составляющего приложение.
source directory
Директория, которая, включая её поддиректории, содержит все исходные файлы для одного проекта Sphinx.

© 2009–2017 Marcel Hellkamp
Licensed under the MIT License.
https://bottlepy.org/docs/0.11/tutorial.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API