Китайская документация по асинхронному фреймворку Sanic

Python

typora-copy-images-to: ipic


[TOC]

быстрый старт

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

Во-первых, версия Python 3.4 представилаasyncio, что позволяет Python иметь стандартную библиотеку, поддерживающую асинхронный ввод-вывод, а более поздняя версия 3.5 предоставляет два новых ключевых словаasync/await, цель состоит в том, чтобы лучше идентифицировать асинхронный ввод-вывод и сделать асинхронное программирование более удобным. Наконец, версия 3.6 пошла еще дальше и выпустила стабильную версиюasyncio, Из этой серии обновлений видно, что сообщество Python делает твердый и уверенный шаг в сторону асинхронного программирования.

Установить

Саныч это поддержкаasync/awaitАсинхронная неблокирующая структура Syntax, что означает, что мы можем полагаться на его новые функции для обработки асинхронных запросов для повышения производительности службы, если у вас естьFlaskФреймворк опыт, то вы можете быстро использоватьSanicЧтобы создать приложение, которое я хочу, и производительность будет значительно улучшена, я написал один и тот же сервис на Flask и Sanic соответственно, а затем сравнил результаты стресс-теста и обнаружил, что сервис, написанный Sanic, вероятно,Falsk1,5 раза.

Только ли асинхронная природа Sanic делает его намного быстрее? Да, но этот ответ не стандартный, более критично, что использует Sanicuvloopв видеasyncioцикл событий,uvloopНаписанное на Cython, его появление делаетasyncioБыстрее, как быстро?этоВ статье есть введение, в котором предполагается, что скорость как минимум в два раза выше, чем у nodejs, gevent и других асинхронных фреймворков Python, а производительность близка к программе, написанной на Go.Кстати, автором Sanic является под влиянием этой статьи.

Как насчет этого? Пробудил ли он у вас интерес к изучению Sanic? Если да, давайте начнем учиться вместе. Прежде чем начать, вам нужен только компьютер с установленным Python.

Примечание. Поскольку установка uvloop не поддерживается в Windows, здесь рекомендуется использовать Mac или Linux.

виртуальная среда

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

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

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

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

Многие инструменты для создания виртуальной среды, следующим образом:

 …

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

После завершения установки и настройки просто посмотрите на официальный метод использования, и вы можете начать.Например, моя машина используетvenv(После python3.5 официально рекомендуется использовать этот venv для управления виртуальной средой.) После завершения установки вы можете легко создать виртуальную среду.Например, Python3.6 используется здесь как среда по умолчанию для проект в этой книге:

cd ~/
# 新建一个python3.6环境
python3 -m venv pyenv
# 安装好之后 输入下面命令进入名为python36的环境
cd pyenv/
source bin/activate
# 查看版本
python -V

Если скорость установки относительно низкая, вы можете рассмотреть возможность перехода на внутренние источники, такие какдомашнее зеркало, Что касается того, почему Python3.6 выбирается в качестве среды по умолчанию, одна из них является то, что Sanic поддерживает только Python3.5 +, а другой - это то, что проект, который мы создаем, в конечном итоге запустится в производственной среде, поэтому рекомендуется установить стабильный Версия под Python3.6 изasyncio.

Установить Саник

Python устанавливает сторонние модули, используяpipИнструменты для установки, здесь не исключение, сначала введите новый, который мы создали на предыдущем шаге.python3.6виртуальную среду, затем установите:

# 安装Sanic,请先使用 source activate python36 进入虚拟环境
pip install sanic
# 如果不想使用uvloop和ujson 可以这样安装
SANIC_NO_UVLOOP=true SANIC_NO_UJSON=true pip install sanic

С помощью приведенной выше команды вы можетеpython3.6Установите Sanic и его зависимости в виртуальную среду, если вы хотите увидеть, правильно ли установлен Sanic, вы можете войти в соответствующую виртуальную среду под терминалом, запустить интерпретатор Python, импортировать библиотеку Sanic:

python
>>> 
>>> import sanic

Если ошибок нет, это означает, что вы правильно установили Sanic. Перейдите к следующему разделу, чтобы узнать, как использовать Sanic для создания веб-проекта.

Начинать

Мы будем официально использовать Sanic для создания веб-проекта, давайте сделаем первый шаг и напишем возврат с помощью Sanic.Hello World!Программа обслуживания строк.

Создать новую папкуsanicweb:

$ mkdir sanicweb
$ cd sanicweb/
$ pwd
/Users/junxi/pyenv/sanicweb

Создайте нормальный пример, чтобы сохранитьmain.py :

from sanic import Sanic
from sanic.response import text

app = Sanic()


@app.route("/")
async def index(request):
    return text('Hello World!')


if __name__ == "__main__":
    app.run(host="0.0.0.0", port=9000)

бегатьmain.py, затем посетите адресhttp://127.0.0.1:9000/

$ curl -X GET http://127.0.0.1:9000/
Hello World!

Это завершает наш первый нормальный пример.

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

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

Маршрутизация позволяет пользователям указывать функции обработчика для разных конечных точек URL.

Пример:

from sanic.response import json
@app.route("/")
async def index(request):
    return json({ "hello": "world" })

url server.url/Доступ (базовый URL-адрес сервера), в конечном итоге / маршрутизатором, чтобы соответствовать функции обработчика, проверить, а затем вернуть объект JSON.

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

параметры запроса

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

Пример:

@app.router('/tag/<tag>')
async def tag_handler(request, tag):
    return text('Tag - {}'.format(tag))

Перезапускаем сервис, вводим адресhttp://127.0.0.1:9000/tag/pythonпосещать

$ curl -X GET http://127.0.0.1:9000/tag/python
Tag - python

Чтобы указать тип параметра, добавьте (:type) после имени параметра. Если параметр не соответствует указанному типу, Sanic выдаст несуществующее исключение, что приведет к странице 404.

@app.route('/number/<integer_arg:int>')
async def integer_handler(request, integer_arg):
    return text('Integer - {}'.format(integer_arg))


@app.route('/number/<number_arg:number>')
async def number_handler(request, number_arg):
    return text('Number - {}'.format(number_arg))


@app.route('/person/<name:[A-z]+>')
async def person_handler(request, name):
    return text('Person - {}'.format(name))


@app.route('/folder/<folder_id:[A-z0-9]{0,4}>')
async def folder_handler(request, folder_id):
    return text('Folder - {}'.format(folder_id))

Результаты теста следующие:

