REST API с помощью Bottle Framework: учебное пособие

  1. Установка и настройка
  2. Монтаж
  3. сервер
  4. База данных
  5. Основы бутылочного каркаса
  6. Приложения в бутылке
  7. Примечание о структуре файла
  8. Создание вашего REST API
  9. маршрутизация
  10. POST: создание ресурсов
  11. GET: список ресурсов
  12. DELETE: удаление ресурсов
  13. Последний шаг: активация API
  14. Бонус: кросс-ресурсное распределение ресурсов (CORS)
  15. Заворачивать

API REST стали распространенным способом установления интерфейса между веб бэкендов а также передние концы и между различными веб-сервисами. Простота такого рода интерфейса и повсеместная поддержка протоколов HTTP и HTTPS в разных сетях и средах делают его легким выбором при рассмотрении вопросов совместимости.

бутылка минималистичный веб-фреймворк Python Это легкий, быстрый и простой в использовании инструмент, который хорошо подходит для создания сервисов RESTful. голое сравнение сделано в Андрей Корнацкий поместите его в тройку лучших по времени отклика и пропускной способности (количество запросов в секунду). В своих собственных тестах на виртуальных серверах, доступных от DigitalOcean, я обнаружил, что комбинация стека серверов uWSGI и Bottle может достигать всего лишь 140 мкс на запрос.

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

В этой статье я предоставлю пошаговое руководство по созданию службы API RESTful с использованием Bottle

Установка и настройка

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

Гибкость Bottle делает подробное описание настройки платформы немного бесполезной, поскольку она может не отражать ваш собственный стек. Тем не менее, краткий обзор параметров и того, где можно узнать больше о том, как их настроить, уместен здесь:

Монтаж

Установка Bottle так же проста, как и установка любого другого пакета Python. Ваши варианты:

  • Установите в своей системе, используя менеджер пакетов системы. Debian Jessie (текущая стабильная версия) упаковывает версию 0.12 как python-bottle .
  • Установите в своей системе, используя индекс пакетов Python с бутылкой установки pip.
  • Установка в виртуальной среде (рекомендуется).

Чтобы установить Bottle в виртуальной среде, вам понадобятся инструменты virtualenv и pip . Чтобы установить их, пожалуйста, обратитесь к virtualenv а также зернышко документация, хотя они, вероятно, уже есть в вашей системе.

В Bash создайте среду с Python 3:

$ virtualenv -p `which python3` env

Подавление параметра -p `which python3` приведет к установке стандартного интерпретатора Python, присутствующего в системе - обычно Python 2.7. Python 2.7 поддерживается, но этот учебник предполагает Python 3.4.

Теперь активируйте среду и установите Bottle:

$. env / bin / activ $ pip установить бутылку

Вот и все. Бутылка установлена ​​и готова к использованию. Если вы не знакомы с virtualenv или pip , их документация на высшем уровне. Взглянуть! Они того стоят.

сервер

Бутылка соответствует стандарту Python Интерфейс шлюза веб-сервера (WSGI) Это означает, что он может использоваться с любым WSGI-совместимым сервером. Это включает uWSGI , Торнадо , Gunicorn , апаш , Amazon Beanstalk , Google App Engine , и другие.

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

Чтобы узнать больше о том, как настроить свой сервер, обратитесь к документации по серверу и к документации по бутылке, Вот ,

База данных

Бутылка не зависит от базы данных, и ей все равно, откуда поступают данные. Если вы хотите использовать базу данных в своем приложении, Индекс пакета Python имеет несколько интересных опций, например SQLAlchemy , PyMongo , MongoEngine , CouchDB а также Boto для DynamoDB. Вам нужен только соответствующий адаптер, чтобы он работал с выбранной вами базой данных.

Основы бутылочного каркаса

Теперь давайте посмотрим, как сделать простое приложение в бутылке. Для примеров кода я буду предполагать Python> = 3.4. Однако большая часть того, что я напишу здесь, будет работать и на Python 2.7.

Базовое приложение в Bottle выглядит так:

импортировать бутылку app = application = bottle.default_app (), если __name__ == '__main__': bottle.run (host = '127.0.0.1', port = 8000)

