Spec-Zone.ru › Bottle 0.12

Учебник: Приложение для списка дел

Примечание

Этот учебник находится в разработке и написан noisefloor.

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

Для понимания данного материала не требуется базовое знание WSGI, так как Bottle старается изолировать WSGI от пользователя. Вам должно быть хорошо знакомо программирование на языке Python. Кроме того, пример, используемый в учебнике, извлекает и сохраняет данные в базе данных SQL, поэтому базовые знания SQL полезны, но не являются обязательными для понимания концепций Bottle. Здесь используется SQLite. Вывод Bottle, отправленный в браузер, в некоторых примерах форматируется с помощью HTML. Таким образом, базовые знания об основных тегах HTML также помогут.

В целях ознакомления с Bottle код Python «между» сохраняется кратким, чтобы сосредоточиться на основном. Также весь код в учебнике работает, но вы, возможно, не будете использовать его «в дикой природе», например, на общедоступном веб-сервере. Для этого вы можете добавить, например, более сложную обработку ошибок, защитить базу данных паролем, проверить и экранировать входные данные и т. д.

Содержание

  • Учебник: Приложение для списка дел
    • Цели
    • Перед началом...
    • Использование Bottle для веб-приложения списка дел
    • Настройка сервера
    • Заключительные слова
    • Полный пример

Цели

В конце этого учебника у нас будет простой веб-приложение для списка дел. Список содержит текст (максимум 100 символов) и статус (0 для закрытого, 1 для открытого) для каждого элемента. Через веб-интерфейс можно просматривать, редактировать открытые элементы и добавлять новые.

Во время разработки все страницы будут доступны только по адресу localhost, но позже будет показано, как адаптировать приложение для «реального» сервера, включая использование с mod_wsgi Apache.

Bottle будет выполнять маршрутизацию и форматировать вывод с помощью шаблонов. Элементы списка будут храниться в базе данных SQLite. Чтение и запись в базу данных будут выполняться кодом Python.

В итоге мы получим приложение со следующими страницами и функциональностью:

  • Главная страница http://localhost:8080/todo
  • Добавление новых элементов в список: http://localhost:8080/new
  • Страница для редактирования элементов: http://localhost:8080/edit/:no
  • валидация данных, присвоенных динамическими маршрутами с помощью декоратора @validate
  • обработка ошибок

Перед началом...

Установка Bottle

Предполагая, что у вас установлена сравнительно новая версия Python (версия 2.5 или выше), вам нужно только установить Bottle помимо этого. Bottle не имеет зависимостей, кроме самого Python.

Вы можете установить Bottle вручную или использовать easy_install Python: easy_install bottle

Дополнительные программные компоненты

Так как мы используем SQLite3 в качестве базы данных, убедитесь, что она установлена. На Linux большинство дистрибутивов имеют SQLite3 по умолчанию. SQLite также доступен для Windows и MacOS X, а модуль sqlite3 является частью стандартной библиотеки Python.

Создание базы данных SQL

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

import sqlite3
con = sqlite3.connect('todo.db') # Warning: This file is created in the current directory
con.execute("CREATE TABLE todo (id INTEGER PRIMARY KEY, task char(100) NOT NULL, status bool NOT NULL)")
con.execute("INSERT INTO todo (task,status) VALUES ('Read A-byte-of-python to get a good introduction into Python',0)")
con.execute("INSERT INTO todo (task,status) VALUES ('Visit the Python website',1)")
con.execute("INSERT INTO todo (task,status) VALUES ('Test various editors for and check the syntax highlighting',1)")
con.execute("INSERT INTO todo (task,status) VALUES ('Choose your favorite WSGI-Framework',0)")
con.commit()

Это создаёт файл базы данных todo.db с таблицами todo и тремя столбцами id, task, и status. id — уникальный идентификатор для каждой строки, который используется позже для ссылки на строки. Столбец task содержит текст, описывающий задачу, его длина может быть максимум 100 символов. Наконец, столбец status используется для обозначения задачи как открытой (значение 1) или закрытой (значение 0).

Использование Bottle для веб-приложения списка дел

Теперь пришло время использовать Bottle для создания веб-приложения. Но сначала нам нужно рассмотреть базовый принцип Bottle: маршруты.

Понимание маршрутов