$ curl -X GET http://127.0.0.1:9000/number/1
Integer - 1
$ curl -X GET http://127.0.0.1:9000/number/asds
Error: Requested URL /number/asds not found
$ curl -X GET http://127.0.0.1:9000/number/12.0
Number - 12.0
$ curl -X GET http://127.0.0.1:9000/person/junxi
Person - junxi
$ curl -X GET http://127.0.0.1:9000/person/123
Error: Requested URL /person/123 not found
$ curl -X GET http://127.0.0.1:9000/folder/img
Folder - img
$ curl -X GET http://127.0.0.1:9000/folder/img1
Folder - img1
$ curl -X GET http://127.0.0.1:9000/folder/images
Error: Requested URL /folder/images not found
$ curl -X GET http://127.0.0.1:9000/folder/2018
Folder - 2018

тип запроса

Декоратор маршрута принимает необязательный параметр, метод, который позволяет функции-обработчику работать с любым методом HTTP в списке.

Пример 1:

@app.route('/post1', methods=['POST'])
async def post_handler(request):
    return text('POST request - {}'.format(request.json))


@app.route('/get1', methods=['GET'])
async def get_handler(request):
    return text('GET request - {}'.format(request.args))

Пример 2:

@app.post('/post2')
async def post_handler(request):
    return text('POST request - {}'.format(request.json))


@app.get('/get2')
async def get_handler(request):
    return text('GET request - {}'.format(request.args))

Результаты теста:

$ curl -X GET http://127.0.0.1:9000/get1?name=junxi
GET request - {'name': ['junxi']}
$ curl -X GET http://127.0.0.1:9000/get2?name=junxi
GET request - {'name': ['junxi']}
$ curl -H "Content-type: application/json" -X POST -d '{"name":"junxi", "gender":"male"}' http://127.0.0.1:9000/post1 
POST request - {'name': 'junxi', 'gender': 'male'}
$ curl -H "Content-type: application/json" -X POST -d '{"name":"junxi", "gender":"male"}' http://127.0.0.1:9000/post2
POST request - {'name': 'junxi', 'gender': 'male'}

добавить маршрут

Пример:

async def handler1(request):
    return text('ok')


async def handler2(request, name):
    return text('Folder - {}'.format(name))


async def personal_handler2(request, name):
    return text('Person - {}'.format(name))


app.add_route(handler1, '/test1')
app.add_route(handler2, '/folder2/<name>')
app.add_route(personal_handler2, '/personal2/<name:[A-z]>', methods=['GET'])

Результаты теста:

$ curl -X GET http://127.0.0.1:9000/test1 
ok
$ curl -X GET http://127.0.0.1:9000/folder2/aaa
Folder - aaa
$ curl -X GET http://127.0.0.1:9000/personal2/A
Person - A
$ curl -X GET http://127.0.0.1:9000/personal2/a
Person - a

url_for

Sanic предоставляет метод urlfor, который генерирует URL-адрес на основе имени метода обработчика. Избегайте жестких URL-адресов вашего приложения.

Пример:

@app.router("/")
async def index(request):
    url = app.url_for('post_handler', post_id=5)
    return redirect(url)


@app.route('posts/<post_id>')
async def post_handler(request, post_id):
    return text('Post - {}'.format(post_id))

Аргументы ключевого слова, переданные url_for, не являются параметрами запроса, они будут включены в строку запроса URL. Например:

url = app.url_for('post_handler', post_id=5, arg_one='one', arg_two='two')
# /posts/5?arg_one=one&arg_two=two

Все допустимые параметры должны быть переданы в URL-адрес для создания URL-адреса. Если параметр не указан или параметр не соответствует указанному типу, выдается ошибка URLBuildError.

Может пройти несколько важных параметров для URL

url = app.url_for('post_handler', post_id=5, arg_one=['one', 'two'])
# /posts/5?arg_one=one&arg_one=two

Тестовый доступ/Мы обнаружим, что URL переходит на/posts/5, и распечатайте результат Post-5.

redirectОтsanic.responseИмпортированный метод для обработки перенаправления URL.

Сетевой сокет маршрутизации

WebSocket routes

websocketМожет быть реализован с помощью декоративной трассировки

Пример:

@app.websocket('/feed')
async def feed(request, ws):
    while True:
        data = 'hello!'
        print('Sending:' + data)
        await ws.send(data)
        data = await ws.recv()
        print('Received:', data)

Кроме того, добавление метода маршрута веб-сокета может заменить декоратор

async def feed(request, ws):
    pass
app.add_websocket_route(my_websocket_handler, '/feed')

просить

request

Обычный тип

Когда конечная точка получает HTTP-запрос, функция маршрутизации передаетсяRequestобъект.

Следующие переменные доступны какRequestДоступ к свойствам объекта:

  • json (any) - JSON body
from sanic.response import json

@app.route("/json")
def post_json(request):
    return json({ "received": True, "message": request.json })
  • args(dict) — строковая переменная запроса. Строка запроса — это часть, аналогичная URL-адресу.?key1=value1&key2=value2. Если URL разрешен, тоargsСловарь будет выглядеть так:{'key1': ['value1'], 'key2': ['value2']}. просилquery_stringПеременные содержат неразобранные строковые значения.
from sanic.response import json

@app.route("/query_string")
def query_string(request):
    return json({ "parsed": True, "args": request.args, "url": request.url, "query_string": request.query_string })
  • raw_args(dict) — во многих случаях вам потребуется доступ к параметрам URL в менее сжатом словаре. для того же URL, что и раньше?key1=value1&key2=value2,raw_argsСловарь выглядит так:{'key1': 'value1', 'key2': 'value2'}.

  • files(словарьFileобъекты) - список файлов с именем, телом и типом

from sanic.response import json

@app.route("/files")
def post_json(request):
    test_file = request.files.get('test')

    file_parameters = {
        'body': test_file.body,
        'name': test_file.name,
        'type': test_file.type,
    }

    return json({ "received": True, "file_names": request.files.keys(), "test_file_parameters": file_parameters })
  • form(dict) - переменные формы публикации.
from sanic.response import json

@app.route("/form")
def post_json(request):
    return json({ "received": True, "form_data": request.form, "test": request.form.get('test') })
  • body(байты) - Отправить исходное тело. Это свойство позволяет извлекать запрошенные необработанные данные независимо от типа содержимого.
from sanic.response import text

@app.route("/users", methods=["POST",])
def create_user(request):
    return text("You are trying to create a user with the following POST: %s" % request.body)
  • headers(dict) — словарь без учета регистра, содержащий заголовки запросов.

  • ip(str) — IP-адрес запрашивающей стороны.

  • port(str) — адрес порта запрашивающей стороны.

  • socket(кортеж) - Заявитель (IP, порт).

  • app- Ссылка на объект приложения Sanic, обрабатывающий этот запрос. Когда план или другой обработчик внутри модуля не может получить доступ к глобальномуappобъект, это очень полезно.

    from sanic.response import json
    from sanic import Blueprint
    
    bp = Blueprint('my_blueprint')
    
    @bp.route('/')
    async def bp_root(request):
        if request.app.config['DEBUG']:
            return json({'status': 'debug'})
        else:
            return json({'status': 'production'})
    

  • url: полный URL-адрес запроса, т. е.:http://localhost:8000/posts/1/?foo=bar

  • scheme: схема URL, связанная с запросом:httpилиhttps

  • host: хост, связанный с запросом:localhost:8080

  • path: Запрошенный путь:/posts/1/

  • query_string: Строка запроса для запроса:foo=barили пустая строка''

  • uri_template: Шаблон, соответствующий обработчику маршрута:/posts/<id>/

  • token: Значение заголовка Authorization (Авторизация):Basic YWRtaW46YWRtaW4=