Когда я говорю «основной», я имею в виду, что эта программа даже не «Привет, мир». (Когда вы в последний раз обращались к интерфейсу REST, который ответил «Hello World?») Все HTTP-запросы к 127.0.0.1:8000 получат статус ответа 404 Not Found.

Приложения в бутылке

В Bottle может быть создано несколько экземпляров приложений, но для удобства первый экземпляр создан для вас; это приложение по умолчанию. Бутылка хранит эти экземпляры в стеке внутри модуля. Всякий раз, когда вы что-то делаете с Bottle (например, запускаете приложение или присоединяете маршрут) и не указываете, о каком приложении вы говорите, это относится к приложению по умолчанию. Фактически, строка app = application = bottle.default_app () даже не должна существовать в этом базовом приложении, но она есть, чтобы мы могли легко вызывать приложение по умолчанию с помощью Gunicorn, uWSGI или некоторого универсального сервера WSGI.

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

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

bottle.run (сервер = 'gunicorn', хост = '127.0.0.1', порт = 8000)

Это синтаксический сахар, который позволяет запустить приложение, запустив этот скрипт. Например, если этот файл называется main.py, вы можете просто запустить python main.py, чтобы запустить приложение. Бутылка несет довольно обширный список серверных адаптеров это можно использовать таким образом.

Некоторые WSGI-серверы не имеют бутылочных адаптеров. Их можно запустить с помощью собственных команд запуска сервера. Например, в uWSGI все, что вам нужно сделать, это вызвать uwsgi следующим образом:

$ uwsgi --http: 8000 --wsgi-file main.py

Примечание о структуре файла

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

Создание вашего REST API

Конечно, никому не нужен сервер, который возвращает только 404 для каждого запрошенного URI. Я обещал вам, что мы создадим REST API, так что давайте сделаем это.

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

Скелет нашего API может выглядеть следующим образом. Вы можете разместить этот код в любом месте проекта, но моя рекомендация будет отдельным файлом API, например, api / names.py.

из запроса на импорт из бутылки, ответа от сообщения об импорте из бутылки, получения, установки, удаления _names = set () # набор имен @post ('/ names') def creation_handler (): '' 'Обрабатывает создание имени' '' pass @ get ('/ names') def перечисление_handler (): '' 'Обрабатывает список имен' '' pass @put ('/ names / <name>') def update_handler (name): '' 'Обрабатывает обновления имени' '' pass @delete ('/ names / <name>') def delete_handler (name): '' 'Обрабатывает удаление имени' '' pass

маршрутизация

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

  • Все вышеперечисленные декораторы являются ярлыком для декораторов маршрутизации default_app. Например, декоратор @get () применяет bottle.default_app (). Get () к обработчику.
  • Все методы маршрутизации в default_app являются ярлыками для route (). Так что default_app (). Get ('/') эквивалентно default_app (). Route (method = 'GET', '/').

Таким образом, @get ('/') - это то же самое, что и @route (method = 'GET', '/'), что совпадает с @ bottle.default_app (). Route (method = 'GET', '/'). и они могут быть использованы взаимозаменяемо.

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

@route ('/ names / <name>', method = ['PUT', 'DELETE']) def update_delete_handler (name): '' 'Обрабатывает обновления и удаления имен' '' pass

Хорошо, давайте реализуем некоторые из этих обработчиков.

Хорошо, давайте реализуем некоторые из этих обработчиков

API RESTful являются одним из основных элементов современной веб-разработки. Подарите своим клиентам API мощную смесь с бэк-эндом из бутылки.

POST: создание ресурсов

Наш обработчик POST может выглядеть так:

import re, json namepattern = re.compile (r '^ [a-zA-Z \ d] {1,64} $') @post ('/ names') def creation_handler (): '' 'Обрабатывает создание имени' '' try: # парсинг входных данных try: data = request.json () кроме: поднять ValueError, если данных нет: поднять ValueError # извлечь и проверить имя try: если namepattern.match (data ['name']) равен None: поднять ValueError name = data ['name'] кроме (TypeError, KeyError): поднять ValueError # проверить существование, если имя в _names: поднять KeyError кроме ValueError: # если данные неверного запроса, вернуть 400 Bad Request response.status = 400 return кроме KeyError: # если имя уже существует, вернуть 409 Conflict response.status = 409 return # добавить имя _names.add (name) # return 200 Success response.headers ['Content-Type'] = 'application / json' вернуть json.dumps ({'name': name})

