В преддверии старта курса "Python Developer. Professional" подготовили традиционный перевод полезной статьи.
Всем привет, в этой статье я расскажу о создании простых асинхронных проектов на фреймворке Sanic.
Введение
Sanic это
очень похожий на Flask открытый веб-сервер и веб-фреймворк на
Python с более чем 10К
звездами, который быстро развивается. Он позволяет использовать
синтаксис async/await
, который был добавлен в
Python 3.5, помогая делать ваш код
неблокирующим и быстрым.
У Sanic очень хорошая документация, которая пишется членами сообщества для самого сообщества.
Цель проекта предложить простой способ запуска высокопроизводительного HTTP-сервера, который легко создавать, расширять и в конечном итоге масштабировать.
Требования
Перед тем, как начать, давайте установим несколько пакетов и убедимся, что у нас есть все необходимое для создания проекта.
Примечание: Исходный код есть на моем github. Каждый шаг отражен в отдельном коммите.
Предустановки
-
Python3.6+
-
pipenv (или любой другой установщик пакетов)
-
PostgreSQL (для баз данных, можно также взять MySQL или SQLite)
Пакеты
-
secure это легкий пакет, который добавляет заголовки безопасности и атрибуты файлов cookie для веб-фреймворков Python.
-
environs библиотека Python для синтаксического анализа переменных окружения. Она позволяет хранить конфигурацию отдельно от кода, в соответствии с методологией приложения двенадцати факторов.
-
sanic-envconfig это расширение, которое помогает вам переносить командную строку и переменные окружения в вашу конфигурацию Sanic.
-
databases это пакет Python, который позволяет создавать запросы с использованием мощного языка выражений SQLAlchemy Core и обеспечивает поддержку PostgreSQL, MySQL и SQLite.
Давайте создадим пустой каталог и инициализируем пустой
Pipfile
.
pipenv --python python3.6
Установите все необходимые пакеты, используя приведенные ниже
команды pipenv
.
pipenv install sanic secure environs sanic-envconfig
Для базы данных:
pipenv install databases[postgresql]
На выбор postgresql, mysql, sqlite.
Структура
Теперь давайте создадим несколько файлов и папок, где мы будем писать код.
.env Pipfile Pipfile.lock setup.py project __init__.py __main__.py main.py middlewares.py routes.py settings.py tables.py
Мы будем использовать файл setup.py
, чтобы сделать
папку project
доступной как пакет в нашем коде.
from setuptools import setupsetup( name='project',)
Установка
pipenv install -e .
В файле .env
мы будем хранить глобальные
переменные, такие как строку подключения к базе данных.
_main_.py
создан для того, чтобы наш пакет
project
мог выполняться из командной строки.
pipenv run python -m project
Инициализация
Давайте сделаем первый вызов в файле main.py
from project.main import initinit()
Это начало нашего приложения. Теперь нам нужно создать функцию
init
в файле main.py.
from sanic import Sanicapp = Sanic(__name__)def init(): app.run(host='0.0.0.0', port=8000, debug=True)
Просто создав app
из класса Sanic
, мы
можем запустить его, указав хост, порт и необязательный аргумент с
ключевым словом debug
.
Запускаем
pipenv run python -m project
Sanic console output
При успешном запуске приложения Sanic вывод будет выглядеть именно так. Если в браузере вы откроете http://0.0.0.0:8000, то увидите:
Error: Requested URL / not found
Мы еще не создали никаких маршрутов, так что все в порядке. Мы займемся этим дальше.
Настройка
Теперь мы можем модифицировать среду и настройки. Нам нужно добавить некоторые переменные в файл .env, прочитать их и передать в файл конфигурации приложения Sanic.
Файл .env
DEBUG=TrueHOST=0.0.0.0POST=8000
Конфигурация
from sanic import Sanicfrom environs import Envfrom project.settings import Settingsapp = Sanic(__name__)def init(): env = Env() env.read_env() app.config.from_object(Settings) app.run( host=app.config.HOST, port=app.config.PORT, debug=app.config.DEBUG, auto_reload=app.config.DEBUG, )
Файл settings.py
from sanic_envconfig import EnvConfigclass Settings(EnvConfig): DEBUG: bool = True HOST: str = '0.0.0.0' PORT: int = 8000
Обратите внимание, что я добавил необязательный аргумент
auto_reload
, который активирует или деактивирует
автоматическую перезагрузку.
База данных
Сейчас самое время настроить базу данных.
Одно замечание о базе данных прежде, чем мы пойдем дальше.
Пакет databases использует asyncpg, который является асинхронным интерфейсом библиотеки для работы с PostgreSQL. Работает достаточно быстро. Результаты вы можете посмотреть ниже.
Мы будем использовать двух слушателей Sanic, для которых укажем операции подключения и отключения от базы данных. Вот слушатели, которых мы будем использовать:
-
afterserverstart
-
afterserverstop
Файл main.py
from sanic import Sanicfrom databases import Databasefrom environs import Envfrom project.settings import Settingsapp = Sanic(__name__)def setup_database(): app.db = Database(app.config.DB_URL) @app.listener('after_server_start') async def connect_to_db(*args, **kwargs): await app.db.connect() @app.listener('after_server_stop') async def disconnect_from_db(*args, **kwargs): await app.db.disconnect()def init(): env = Env() env.read_env() app.config.from_object(Settings) setup_database() app.run( host=app.config.HOST, port=app.config.PORT, debug=app.config.DEBUG, auto_reload=app.config.DEBUG, )
И еще кое-что. Нам нужно указать DB_URL в настройках проекта и среде.
Файл .env
DEBUG=TrueHOST=0.0.0.0POST=8000DB_URL=postgresql://postgres:postgres@localhost/postgres
И файл settings.py:
from sanic_envconfig import EnvConfigclass Settings(EnvConfig): DEBUG: bool = True HOST: str = '0.0.0.0' PORT: int = 8000 DB_URL: str = ''
Убедитесь, что DB_URL верный и ваша база данных
запущена. Теперь вы можете получить доступ к базе данных с помощью
app.db
. Более подробную информацию можно получить в
следующем разделе.
Таблицы
Теперь у нас есть подключение к нашей базе данных, и мы можем попробовать сделать несколько SQL-запросов.
Давайте объявим таблицу в файле tables.py с помощью SQLAlchemy.
import sqlalchemymetadata = sqlalchemy.MetaData()books = sqlalchemy.Table( 'books', metadata, sqlalchemy.Column('id', sqlalchemy.Integer, primary_key=True), sqlalchemy.Column('title', sqlalchemy.String(length=100)), sqlalchemy.Column('author', sqlalchemy.String(length=60)),)
Сейчас я предполагаю, что вы уже сделали миграцию базы данных с таблицей books в ней. Для создания миграций баз данных я рекомендую использовать Alembic легкий и простой инструмент, который можно использовать вместе с инструментарием базы данных SQLAlchemy для Python.
Теперь мы можем использовать любые запросы SQLAlchemy. Ниже представлены несколько примеров.
# Executing manyquery = books.insert()values = [ {"title": "No Highway", "author": "Nevil Shute"}, {"title": "The Daffodil", "author": "SkyH. E. Bates"},]await app.db.execute_many(query, values)# Fetching multiple rowsquery = books.select()rows = await app.db.fetch_all(query)# Fetch single rowquery = books.select()row = await app.db.fetch_one(query)
Маршруты
Теперь нам нужно настроить маршруты. Давайте перейдем в routes.py и добавим новый маршрут для books.
from sanic.response import jsonfrom project.tables import booksdef setup_routes(app): @app.route("/books") async def book_list(request): query = books.select() rows = await request.app.db.fetch_all(query) return json({ 'books': [{row['title']: row['author']} for row in rows] })
Конечно же, чтобы все работало, нам нужно в init
вызвать setup_routes
.
from project.routes import setup_routesapp = Sanic(__name__)def init(): ... app.config.from_object(Settings) setup_database() setup_routes(app) ...
Делаем запрос
$ curl localhost:8000/books{"books":[{"No Highway":"Nevil Shute"},{"The Daffodil":"SkyH. E. Bates"}]}
Middleware
Может проверим заголовки ответов и посмотрим, можем ли мы добавить или исправить там что-нибудь?
$ curl -I localhost:8000Connection: keep-aliveKeep-Alive: 5Content-Length: 32Content-Type: text/plain; charset=utf-8
Как видите, в вопросах безопасности нам есть что улучшить. Есть несколько пропущенных заголовков, таких как X-XSS-Protection, Strict-Transport-Security и т.д. Поэтому давайте разберемся с ними с помощью дополнительного ПО и пакетов secure.
Файл middlewares.py
from secure import SecureHeaderssecure_headers = SecureHeaders()def setup_middlewares(app): @app.middleware('response') async def set_secure_headers(request, response): secure_headers.sanic(response)
Настройка middleware в файле main.py:
from project.middlewares import setup_middlewaresapp = Sanic(__name__)def init(): ... app.config.from_object(Settings) setup_database() setup_routes(app) setup_middlewares(app) ...
А вот и результат:
$ curl -I localhost:8000/booksConnection: keep-aliveKeep-Alive: 5Strict-Transport-Security: max-age=63072000; includeSubdomainsX-Frame-Options: SAMEORIGINX-XSS-Protection: 1; mode=blockX-Content-Type-Options: nosniffReferrer-Policy: no-referrer, strict-origin-when-cross-originPragma: no-cacheExpires: 0Cache-control: no-cache, no-store, must-revalidate, max-age=0Content-Length: 32Content-Type: text/plain; charset=utf-8
Как я и обещал в самом начале, для каждого раздела этой статьи есть отдельный репозиторий на github. Надеюсь, этот небольшой туториал поможет вам начать работу с Sanic. В фреймворке Sanic есть еще много неисследованных функций, с ними вы можете познакомиться в документации.
Подробнее о курсе. Посмотреть запись открытого урока "Расширение Python на C: заставляем Python ползти быстрее" можно здесь.