использоватьgetиgetlistдоступ к данным

Атрибут запроса, который возвращает словарь, фактически вернетdictназывается подклассомRequestParameters. Ключевое отличие в использовании этого объекта заключается в том, чтоgetиgetlistразница между методами.

  • get(key, default=None)Работает как обычно, за исключением случаев, когда значением данного ключа является список,возвращает только первый элемент.
  • getlist(key, default=None)Нормальная операция,вернуть весь список.

отклик

response

text

from sanic import response

@app.route('/text')
def handle_request(request):
    return response.text('Hello world!')

HTML

from sanic import response

@app.route('/html')
def handle_request(request):
    return response.html('<p>Hello world!</p>')

JSON

from sanic import response

@app.route('/json')
def handle_request(request):
    return response.json({'message': 'Hello world!'})

File

from sanic import response

@app.route('/file')
async def handle_request(request):
    return await response.file('/srv/www/whatever.png')

Streaming

потоковое мультимедиа

from sanic import response

@app.route("/streaming")
async def index(request):
    async def streaming_fn(response):
        response.write('foo')
        response.write('bar')
    return response.stream(streaming_fn, content_type='text/plain')

File Streaming

Для больших файлов комбинация файлов и потоков

from sanic import response

@app.route('/big_file.png')
async def handle_request(request):
    return await response.file_stream('/srv/www/whatever.png')

Redirect

from sanic import response

@app.route('/redirect')
def handle_request(request):
    return response.redirect('/json')

Raw

Ответ без кодировки

from sanic import response

@app.route('/raw')
def handle_request(request):
    return response.raw('raw data')

Modify headers or status

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

from sanic import response

@app.route('/json')
def handle_request(request):
    return response.json(
        {'message': 'Hello world!'},
        headers={'X-Served-By': 'sanic'},
        status=200
    )

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

static_files

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

from sanic import Sanic

app = Sanic(__name__)
# Serves files from the static folder to the URL /static
app.static('/static', './static')
# Serves the file /home/ubuntu/test.png when the URL /the_best.png
# is requested
app.static('/the_best.png', '/home/ubuntu/test.png')

app.run(host="0.0.0.0", port=8000)

Примечание. В настоящее время вы не можете использовать URL-адрес для создания URL-адреса для статического файла.

трафарет

написание html шаблонов

Написание веб-сервисов, естественно, будет включать HTML.Sanic поставляется с функциями HTML, но это не отвечает некоторым потребностям, поэтому внедрение jinja2 неизбежно. Способ применения также очень прост:

# novels_blueprint.py片段
from sanic import Blueprint
from jinja2 import Environment, PackageLoader, select_autoescape

# 初始化blueprint并定义静态文件夹路径
bp = Blueprint('novels_blueprint')
bp.static('/static', './static/novels')

# jinjia2 config
env = Environment(
    loader=PackageLoader('views.novels_blueprint', '../templates/novels'),
    autoescape=select_autoescape(['html', 'xml', 'tpl']))

def template(tpl, **kwargs):
    template = env.get_template(tpl)
    return html(template.render(kwargs))
    
@bp.route("/")
async def index(request):
    return template('index.html', title='index')

Таким образом реализуется внедрение шаблона jinja2.

аномальный

Exceptions

Выбросить исключение

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

from sanic.exceptions import ServerError
@app.route('/killme')
def i_am_ready_to_die(request):
    raise ServerError("Something bad happened", status_code=500)

Вы также можете настроить код состояния

from sanic.exceptions import abort
from sanic.response import text
@app.route('/youshallnotpass')
def no_no(request):
        abort(401)
        # this won't happen
        text("OK")

Обработка исключений

Handling exceptions

Декоратор принимает список исключений для обработки в качестве параметра. Вы можете поймать их с помощью SanicException! Декорированный обработчик исключений должен принимать запрос и объект исключения в качестве параметров.

from sanic.response import text
from sanic.exceptions import NotFound

@app.exception(NotFound)
def ignore_404s(request, exception):
    return text("Yep, I totally found the page: {}".format(request.url))

@app.exception(NotFound)
def handle_404_redirect(request, exception):
    uri = app.url_for('index')
    return redirect(uri)

полезное исключение

Useful exceptions

Обычно используется

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

Промежуточное ПО и слушатели

Middleware And Listeners

промежуточное ПО

Middleware

Существует два типа промежуточного программного обеспечения: запрос и ответ. Оба используют@appЗаявление. Промежуточный декоратор и параметры декоратора представляют собой строку, представляющую его тип: «Запрос» или «Ответ». Отвечая на запрос приема промежуточного программного обеспечения и ответ в качестве параметров.

Простейшее промежуточное ПО вообще не изменяет ни запрос, ни ответ.

@app.middleware('request')
async def print_on_request(request):
    print("I print when a request is received by the server")
    
@app.middleware('response')
async def print_on_response(request, response):
    print("I print when a response is returned by the server")

Изменить запрос или ответ

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

app = Sanic(__name__)

@app.middleware('response')
async def custom_banner(request, response):
    response.headers["Server"] = "Fake-Server"
    
@app.middleware('response')
async def prevent_xss(request, response):
    response.headers["x-xss-protection"] = "1; mode=block"
    
app.run(host="0.0.0.0", port=8000)

Приведенный выше код будет применять два промежуточных ПО по порядку. Во-первых, настраиваемый баннер промежуточного программного обеспечения изменит сервер заголовков ответа HTTP на поддельный сервер, а второе промежуточное программное обеспечение Prevent XSS добавит заголовки HTTP для предотвращения атак межсайтового скриптинга (XSS). Эти две функции вызываются после того, как пользовательская функция возвращает ответ.

Слушатель

Listeners

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

  • before_server_start
  • after_server_start
  • before_server_stop
  • after_server_stop

Эти слушатели реализованы как модификаторы в функциях, которые принимают объект приложения и асинхронный цикл.

@app.listener('before_server_start')
async def setup_db(app, loop):
    app.db = await db_setup()
    
@app.listener('after_server_start')
async def notify_server_started(app, loop):
    print('Server successfully started!')
    
@app.listener('before_server_stop')
async def notify_server_stopping(app, loop):
    print('Server shutting down!')
    