По существу, каждая страница, видимая в браузере, динамически генерируется при вызове адреса страницы. Таким образом, нет статического контента. Это как раз то, что называется «маршрутом» в Bottle: определённый адрес на сервере. Например, при вызове страницы http://localhost:8080/todo из браузера Bottle «перехватывает» вызов и проверяет, определена ли какая-либо функция (Python) для маршрута «todo». Если да, Bottle выполнит соответствующий Python-код и вернёт результат.

Первый шаг — отображение всех открытых элементов

Итак, после понимания концепции маршрутов, давайте создадим первый. Цель — отобразить все открытые элементы из списка дел:

import sqlite3
from bottle import route, run

@route('/todo')
def todo_list():
    conn = sqlite3.connect('todo.db')
    c = conn.cursor()
    c.execute("SELECT id, task FROM todo WHERE status LIKE '1'")
    result = c.fetchall()
    return str(result)

run()

Сохраните код в todo.py, предпочтительно в том же каталоге, что и файл todo.db. В противном случае вам нужно добавить путь к todo.db в sqlite3.connect().

Давайте посмотрим, что мы только что сделали: мы импортировали необходимый модуль sqlite3 для доступа к базе данных SQLite и из Bottle мы импортировали route и run. run() просто запускает встроенный веб-сервер Bottle. По умолчанию веб-сервер обслуживает страницы по адресу localhost и порту 8080. Кроме того, мы импортировали route, которая является функцией, ответственной за маршрутизацию в Bottle. Как вы можете видеть, мы определили одну функцию todo_list(), содержащую несколько строк кода для чтения из базы данных. Важным моментом является декоратор @route('/todo') перед def todo_list(). Сделав это, мы связываем эту функцию с маршрутом /todo, поэтому каждый раз, когда браузер вызывает http://localhost:8080/todo, Bottle возвращает результат функции todo_list(). Вот как работает маршрутизация в Bottle.

На самом деле вы можете связать более одного маршрута с одной функцией. Следующий код:

@route('/todo')
@route('/my_todo_list')
def todo_list():
    ...

также будет работать. Не будет работать только связывание одного маршрута с более чем одной функцией.

То, что вы увидите в браузере, — это возвращаемое значение, а именно значение return. В этом примере нам нужно преобразовать result в строку с помощью str(), так как Bottle ожидает строку или список строк от инструкции return. Но здесь результат запроса к базе данных — список кортежей, что является стандартным определением, заданным в Python DB API.

Теперь, после понимания небольшого скрипта выше, пришло время его выполнить и посмотреть результат самостоятельно. Помните, что на системах на базе Linux/Unix файлу todo.py необходимо присвоить исполняемые права. Затем просто запустите python todo.py и вызовите страницу http://localhost:8080/todo в браузере. Если вы не допустили ошибок при написании скрипта, вывод должен выглядеть следующим образом:

[(2, u'Visit the Python website'), (3, u'Test various editors for and check the syntax highlighting')]

Если это так — поздравляем! Вы теперь успешно используете Bottle. Если у вас возникли проблемы и вам нужно внести изменения в скрипт, не забудьте остановить Bottle, иначе переработанная версия не будет загружена.

На самом деле вывод не очень интересный и нелегко читаемый. Это необработанный результат, возвращаемый запросом к SQL.

Итак, на следующем шаге мы отформатируем вывод более удобным способом. Но прежде чем мы это сделаем, мы упростим свою работу.

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

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

from bottle import run, route, debug
...
#add this at the very end:
debug(True)
run()

Включив «debug», вы получите полный стек-трейс интерпретатора Python, который обычно содержит полезную информацию для поиска ошибок. Кроме того, шаблоны (см. ниже) не кэшируются, поэтому изменения в шаблонах вступят в силу без остановки сервера.

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

Эта функция debug(True) предназначена только для разработки, её не следует использовать в производственных средах.

Ещё одной полезной функцией является автоматическая перезагрузка, которая включена путём изменения run() на

run(reloader=True)

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

Опять же, функция в основном предназначена для использования во время разработки, а не в производственных системах.

Шаблоны Bottle для форматирования вывода

Теперь давайте посмотрим на преобразование вывода скрипта в правильный формат.

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