Ну, это довольно много. Давайте рассмотрим эти шаги по частям.

Разбор тела

Этот API требует, чтобы пользователь поместил в тело строку JSON с атрибутом с именем «name».

Объект запроса, импортированный ранее из бутылки, всегда указывает на текущий запрос и содержит все данные запроса. Его атрибут body содержит поток байтов тела запроса, доступ к которому может получить любая функция, способная прочитать объект потока (например, чтение файла).

Метод request.json () проверяет заголовки запроса на тип содержимого «application / json» и анализирует тело, если оно правильное. Если Bottle обнаруживает искаженное тело (например, пустое или с неправильным типом содержимого), этот метод возвращает None, и, таким образом, мы вызываем ValueError. Если искаженное содержимое JSON обнаружено анализатором JSON; это вызывает исключение, которое мы перехватываем и повторно вызываем, снова как ValueError.

Анализ и проверка объектов

Если ошибок нет, мы преобразовали тело запроса в объект Python, на который ссылается переменная данных. Если мы получили словарь с ключом «name», мы сможем получить к нему доступ через data ['name']. Если мы получили словарь без этого ключа, попытка получить к нему доступ приведет к исключению KeyError. Если мы получили что-то кроме словаря, мы получим исключение TypeError. Если возникает какая-либо из этих ошибок, мы снова переоцениваем ее как ValueError, что указывает на неверный ввод.

Чтобы проверить, имеет ли ключ имени правильный формат, мы должны проверить его на соответствие маске регулярных выражений, такой как маска имени шаблона, которую мы создали здесь. Если имя ключа не является строкой, namepattern.match () вызовет ошибку TypeError, а если она не совпадает, то вернет None.

С маской в ​​этом примере имя должно быть буквенно-цифровым ASCII без пробелов от 1 до 64 символов. Это простая проверка и, например, она не проверяет объект с мусорными данными. Более сложная и полная проверка может быть достигнута с помощью таких инструментов, как FormEncode ,

Тестирование на Существование

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

Мы сообщаем о существовании имени, вызывая KeyError.

Ответы об ошибках

Так же, как объект запроса содержит все данные запроса, объект ответа делает то же самое для данных ответа. Есть два способа установить статус ответа:

response.status = 400

а также:

response.status = '400 Bad Request'

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

Ответ успеха

Если все шаги выполнены успешно, мы выполняем запрос, добавляя имя в набор _names, устанавливая заголовок ответа Content-Type и возвращая ответ. Любая строка, возвращаемая функцией, будет обрабатываться как тело ответа 200 Success, поэтому мы просто сгенерируем его с помощью json.dumps.

GET: список ресурсов

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

@get ('/ names') def перечисление_handler (): '' 'Обработка списка имен' '' response.headers ['Content-Type'] = 'application / json' response.headers ['Cache-Control'] = ' no-cache 'return json.dumps ({' names ': list (_names)})

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

Теперь давайте посмотрим, как реализовать метод обновления. Он не очень отличается от метода create, но мы используем этот пример для введения параметров URI.

@put ('/ names / <oldname>') def update_handler (name): '' 'Обрабатывает обновления имени' '' try: # анализирует входные данные try: data = json.load (utf8reader (request.body)), кроме: поднять ValueError # извлечь и проверить новое имя try: если namepattern.match (data ['name']) равен None: повысить ValueError newname = data ['name'] кроме (TypeError, KeyError): повысить ValueError # проверить, существует ли обновленное имя если старое имя отсутствует в _names: поднять KeyError (404) # проверить, существует ли новое имя, если имя в _names: поднять KeyError (409), за исключением ValueError: response.status = 400 вернуть, кроме KeyError, как e: response.status = e.args [0 ] return # добавить новое имя и удалить старое имя _names.remove (старое имя) _names.add (новое имя) # return 200 Success response.headers ['Content-Type'] = 'application / json' return json.dumps ({'name ': новое имя})

Схема тела для действия обновления такая же, как и для действия создания, но теперь у нас также есть новый параметр oldname в URI, как определено в маршруте @put ('/ names / <oldname>').

Параметры URI