@app.listener('after_server_stop')
async def close_db(app, loop):
    await app.db.close()

Если вы хотите запустить фоновую задачу после запуска цикла, Sanic предоставляет метод addtask, чтобы сделать это легко.

async def notify_server_started_after_five_seconds():
    await asyncio.sleep(5)
    print('Server successfully started!')
    
app.add_task(notify_server_started_after_five_seconds())

план

Blueprints

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

simple Blueprint

Предположим, вы сохранили файл как myblueprint. py, который впоследствии можно будет импортировать в основное приложение.

from sanic.response import json
from sanic import Blueprint

bp = Blueprint('my_blueprint')

@bp.route('/')
async def bp_root(request):
    return json({'my': 'blueprint'})

Схема регистрации

registering blueprints

Чертежи должны быть зарегистрированы в приложении

from sanic import Sanic
from my_blueprint import bp

app = Sanic(__name__)
app.blueprint(bp)

app.run(host='0.0.0.0', port=8000, debug=True)

Используйте чертежи

Use_blueprint

маршрутизация сетевых сокетов

WebSocket routes

Обработчики WebSocket могут быть зарегистрированы с помощью@bp.websocketдекоративный илиbp.add_websocket_routeметод

промежуточное ПО

Middleware

Использование чертежей также может регистрироваться в глобальном промежуточном программном обеспечении.

@bp.middleware
async def print_on_request(request):
    print("I am a spy")
    
@bp.middleware('request')
async def halt_request(request):
    return text('I halted the request')

@bp.middleware('response')
async def halt_response(request, response):
    return text('I halted the response')

аномальный

Exception

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

@bp.exception(NotFound)
def ignore_404s(request, exception):
    return text("Yep, I totally found the page: {}".format(request.url))

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

Static files

Статические файлы могут иметь префикс

bp.static('/folder/to/serve', '/web/path')

Start and stop

Чертежи могут запускать функции во время запуска и остановки сервера. При работе в многопроцессорном режиме (более 1 воркера) они запускаются после разветвления воркеров.

  • FORE_SERVER_START: выполнить сервер перед приемом соединения
  • after_server_start: выполняется после того, как сервер начинает принимать соединения
  • before_server_stop: выполняется до того, как сервер перестанет принимать соединения
  • after_server_stop: выполняется после остановки сервера и выполнения всех запросов.
bp = Blueprint('my_blueprint')

@bp.listener('before_server_start')
async def setup_connection(app, loop):
    global database
    database = mysql.connect(host='127.0.0.1'...)
    
@bp.listener('after_server_stop')
async def close_connection(app, loop):
    await database.close()

Вариант использования: управление версиями API

Use-case: API versioning

Схемы полезны для управления версиями API, когда одна схема может указывать на /v1/, а другая — на /v2/.

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

# blueprints.py
from sanic.response import text
from sanic import Blueprint

blueprint_v1 = Blueprint('v1', url_prefix='/v1')
blueprint_v2 = Blueprint('v2', url_prefix='/v2')

@blueprint_v1.route('/')
async def api_v1_root(request):
    return text('Welcome to version 1 of our documentation')

@blueprint_v2.route('/')
async def api_v2_root(request):
    return text('Welcome to version 2 of our documentation')

Когда мы регистрируем наши схемы в приложении, пути /v1 и /v2 будут указывать на одну схему, что позволяет создавать дочерние сайты для каждой версии API.

# main.py
from sanic import Sanic
from blueprints import blueprint_v1, blueprint_v2

app = Sanic(__name__)
app.blueprint(blueprint_v1, url_prefix='/v1')
app.blueprint(blueprint_v2, url_prefix='/v2')

app.run(host='0.0.0.0', port=8000, debug=True)

Создайте URL-адрес с url_for

Если вы хотите направить URL-адрес внутри схемы, помните, что имя конечной точки имеет формат .

@blueprint_v1.route('/')
async def root(request):
    url = app.url_for('v1.post_handler', post_id=5) # --> '/v1/post/5'
    # url = request.app.url_for('v1.post_handler', post_id=5) # --> '/v1/post/5'
    return redirect(url)

@blueprint_v1.route('/post/<post_id>')
async def post_handler(request, post_id):
    return text('Post {} in Blueprint V1'.format(post_id))

Note: не забудьте добавить, если приложение и план не находятся в одном модуле.request

Например:url = request.app.url_for('v1.post_handler', post_id=5) # --> '/v1/post/5'

настроить

Configuration

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

базовая конфигурация

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

app = Sanic('myapp')
app.config.DB_NAME = 'appdb'
app.config.DB_USER = 'appuser'

Поскольку объект конфигурации на самом деле является словарем, его метод обновления можно использовать для установки сразу нескольких значений:

db_settings = {
    'DB_HOST': 'localhost',
    'DB_NAME': 'appdb',
    'DB_USER': 'appuser'
}
app.config.update(db_settings)

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

загрузить конфигурацию

Существует несколько способов загрузки конфигурации.

из переменных окружения

使用SANIC_前缀定义的任何变量都将应用于sanic config。 Например, установитеSANIC_REQUEST_TIMEOUTбудут автоматически загружены и введены приложениемREQUEST_TIMEOUTПеременные конфигурации. Вы можете передать другой префикс в Sanic:

app = Sanic(load_env='MYAPP_')

Тогда указанная выше переменная будет MYAPP_REQUEST_TIMEOUT. Если вы хотите отключить загрузку переменных среды, вы можете установить для этого параметра значение False:

app = Sanic(load_env=False)

от объекта

Если значений конфигурации много и у них разумные значения по умолчанию, может помочь поместить их в модуль:

import myapp.default_settings

app = Sanic('myapp')
app.config.from_object(myapp.default_settings)

Вы также можете использовать класс или любой другой объект.

из файла

Как правило, вам нужно загрузить конфигурацию из файла, который не является частью распространяемого приложения. можно использоватьfrom_pyfile(/path/to/config_file)Загрузить конфигурацию из файла. Однако для этого необходимо, чтобы программа знала путь к файлу конфигурации. Таким образом, вы можете указать расположение файла конфигурации в переменной среды и указать Sanic использовать его для поиска файла конфигурации:

app = Sanic('myapp')
app.config.from_envvar('MYAPP_SETTINGS')

Затем вы можете запустить свое приложение с установленной переменной среды MYAPP_SETTINGS:

$ MYAPP_SETTINGS=/path/to/config_file python3 myapp.py
INFO: Goin' Fast @ http://0.0.0.0:8000

Файлы конфигурации — это обычные файлы Python, которые выполняются для их загрузки. Это позволяет использовать произвольную логику для построения правильной конфигурации. В конфигурацию добавляются только переменные в верхнем регистре. Наиболее распространенная конфигурация состоит из простых пар ключ-значение:

# config_file
DB_HOST = 'localhost'
DB_NAME = 'appdb'
DB_USER = 'appuser'

Встроенные значения конфигурации

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

| Variable           | Default   | Description                                   |
| ------------------ | --------- | --------------------------------------------- |
| REQUEST_MAX_SIZE   | 100000000 | How big a request may be (bytes)              |
| REQUEST_TIMEOUT    | 60        | How long a request can take to arrive (sec)   |
| RESPONSE_TIMEOUT   | 60        | How long a response can take to process (sec) |
| KEEP_ALIVE         | True      | Disables keep-alive when False                |
| KEEP_ALIVE_TIMEOUT | 5         | How long to hold a TCP connection open (sec)  |

Различные переменные тайм-аута

Метрики тайм-аута запроса передаются по вновь открытым TCP-соединениямSanicИнтервал времени получения внутреннего сервера и момент получения всего HTTP-запроса. Если время превышаетREQUEST_TIMEOUTзначение (в секундах), то это считается ошибкой клиента, поэтому Sanic генерирует ответ HTTP 408 и отправляет его клиенту. Настройте это значение, если ваши клиенты часто передают очень большие нагрузки запросов или загружают запросы очень медленно.

Тайм-аут ответа измеряет время между тем, когда сервер Sanic доставляет HTTP-запрос приложению Sanic, и временем, когда HTTP-ответ отправляется клиенту. Если время превышаетRESPONSE_TIMEOUTзначение (в секундах), это считается ошибкой сервера, поэтому Sanic генерирует ответ HTTP 503 и передает его клиенту. Установите это значение выше, если приложение может работать в течение длительного времени, задерживая генерацию ответов.

Держитесь живым, что такое? Что такое роль сохраняет значение времени ожидания!

Keep-alive — это функция HTTP в HTTP 1.1. При отправке HTTP-запроса клиент (обычно приложение веб-браузера) может установить заголовок сообщения KeepAlive, чтобы указать, что HTTP-сервер (SANIC) не отключает TCP-соединение после отправки ответа. Это позволяет клиенту повторно использовать существующее TCP-соединение для отправки последующих HTTP-запросов и обеспечения большей эффективности сетевого трафика клиента и сервера.

В Санича,KEEP_ALIVEПеременная конфигурации по умолчанию имеет значениеTrue. Если вам не нужна эта функция в вашем приложении, установите для нее значениеFalse, чтобы все клиентские соединения закрывались сразу после отправки ответа, независимо от заголовка keepalive в запросе.

Время, в течение которого сервер поддерживает TCP-соединение открытым, определяется самим сервером. В Sanic используется значениеKEEP_ALIVE_TIMEOUTзначение для настройки. По умолчанию он установлен на 5 секунд, что является тем же значением по умолчанию, что и у HTTP-сервера Apache, и поддерживает хороший баланс между предоставлением клиентам достаточного времени для отправки новых запросов и отсутствием одновременного открытия слишком большого количества соединений. Не превышайте 75 секунд, если только вы не знаете, что ваш клиент использует браузер, поддерживающий TCP-соединения.

для справки:

Apache httpd server default keepalive timeout = 5 seconds
Nginx server default keepalive timeout = 75 seconds
Nginx performance tuning guidelines uses keepalive = 15 seconds
IE (5-9) client hard keepalive limit = 60 seconds
Firefox client hard keepalive limit = 115 seconds
Opera 11 client hard keepalive limit = 120 seconds
Chrome 13+ client keepalive limit > 300+ seconds

Cookie

Файл cookie — это часть данных, хранящихся в браузере пользователя. Sanic может как читать, так и записывать файлы cookie и сохранять их в виде пар ключ-значение.

Warning

Файлы cookie могут быть свободно изменены клиентом. Таким образом, вы не можете просто хранить данные, такие как информация для входа, в файл cookie, потому что клиент может изменить их по своему желанию. Чтобы гарантировать, что данные, хранящиеся в файле cookie, не могут быть подделаны или изменены клиентом, используйте что-то вродеitsdangerous to cryptographically sign the data.

читать куки

Доступ к файлам cookie пользователя можно получить через словарь файлов cookie объекта запроса.

from sanic.response import text

@app.route("/cookie")
async def test(request):
    test_cookie = request.cookies.get('test')
    return text("Test cookie set to: {}".format(test_cookie))

Написать куки

Когда ответ возвращается, файл cookie может быть установлен для объекта ответа.

from sanic.response import text

@app.route("/cookie")
async def test(request):
    response = text("There's a cookie up in this response")
    response.cookies['test'] = 'It worked!'
    response.cookies['test']['domain'] = '.gotta-go-fast.com'
    response.cookies['test']['httponly'] = True
    return response

удалить куки

Файлы cookie могут быть удалены семантически или явным образом.

from sanic.response import text

@app.route("/cookie")
async def test(request):
    response = text("Time to eat some cookies muahaha")

    # This cookie will be set to expire in 0 seconds
    del response.cookies['kill_me']

    # This cookie will self destruct in 5 seconds
    response.cookies['short_life'] = 'Glad to be here'
    response.cookies['short_life']['max-age'] = 5
    del response.cookies['favorite_color']

    # This cookie will remain unchanged
    response.cookies['favorite_color'] = 'blue'
    response.cookies['favorite_color'] = 'pink'
    del response.cookies['favorite_color']

    return response

Файл cookie ответа может быть установлен как значение словаря и имеет следующие параметры:

  • expires (datetime): время истечения срока действия файла cookie в клиентском браузере.

  • путь (строка): подмножество URL-адресов, к которым применяется этот файл cookie. По умолчанию /.

  • Комментарий (строка): комментарий (метаданные).

  • домен (строка): указывает домен, в котором действителен файл cookie. Явно указанные домены всегда должны начинаться с точки.

  • max-age(number): количество секунд, в течение которых cookie должен жить.

  • безопасный (логический): указывает, отправляется ли файл cookie только через HTTPS.

  • httponly (логическое значение): указывает, не может ли Javascript читать файлы cookie.

session

у sonic есть сторонний плагин для этогоsanic_session, использование очень простое, смотрите официальный пример следующим образом:

import asyncio_redis

from sanic import Sanic
from sanic.response import text
from sanic_session import RedisSessionInterface

app = Sanic()


# Token from https://github.com/subyraman/sanic_session

class Redis:
    """
    A simple wrapper class that allows you to share a connection
    pool across your application.
    """
    _pool = None

    async def get_redis_pool(self):
        if not self._pool:
            self._pool = await asyncio_redis.Pool.create(
                host='localhost', port=6379, poolsize=10
            )

        return self._pool


redis = Redis()

# pass the getter method for the connection pool into the session
session_interface = RedisSessionInterface(redis.get_redis_pool, expiry=604800)