Bottle поставляется со своим собственным простым в использовании движком шаблонов. Шаблоны хранятся в отдельных файлах с расширением .tpl. Затем шаблон можно вызвать из функции. Шаблоны могут содержать любой тип текста (вероятно, HTML-разметка, смешанная с Python-выражениями). Кроме того, шаблоны могут принимать аргументы, например, результат набора данных базы данных, который затем будет красиво отформатирован в шаблоне.

Здесь мы преобразуем результат запроса, отображающего открытые элементы списка дел, в простую таблицу с двумя столбцами: первый столбец будет содержать идентификатор элемента, а второй столбец — текст. Как видно выше, результат — список кортежей, каждый кортеж содержит один набор результатов.

Чтобы включить шаблон в наш пример, просто добавьте следующие строки:

from bottle import route, run, debug, template
...
result = c.fetchall()
c.close()
output = template('make_table', rows=result)
return output
...

Итак, мы делаем здесь две вещи: во-первых, импортируем template из Bottle, чтобы иметь возможность использовать шаблоны. Во-вторых, мы присваиваем результат работы шаблона make_table переменной output, которая затем возвращается. В дополнение к вызову шаблона, мы присваиваем значение result, полученное из запроса к базе данных, переменной rows, которая впоследствии используется внутри шаблона. При необходимости вы можете присвоить более одной переменной/значения шаблону.

Шаблоны всегда возвращают список строк, поэтому нет необходимости в преобразовании. Конечно, мы можем сократить одну строку кода, написав return template('make_table', rows=result), что даёт точно такой же результат, как и выше.

Теперь пришло время написать соответствующий шаблон, который выглядит так:

%#template to generate a HTML table from a list of tuples (or list of lists, or tuple of tuples or ...)
<p>The open items are as follows:</p>
<table border="1">
%for row in rows:
  <tr>
  %for col in row:
    <td>{{col}}</td>
  %end
  </tr>
%end
</table>

Сохраните код как make_table.tpl в той же директории, где хранится todo.py.

Давайте посмотрим на код: каждая строка, начинающаяся с %, интерпретируется как код Python. Обратите внимание, что, конечно, допускаются только допустимые операторы Python, в противном случае шаблон сгенерирует исключение, как и любой другой код Python. Другие строки представляют собой обычную разметку HTML.

Как видите, мы дважды используем оператор Python for для обработки rows. Как показано выше, rows — это переменная, которая хранит результат запроса к базе данных, поэтому это список кортежей. Первый оператор for обращается к кортежам в списке, второй — к элементам внутри кортежа, которые помещаются в ячейку таблицы. Важно, чтобы вы закрывали все операторы for, if, while и т. д. оператором %end, иначе вывод может не соответствовать ожиданиям.

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

Запустите скрипт снова и посмотрите на вывод. Всё ещё не очень красиво, но, по крайней мере, более читабельно, чем список кортежей. Конечно, вы можете улучшить простую HTML-разметку выше, например, используя встроенные стили для получения более привлекательного вывода.

Использование значений GET и POST

Поскольку мы можем корректно просмотреть все открытые задачи, переходим к следующему шагу, который заключается в добавлении новых задач в список задач. Новая задача должна поступать из обычной HTML-формы, которая отправляет данные с помощью метода GET.

Для этого мы сначала добавляем новый маршрут в наш скрипт и указываем маршруту, что он должен получить данные GET:

from bottle import route, run, debug, template, request
...
return template('make_table', rows=result)
...

@route('/new', method='GET')
def new_item():

    new = request.GET.get('task', '').strip()

    conn = sqlite3.connect('todo.db')
    c = conn.cursor()

    c.execute("INSERT INTO todo (task,status) VALUES (?,?)", (new,1))
    new_id = c.lastrowid

    conn.commit()
    c.close()

    return '<p>The new task was inserted into the database, the ID is %s</p>' % new_id

Чтобы получить доступ к данным GET (или POST), нам нужно импортировать request из Bottle. Для присвоения фактических данных переменной мы используем оператор request.GET.get('task','').strip(), где task — это имя данных GET, к которому мы хотим получить доступ. Это всё. Если ваши данные GET содержат более одной переменной, можно использовать несколько операторов request.GET.get() и присвоить их другим переменным.

Остальная часть этого кода — это просто обработка полученных данных: запись в базу данных, извлечение соответствующего идентификатора из базы данных и генерация вывода.

