Знакомство с библиотекой Spyne


В данной статье я хочу рассказать о замечательной Python-библиотеке Spyne. Мое знакомство с Spyne началось в тот момент, когда передо мной поставили задачу написать Веб-сервис, который будет принимать и отдавать запросы через SOAP-протокол. Немного погуглив я наткнулся на Spyne, которая является форком библиотеки soaplib. А еще я был удивлен, насколько мало русскоязычной информации встречается о данной библиотеке.

С помощью Spyne можно писать веб-сервисы, которые умеют работать с SOAP, JSON, YAML, а написанный скрипт можно запустить через mod_wsgi Apache. Итак, давайте рассмотрим несколько примеров, напишем работающие скрипты и настроим так, чтобы скрипты работали через apache.

1. SOAP-сервис


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

Первым делом необходимо получить API-ключ, чтобы сказать Яндексу, что мы свои. Как можно это сделать, смотрим тут.

Теперь переходим непосредственно к разработке.

Устанавливаем необходимые библиотеки: «pytz», «spyne», а также «yandex_translate». Библиотеки ставятся очень легко через pip.

Код приложения выглядит следующим образом:

from spyne import Application, rpc, ServiceBase, Unicode
from lxml import etree
from spyne.protocol.soap import Soap11
from spyne.protocol.json import JsonDocument
from spyne.server.wsgi import WsgiApplication
from yandex_translate import YandexTranslate

class Soap(ServiceBase):
    @rpc(Unicode, _returns=Unicode)
    def Insoap(ctx, words):
        print(etree.tostring(ctx.in_document))
        translate = YandexTranslate('trnsl.1.1.201somesymbols')
        tr = translate.translate(words, 'en')
        tr_answer = tr['text'][0]
        return tr_answer
app = Application([Soap], tns='Translator',
                          in_protocol=Soap11(validator='lxml'),
                         out_protocol=Soap11()
application = WsgiApplication(app)
if __name__ == '__main__':
    from wsgiref.simple_server import make_server
    server = make_server('0.0.0.0', 8000, application)
    server.serve_forever()

Разберем код:

После импортирования необходимых библиотек, мы создали класс «Soap» с аргументом «ServiceBase». Декоратор "@rpc(Unicode, _returns=Unicode)" определяет тип входящих аргументов («Unicode») и исходящих ответов ("_returns=Unicode"). Список доступных типов аргументов можно посмотреть в официальной документации.. Далее создается метод «Insoap» с аргументами «ctx» и «words». Аргумент «ctx» очень важен, так как в нем содержится много информации о входящих запросах. Строка «print(etree.tostring(ctx.in_document))» выводит на экран входящий xml-запрос, в таком виде, в каком нам его отправил пользователь. В некоторых моментах это может быть важно.

Например, мне в ходе написания веб-сервиса нужно было вытащить входящий xml-запрос и записать в базу данных. Но как вытащить этот xml-запрос не упомянуто в официальной документации Spyne. Burak Arslan (автор Spyne) порекомендовал смотреть в сторону библиотеки lxml. Только после этого я нашел ответ и результат видите в данноме скрипте. Далее наш метод обращается в Яндекс-переводчик и возвращает клиенту полученный от Яндекс-переводчика результат.

Переменная «app» определяет настройки нашего веб-сервиса: «Application([Soap]» — указывается, какой класс инициализируется (их может быть несколько), параметры «in_protocol» и «out_protocol» определяет тип входящих и исходящих запросов, в нашем случае это SOAP v1.1.

Строкой «application = WsgiApplication(app)» определяется, чтобы наш скрипт мог работать через wsgi.

Важно! имя переменного обязательно должен быть «application», чтобы наше приложение мог работать через apache с помощью mod_wsgi. Последующие строки кода инициализирует и запускает Веб-сервер по порту 8000.

Запускаем скрипт и можно приступать к тестированию. Для этих целей я использую SoapUI. Удобство состоит в том, что после запуска и настройки для работы с SOAP сервером, SoapUI автоматически формирует xml-запрос. Настроимся на URL: localhost:8000?wsdl (при условии, что скрипт запущен на локальной машине), и наш xml-запрос выглядит следующим образом:

Тело xml-запроса
<soapenv:Envelope xmlns:soapenv=«schemas.xmlsoap.org/soap/envelope» xmlns:tran=«Translator»>
<soapenv:Header/>
<soapenv:Body>
<tran:Insoap>
<tran:words>Тестируем наше приложение</tran:words>
</tran:Insoap>
</soapenv:Body>
</soapenv:Envelope>

Наш веб-сервис дал следующий ответ:

Ответ от сервера
<soap11env:Envelope xmlns:soap11env=«schemas.xmlsoap.org/soap/envelope» xmlns:tns=«Translator»>
<soap11env:Body>
<tns:InsoapResponse>
<tns:InsoapResult>Test our app</tns:InsoapResult>
</tns:InsoapResponse>
</soap11env:Body>
</soap11env:Envelope>

Все просто, не правда ли?

2. REST-сервис


Предположим, что теперь у нас поменялось тех. задание, и нужно сделать веб-сервис, который работает через JSON. Что делать? Переписывать наш сервис на другом фреймворке, например Django Rest Framework или Flask? или можно обойтись меньшими усилиями? Да, можно! И нужно!

Библиотека Spyne нам в помошь.

Все что потребуется поменять в нашем приложении, это переменную «app» привести к следующему виду:

app = Application([Soap], tns='Translator',
                          in_protocol=JsonDocument(validator='soft'),
                          out_protocol=JsonDocument())

Запускаем наш веб-сервис и тестируемся.

Наш JSON-запрос выглядит так:

Тело JSON-запроса
{«Insoap»: {«words»:«тестируем наш веб-сервис. Используем JSON»}}

Веб сервер вернул следующий ответ:

Ответ веб-сервера
«test our web service. Use JSON»

3. Вывод в продакшн


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

Строки для удаления
if __name__ == '__main__':
    from wsgiref.simple_server import make_server
    server = make_server('0.0.0.0', 8000, application)
    server.serve_forever()


Ура! Наш веб-сервис готов к использованию в эксплуатации.

P.S. о дополнительных возможностях Spyne (а их немало) всегда можно ознакомиться на официальном сайте, чего я вам очень рекомендую.
Поделиться с друзьями
-->

Комментарии (5)


  1. alexZzZzZzZ
    28.07.2017 11:42

    Мне кажется, или ответ сервера в режиме REST-сервиса не похож на json?


    1. alexZzZzZzZ
      28.07.2017 11:46

      А вот возможность быстро поменять тип транспорта — это прекрасно.
      Где еще можно запрос с http json поменять на zeromq очередь в одну строку?
      Вопрос только в производительности.


    1. maxpy
      30.07.2017 20:53

      Если вы подразумеваете, что ответ не в формате «key»: «value», то да, ответ отличается. Но все REST-клиенты понимают ответ как json, будь это SoapUI, или замечательный requests.


  1. Prostakov_Alexey
    30.07.2017 20:48

    Если Вы упомянули про SoapUI хорошо бы написать, какой адрес WSDL описания для создания проекта. http://localhost:8000/Translator?wsdl А так очень полезная статья, я делаю по старинке через flask, предложенный способ намного лучше и проще.


    1. maxpy
      30.07.2017 20:59

      Добавил в статью