Учебник: Приложение для списка задач (ToDo)
Примечание
Этот учебник находится в процессе разработки и написан noisefloor.
Этот учебник должен дать краткое введение в фреймворк WSGI Bottle. Основная цель — научиться создавать проект с помощью Bottle после прочтения этого учебника. В данном документе не будут показаны все возможности, но по крайней мере основные и важные, такие как маршрутизация, использование возможностей шаблонизации Bottle для форматирования вывода и обработка параметров GET/POST.
Для понимания содержимого здесь не требуется базовое знание WSGI, так как Bottle старается изолировать WSGI от пользователя. Вам должно быть хорошо знакомо программирование на языке Python. Кроме того, пример, используемый в учебнике, извлекает и сохраняет данные в базе данных SQL, поэтому базовое представление о SQL поможет, но не является обязательным для понимания концепций Bottle. Здесь используется SQLite. Вывод Bottle, отправляемый в браузер, в некоторых примерах форматируется с помощью HTML. Таким образом, базовое понимание основных тегов HTML также полезно.
Для введения в Bottle код Python «между» удерживается коротким, чтобы сохранить фокус. Также весь код в учебнике работает нормально, но вы необязательно можете использовать его «в реальном мире», например, на публичном веб-сервере. Для этого вы можете добавить, например, больше обработки ошибок, защитить базу данных паролем, протестировать и экранировать входные данные.
Содержание
Цели
В конце этого учебника у нас будет простой веб-приложение для списка задач (ToDo). Список содержит текст (максимум 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 для веб-приложения списка задач (ToDo)
Теперь пришло время представить Bottle для создания веб-приложения. Но сначала нам нужно взглянуть на основную концепцию Bottle: маршруты.
Понимание маршрутов
По сути, каждая страница, видимая в браузере, динамически генерируется при вызове адреса страницы. Таким образом, нет статического содержимого. Именно это называется «маршрутом» в Bottle: определенный адрес на сервере. Например, при вызове страницы http://localhost:8080/todo из браузера Bottle «захватывает» вызов и проверяет, существует ли какая-либо (Python) функция, определенная для маршрута «todo». Если да, Bottle выполнит соответствующий код Python и вернёт результат.
Первый шаг — отображение всех открытых задач
Итак, после понимания концепции маршрутов давайте создадим первый маршрут. Цель — отобразить все открытые задачи из списка задач (ToDo):
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 ожидает строки или список строк от оператора возврата. Но здесь результат запроса к базе данных — это список кортежей, который соответствует стандарту, определённому в 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, обслуживающий страницу, иначе переработанная версия не будет загружена.
На самом деле вывод не очень интересный и не удобный для чтения. Это сырой результат, возвращаемый из запроса к базе данных.
Итак, на следующем шаге мы отформатируем вывод более красивым способом. Но прежде чем мы это сделаем, упростим свою работу.
Отладка и автоматическая перезагрузка
Возможно, вы уже заметили, что 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). Кроме того, шаблоны могут принимать аргументы, например, результат набора данных из запроса к базе данных, который затем будет красиво отформатирован в шаблоне.
Здесь мы преобразуем результат нашего запроса, показывающего открытые задачи ToDo, в простую таблицу с двумя столбцами: первый столбец будет содержать идентификатор задачи, а второй — текст.
Чтобы включить шаблон в наш пример, добавьте следующие строки:
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() вместо этого.
Редактирование существующих задач
Последним пунктом является возможность редактирования существующих задач.
Используя только известные нам маршруты, это возможно, но может быть довольно сложно. Но 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 также может обрабатывать динамические маршруты, где «динамическая часть» маршрута может быть регулярным выражением.
Итак, просто для демонстрации предположим, что все отдельные задачи в нашем списке задач должны быть доступны по их номеру, например, как «item1». По понятным причинам вы не хотите создавать маршрут для каждой задачи. Кроме того, простые динамические маршруты также не работают, поскольку часть маршрута — термин «item» — статична.
Как сказано выше, решение — регулярное выражение:
@route('/item:item#[1-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_#[1-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#[1-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(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#[1-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#[1-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.11/tutorial_app.html