Но откуда мы получаем данные GET? Ну, мы можем использовать статическую HTML-страницу, содержащую форму. Или, что мы делаем прямо сейчас, используем шаблон, который выводится при вызове маршрута /new без данных GET.

Код необходимо расширить до:

...
@route('/new', method='GET')
def new_item():

    if request.GET.get('save','').strip():

        new = request.GET.get('task', '').strip()
        conn = sqlite3.connect('todo.db')
        c = conn.cursor()

        c.execute("INSERT INTO todo (task,status) VALUES (?,?)", (new,1))
        new_id = c.lastrowid

        conn.commit()
        c.close()

        return '<p>The new task was inserted into the database, the ID is %s</p>' % new_id
    else:
        return template('new_task.tpl')

new_task.tpl выглядит так:

<p>Add a new task to the ToDo list:</p>
<form action="/new" method="GET">
<input type="text" size="100" maxlength="100" name="task">
<input type="submit" name="save" value="save">
</form>

Это всё. Как видите, на этот раз шаблон — обычный HTML.

Теперь мы можем расширить наш список задач.

Кстати, если вы предпочитаете использовать данные POST, это работает точно так же, просто используйте request.POST.get() вместо GET.

Редактирование существующих задач

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

Используя только известные нам маршруты, это возможно, но может быть довольно сложно. Но Bottle знает о «динамических маршрутах», которые делают эту задачу довольно простой.

Основной оператор для динамического маршрута выглядит так:

@route('/myroute/:something')

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