@app.middleware('request')
async def add_session_to_request(request):
    # before each request initialize a session
    # using the client's request
    await session_interface.open(request)


@app.middleware('response')
async def save_session(request, response):
    # after each request save the session,
    # pass the response to set client cookies
    await session_interface.save(request, response)


@app.route("/")
async def test(request):
    # interact with the session like a normal dict
    if not request['session'].get('foo'):
        request['session']['foo'] = 0

    request['session']['foo'] += 1

    response = text(request['session']['foo'])

    return response


if __name__ == "__main__":
    app.run(host="0.0.0.0", port=8888, debug=True)

Handler Decorators

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

Authorization Decorator

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

from functools import wraps
from sanic.response import json

def authorized():
    def decorator(f):
        @wraps(f)
        async def decorated_function(request, *args, **kwargs):
            # run some method that checks the request
            # for the client's authorization status
            is_authorized = check_request_for_authorization_status(request)

            if is_authorized:
                # the user is authorized.
                # run the handler method and return the response
                response = await f(request, *args, **kwargs)
                return response
            else:
                # the user is not authorized. 
                return json({'status': 'not_authorized'}, 403)
        return decorated_function
    return decorator


@app.route("/")
@authorized()
async def test(request):
    return json({status: 'authorized'})

Streaming

потоковое мультимедиа

Request Streaming

Sanic позволяет получать данные запроса через потоки, как показано ниже. Когда запрос заканчивается,request.stream.get()возвращениеNone. Только декораторы post, put и patch имеют параметры потока.

from sanic import Sanic
from sanic.views import CompositionView
from sanic.views import HTTPMethodView
from sanic.views import stream as stream_decorator
from sanic.blueprints import Blueprint
from sanic.response import stream, text

bp = Blueprint('blueprint_request_stream')
app = Sanic('request_stream')


class SimpleView(HTTPMethodView):

    @stream_decorator
    async def post(self, request):
        result = ''
        while True:
            body = await request.stream.get()
            if body is None:
                break
            result += body.decode('utf-8')
        return text(result)


@app.post('/stream', stream=True)
async def handler(request):
    async def streaming(response):
        while True:
            body = await request.stream.get()
            if body is None:
                break
            body = body.decode('utf-8').replace('1', 'A')
            response.write(body)
    return stream(streaming)


@bp.put('/bp_stream', stream=True)
async def bp_handler(request):
    result = ''
    while True:
        body = await request.stream.get()
        if body is None:
            break
        result += body.decode('utf-8').replace('1', 'A')
    return text(result)


async def post_handler(request):
    result = ''
    while True:
        body = await request.stream.get()
        if body is None:
            break
        result += body.decode('utf-8')
    return text(result)

app.blueprint(bp)
app.add_route(SimpleView.as_view(), '/method_view')
view = CompositionView()
view.add(['POST'], post_handler, stream=True)
app.add_route(view, '/composition_view')


if __name__ == '__main__':
    app.run(host='127.0.0.1', port=8000)

Response Streaming

Sanic позволяет использоватьstreamПоток содержимого методов клиенту. Метод принимает обратный вызов сопрограммы, который передается на записьStreamingHTTPResponseобъект. Простой пример выглядит следующим образом:

from sanic import Sanic
from sanic.response import stream

app = Sanic(__name__)

@app.route("/")
async def test(request):
    async def sample_streaming_fn(response):
        response.write('foo,')
        response.write('bar')

    return stream(sample_streaming_fn, content_type='text/csv')

Это полезно, когда вы хотите передавать клиентам контент из внешних служб, таких как базы данных. Например, вы можете использоватьasyncpgПредоставленный асинхронный курсор передает записи базы данных клиенту:

@app.route("/")
async def index(request):
    async def stream_from_db(response):
        conn = await asyncpg.connect(database='test')
        async with conn.transaction():
            async for record in conn.cursor('SELECT generate_series(0, 10)'):
                response.write(record[0])

    return stream(stream_from_db)

представление на основе классов

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

Определить вид

Представления на основе классов должны быть подклассамиHTTPMethodView. Затем вы можете реализовать методы класса для каждого типа HTTP-запроса, который вы хотите поддерживать. Если полученный запрос не имеет определенного метода, генерируется ответ 405: Метод не разрешен.

Чтобы зарегистрировать представление на основе классов на конечной точке, используйтеapp.add_routeметод. Первым параметром должен быть вызываемый методas_viewКласс определения, второй параметр должен быть конечной точкой URL.

Доступные методы:get,post,put,patchиdelete. Класс, использующий все эти методы, выглядит следующим образом.

from sanic import Sanic
from sanic.views import HTTPMethodView
from sanic.response import text

app = Sanic('some_name')

class SimpleView(HTTPMethodView):

  def get(self, request):
      return text('I am get method')

  def post(self, request):
      return text('I am post method')

  def put(self, request):
      return text('I am put method')

  def patch(self, request):
      return text('I am patch method')

  def delete(self, request):
      return text('I am delete method')

app.add_route(SimpleView.as_view(), '/')

вы также можете использоватьasyncАсинхронный синтаксис.

from sanic import Sanic
from sanic.views import HTTPMethodView
from sanic.response import text

app = Sanic('some_name')

class SimpleAsyncView(HTTPMethodView):

  async def get(self, request):
      return text('I am async get method')

app.add_route(SimpleAsyncView.as_view(), '/')

URL-параметры

Если вам нужны какие-либо параметры URL, как указано в Руководстве по маршрутизации, включите их в определение метода.

class NameView(HTTPMethodView):

  def get(self, request, name):
    return text('Hello {}'.format(name))

app.add_route(NameView.as_view(), '/<name>')

декоратор

Если вы хотите добавить какой-либо декоратор в свой класс, вы можете установитьdecoratorпеременная класса. при звонкеas_view, они будут применены к классу.

class ViewWithDecorator(HTTPMethodView):
  decorators = [some_decorator_here]

  def get(self, request, name):
    return text('Hello I have a decorator')

app.add_route(ViewWithDecorator.as_view(), '/url')

построение URL

Если вы хотите создать URL-адрес для HTTPMethodView, помните, что имя класса будет тем, которое вы передадите.url_forКонечная точка. Например:

@app.route('/')
def index(request):
    url = app.url_for('SpecialClassView')
    return redirect(url)


class SpecialClassView(HTTPMethodView):
    def get(self, request):
        return text('Hello from the Special Class View!')


app.add_route(SpecialClassView.as_view(), '/special_class_view')

Используйте комбинированный вид

Using CompositionView

в видеHTTPMethodViewВ качестве альтернативы вы можете использоватьCompositionViewПереместите функцию обработчика за пределы класса представления.