Как видите, нотация бутылки для параметров URI очень проста. Вы можете создавать URI с таким количеством параметров, сколько захотите. Bottle автоматически извлекает их из URI и передает их обработчику запросов:

Обработчик @get ('/ <param1> / <param2>') def (param1, param2): pass

Используя каскадные декораторы маршрутов, вы можете создавать URI с необязательными параметрами:

@get ('/ <param1>') @get ('/ <param1> / <param2>') обработчик def (param1, param2 = None) pass

Кроме того, в бутылке предусмотрены следующие фильтры маршрутизации в URI:

Соответствует только параметрам, которые могут быть преобразованы в int, и передает преобразованное значение обработчику:

@get ('/ <param: int>') обработчик def (param): pass

То же, что int, но со значениями с плавающей запятой:

@get ('/ <param: float>') обработчик def (param): pass

Соответствует только параметрам, которые соответствуют данному регулярному выражению:

@get ('/ <param: re: ^ [az] + $>') обработчик def (param): pass

Сопоставляет подсегменты пути URI гибким способом:

@get ('/ <param: path> / id>') обработчик def (param): pass

Матчи:

  • / х / id, передавая х в качестве параметра.
  • / x / y / id, передавая x / y в качестве параметра.

DELETE: удаление ресурсов

Как и метод GET, метод DELETE приносит нам мало новостей. Просто отметьте, что при возврате None без установки состояния возвращается ответ с пустым телом и кодом состояния 200.

@delete ('/ names / <name>') def delete_handler (name): '' 'Обрабатывает обновления имени' '' try: # Проверить, существует ли имя, если имя отсутствует в _names: поднять KeyError за исключением KeyError: response.status = 404 return # Удалить имя _names.remove (имя) return

Последний шаг: активация API

Предположим, что мы сохранили наш API имен как api / names.py, теперь мы можем включить эти маршруты в главном файле приложения main.py.

импортировать бутылку из API-интерфейса импортировать имена app = application = bottle.default_app (), если __name__ == '__main__': bottle.run (host = '127.0.0.1', port = 8000)

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

Вы можете использовать такие инструменты, как Curl или Postman, чтобы использовать API и протестировать его вручную. (Если вы используете Curl, вы можете использовать JSON форматер чтобы ответ выглядел менее загроможденным.)

Бонус: кросс-ресурсное распределение ресурсов (CORS)

Одной из распространенных причин создания REST API является взаимодействие с клиентским интерфейсом JavaScript через AJAX. Для некоторых приложений эти запросы должны разрешаться из любого домена, а не только из домашнего домена вашего API. По умолчанию большинство браузеров запрещают такое поведение, поэтому позвольте мне показать вам, как настроить совместное использование ресурсов (CORS) в бутылке, чтобы позволить это:

из ловушки импорта бутылки, маршрута, ответа _allow_origin = '*' _allow_methods = 'PUT, GET, POST, DELETE, OPTIONS' _allow_headers = 'Авторизация, Происхождение, Принять, Тип контента, X-Requested-With' @hook ('after_request ') def enable_cors ():' '' Добавить заголовки для включения CORS '' 'response.headers [' Access-Control-Allow-Origin '] = _allow_origin response.headers [' Access-Control-Allow-Methods '] = _allow_methods response.headers ['Access-Control-Allow-Headers'] = _allow_headers @route ('/', method = 'OPTIONS') @route ('/ <path: path>', method = 'OPTIONS') def options_handler ( путь = нет): возврат

Декоратор хуков позволяет нам вызывать функцию до или после каждого запроса. В нашем случае, чтобы включить CORS, мы должны установить заголовки Access-Control-Allow-Origin, -Allow-Methods и -Allow-Headers для каждого из наших ответов. Они указывают запрашивающей стороне, что мы будем обслуживать указанные запросы.

Кроме того, клиент может сделать HTTP-запрос OPTIONS к серверу, чтобы проверить, действительно ли он может отправлять запросы другими методами. В этом примере для общего охвата мы отвечаем на все запросы OPTIONS кодом состояния 200 и пустым телом.

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

Заворачивать

Это все, что нужно сделать!

В этом руководстве я попытался охватить основные этапы создания REST API для приложения Python с веб-инфраструктурой Bottle.

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

Когда вы в последний раз обращались к интерфейсу REST, который ответил «Hello World?