Для нашего списка задач мы создадим маршрут @route('/edit/:no), где no — это идентификатор редактируемой задачи.

Код выглядит так:

@route('/edit/:no', method='GET')
def edit_item(no):

    if request.GET.get('save','').strip():
        edit = request.GET.get('task','').strip()
        status = request.GET.get('status','').strip()

        if status == 'open':
            status = 1
        else:
            status = 0

        conn = sqlite3.connect('todo.db')
        c = conn.cursor()
        c.execute("UPDATE todo SET task = ?, status = ? WHERE id LIKE ?", (edit, status, no))
        conn.commit()

        return '<p>The item number %s was successfully updated</p>' % no
    else:
        conn = sqlite3.connect('todo.db')
        c = conn.cursor()
        c.execute("SELECT task FROM todo WHERE id LIKE ?", (str(no)))
        cur_data = c.fetchone()

        return template('edit_task', old=cur_data, no=no)

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

Шаблон edit_task.tpl, вызываемый внутри функции, выглядит так:

%#template for editing a task
%#the template expects to receive a value for "no" as well a "old", the text of the selected ToDo item
<p>Edit the task with ID = {{no}}</p>
<form action="/edit/{{no}}" method="get">
<input type="text" name="task" value="{{old[0]}}" size="100" maxlength="100">
<select name="status">
<option>open</option>
<option>closed</option>
</select>
<br/>
<input type="submit" name="save" value="save">
</form>

Опять же, этот шаблон — это смесь операторов Python и HTML, как уже объяснялось выше.

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

Проверка динамических маршрутов

Использование динамических маршрутов нормально, но во многих случаях имеет смысл проверить динамическую часть маршрута. Например, мы ожидаем целое число в нашем маршруте для редактирования выше. Но если приходят дробные числа, символы и т. д., интерпретатор Python генерирует исключение, чего мы не хотим.

В таких случаях Bottle предлагает декоратор @validate, который проверяет «входные данные» перед передачей их функции. Чтобы применить валидатор, расширьте код следующим образом:

from bottle import route, run, debug, template, request, validate
...
@route('/edit/:no', method='GET')
@validate(no=int)
def edit_item(no):
...

Сначала мы импортировали validate из фреймворка Bottle, а затем применили декоратор @validate. Здесь мы проверяем, является ли no целым числом. В принципе, проверка работает со всеми типами данных, такими как дробные числа, списки и т. д.

Сохраните код и вызовите страницу ещё раз, используя значение «403 запрещено» для :no, например, дробное число. Вы получите не исключение, а ошибку «403 — Запрещено», говорящую о том, что ожидалось целое число.

Динамические маршруты с использованием регулярных выражений

Bottle также может обрабатывать динамические маршруты, где «динамическая часть» маршрута может быть регулярным выражением.

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

Как сказано выше, решение — регулярное выражение:

@route('/item:item#[0-9]+#')
def show_item(item):
    conn = sqlite3.connect('todo.db')
    c = conn.cursor()
    c.execute("SELECT task FROM todo WHERE id LIKE ?", (item))
    result = c.fetchall()
    c.close()
    if not result:
        return 'This item number does not exist!'
    else:
        return 'Task: %s' %result[0]

Конечно, этот пример несколько искусственно построен — было бы проще использовать только обычный динамический маршрут в сочетании с проверкой. Тем не менее, мы хотим увидеть, как работают маршруты с регулярными выражениями: строка @route(/item:item_#[0-9]+#) начинается как обычный маршрут, но часть, окружённая символами #, интерпретируется как регулярное выражение, которое является динамической частью маршрута. В данном случае мы хотим сопоставить любую цифру от 0 до 9. Функция «show_item» просто проверяет, присутствует ли заданная задача в базе данных или нет. В случае наличия возвращается соответствующий текст задачи. Как вы видите, передаётся только часть маршрута с регулярным выражением. Кроме того, она всегда передаётся как строка, даже если это просто целое число, как в данном случае.

Возврат статических файлов

Иногда может потребоваться связать маршрут не с функцией Python, а просто вернуть статический файл. Например, если у вас есть страница справки для вашего приложения, вы можете вернуть эту страницу как обычный HTML. Это работает следующим образом:

from bottle import route, run, debug, template, request, validate, static_file

@route('/help')
def help():
    return static_file('help.html', root='/path/to/file')

Сначала нам нужно импортировать функцию static_file из Bottle. Как видите, оператор return static_file заменяет оператор return. Он принимает как минимум два аргумента: имя возвращаемого файла и путь к файлу. Даже если файл находится в той же директории, что и ваше приложение, путь должен быть указан. Но в этом случае вы также можете использовать '.' в качестве пути. Bottle автоматически определяет MIME-тип файла, но в случае необходимости явно указать его, добавьте третий аргумент к static_file, который в данном случае был бы mimetype='text/html'. static_file работает с любым типом маршрута, включая динамические.

Возврат данных JSON

Возможны случаи, когда вы не хотите, чтобы ваше приложение генерировало вывод напрямую, а хотите вернуть данные для дальнейшей обработки, например, JavaScript. В таких случаях Bottle предлагает возможность возвращать JSON-объекты, что является своего рода стандартным способом обмена данными между веб-приложениями. Кроме того, JSON может обрабатываться многими языками программирования, включая Python

Предположим, что мы хотим вернуть данные, сгенерированные в примере маршрута с регулярным выражением, как JSON-объект. Код выглядит так:

@route('/json:json#[0-9]+#')
def show_json(json):
    conn = sqlite3.connect('todo.db')
    c = conn.cursor()
    c.execute("SELECT task FROM todo WHERE id LIKE ?", (json))
    result = c.fetchall()
    c.close()

    if not result:
        return {'task':'This item number does not exist!'}
    else:
        return {'Task': result[0]}

Как видите, это довольно просто: просто верните обычный словарь Python, и Bottle автоматически преобразует его в JSON-объект перед отправкой. Итак, если вы, например, вызовете «http://localhost/json1», Bottle в этом случае должен вернуть JSON-объект {"Task": ["Read A-byte-of-python to get a good introduction into Python"]}.

Обработка ошибок

Следующий шаг может заключаться в перехвате ошибки с помощью самого Bottle, чтобы не показывать пользователю приложения любые сообщения об ошибках. Для этого Bottle имеет «маршрут ошибок», который можно назначить HTML-ошибке.

В нашем случае мы хотим перехватить ошибку 403. Код выглядит следующим образом:

from bottle import error

@error(403)
def mistake(code):
    return 'The parameter you passed has the wrong format!'

Итак, сначала нам нужно импортировать error из Bottle и определить маршрут с помощью error(403), который перехватывает все ошибки «403 запрещено». Функция «mistake» назначена этому маршруту. Обратите внимание, что error() всегда передаёт код ошибки в функцию — даже если вам это не нужно. Таким образом, функция всегда должна принимать один аргумент, иначе она не будет работать.

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

@error(404)
@error(403)
def mistake(code):
    return 'There is something wrong!'

работает нормально, как и следующий:

@error(403)
def mistake403(code):
    return 'The parameter you passed has the wrong format!'

@error(404)
def mistake404(code):
    return 'Sorry, this page does not exist!'

Заключение

После ознакомления со всеми разделами выше у вас должно быть общее представление о том, как работает фреймворк Bottle WSGI. Кроме того, у вас есть все знания, необходимые для использования Bottle в ваших приложениях.

Следующая глава даёт краткое введение в адаптацию Bottle для больших проектов. Кроме того, мы покажем, как запустить Bottle с веб-серверами, которые работают лучше при высокой нагрузке/большом объёме веб-трафика, чем тот, который мы использовали до сих пор.

Настройка сервера

До сих пор мы использовали стандартный сервер, используемый Bottle, который представляет собой сервер WSGI, поставляемый вместе с Python. Хотя этот сервер идеально подходит для целей разработки, он не очень подходит для больших приложений. Но прежде чем мы рассмотрим альтернативы, давайте сначала посмотрим, как настроить параметры стандартного сервера.

Запуск Bottle на другом порту и IP-адресе

По умолчанию Bottle предоставляет страницы по IP-адресу 127.0.0.1, также известному как localhost, и на порту 8080. Изменить настройки довольно просто, так как дополнительные параметры могут быть переданы в функцию run() Bottle для изменения порта и адреса.

Чтобы изменить порт, просто добавьте port=portnumber к команде run. Например:

run(port=80)

это заставит Bottle прослушивать порт 80.

Чтобы изменить IP-адрес, на котором Bottle прослушивает:

run(host='123.45.67.89')

Конечно, оба параметра можно комбинировать, например:

run(port=80, host='123.45.67.89')

Параметр port и host также могут быть применены, когда Bottle работает с другим сервером, как показано в следующем разделе.

Запуск Bottle с другим сервером

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

Но у Bottle уже есть различные адаптеры для многопоточных серверов, которые работают лучше при высокой нагрузке. Bottle поддерживает Cherrypy, Fapws3, Flup и Paste.

Если вы хотите запустить, например, Bottle с сервером Paste, используйте следующий код:

from bottle import PasteServer
...
run(server=PasteServer)

Это работает точно так же с FlupServer, CherryPyServer и FapwsServer.

Запуск Bottle на Apache с mod_wsgi

Возможно, у вас уже есть Apache, или вы хотите запустить приложение на основе Bottle в масштабе - тогда пришло время подумать об Apache с mod_wsgi.

Мы предполагаем, что ваш сервер Apache работает и mod_wsgi тоже работает нормально. Во многих дистрибутивах Linux mod_wsgi можно легко установить с помощью любой системы управления пакетами.

Bottle предоставляет адаптер для mod_wsgi, поэтому предоставление вашего приложения — простая задача.

В следующем примере мы предполагаем, что вы хотите сделать доступным ваше приложение «Список дел» через http://www.mypage.com/todo и ваш код, шаблоны и базу данных SQLite хранятся в пути /var/www/todo.

Когда вы запускаете свое приложение через mod_wsgi, необходимо удалить инструкцию run() из вашего кода, иначе она не сработает здесь.

После этого создайте файл с именем adapter.wsgi со следующим содержимым:

import sys, os, bottle

sys.path = ['/var/www/todo/'] + sys.path
os.chdir(os.path.dirname(__file__))

import todo # This loads your application

application = bottle.default_app()

и сохраните его в том же пути, /var/www/todo. Фактически, имя файла может быть любым, при условии, что расширение — .wsgi. Имя используется только для ссылки на файл из вашего виртуального хоста.

Наконец, нам нужно добавить виртуальный хост в конфигурацию Apache, который выглядит следующим образом:

<VirtualHost *>
    ServerName mypage.com

    WSGIDaemonProcess todo user=www-data group=www-data processes=1 threads=5
    WSGIScriptAlias / /var/www/todo/adapter.wsgi

    <Directory /var/www/todo>
        WSGIProcessGroup todo
        WSGIApplicationGroup %{GLOBAL}
        Order deny,allow
        Allow from all
    </Directory>
</VirtualHost>

После перезапуска сервера ваш список дел должен быть доступен по адресу http://www.mypage.com/todo

Заключение

Теперь мы подошли к концу этого введения и учебника по Bottle. Мы изучили основные понятия Bottle и написали первое приложение с использованием фреймворка Bottle. Кроме того, мы увидели, как адаптировать Bottle для больших задач и предоставить Bottle через веб-сервер Apache с mod_wsgi.

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

Полный пример

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

Основной код приложения todo.py:

import sqlite3
from bottle import route, run, debug, template, request, validate, static_file, error

# only needed when you run Bottle on mod_wsgi
from bottle import default_app

@route('/todo')
def todo_list():

    conn = sqlite3.connect('todo.db')
    c = conn.cursor()
    c.execute("SELECT id, task FROM todo WHERE status LIKE '1';")
    result = c.fetchall()
    c.close()

    output = template('make_table', rows=result)
    return output

@route('/new', method='GET')
def new_item():

    if request.GET.get('save','').strip():

        new = request.GET.get('task', '').strip()
        conn = sqlite3.connect('todo.db')
        c = conn.cursor()

        c.execute("INSERT INTO todo (task,status) VALUES (?,?)", (new,1))
        new_id = c.lastrowid

        conn.commit()
        c.close()

        return '<p>The new task was inserted into the database, the ID is %s</p>' % new_id

    else:
        return template('new_task.tpl')

@route('/edit/:no', method='GET')
@validate(no=int)
def edit_item(no):

    if request.GET.get('save','').strip():
        edit = request.GET.get('task','').strip()
        status = request.GET.get('status','').strip()

        if status == 'open':
            status = 1
        else:
            status = 0

        conn = sqlite3.connect('todo.db')
        c = conn.cursor()
        c.execute("UPDATE todo SET task = ?, status = ? WHERE id LIKE ?", (edit,status,no))
        conn.commit()

        return '<p>The item number %s was successfully updated</p>' %no

    else:
        conn = sqlite3.connect('todo.db')
        c = conn.cursor()
        c.execute("SELECT task FROM todo WHERE id LIKE ?", (str(no)))
        cur_data = c.fetchone()

        return template('edit_task', old = cur_data, no = no)

@route('/item:item#[0-9]+#')
def show_item(item):

        conn = sqlite3.connect('todo.db')
        c = conn.cursor()
        c.execute("SELECT task FROM todo WHERE id LIKE ?", (item))
        result = c.fetchall()
        c.close()

        if not result:
            return 'This item number does not exist!'
        else:
            return 'Task: %s' %result[0]

@route('/help')
def help():

    static_file('help.html', root='.')

@route('/json:json#[0-9]+#')
def show_json(json):

    conn = sqlite3.connect('todo.db')
    c = conn.cursor()
    c.execute("SELECT task FROM todo WHERE id LIKE ?", (json))
    result = c.fetchall()
    c.close()

    if not result:
        return {'task':'This item number does not exist!'}
    else:
        return {'Task': result[0]}


@error(403)
def mistake403(code):
    return 'There is a mistake in your url!'

@error(404)
def mistake404(code):
    return 'Sorry, this page does not exist!'


debug(True)
run(reloader=True)
#remember to remove reloader=True and debug(True) when you move your application from development to a productive environment

Шаблон make_table.tpl:

%#template to generate a HTML table from a list of tuples (or list of lists, or tuple of tuples or ...)
<p>The open items are as follows:</p>
<table border="1">
%for row in rows:
  <tr>
  %for col in row:
    <td>{{col}}</td>
  %end
  </tr>
%end
</table>

Шаблон edit_task.tpl:

%#template for editing a task
%#the template expects to receive a value for "no" as well a "old", the text of the selected ToDo item
<p>Edit the task with ID = {{no}}</p>
<form action="/edit/{{no}}" method="get">
<input type="text" name="task" value="{{old[0]}}" size="100" maxlength="100">
<select name="status">
<option>open</option>
<option>closed</option>
</select>
<br/>
<input type="submit" name="save" value="save">
</form>

Шаблон new_task.tpl:

%#template for the form for a new task
<p>Add a new task to the ToDo list:</p>
<form action="/new" method="GET">
<input type="text" size="100" maxlength="100" name="task">
<input type="submit" name="save" value="save">
</form>

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

Spec-Zone.ru

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