Функции обработчика для каждого поддерживаемого метода HTTP определяются в другом месте исходного кода, а затем используютсяCompositionView.addМетод добавлен в представление. Первый параметр — это список обрабатываемых методов HTTP (например,['GET', 'POST']), Второй параметр — это обработчик. В следующем примере показано использование внешней функции-обработчика и встроенного лямбда-выражения.CompositionViewиспользование:

from sanic import Sanic
from sanic.views import CompositionView
from sanic.response import text

app = Sanic(__name__)

def get_handler(request):
    return text('I am a get method')

view = CompositionView()
view.add(['GET'], get_handler)
view.add(['POST', 'PUT'], lambda request: text('I am a post/put method'))

# Use the new view to handle requests to the base URL
app.add_route(view, '/')

Note: В настоящее время вы не можете использоватьurl_forСоздайте URL-адрес для CompositionView.

пользовательский протокол

ПРИМЕЧАНИЕ. Это расширенное использование, и большинству читателей такая функция не понадобится.

Вы можете изменить поведение санатового протокола, указав пользовательский протокол, который должен бытьasyncio.protocolподкласс .然后,该协议可以作为sanic.runПротокол аргумента ключевого слова метода передается.

Конструктор пользовательского класса протокола получает от Sanic следующие аргументы ключевого слова.

  • loop: асинхронно-совместимый цикл обработки событий.
  • connections: Коллекция, используемая для хранения объектов протокола. Когда Саник получаетSIGINTилиSIGTERMкогда он выполняетсяprotocol.close_if_idleЗакрывает все объекты протокола, хранящиеся в этой коллекции.
  • signal: с участиемstoppedатрибутsanic.server.Signalобъект. Когда Санич получилSIGINTилиSIGTERM,signal.stoppedраспределятьTrue.
  • request_handler: взять одинsanic.request.Requestобъект иresponseСопрограмма с обратным вызовом в качестве аргумента.
  • error_handler: обработчик, вызываемый при возникновении исключенияsanic.exceptions.Handler.
  • request_timeout: количество секунд до истечения срока ожидания запроса.
  • request_max_size: Целое число, указывающее максимальный размер запроса в байтах.

Example

Если обработчик не возвращаетHTTPResponseобъект, в протоколе по умолчанию произошла ошибка.

путем переписыванияwrite_responseметод протокола, если обработчик возвращает строку, она будет преобразована вHTTPResponseобъект.

from sanic import Sanic
from sanic.server import HttpProtocol
from sanic.response import text

app = Sanic(__name__)


class CustomHttpProtocol(HttpProtocol):

    def __init__(self, *, loop, request_handler, error_handler,
                 signal, connections, request_timeout, request_max_size):
        super().__init__(
            loop=loop, request_handler=request_handler,
            error_handler=error_handler, signal=signal,
            connections=connections, request_timeout=request_timeout,
            request_max_size=request_max_size)

    def write_response(self, response):
        if isinstance(response, str):
            response = text(response)
        self.transport.write(
            response.output(self.request.version)
        )
        self.transport.close()


@app.route('/')
async def string(request):
    return 'string'


@app.route('/1')
async def response(request):
    return text('response')

app.run(host='0.0.0.0', port=8000, protocol=CustomHttpProtocol)

SSL Example

SSLContext может быть передан в:

import ssl
context = ssl.create_default_context(purpose=ssl.Purpose.CLIENT_AUTH)
context.load_cert_chain("/path/to/cert", keyfile="/path/to/keyfile")

app.run(host="0.0.0.0", port=8443, ssl=context)

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

ssl = {'cert': "/path/to/cert", 'key': "/path/to/keyfile"}
app.run(host="0.0.0.0", port=8443, ssl=ssl)

бревно

Logging

Sanic позволяет вамpython3 logging APIРазличные типы журналирования запросов (журналы доступа, журналы ошибок). Если вы хотите создать новую конфигурацию, у вас должно быть некоторое базовое представление о ведении журнала python3.

Quick Start

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

from sanic import Sanic

app = Sanic('test')

@app.route('/')
async def test(request):
    return response.text('Hello World!')

if __name__ == "__main__":
  app.run(debug=True, access_log=True)

Чтобы использовать собственную конфигурацию ведения журнала, просто используйтеlogging.config.dictConfigили во время инициализацииSanicпройти черезlog_confiКусок:

app = Sanic('test', log_config=LOGGING_CONFIG)

Чтобы отключить ведение журнала, просто назначьте access_log=False:

if __name__ == "__main__":
  app.run(access_log=False)

Это позволит пропустить вызов функции регистрации при обработке запроса. Вы даже можете продолжить производство для дополнительной скорости:

if __name__ == "__main__":
  # disable debug messages
  app.run(debug=False, access_log=False)

Configuration

по умолчанию,log_configпараметры настроены на использованиеsanic.log.LOGGING_CONFIG_DEFAULTSНастройка словаря.

В sanic используются три регистратора.loggers, если вы хотите создать свою собственную конфигурацию ведения журнала, вы должны определить:

root: используется для регистрации внутренних сообщений.

sanic.error: для регистрации ошибок.

sanic.access: для доступа к журналу.

Log format

В дополнение к параметрам по умолчанию, предоставляемым python (asctime, levelname, message), Sanic также предоставляет регистратор для доступаloggerДругие параметры:

host (str): request.ip request (str): request.method + " " + request.url status (int): response.status byte (int): len(response.body)

Формат журнала доступа по умолчанию:

%(asctime)s - (%(name)s)[%(levelname)s][%(host)s]: %(request)s %(message)s %(status)d %(byte)d

контрольная работа

Testing

Конечные точки Sanic можно протестировать локально с помощью объекта test_client, в зависимости от подключенногоaiohttpбиблиотека.

test_clientпубличныйget,post,put,delete,patch,headиoptionsспособ запуска с приложением. Простой пример (с использованием pytest) выглядит следующим образом:

# Import the Sanic app, usually created with Sanic(__name__)
from external_server import app

def test_index_returns_200():
    request, response = app.test_client.get('/')
    assert response.status == 200

def test_index_put_not_allowed():
    request, response = app.test_client.put('/')
    assert response.status == 405

Внутренне каждый вызовtest_clientметод, приложение Sanic работает на127.0.0.1:42101, ваш тестовый запрос используетaiohttpвоплощать в жизнь.

test_clientМетод принимает следующие параметры и параметры ключевого слова:

uri(default '/') строка, представляющая тестовый uri.

gather_request(default True) логическое значение, определяющее, был ли исходный запрос возвращен функцией. Если установленоTrue, возвращаемое значение равно(request, response)кортеж, еслиFalseВозвращается только ответ.

server_kwargs (default {}) передается перед запуском тестового запросаapp.runдополнительные параметры.

debug(default False) логическое значение, определяющее, следует ли запускать сервер в режиме отладки.

Далее функция принимает*request_argsи**request_kwargs, которые передаются непосредственно в запросы aiohttp ClientSession.

Например, чтобы предоставить данные для запроса GET, вы должны сделать следующее:

def test_get_request_includes_data():
    params = {'key1': 'value1', 'key2': 'value2'}
    request, response = app.test_client.get('/', params=params)
    assert request.args.get('key1') == 'value1'

и предоставить данные для запроса JSON POST:

def test_post_json_request_includes_data():
    data = {'key1': 'value1', 'key2': 'value2'}
    request, response = app.test_client.post('/', data=json.dumps(data))
    assert request.json.get('key1') == 'value1'

Дополнительную информацию о доступных параметрах aiohttp можно найти по адресуClientSessionнашел в документации.

pytest-sanic

pytest-sanic— это плагин pytest, который помогает вам асинхронно тестировать код. Напишите тест:

async def test_sanic_db_find_by_id(app):
    """
    Let's assume that, in db we have,
        {
            "id": "123",
            "name": "Kobe Bryant",
            "team": "Lakers",
        }
    """
    doc = await app.db["players"].find_by_id("123")
    assert doc.name == "Kobe Bryant"
    assert doc.team == "Lakers"

pytest-sanicТакже предоставляет некоторые полезные устройства, такие как loop, unused_port, test_server, test_client.

@pytest.yield_fixture
def app():
    app = Sanic("test_sanic_app")

    @app.route("/test_get", methods=['GET'])
    async def test_get(request):
        return response.json({"GET": True})

    @app.route("/test_post", methods=['POST'])
    async def test_post(request):
        return response.json({"POST": True})

    yield app


@pytest.fixture
def test_cli(loop, app, test_client):
    return loop.run_until_complete(test_client(app, protocol=WebSocketProtocol))


#########
# Tests #
#########

async def test_fixture_test_client_get(test_cli):
    """
    GET request
    """
    resp = await test_cli.get('/test_get')
    assert resp.status == 200
    resp_json = await resp.json()
    assert resp_json == {"GET": True}

async def test_fixture_test_client_post(test_cli):
    """
    POST request
    """
    resp = await test_cli.post('/test_post')
    assert resp.status == 200
    resp_json = await resp.json()
    assert resp_json == {"POST": True}

развертывать

Deploying

Развертывание Sanic упрощается благодаря встроенному веб-серверу. в определенииsanic.Sanicпосле экземпляра. Мы можем вызвать со следующими ключевыми аргументамиrunметод:

  • host (default "127.0.0.1"): адрес для размещения сервера.
  • port (default 8000): Откройте порт сервера.
  • debug (default False): Включить отладочный вывод (замедлить сервер).
  • ssl (default None): зашифрованный SSLContext.
  • sock (default None): сокет, используемый сервером для приема соединений.
  • workers (default 1): количество созданных рабочих процессов.
  • loop (default None): Asyncio Convitible Coop. Если не указано, Sanic создаст свой собственный контур событий.
  • protocol (default HttpProtocol): asyncio.protocolподкласс .

процесс

Workers

По умолчанию Sanic использует только одно ядро ​​ЦП для прослушивания основного процесса. Чтобы провернуть сок, простоrunКоличество рабочих, указанное в параметре.

app.run(host='0.0.0.0', port=1337, workers=4)

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

запустить через командную строку

Если вы предпочитаете использовать аргументы командной строки, вы можете запустить сервер Sanic, выполнив модуль. Например, если вас зовутserver.pyИнициализируйте Sanic в файле какapp, вы можете запустить сервер следующим образом:

python -m sanic server.app --host=0.0.0.0 --port=1337 --workers=4

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

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=1337, workers=4)

Бег через Гуникорна

Gunicorn«Зеленый единорог» — это HTTP-сервер WSGI для UNIX. Это предварительная рабочая модель, портированная из проекта Ruby Unicorn.

Для того, чтобы использовать Sunicorn Run Sanic Application, вам нужно болетьюworker-classпараметры используют спец.sanic.worker.GunicornWorker:

gunicorn myapp:app --bind 0.0.0.0:1337 --worker-class sanic.worker.GunicornWorker

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

Для получения дополнительной информации см.Документация Гуникорна.

Асинхронная поддержка

Asynchronous support

Если вам нужно поделиться процессом sanic с другими приложениями, особенноloop, что уместно. Однако обратите внимание, что этот метод не поддерживает использование многопроцессорной обработки и не является предпочтительным способом запуска приложения в целом.

Вот неполный пример (см.run_asyn.pyв некоторых более практических примерах):

server = app.create_server(host="0.0.0.0", port=8000)
loop = asyncio.get_event_loop()
task = asyncio.ensure_future(server)
loop.run_forever()

расширять

Extensions

Список расширений Sanic, созданных сообществом.

  • Sanic-Plugins-Framework: библиотека для простого создания и использования плагинов Sanic.

  • Sessions: поддержка сессий. Позволяет использовать Redis, memcache или хранилище в памяти.

  • CORS: A port of flask-cors.

  • Compress: позволяет легко сжимать ответы Sanic. Порт Flask-Compress.

  • Jinja2: Поддержка шаблона Jinja2.

  • JWT: расширение аутентификации для веб-токенов JSON (JWT).

  • OpenAPI/Swagger: поддержка OpenAPI, а также пользовательский интерфейс Swagger.

  • Pagination: Простое пейджинговое обслуживание.

  • Motor: Простая оболочка двигателя.

  • Sanic CRUD: Генерация CRUD REST API с моделями peewee.

  • UserAgent: Add user_agentзапрашивать.

  • Limiter: ограничение скорости для sanic.

  • Sanic EnvConfig: Введите переменные среды в конфигурацию Sanic.

  • Babel: существуетBabelДобавьте поддержку i18n/l10n в приложение Sanic с помощью библиотеки.

  • Dispatch: В среднем WerkzeugDispatcherMiddlewareУволенный планировщик. Может действовать как адаптер sanicto-wsgi.

  • Sanic-OAuth: библиотека OAuth для подключения и создания собственного поставщика токенов.

  • Sanic-nginx-docker-example: использоватьSanicЧтобы построить простой в использовании каркасный проект, вам нужно использоватьnginx, организованный docker-compose.

  • sanic-graphql: Графико интегрируется с Sanic.

  • sanic-prometheus: Стандарт PROMETHEUS SANIC.

  • Sanic-RestPlus: порт Flask-RestPlus для Sanic.Полнофункциональный REST API с генерацией SwaggerUI.

  • sanic-transmute: расширения Sanic, которые генерируют API из функций и классов Python и автоматически генерируют документацию пользовательского интерфейса Swagger.

  • pytest-sanic: плагин pytest для Sanic. Это поможет вам протестировать ваш код асинхронно.

  • jinja2-sanic: средство визуализации шаблонов jinja2 от Sanic.

API Reference

sanic - официальная документация

sanic-githup