Python web service: Как просто написать распределенный веб-сервис на Python + AMQP / Хабр

Содержание

Как просто написать распределенный веб-сервис на Python + AMQP / Хабр

Привет, Хабр. Я уже довольно давно пишу на Python. Недавно пришлось разбираться с RabbitMQ. Мне понравилось. Потому что он без всяких проблем (понятно, что с некоторыми тонкостями) собирается в кластер. Тут я подумал: а неплохо бы его использовать в качестве очереди сообщений в кусочке API проекта, над которым я работаю. Сам API написан на tornado, основная мысль была в исключении блокирующего кода из API. Все синхронные операции выполнялись в пуле тредов.

Первое, что я решил, это сделать отдельный процесс(ы) «worker», который бы брал на себя всю синхронную работу. Задумал, чтобы «worker» был максимально прост, и делал задачи из очереди одну за другой. Скажем, выбрал из базы что-нибудь, ответил, взял на себя следующую задачу и так далее. Самих «worker»ов можно запустить много и тогда AMQP выступает уже в роли некоего подобия IPC.

Спустя некоторое время из этого вырос модуль, который берет на себя всю рутину связанную с AMQP и передачей сообщений туда и назад, а также сжимает их gzipом, если данных слишком много. Так родился crew. Собственно, используя его, мы с вами напишем простой API, который будет состоять из сервера на tornado и простых и незамысловатых «worker» процессов. Забегая вперед скажу, что весь код доступен на github, а то, о чем я буду рассказывать дальше, собрано в папке example.


Подготовка

Итак, давайте разберемся по порядку. Первое, что нам нужно будет сделать — это установить RabbitMQ. Как это делать я описывать не буду. Скажу лишь то, что на той-же убунте он ставится и работает из коробки. У меня на маке единственное, что пришлось сделать, это поставить LaunchRocket, который собрал все сервисы, что были установлены через homebrew и вывел в GUI:

Дальше создадим наш проект virtualenv и установим сам модуль через pip:

mkdir -p api
cd api
virtualenv env
source env/bin/activate
pip install crew tornado

В зависимостях модуля умышленно не указан tornado, так как на хосте с workerом его может и не быть. А на веб-части обычно создают requirements.txt, где указаны все остальные зависимости.

Код я буду писать частями, чтобы не нарушать порядок повествования. То, что у нас получится в итоге, можно посмотреть тут.

Пишем код

Сам tornado сервер состоит из двух частей. В первой части мы определяем обработчики запросов handlers, а во второй запускается event-loop. Давайте напишем сервер и создадим наш первый метод api.

Файл master.py:

# encoding: utf-8

import tornado.ioloop
import tornado.gen
import tornado.web
import tornado.options


class MainHandler(tornado.web.RequestHandler):
    @tornado.gen.coroutine
    def get(self):
        # Вызываем задачу test c приоритетом 100
        resp = yield self.application.crew.call('test', priority=100)
        self.write("{0}: {1}".format(type(resp).__name__, str(resp)))


application = tornado.web.Application(
    [
        ('/', MainHandler),
    ],
    autoreload=True,
    debug=True,
)


if __name__ == "__main__":
    tornado.options.parse_command_line()
    application.listen(8888)
    tornado.ioloop.IOLoop.instance().start()

Благодаря coroutine в торнадо, код выглядит просто. Можно написать тоже самое без coroutine.

Файл master.py:

class MainHandler(tornado.web.RequestHandler):
    def get(self):
        # Вызываем задачу test c приоритетом 100
        self.application.crew.call('test', priority=100, callback=self._on_response)

    def _on_response(resp, headers):
        self.write("{0}: {1}".format(type(resp).__name__, str(resp)))

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

Теперь напишем простой worker:

Файл worker.py:

# encoding: utf-8

from crew.worker import run, context, Task

@Task('test')
def long_task(req):
    context.settings.counter += 1
    return 'Wake up Neo.\n'

run(
    counter=0,      # This is a part of this worker context
)

Итак, как видно в коде, есть простая функция, обернутая декоратором Task(«test»), где test — это уникальный идентификатор задачи. В вашем worker не может быть двух задач с одинаковыми идентификаторами. Конечно, правильно было бы назвать задачу «crew.example.test» (так обычно и называю в продакшн среде), но для нашего примера достаточно просто «test».

Сразу бросается в глаза context.settings.counter. Это некий контекст, который инициализируется в worker процессе при вызове функции run. Также в контексте уже есть context.headers — это заголовки ответа для отделения метаданных от ответа. В примере с callback-функцией именно этот словарь передается в _on_response.

Заголовки сбрасываются после каждого ответа, а вот context.settings — нет. Я использую context.settings для передачи в функции worker(ы) соединения с базой данных и вообще любого другого объекта.

Также worker обрабатывает ключи запуска, их не много:

$ python worker.py --help
Usage: worker.py [options]

Options:
  -h, --help            show this help message and exit
  -v, --verbose         make lots of noise
  --logging=LOGGING     Logging level
  -H HOST, --host=HOST  RabbitMQ host
  -P PORT, --port=PORT  RabbitMQ port

URL подключения к базе и прочие переменные можно брать из переменный окружения. Поэтому worker в параметрах ждет только как ему соединиться c AMQP (хост и порт) и уровень логирования.

Итак, запускаем все и проверяем:

$ python master.py & python worker.py

Работает, но что случилось за ширмой?

При запуске tornado-сервера tornado подключился к RabbitMQ, создал Exchange DLX и начал слушать очередь DLX. Это Dead-Letter-Exchange — специальная очередь, в которую попадают задачи, которые не взял ни один worker за определенный timeout. Также он создал очередь с уникальным идентификатором, куда будут поступать ответы от workerов.

После запуска worker создал по очереди на каждую обернутую декоратором Task очередь и подписался на них. При поступлении задачи воркер main-loop создает один поток, контролируя в главном потоке время исполнения задачи и выполняет обернутую функцию. После return из обернутой функции сериализует его и ставит в очередь ответов сервера.

После поступления запроса tornado-сервер cтавит задачу в соответствующую очередь, указывая при этом идентификатор своей уникальной очереди, в которую должен поступить ответ. Если ни один воркер не взял задачу, тогда RabbitMQ перенаправляет задачу в exchange DLX и tornado-сервер получает сообщение о том, что истек таймаут пребывания очереди, генерируя исключение.

Зависшая задача

Чтобы продемонстрировать, как работает механизм завершения задач, которые повисли в процессе выполнения, напишем еще один веб-метод и задачу в worker.

В файл master.py добавим:

class FastHandler(tornado.web.RequestHandler):
    @tornado.gen.coroutine
    def get(self):
        try:
            resp = yield self.application.crew.call(
                'dead', persistent=False, priority=255, expiration=3,
            )
            self.write("{0}: {1}".format(type(resp).__name__, str(resp)))
        except TimeoutError:
            self.write('Timeout')
        except ExpirationError:
            self.write('All workers are gone')

И добавим его в список хендлеров:

application = tornado.web.Application(
    [
        (r"/", MainHandler),
        (r"/stat", StatHandler),
    ],
    autoreload=True,
    debug=True,
)

А в worker.py:

@Task('dead')
def infinite_loop_task(req):
    while True:
        pass

Как видно из приведенного выше примера, задача уйдет в бесконечный цикл. Однако, если задача не выполнится за 3 секунды (считая время получения из очереди), main-loop в воркере пошлет потоку исключение SystemExit. И да, вам придется обработать его.

Контекст

Как уже упоминалось выше, контекст — это такой специальный объект, который импортируется и имеет несколько встроенных переменных.

Давайте сделаем простую статистику по ответам нашего worker.

В файл master.py добавим следующий handler:

class StatHandler(tornado.web.RequestHandler):

    @tornado.gen.coroutine
    def get(self):
        resp = yield self.application.crew.call('stat', persistent=False, priority=0)
        self.write("{0}: {1}".format(type(resp).__name__, str(resp)))

Также зарегистрируем в списке обработчиков запросов:

application = tornado.web.Application(
    [
        (r"/", MainHandler),
        (r"/fast", FastHandler),
        (r"/stat", StatHandler),
    ],
    autoreload=True,
    debug=True,
)

Этот handler не очень отличается от предыдущих, просто возвращает значение, которое ему передал worker.

Теперь сама задача.

В файл worker.py добавим:

@Task('stat')
def get_counter(req):
    context.settings.counter += 1
    return 'I\'m worker "%s". And I serve %s tasks' % (context.settings.uuid, context.settings.counter)

Функция возвращает строку, с информацией о количестве задач, обработанных workerом.

PubSub и Long polling

Теперь реализуем пару обработчиков. Один при запросе будет просто висеть и ждать, а второй будет принимать POST данные. После передачи последних первый будет их отдавать.

master.py:

class LongPoolingHandler(tornado.web.RequestHandler):
    LISTENERS = []

    @tornado.web.asynchronous
    def get(self):
        self.LISTENERS.append(self.response)

    def response(self, data):
        self.finish(str(data))

    @classmethod
    def responder(cls, data):
        for cb in cls.LISTENERS:
            cb(data)

        cls.LISTENERS = []

class PublishHandler(tornado.web.RequestHandler):

    @tornado.gen.coroutine
    def post(self, *args, **kwargs):
        resp = yield self.application.crew.call('publish', self.request.body)
        self.finish(str(resp))

...

application = tornado.web.Application(
    [
        (r"/", MainHandler),
        (r"/stat", StatHandler),
        (r"/fast", FastHandler),
        (r'/subscribe', LongPoolingHandler),
        (r'/publish', PublishHandler),
    ],
    autoreload=True,
    debug=True,
)

application.crew = Client()
application.crew.subscribe('test', LongPoolingHandler.responder)

if __name__ == "__main__":
    application.crew.connect()
    tornado.options.parse_command_line()
    application.listen(8888)
    tornado.ioloop.IOLoop.instance().start()

Напишем задачу publish.

worker.py:

@Task('publish')
def publish(req):
    context.pubsub.publish('test', req)

Если же вам не нужно передавать управление в worker, можно просто публиковать прямо из tornado-сервера

class PublishHandler2(tornado.web.RequestHandler):

    def post(self, *args, **kwargs):
        self.application.crew.publish('test', self.request.body)
Параллельное выполнение заданий

Часто бывает ситуация, когда мы можем выполнить несколько заданий параллельно. В crew есть для этого небольшой синтаксический сахар:
class Multitaskhandler(tornado.web.RequestHandler):

    @tornado.gen.coroutine
    def get(self, *args, **kwargs):
        with self.application.crew.parallel() as mc:
            # mc - multiple calls
            mc.call('test')
            mc.call('stat')
            test_result, stat_result = yield mc.result()
            self.set_header('Content-Type', 'text/plain')
            self.write("Test result: {0}\nStat result: {1}".format(test_result, stat_result))

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

Но нужно быть осторожным, так как какая-то задача может вызвать исключение. Оно будет приравнено непосредственно переменной. Таким образом, вам нужно проверить, не является ли test_result и stat_result экземплярами класса Exception.

Планы на будущее

Когда eigrad предложил написать прослойку, которой можно запустить любое wsgi приложение с помощью crew, мне эта идея сразу понравилась. Только представьте, запросы хлынут не на ваше wsgi приложение, а будут равномерно поступать через очередь на wsgi-worker.

Я никогда не писал wsgi сервер и даже не знаю, с чего начать. Но вы можете мне помочь, pull-requestы я принимаю.

Также думаю дописать client для еще одного популярного асинхронного фреймворка, для twisted. Но пока разбираюсь с ним, да и свободного времени не хватает.

Благодарности

Спасибо разработчикам RabbitMQ и AMQP. Замечательные идеи.

Также спасибо вам, читатели. Надеюсь, что вы не зря потратили время.

SOAP и REST сервисы с помощью Python-библиотеки Spyne / Хабр

Знакомство с библиотекой 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-запрос выглядит следующим образом:


Наш веб-сервис дал следующий ответ:
Все просто, не правда ли?

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 (а их немало) всегда можно ознакомиться на официальном сайте, чего я вам очень рекомендую.

HTTP и веб-сервисы — Погружение в Python 3

Погружение

HTTP веб-сервисы являются программными способами передачи и получения данных от удаленных серверов, не используя ничего кроме операций HTTP. Если вы хотите получить данные с сервера используйте HTTP GET; если вы хотите отправить данные на сервер, используйте HTTP POST. Более продвинутые функции API HTTP веб-сервиса позволяют создавать, модифицировать, и удалять данные, используя HTTP PUT и HTTP DELETE. Иными словами, «глаголы» встроенные в HTTP протокол (GET, POST, PUT и DELETE) могут отображаться на операции уровня приложения для получения, создания, модифицирования и удаления данных.


HTTP web services are programmatic ways of sending and receiving data from remote servers using nothing but the operations of HTTP. If you want to get data from the server, use HTTP GET; if you want to send new data to the server, use HTTP POST. Some more advanced HTTP web service APIs also allow creating, modifying, and deleting data, using HTTP PUT and HTTP DELETE. In other words, the «verbs» built into the HTTP protocol (GET, POST, PUT, and DELETE) can map directly to application-level operations for retrieving, creating, modifying, and deleting data.


Главное приемущество такого подхода это простота, и эта простота оказалась популярной. Данные — обычно XML или JSON — могут быть построены или сохранены статически, или динамически сгенерированы скриптом на стороне сервера, а все современные языки (включая Python, конечно же!) включают в себя HTTP библиотеку для их загрузки. Отладка также проста; потому что каждый ресурс в HTTP веб-сервесе имеет уникальный адрес (в форме URL), вы можете загрузить его в ваш веб браузер и немедленно увидеть сырые данные.


The main advantage of this approach is simplicity, and its simplicity has proven popular. Data — usually XML or JSON — can be built and stored statically, or generated dynamically by a server-side script, and all major programming languages (including Python, of course!) include an HTTP library for downloading it. Debugging is also easier; because each resource in an HTTP web service has a unique address (in the form of a URL), you can load it in your web browser and immediately see the raw data.


Примеры HTTP веб-сервисов:

* Google Data APIs позволяют вам взаимодействовать с широким набором сервисов Google, включая Blogger и YouTube.
* Flickr Services позволяет вам загружать и выгружать фотографии на Flickr.
* Twitter API позволяет вам публиковать обновления статусов на Twitter.
* и много других


Examples of HTTP web services:

   * Google Data APIs allow you to interact with a wide variety of Google services, including Blogger and YouTube.
   * Flickr Services allow you to upload and download photos from Flickr.
   * Twitter API allows you to publish status updates on Twitter.
   * …and many more


Python3 располагает двумя библиотеками для взаимодействия с HTTP веб-сервисами: http.client — это низкоуровневая библиотека, реализующая RFC 2616 — протокол HTTP. urllib.request — это уровень абстракции, построенный поверх http.client. Она предоставляет стандартный API для доступа к HTTP и FTP серверам, автоматически следует HTTP редиректам (перенаправлениям) и умеет работать с некоторыми распространенными формами HTTP аутентификации.


Python 3 comes with two different libraries for interacting with HTTP web services: http.client is a low-level library that implements RFC 2616, the HTTP protocol. urllib.request is an abstraction layer built on top of http.client. It provides a standard API for accessing both HTTP and FTP servers, automatically follows HTTP redirects, and handles some common forms of HTTP authentication.


Итак, какую же из ни следует использовать? Никакую. Вместо них лучше использовать httplib2 — стороннюю библиотеку с открытым кодом. Она более полно по сравнению с http.client реализует HTTP и в то же время предоставляет лучшую чем в urllib.request абстракцию.


So which one should you use? Neither of them. Instead, you should use httplib2, an open source third-party library that implements HTTP more fully than http.client but provides a better abstraction than urllib.request.


Чтобы понять, почему вашим выбором должна стать httplib2, сначала нужно понять протокол HTTP.


To understand why httplib2 is the right choice, you first need to understand HTTP.

14.2 Особенности HTTP

14.2 Features of HTTP


Есть пять важных особенностей, которые все HTTP клиенты должны поддерживать.


There are five important features which all HTTP clients should support.

14.2.1 Кэширование

14.2.1 Caching


Самая важная вещь в понимании о любом веб-сервисе это то, что доступ в сеть невероятно дорог. Я не имею в виду «доллары и центы» (хотя ширина канала не является бесплатной). Я говорю о том, что для открытия соединения, отправки запроса, и получения ответа от удаленного сервера требуется очень много времени. Даже на быстром ширококанальном соединении, задержка (время на передачу запроса и началом получения ответных данных) может по прежнему быть выше, чем вы ожидали. Ошибки роутеров, пропажа пакетов, хакерские атаки на промежуточные прокси — в интернет не бывает ни одной спокойной минуты, и с этим ничего нельзя поделать.


The most important thing to understand about any type of web service is that network access is incredibly expensive. I don’t mean «dollars and cents» expensive (although bandwidth ain’t free). I mean that it takes an extraordinary long time to open a connection, send a request, and retrieve a response from a remote server. Even on the fastest broadband connection, latency (the time it takes to send a request and start retrieving data in a response) can still be higher than you anticipated. A router misbehaves, a packet is dropped, an intermediate proxy is under attack — there’s never a dull moment on the public internet, and there may be nothing you can do about it.


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


HTTP is designed with caching in mind. There is an entire class of devices (called «caching proxies») whose only job is to sit between you and the rest of the world and minimize network access. Your company or ISP almost certainly maintains caching proxies, even if you’re unaware of them. They work because caching built into the HTTP protocol.


Вот конкретный пример работы кэширования. С помощью своего браузера вы посетили сайт diveintomark.org. На этом сайте имеется картинка wearehugh.com/m.jpg. Когда ваш браузер загружает эту картинку, сервер включает в свой ответ следующие HTTP заголовки:


Here’s a concrete example of how caching works. You visit diveintomark.org in your browser. That page includes a background image, wearehugh.com/m.jpg. When your browser downloads that image, the server includes the following HTTP headers:

HTTP/1.1 200 OK
Date: Sun, 31 May 2009 17:14:04 GMT
Server: Apache
Last-Modified: Fri, 22 Aug 2008 04:28:16 GMT
ETag: "3075-ddc8d800"
Accept-Ranges: bytes
Content-Length: 12405
Cache-Control: max-age=31536000, public
Expires: Mon, 31 May 2010 17:14:04 GMT
Connection: close
Content-Type: image/jpeg

Python и разработка простого веб-приложения, использующего технологии машинного обучения

Тот, кто занимается машинным обучением (Machine Learning, ML), обычно, реализуя различные проекты, выполняет следующие действия: сбор данных, их очистка, разведочный анализ данных, разработка модели, публикация модели в локальной сети или в интернете. Вот хорошее видео, в котором можно узнать подробности об этом.


Жизненный цикл проекта в сфере машинного обучения

Этап публикации модели завершает жизненный цикл ML-проектов. Он так же важен для дата-сайентистов и специалистов по машинному обучению, как и другие этапы. Обычные подходы к публикации моделей предусматривают использование универсальных фреймворков, таких, как Django или Flask. Главные проблемы тут заключаются в том, что для применения подобных инструментов требуются особые знания и навыки, и в том, что работа с ними может потребовать немалых затрат времени.

Автор статьи, перевод которой мы сегодня публикуем, хочет рассказать о том, как, используя Python-библиотеки streamlit, pandas и scikit-learn, создать простое веб-приложение, в котором применяются технологии машинного обучения. Он говорит, что размер этого приложения не превышает 50 строк. Статья основана на этом видео, которое можно смотреть параллельно с чтением. Инструменты, которые будут здесь рассмотрены, кроме прочего, позволяют ускорить и упростить развёртывание ML-проектов. 

Обзор модели, определяющей вид цветка ириса


Сегодня мы создадим простое веб-приложение, использующее технологии машинного обучения. Оно будет классифицировать цветки ириса из выборки Фишера, относя их к одному из четырёх видов: ирис щетинистый (iris setosa), ирис версиколор (iris versicolor), ирис виргинский (iris virginica). Возможно, вы уже видели множество ML-примеров, построенных на основе этого знаменитого набора данных. Но, надеюсь, то, что я тут буду рассматривать ещё один такой пример, вам не помешает. Ведь этот набор — он как «lorem ipsum» — классический бессмысленный текст-заполнитель, который вставляют в макеты страниц.

Нам, чтобы построить модель и опубликовать её где-нибудь, понадобятся библиотеки streamlit, pandas и scikit-learn. Взглянем на общую схему проекта. Он будет состоять из двух больших частей: фронтенд и бэкенд.

Во фронтенд-части приложения, а именно, на веб-странице, будет боковая панель, находящаяся слева, в которой можно будет вводить входные параметры модели, которые связаны с характеристиками цветков ириса: длина лепестка (petal length), ширина лепестка (petal width), длина чашелистика (sepal length), ширина чашелистика (sepal width). Эти данные будут передаваться бэкенду, где предварительно обученная модель будет классифицировать цветки, используя заданные характеристики. Фактически, речь идёт о функции, которая, получая характеристики цветка, возвращает его вид. Результаты классификации отправляются фронтенду.

В бэкенд-части приложения то, что ввёл пользователей, сохраняется в датафрейме, который будет использоваться в виде тестовых данных для модели. Потом будет построена модель для обработки данных. В ней будет применяться алгоритм «случайный лес» из библиотеки scikit-learn. И наконец, модель будет применена для классификации данных, введённых пользователем, то есть — для определения вида цветка. Кроме того, вместе со сведениями о виде цветка, будут возвращаться и данные о прогностической вероятности. Это позволит нам определить степень достоверности результатов классификации.

Установка библиотек


Как уже было сказано, здесь мы будем пользоваться тремя библиотеками: streamlit, pandas и scikit-learn. Установить их можно, пользуясь pip install:
pip install streamlit
pip install pandas
pip install -U scikit-learn

Разработка веб-приложения


Теперь напишем код приложения. Проект у нас довольно скромный. Он состоит из менее чем 50 строк кода. А если точнее — то их тут всего 48. Если же этот код «уплотнить», избавившись от комментариев и пустых строк, то размер текста программы сократится до 36 строк.
import streamlit as st
import pandas as pd
from sklearn import datasets
from sklearn.ensemble import RandomForestClassifier

st.write("""
# Simple Iris Flower Prediction App
This app predicts the **Iris flower** type!
""")

st.sidebar.header('User Input Parameters')

def user_input_features():
    sepal_length = st.sidebar.slider('Sepal length', 4.3, 7.9, 5.4)
    sepal_width = st.sidebar.slider('Sepal width', 2.0, 4.4, 3.4)
    petal_length = st.sidebar.slider('Petal length', 1.0, 6.9, 1.3)
    petal_width = st.sidebar.slider('Petal width', 0.1, 2.5, 0.2)
    data = {'sepal_length': sepal_length,
            'sepal_width': sepal_width,
            'petal_length': petal_length,
            'petal_width': petal_width}
    features = pd.DataFrame(data, index=[0])
    return features

df = user_input_features()

st.subheader('User Input parameters')
st.write(df)

iris = datasets.load_iris()
X = iris.data
Y = iris.target

clf = RandomForestClassifier()
clf.fit(X, Y)

prediction = clf.predict(df)
prediction_proba = clf.predict_proba(df)

st.subheader('Class labels and their corresponding index number')
st.write(iris.target_names)

st.subheader('Prediction')
st.write(iris.target_names[prediction])
#st.write(prediction)

st.subheader('Prediction Probability')
st.write(prediction_proba)

Разбор кода


Теперь разберём этот код.

▍Импорт библиотек

import streamlit as st
import pandas as pd
from sklearn import datasets
from sklearn.ensemble import RandomForestClassifier

В этих строках мы импортируем библиотеки streamlit и pandas, назначая им, соответственно, псевдонимы st и pd. Мы, кроме того, импортируем пакет datasets из библиотеки scikit-learn (sklearn). Мы воспользуемся этим пакетом ниже, в команде iris = datasets.load_iris(), для загрузки интересующего нас набора данных. И наконец, тут мы импортируем функцию RandomForestClassifier() из пакета sklearn.ensemble.

▍Формирование боковой панели

st.sidebar.header('User Input Parameters')

В этой строке мы описываем заголовок боковой панели, используя функцию st.sidebar.header(). Обратите внимание на то, что тут sidebar стоит между st и header(), что и даёт полное имя функции st.sidebar.header(). Эта функция сообщает библиотеке streamlit о том, что мы хотим поместить заголовок в боковую панель.
def user_input_features():
    sepal_length = st.sidebar.slider('Sepal length', 4.3, 7.9, 5.4)
    sepal_width = st.sidebar.slider('Sepal width', 2.0, 4.4, 3.4)
    petal_length = st.sidebar.slider('Petal length', 1.0, 6.9, 1.3)
    petal_width = st.sidebar.slider('Petal width', 0.1, 2.5, 0.2)
    data = {'sepal_length': sepal_length,
            'sepal_width': sepal_width,
            'petal_length': petal_length,
            'petal_width': petal_width}
    features = pd.DataFrame(data, index=[0])
    return features

Здесь мы объявляем функцию user_input_features(), которая берёт данные, введённые пользователем (то есть — четыре характеристики цветка, которые вводятся с использованием ползунков), и возвращает результат в виде датафрейма. Стоит отметить, что каждый входной параметр вводится в систему с помощью ползунка. Например, ползунок для ввода длины чашелистика (sepal length) описывается так: st.sidebar.slider(‘Sepal length’, 4.3, 7.9, 5.4). Первый из четырёх входных аргументов этой функции задаёт подпись ползунка, выводимую выше него. Это, в данном случае, текст Sepal length. Два следующих аргумента задают минимальное и максимальное значения, которые можно задавать с помощью ползунка. Последний аргумент задаёт значение, выставляемое на ползунке по умолчанию, при загрузке страницы. Здесь это — 5.4.

▍Создание модели

df = user_input_features()

Здесь датафрейм, сформированный функцией user_input_features(), которую мы только что обсудили, записывается в переменную df.
iris = datasets.load_iris()

Загрузка набора данных Iris из пакета sklearn.datasets и запись его в переменную iris.
X = iris.data

Создание переменной Х, содержащей сведения о 4 характеристиках цветка, которые имеются в iris.data.
Y = iris.target

Создание переменной Y, которая содержит сведения о виде цветка. Эти сведения хранятся в iris.target.
clf = RandomForestClassifier()

Здесь мы, пользуясь функцией RandomForestClassifier(), назначаем классификатор, основанный на алгоритме «случайный лес», переменной clf.
clf.fit(X, Y)

Тут мы обучаем модель, пользуясь функцией clf.fit(), передавая ей в качестве аргументов переменные X и Y. Суть происходящего заключается в том, что модель будет обучена определению вида цветка (Y) на основе его характеристик (X).
prediction = clf.predict(df)

Получение сведений о виде цветка с помощью обученной модели.
prediction_proba = clf.predict_proba(df)

Получение сведений о прогностической вероятности.

▍Формирование основной панели

st.write("""
# Simple Iris Flower Prediction App
This app predicts the **Iris flower** type!
""")

Здесь мы, пользуясь функцией st.write(), выводим текст. А именно, речь идёт о заголовке, выводимом в главной панели приложения, текст которого задан в формате Markdown. Символ # используется для указания того, что текст является заголовком. За строкой заголовка идёт строка обычного текста.
st.subheader('User Input parameters')

В этой строке, пользуясь функцией st.subheader(), мы указываем подзаголовок, выводимый в основной панели. Этот подзаголовок используется для оформления раздела страницы, в котором будет выведено содержимое датафрейма, то есть того, что было введено пользователем с помощью ползунков.
st.write(df)

Этой командой мы выводим на основную панель содержимое датафрейма df.
st.subheader('Class labels and their corresponding index number')

Данный код описывает второй подзаголовок основной панели. В этом разделе будут выведены данные о видах цветков.
st.write(iris.target_names)

Здесь, во второй раздел основной панели, выводятся названия видов цветков (setosa, versicolor и virginica) и соответствующие им номера (0, 1, 2).
st.subheader('Prediction')

Вывод третьего подзаголовка для раздела, в котором будет находиться результат классификации.
st.write(iris.target_names[prediction])

Вывод результата классификации. Стоит отметить, что содержимое переменной prediction — это номер вида цветка, выданный моделью на основе входных данных, введённых пользователем. Для того чтобы вывести название вида, используется конструкция iris.target_names[prediction].
st.subheader('Prediction Probability')

Выводим заголовок четвёртого (и последнего) раздела основной панели. Здесь будут представлены данные о прогностической вероятности.
st.write(prediction_proba)

Вывод данных о прогностической вероятности.

Запуск веб-приложения


Код приложения сохранён в файле iris-ml-app.py. Мы готовы к тому, чтобы его запустить. Сделать это можно, выполнив следующую команду в терминале:
streamlit run iris-ml-app.py

Если всё идёт как надо, через некоторое время вы должны увидеть следующее:
> streamlit run iris-ml-app.py
You can now view your Streamlit app in your browser.
Local URL: http://localhost:8501
Network URL: http://10.0.0.11:8501

Через несколько секунд должно появиться окно браузера, в котором будет открыт адрес http://localhost:8501.

То, что вы увидите, будет похоже на следующий рисунок.


Скриншот веб-приложения для классификации цветков ириса. Если щёлкнуть по стрелке, находящейся в левом верхнем углу окна, расположенного в верхней части рисунка, будет открыта боковая панель

Итоги


Можете себя поздравить: только что вы создали веб-приложение, в котором используются технологии машинного обучения. Вы вполне можете упомянуть подобное приложение в своём портфолио ML-проектов, а если хотите, можете опубликовать его на своём веб-сайте (правда, вы, вполне возможно, решите построить собственную модель, используя другие данные). 

Пользуетесь ли вы библиотекой streamlit?

Python и быстрые HTTP-клиенты / Блог компании RUVDS.com / Хабр

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

Существует множество HTTP-клиентов для Python. Самым распространённым среди них, и, к тому же, таким, с которым легко работать, можно назвать requests. Сегодня этот клиент является стандартом де-факто.

Постоянные соединения


Первая оптимизация, которую стоит принять во внимание при работе с HTTP, заключается в использовании постоянных соединений с веб-серверами. Постоянные соединения стали стандартом начиная с HTTP 1.1, но многие приложения до сих пор их не применяют. Этот недочёт легко объяснить, зная о том, что при использовании библиотеки requests в простом режиме (например — применяя её метод get) соединение с сервером закрывается после получения ответа от него. Для того чтобы этого избежать, приложению нужно использовать объект Session, который позволяет многократно использовать открытые соединения:
import requests

session = requests.Session()
session.get("http://example.com")
# Соединение используется повторно
session.get("http://example.com")

Соединения хранятся в пуле соединений (он, по умолчанию, рассчитан на 10 соединений). Размер пула можно настраивать:
import requests


session = requests.Session()
adapter = requests.adapters.HTTPAdapter(
    pool_connections=100,
    pool_maxsize=100)
session.mount('http://', adapter)
response = session.get("http://example.org")

Повторное использование TCP-соединения для отправки нескольких HTTP-запросов даёт приложению множество преимуществ в производительности:
  • Снижение нагрузки на процессор и снижение потребности в оперативной памяти (из-за того, что меньше соединений открываются одновременно).
  • Уменьшение задержек при выполнении запросов, идущих друг за другом (нет процедуры TCP-рукопожатия).
  • Исключения могут выбрасываться без дополнительных затрат времени на закрытие TCP-соединения.

Протокол HTTP 1.1, кроме того, поддерживает возможности конвейерной обработки запросов. Это позволяет отправлять несколько запросов в рамках одного и того же соединения, не дожидаясь ответов на ранее отправленные запросы (то есть — отправлять запросы «пакетами»). К несчастью, эту возможность библиотека requests не поддерживает. Однако конвейерная обработка запросов может быть не такой быстрой, как их параллельная обработка. И, кроме того, тут уместно обратить внимание вот на что: ответы на «пакетные» запросы должны отправляться сервером в той же последовательности, в какой он получил эти запросы. В результате получается не самая эффективная схема обработки запросов по принципу FIFO («first in, first out» — «первым пришёл — первым ушёл»).

Параллельная обработка запросов


У requests есть, кроме того, ещё один серьёзный недостаток. Это — синхронная библиотека. Вызов метода наподобие requests.get("http://example.org") блокирует программу до получения полного ответа HTTP-сервера. То, что приложению приходится ждать и ничего не делать, можно счесть минусом данной схемы организации взаимодействия с сервером. Можно ли сделать так, чтобы программа занималась чем-нибудь полезным вместо того, чтобы просто ждать?

Разумно спроектированное приложение может смягчить эту проблему благодаря использованию пула потоков, наподобие тех, которые предоставляет concurrent.futures. Это позволяет быстро организовать параллелизацию HTTP-запросов:

from concurrent import futures

import requests


with futures.ThreadPoolExecutor(max_workers=4) as executor:
    futures = [
        executor.submit(
            lambda: requests.get("http://example.org"))
        for _ in range(8)
    ]

results = [
    f.result().status_code
    for f in futures
]

print("Results: %s" % results)

Этот весьма полезный паттерн реализован в библиотеке requests-futures. При этом использование объектов Session прозрачно для разработчика:
from requests_futures import sessions


session = sessions.FuturesSession()

futures = [
    session.get("http://example.org")
    for _ in range(8)
]

results = [
    f.result().status_code
    for f in futures
]

print("Results: %s" % results)

По умолчанию создаётся воркер с двумя потоками, но программа легко может это значение настроить, передав объекту FuturSession аргумент max_workers или даже собственный исполнитель. Например, это может выглядеть так:
FuturesSession(executor=ThreadPoolExecutor(max_workers=10))

Асинхронная работа с запросами


Как уже было сказано, библиотека requests полностью синхронна. Это приводит к блокировке приложения во время ожидания ответа от сервера, что плохо сказывается на производительности. Одно из решений этой проблемы — выполнение HTTP-запросов в отдельных потоках. Но использование потоков — это дополнительная нагрузка на систему. К тому же это означает введение в программу схемы параллельной обработки данных, что устраивает не всех.

Начиная с Python 3.5 в стандартные возможности языка входят средства асинхронного программирования с использованием asyncio. Библиотека aiohttp предоставляет разработчику асинхронный HTTP-клиент, созданный на базе asyncio. Эта библиотека позволяет приложению отправлять целые серии запросов и продолжать работу. При этом для отправки очередного запроса не нужно ждать ответа на ранее отправленный запрос. В отличие от конвейерной обработки HTTP-запросов, aiohttp отправляет запросы параллельно, пользуясь несколькими соединениями. Это позволяет избежать «проблемы FIFO», описанной выше. Вот как выглядит использование aiohttp:

import aiohttp
import asyncio


async def get(url):
    async with aiohttp.ClientSession() as session:
        async with session.get(url) as response:
            return response


loop = asyncio.get_event_loop()

coroutines = [get("http://example.com") for _ in range(8)]

results = loop.run_until_complete(asyncio.gather(*coroutines))

print("Results: %s" % results)

Все описанные выше подходы (использование Session, потоков, concurrent.futures или asyncio) предлагают разные способы ускорения HTTP-клиентов.

Производительность


Ниже представлен пример кода, в котором HTTP-клиент отправляет запросы серверу httpbin.org. Сервер поддерживает API, умеющее, кроме прочего, имитировать систему, которой для ответа на запрос нужно много времени (в данном случае это 1 секунда). Здесь реализованы все рассмотренные выше техники и выполнено измерение их производительности:
import contextlib
import time

import aiohttp
import asyncio
import requests
from requests_futures import sessions

URL = "http://httpbin.org/delay/1"
TRIES = 10


@contextlib.contextmanager
def report_time(test):
    t0 = time.time()
    yield
    print("Time needed for `%s' called: %.2fs"
          % (test, time.time() - t0))


with report_time("serialized"):
    for i in range(TRIES):
        requests.get(URL)


session = requests.Session()
with report_time("Session"):
    for i in range(TRIES):
        session.get(URL)


session = sessions.FuturesSession(max_workers=2)
with report_time("FuturesSession w/ 2 workers"):
    futures = [session.get(URL)
               for i in range(TRIES)]
    for f in futures:
        f.result()


session = sessions.FuturesSession(max_workers=TRIES)
with report_time("FuturesSession w/ max workers"):
    futures = [session.get(URL)
               for i in range(TRIES)]
    for f in futures:
        f.result()


async def get(url):
    async with aiohttp.ClientSession() as session:
        async with session.get(url) as response:
            await response.read()

loop = asyncio.get_event_loop()
with report_time("aiohttp"):
    loop.run_until_complete(
        asyncio.gather(*[get(URL)
                         for i in range(TRIES)]))

Вот какие результаты были получены после запуска этой программы:
Time needed for `serialized' called: 12.12s
Time needed for `Session' called: 11.22s
Time needed for `FuturesSession w/ 2 workers' called: 5.65s
Time needed for `FuturesSession w/ max workers' called: 1.25s
Time needed for `aiohttp' called: 1.19s

Вот диаграмма результатов.

Результаты исследования производительности разных способов выполнения HTTP-запросов

Совершенно неудивительно то, что самой медленной оказалась простейшая синхронная схема выполнения запросов. Дело тут в том, что здесь запросы выполняются один за другим, без повторного использования соединения. Как результат, на то, чтобы выполнить 10 запросов, уходит 12 секунд.

Применение объекта Session, и, в результате, многократное использование соединений, позволяет сэкономить 8% времени. Это — уже очень хорошо, да и достичь этого весьма просто. Любому, кто заботится о производительности, стоит использовать хотя бы объект Session.

Если ваша система и ваша программа позволяют работать с потоками, то это — веская причина для того, чтобы задуматься об использовании потоков для параллелизации запросов. Потоки, однако, создают некоторую дополнительную нагрузку на систему, они, так сказать, не «бесплатны». Их нужно создавать, запускать, нужно дожидаться завершения их работы.

Если вы хотите пользоваться быстрым асинхронным HTTP-клиентом, то вам, если только вы не пишете на старых версиях Python, стоит обратить самое серьёзное внимание на aiohttp. Это — самое быстрое решение, лучше всего поддающееся масштабированию. Оно способно обрабатывать сотни параллельных запросов.

Альтернатива aiohttp, не особенно хорошая альтернатива — параллельное управление сотнями потоков.

Потоковая обработка данных


Ещё одна оптимизация работы с сетевыми ресурсами, которая может оказаться полезной в плане повышения производительности приложений, заключается в использовании потоковой передачи данных. Стандартная схема обработки запросов выглядит так: приложение отправляет запрос, после чего тело этого запроса загружается за один заход. Параметр stream, который поддерживает библиотека requests, а так же атрибут content библиотеки aiohttp, позволяют отойти от этой схемы.

Вот как выглядит организация потоковой обработки данных с использованием requests:

import requests

# Воспользуемся `with` для того чтобы обеспечить закрытие потока ответа и возможность
# возвращения соединения обратно в пул.
with requests.get('http://example.org', stream=True) as r:
    print(list(r.iter_content()))

Вот как организовать потоковую обработку данных с помощью aiohttp:
import aiohttp
import asyncio


async def get(url):
    async with aiohttp.ClientSession() as session:
        async with session.get(url) as response:
            return await response.content.read()

loop = asyncio.get_event_loop()
tasks = [asyncio.ensure_future(get("http://example.com"))]
loop.run_until_complete(asyncio.wait(tasks))
print("Results: %s" % [task.result() for task in tasks])

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

Итоги


Надеюсь, мой рассказ о разных способах оптимизации работы HTTP-клиентов поможет вам выбрать то, что лучше всего подойдёт именно вашему Python-приложению.

Уважаемые читатели! Если вам известны ещё какие-нибудь способы оптимизации работы с HTTP-запросами в Python-приложениях — просим ими поделиться.


Веб-разработка с использованием Python в Windows

  • Чтение занимает 16 мин

В этой статье

Ниже приведено пошаговое руководство по началу работы с Python для веб-разработки в Windows с помощью подсистемы Windows для Linux (WSL).The following is a step-by-step guide to get you started using Python for web development on Windows, using the Windows Subsystem for Linux (WSL).

Настройка среды разработкиSet up your development environment

При создании веб-приложений мы рекомендуем установить Python на WSL.We recommend installing Python on WSL when building web applications. Многие руководства и инструкции для разработки веб-приложений на Python написаны для пользователей Linux и поэтому они используют средства упаковки и установки на основе Linux.Many of the tutorials and instructions for Python web development are written for Linux users and use Linux-based packaging and installation tools. Большинство веб-приложений также развертываются в Linux, поэтому это обеспечит согласованность между рабочими средами и средами разработки.Most web apps are also deployed on Linux, so this will ensure you have consistency between your development and production environments.

Если вы используете Python не для разработки веб-приложений, мы рекомендуем установить Python непосредственно на Windows 10 с помощью Microsoft Store.If you are using Python for something other than web development, we recommend you install Python directly on Windows 10 using the Microsoft Store. WSL не поддерживает рабочих столов или приложения с графическим пользовательским интерфейсом (например, PyGame, Gnome, KDE и т. д.).WSL does not support GUI desktops or applications (like PyGame, Gnome, KDE, etc). В этих случаях установите и используйте Python непосредственно в Windows.Install and use Python directly on Windows for these cases. Если у вас нет опыта работы в Python, ознакомьтесь с нашим руководством: Get started using Python on Windows for beginners (Приступая к работе с Python в Windows для начинающих).If you’re new to Python, see our guide: Get started using Python on Windows for beginners. Если вы заинтересованы в автоматизации общих задач в операционной системе, ознакомьтесь с нашим руководством: Начало работы с Python в Windows для создания сценариев и автоматизацииIf you’re interested in automating common tasks on your operating system, see our guide: Get started using Python on Windows for scripting and automation. В некоторых расширенных сценариях может потребоваться загрузка определенного выпуска Python непосредственно из python.org или установка альтернативы, например Anaconda, Jython, PyPy, WinPython, IronPython и т. д. Мы рекомендуем это только в том случае, если вы более продвинутый программист на Python и у вас есть конкретная причина выбрать альтернативную реализацию.For some advanced scenarios, you may want to consider downloading a specific Python release directly from python.org or consider installing an alternative, such as Anaconda, Jython, PyPy, WinPython, IronPython, etc. We only recommend this if you are a more advanced Python programmer with a specific reason for choosing an alternative implementation.

Установка подсистемы Windows для LinuxInstall Windows Subsystem for Linux

WSL позволяет запускать среду командной строки GNU/Linux, интегрированную непосредственно с Windows и привычными вам инструментами, такими как Visual Studio Code, Outlook и т. д.WSL lets you run a GNU/Linux command line environment integrated directly with Windows and your favorite tools, like Visual Studio Code, Outlook, etc.

Чтобы включить и установить WSL (или WSL 2 в зависимости от ваших потребностей), выполните инструкции из документации по установке WSL.To enable and install WSL (or WSL 2 depending on your needs), follow the steps in the WSL install documentation. Эти инструкции включают возможность выбора дистрибутива Linux (например, Ubuntu).These steps will include choosing a Linux distribution (for example, Ubuntu).

Установив WSL и дистрибутив Linux, откройте дистрибутив Linux (его можно найти в меню «Пуск» в Windows) и проверьте

Создание Web Service на языке Python

Для запуска веб-приложения, а в нашем случае лишь WebService воспользуемся библиотекой Werkzeug.
Вызовем метод

 run_simple('localhost', 4000, application)

В качестве аргументов метод принимает адрес сервера, где будет распологаться веб-сервис, порт на , котором , будет «слушать» сервер и имя метода реализующего саму логику работы с запросами от клиента и возвратом ему ответов.
application — это и есть имя метода , реализующего логику Request-Responce.
Данный метод нужно помечать тегом @Request.application , в качестве аргумента ему передается request , а возвращает он клиенту Response .
Простой пример веб-приложения можно увидеть ниже:

from werkzeug.wrappers import Request, Response

@Request.application
def application(request):
    return Response('Hello World!')

if __name__ == '__main__':
    from werkzeug.serving import run_simple
    run_simple('localhost', 4000, application)

Реализуем веб-сервис, который выполняет 3 метода : "get_products","get_alcohol_products","get_eatable_products". Для этого импортируем следующие пакеты:

from jsonrpc import JSONRPCResponseManager, dispatcher

Внутри метода application привяжем в словаре dispatcher реализацию желаемых методов следующим образом:

@Request.application
    def application(self,request):        
        dispatcher["get_alcohol_products"] = self.get_alcohol_products
        dispatcher["get_eatable_products"] = self.get_eatable_products
        dispatcher["get_products"] = self.get_products
        response = JSONRPCResponseManager.handle(request.data, dispatcher)          
        return Response(response.json, mimetype='application/json')

Таким образом при получении JSON запроса от клиента, содержащего название желаемого метода, будет вызываться соответствующий метод класса.
К примеру, HTTP Post запрос от клиента с JSON :

{"jsonrpc": "2.0", "params": [], "method": "get_products", "id": 0}

Вызовет метод self.get_products() в нашем классе.

Проверить работоспособность веб-сервиса можно с помощью плагина Postman Launcher к броузеру Chrome. Либо воспользоваться тестом из самого проекта.
Полная версия веб-сервиса представлена ниже и ее также можно скачать здесь.

'''
Created on Aug 25, 2016
@author: Maria Shpatserman
'''
from werkzeug.wrappers import Request, Response
from werkzeug.serving import run_simple

from jsonrpc import JSONRPCResponseManager, dispatcher
import xml.etree.ElementTree as ET
import logging
import ast
import json


class ServerJson(object):
    '''
    classdocs
    '''
    logging.basicConfig(filename='../logs/myapp.log', level=logging.INFO)
    

    def __init__(self):
        '''
        Constructor
        '''
        self.readJsonFile()
        
        logging.info(self.json_object)
        
        
    def readJsonFile(self):
        with open('../data/products.json', 'r') as f:
            self.json_object = json.load(f) 
    
              
    
    @Request.application
    def application(self,request):
        logging.info(request.data)
        dispatcher["get_alcohol_products"] = self.get_alcohol_products
        dispatcher["get_eatable_products"] = self.get_eatable_products
        dispatcher["get_products"] = self.get_products
        response = JSONRPCResponseManager.handle(request.data, dispatcher)
        logging.info(response)
        logging.info(response.json)
    
        return Response(response.json, mimetype='application/json')
    def main(self):
        run_simple('localhost', 4000, self.application)
    
    def get_products(self):
        logging.info(self.json_object)
        return self.json_object
    
    def get_alcohol_products(self):        
        return self.json_object["products"]["alcohol"]
    
    
    def get_eatable_products(self):
        return self.json_object['products']['eatable']
    
if __name__ == '__main__':
    ServerJson().main()
   

Учебное пособие по Python: веб-службы Python HTTP

Веб-службы Python HTTP — urllib, httplib2




bogotobogo.com поиск по сайту:

Веб-служба

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

Мы можем выделить два основных класса Web-сервисов, REST-совместимых Web-сервисов, в которых основной целью сервиса является манипулирование XML-представлениями Web-ресурсов с использованием унифицированного набора операций без сохранения состояния; и произвольные Web-сервисы, в которых сервис может предоставлять произвольный набор операций.

С философской точки зрения, мы можем описать веб-службы HTTP в 12 словах: , обменивающиеся данными с удаленными серверами, не используя ничего, кроме операций http .

HTTP-запросов

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

Например, когда мы заходим и открываем веб-страницу, используя наш браузер, обычно мы отправляем запрос с методом запроса get , а ресурсом обычно является веб-страница, например index.html ‘, который обычно является основной веб-страницей веб-сайта. В этом случае, get — это метод запроса, а ресурс — это index.html.

Ресурс обычно указывается как путь к ресурсу на сервере. Итак, ресурс обычно будет путем. Мы увидим что-то вроде этого, ‘/index.html’ или ‘foo / mypage’ или какой-нибудь другой ресурс, который мы хотели бы доступ.

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

Другой важный — POST . POST обычно используется, когда мы хотим отправить на сервер много данных. Так, например, если мы хотим пойти и опубликовать изображение на сервере, чтобы его можно было сохранить и использовать в более поздний момент времени. Мы, вероятно, будем использовать для этого сообщения. Скорее всего, мы не будем отправлять изображение через get.Мы собираемся отправить небольшой объем данных через Get.

Post используется для более общих целей, например для отправки данных на сервер.


Если мы хотим получить данные с сервера, используйте http GET .

Итак, когда мы ввели в URL следующее:

http://www.bogotobogo.com/foo
 

Внутри мы выдаем следующую строку запроса, запрашивая страницу с http-сервера:

ПОЛУЧИТЬ / foo http / 1.1
 

  1. GET — метод
  2. / foo — путь
  3. http / 1.1 — версия

Вот еще один пример, немного более сложный, и угадайте, какой будет строка запроса:

http://www.bogotobogo.com/foo/bogo.png?p=python#fragment.
 

Ответ:

ПОЛУЧИТЬ /foo/bogo.png?p=python http / 1.1
 

Хост www.bogotobogo.com будет использоваться для соединения , а фрагмент останется на стороне клиента .

На самом деле, строка запроса http имеет больше, называемое заголовком , который состоит из имя: значение пара:

Хост: www.bogotobogo.com
Пользовательский агент: Chrome
 

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

Если мы хотим отправить новые данные на сервер, используйте http POST . Некоторые более продвинутые API веб-служб http также позволяют создавать, изменять и удалять данные, используя http PUT и http DELETE . Вот и все. Ни реестров, ни конвертов, ни оберток, ни туннелирования. Глаголы , встроенные в протокол http ( GET , POST , PUT и DELETE ), отображаются непосредственно на операции уровня приложения для извлечения, создания, изменения и удаления данных.
Основным преимуществом этого подхода является простота, и она оказалась популярной. Данные — обычно xml, или json — могут быть построены и сохранены статически или сгенерированы динамически серверным скриптом, и все основные языки программирования (включая Python, конечно!) Включают http-библиотеку для их загрузки. Отладка также проще; поскольку каждый ресурс в веб-службе http имеет уникальный адрес (в виде url ), мы можем загрузить его в наш веб-браузер и сразу увидеть необработанные данные.- с http://getpython3.com/diveintopython3/http-web-services.html

HTTP-ответов

На следующий http-запрос:

ПОЛУЧИТЬ / foo http / 1.1
 

Ответ сервера должен быть примерно таким;

http / 1.1 200 ОК
 

Линия называется Статусная строка :

  1. 200 : код состояния
  2. OK : фраза причины

Вот некоторые коды состояния:

Статус Значение Пример
1xx Информация 100 = сервер соглашается обработать запрос клиента
2xx Успех (ОК) 200 = запрос выполнен успешно, 204 = нет содержимого
3xx Перенаправление 301 = страница перемещена, 304 = кешированная страница все еще действительна
4xx Ошибка клиента 403 = запрещенная страница, 404 = страница не найдена
5xx Ошибка сервера 500 = внутренняя ошибка сервера, 503 = повторить попытку позже

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

Веб-служба Python

Python 3 поставляется с двумя разными библиотеками для взаимодействия с веб-службами http:

  1. http.client — это низкоуровневая библиотека, которая реализует протокол http rfc 2616.
  2. urllib.request — это уровень абстракции, построенный поверх http.client. Он предоставляет стандартный API для доступа к серверам http и ftp, автоматически следует перенаправлениям http и обрабатывает некоторые распространенные формы аутентификации http.

Итак, какой из них мы должны использовать? Никто из них.Вместо этого мы должны использовать httplib2, стороннюю библиотеку с открытым исходным кодом , которая реализует http более полно, чем http.client , но обеспечивает лучшую абстракцию, чем urllib.request .

На HTTP-клиентах

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

  1. Кэширование
  2. Проверка последних изменений
  3. Проверка ETag
  4. Компрессия
  5. Перенаправить

Функции http-клиентов должны поддерживать — 1.Кеширование

Доступ к сети невероятно дорог!

Самое важное, чтобы понять, что любой тип веб-сервиса — это то, что доступ к сети невероятно дорог. Открытие соединения, отправка запроса и получение ответа с удаленного сервера занимает необычайно много времени. Даже при самом быстром широкополосном соединении задержка может быть выше, чем мы ожидали. Маршрутизатор ведет себя неправильно, пакет отбрасывается, промежуточный прокси-сервер подвергается атаке и т. Д.

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

Хром

После установки chrome-extension-http-headers

мы можем получить информацию заголовка http:

Firefox

Установите надстройки для веб-разработчиков Firefox

Информация о странице-> Заголовки

Кэширование ускоряет повторные просмотры страниц и экономит много трафика, предотвращая загрузку неизмененного содержимого при каждом просмотре страницы.Мы можем использовать Cache-Control: max-age = , чтобы сообщить браузеру, что компонент не будет изменен в течение определенного периода. Таким образом, мы избегаем ненужных дальнейших запросов, если браузер уже имеет компонент в своем кеше, и поэтому просмотры страниц с подготовленным кешем будут выполняться быстрее.

После установки браузера HTTP-ответов мы можем увидеть ответ на наш запрос.


В следующем примере показан ответ на запрос изображения.
Заходим на http: //www.bogotobogo.com / python / images / python_http_web_services / Browsers.png в нашем браузере. Эта страница включает изображение. Когда наш браузер загружает это изображение, сервер включает следующие заголовки http:

К сожалению, на моем сайте нет cache-control / Expires . Итак, посмотрим на другой сайт:


Cache-Control и Expires Заголовки сообщают нашему браузеру (и любым кэширующим прокси между нами и сервером), что это изображение можно кэшировать на срок до 2 минут (от вс, 20 января 2013 г., 22:16:26 GMT — Вс, 20 января 2013 г., 22:18:26 GMT).И если в течение этого периода мы посетим страницу, наш браузер загрузит страницу из своего кеша, не генерируя никакой сетевой активности.

Но предположим, что мы пытаемся загрузить образ, и у нас есть около месяца до истечения его срока действия. И наш браузер по какой-то причине очищает изображение из локального кеша. Но в заголовках http говорилось, что эти данные могут быть кэшированы общедоступными кэширующими прокси. (Технически важно то, что не говорится в заголовках; заголовок Cache-Control не имеет ключевого слова private, поэтому по умолчанию эти данные кэшируются.Прокси-серверы для кэширования имеют массу места для хранения, вероятно, намного больше, чем выделил наш локальный браузер.

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

HTTP-кеширование работает только тогда, когда все делают свое дело. С одной стороны, серверы должны отправлять правильные заголовки в своем ответе. С другой стороны, клиенты должны понимать и уважать эти заголовки, прежде чем они будут запрашивать одни и те же данные дважды. Прокси посередине — не панацея; они могут быть настолько умными, насколько позволяют им серверы и клиенты.

HTTP-библиотеки Python не поддерживают кеширование , но httplib2 поддерживает.

Функции http-клиентов должны поддерживать — 2. Проверка последних изменений

Некоторые данные никогда не меняются, в то время как другие данные меняются все время. Между ними есть обширное поле данных, которые могли измениться, но не изменились. Канал gizmodo.com обновляется каждые несколько минут, но канал сайта может не меняться в течение нескольких дней или недель. В последнем случае я не хочу указывать клиентам, что они должны кэшировать мой канал неделями, потому что тогда, когда я действительно что-то публикую, люди могут не читать его неделями (потому что они уважают заголовки моего кеша, в которых говорится не утруждайте себя проверкой этого фида неделями ).С другой стороны, я не хочу, чтобы клиенты загружали весь мой канал один раз в час, если он не изменился!

У http есть решение и для этого. Когда мы запрашиваем данные в первый раз, сервер может отправить обратно заголовок Last-Modified . Это именно то, на что это похоже: дата изменения данных.

Когда мы запрашиваем одни и те же данные во второй (или третий или четвертый) раз, мы можем отправить заголовок If-Modified-Since с нашим запросом с датой, которую мы получили с сервера в последний раз.Если с тех пор данные изменились, то сервер предоставляет нам новые данные с кодом состояния 200 . Но если с тех пор данные не изменились, сервер отправляет специальный код статуса http 304 , что означает, что эти данные не изменились с момента последнего запроса. Мы можем проверить это в командной строке, используя cURL :

$ curl -I -H "If-Modified-Since: Sun, 20 Jan 2013 19:56:58 GMT"
 http://www.bogotobogo.com/python/images/python_http_web_services/Browsers.PNG
HTTP / 1.1 304 без изменений
Дата: вс, 20 января 2013 г., 23:21:23 GMT
Сервер: Apache
ETag: "664002c-2f09-4d3bdbf48a672"
Vary: Accept-Encoding
 

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

HTTP-библиотеки Python не поддерживают проверку даты последнего изменения , но httplib2 поддерживает.

Функции http-клиентов должны поддерживать — 3. Проверка ETag

ETag или тег объекта , является частью HTTP, протокола для всемирной паутины.Это один из нескольких механизмов, которые HTTP предоставляет для проверки веб-кеша , и который позволяет клиенту делать условных запросов . Это позволяет кэшировать более эффективно и экономить полосу пропускания, поскольку веб-серверу не нужно отправлять полный ответ, если содержимое не изменилось. — из вики

ETag s — это альтернативный способ выполнить то же самое, что и проверка с последними изменениями . С ETag s сервер отправляет хэш-код в заголовке ETag вместе с запрошенными нами данными.

 200 ОК
Дата: понедельник, 21 января 2013 г., 00:07:51 GMT
Кодирование содержимого: gzip
Последнее изменение: вс, 20 января 2013 г., 19:56:58 GMT
Сервер: Apache
ETag: "664002c-2f09-4d3bdbf48a672"
Vary: Accept-Encoding
Тип содержимого: изображение / png
Accept-Ranges: байты
Длина содержимого: 12054
 

Во второй раз, когда мы запрашиваем те же данные, мы включаем хэш ETag в заголовок If-None-Match нашего запроса. Если данные не изменились, сервер вернет нам код статуса 304 .Как и при проверке даты последнего изменения , сервер отправляет обратно только код состояния 304 ; он не отправляет нам одни и те же данные второй раз. Включая ETag hash в наш второй запрос, мы сообщаем серверу, что нет необходимости повторно отправлять те же данные, если они все еще соответствуют этому хешу, поскольку у нас все еще есть данные с прошлого раза.

$ curl -I -H "Если-None-Match: \" 664002c-2f09-4d3bdbf48a672 \ ""
 http://www.bogotobogo.com/python/images/python_http_web_services/Browsers.PNG
HTTP / 1.1 304 без изменений
Дата: понедельник, 21 января 2013 г., 00:14:50 GMT
Сервер: Apache
ETag: "664002c-2f09-4d3bdbf48a672"
Vary: Accept-Encoding
 

Обратите внимание, что ETag обычно заключаются в кавычки , но кавычки являются частью значения. Это означает, что нам нужно отправить кавычки обратно на сервер в заголовке If-None-Match .

HTTP-библиотеки Python не поддерживают Etag s, но httplib2 поддерживает.

Функции http-клиентов должны поддерживать — 4. Сжатие

Когда мы говорим о веб-сервисах http , мы почти всегда говорим о перемещении текстовых данных туда и обратно по сети. Это может быть xml , json или просто простой текст . Независимо от формата, текст хорошо сжимается . Пример фида в главе XML составляет 25 Кбайт без сжатия, но будет иметь 6 Кбайт после сжатия gzip. Это всего 25% от исходного размера!

http поддерживает несколько алгоритмов сжатия.Два наиболее распространенных типа — это gzip и deflate . Когда мы запрашиваем ресурс по http , мы можем попросить сервер отправить его в сжатом формате. Мы включаем заголовок Accept-encoding в наш запрос, в котором перечислены поддерживаемые нами алгоритмы сжатия. Если сервер поддерживает какой-либо из тех же алгоритмов, он отправит нам обратно сжатые данные (с заголовком Content-encoding , который сообщает нам, какой алгоритм он использовал). Затем нам нужно распаковать данные.


HTTP-библиотеки Python не поддерживают сжатие s, но httplib2 поддерживает.

Функции http-клиентов должны поддерживать — 5. Перенаправление

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

  1. 200: все нормально
  2. 404: страница не найдена
  3. 300: перенаправление

http имеет несколько различных способов обозначения перемещения ресурса.Двумя наиболее распространенными методами являются коды состояния 302 и 301 .

  1. 302: временное перенаправление; это означает, что ой, это временно переместилось сюда , а затем дает временный адрес в заголовке Location.
    Если мы получим код состояния 302 и новый адрес, спецификация http говорит, что мы должны использовать новый адрес, чтобы получить то, что мы просили, но в следующий раз, когда мы захотим получить доступ к тому же ресурсу, мы должны повторить попытку старый адрес.
  2. 301: постоянное перенаправление; это означает, что упс, который был перемещен навсегда на , а затем дает новый адрес в заголовке Location.
    , если мы получим код состояния 301 и новый адрес, с этого момента мы должны использовать новый адрес.

Модуль urllib.request автоматически выполняет перенаправления , когда получает соответствующий код состояния от HTTP-сервера, но не сообщает нам об этом. В конечном итоге мы получим данные, которые мы запрашивали, но мы никогда не узнаем, что базовая библиотека услужливо выполнила для нас перенаправление.Итак, мы продолжим работать со старым адресом, и каждый раз мы будем перенаправляться на новый адрес, и каждый раз модуль urllib.request будет помогать перенаправлению. Другими словами, постоянные перенаправления рассматриваются как временные. Это означает, что вместо одного обхода происходит два, что плохо для сервера и плохо для нас.

сжатие с, но httplib2 обрабатывает за нас постоянные перенаправления. Он не только сообщит нам, что произошло постоянное перенаправление, но и будет отслеживать их локально и автоматически перезапишет перенаправленные URL-адреса перед их запросом.

Загрузка через HTTP

Предположим, мы хотим загрузить ресурс через http , например канал Atom. Поскольку мы являемся фидом, мы не собираемся просто скачивать его один раз; мы будем загружать его снова и снова. (Большинство читателей каналов будут проверять наличие изменений раз в час.) Давайте сначала сделаем это быстро и грязно, а потом посмотрим, как мы можем улучшить его позже.

>>> импортировать urllib.request
>>> a_url = 'http://www.w3.org/2005/Atom'
>>> данные = urllib.request.urlopen (a_url) .read ()
>>> тип (данные)
<класс 'байты'>
>>> печать (данные)
b ' \ n
\n\n
 \ n
 \ n

 

В модуле urllib.request есть функция urlopen () , которая принимает адрес нужной нам страницы и возвращает файловый объект, из которого мы можем просто read () получить полное содержимое страница.

Метод urlopen (). Read () всегда возвращает объект байтов , а не строку . Помните, что байтов - это байтов; символов - это абстракция . Серверы http не занимаются абстракциями. Если мы запрашиваем ресурс, мы получаем байты. Если мы хотим, чтобы это было строкой, нам нужно определить кодировку символов и явно преобразовать ее в строку.

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

Загрузка через HTTP - httplib2

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

>>> из http.client import HTTPConnection
>>> HTTPConnection.debuglevel = 1
>>> из urllib.request import urlopen
>>> response = urlopen ('http://www.w3.org/2005/Atom')
отправить: b'GET / 2005 / Atom HTTP / 1.1 \ r \ n
Accept-Encoding: идентификатор \ r \ n
Хост: www.w3.org \ r \ n
Подключение: закрыть \ r \ n
Пользовательский агент: Python-urllib / 3.2 \ r \ n
\ r \ n '
ответ: 'HTTP / 1.1 200 ОК \ r \ n'
заголовок: Заголовок даты: Заголовок сервера: Заголовок Content-Location: Заголовок Vary:
Заголовок TCN: Заголовок Last-Modified: Заголовок ETag: Заголовок Accept-Ranges:
Заголовок Content-Length: Заголовок Cache-Control: Заголовок Expires: Заголовок P3P:
Заголовок соединения: Content-Type
 

urllib.request полагается на другую стандартную библиотеку Python, http.client . Обычно нам не нужно трогать http.клиент напрямую, поскольку модуль urllib.request импортирует его автоматически. Но мы импортируем его сюда, чтобы мы могли переключить флаг отладки на HTTPConnection class , который urllib.request использует для подключения к серверу http .

Теперь, когда установлен флаг отладки, информация о запросе и ответе http распечатывается в реальном времени. Как мы видим, когда мы запрашиваем фид Atom, модуль urllib.request отправляет на сервер пять строк.

  1. В первой строке указывается HTTP-команда, которую мы используем, и путь к ресурсу (без имени домена).
  2. Во второй строке указывается доменное имя, с которого мы запрашиваем этот фид.
  3. В третьей строке указаны алгоритмы сжатия, которые поддерживает клиент. urllib.request по умолчанию не поддерживает сжатие.
  4. В четвертой строке указывается имя библиотеки, которая делает запрос. По умолчанию это Python- urllib плюс номер версии urllib.request , и httplib2 поддерживают изменение пользовательского агента, просто добавив заголовок User-Agent к запросу, который переопределит значение по умолчанию.

Теперь давайте посмотрим, что сервер отправил в ответ. Продолжение предыдущего примера.

>>> печать (response.headers.as_string ())
Дата: Пн, 21 января 2013 г., 04:37:06 GMT
Сервер: Apache / 2
Расположение содержимого: Atom.html
Варьировать: согласовать, принять-кодирование
TCN: выбор
Последнее изменение: сб, 13 октября 2007 г., 02:19:32 GMT
ETag: "90a-43c56773a3500; 4bc4eec228980"
Accept-Ranges: байты
Длина содержимого: 2314
Cache-Control: max-age = 21600.
Истекает: Mon, 21 Jan 2013 10:37:06 GMT
P3P: policyref = "http: // www.w3.org/2001/05/P3P/p3p.xml "
Подключение: закрыть
Тип содержимого: текст / html; charset = utf-8
>>> data = response.read ()
>>> len (данные)
2314
 

Ответ, возвращенный функцией urllib.request.urlopen () , содержит все заголовки http , которые сервер отправил обратно. Он также содержит методы для загрузки фактических данных.

Сервер сообщает нам, когда он обработал наш запрос. Этот ответ включает заголовок Last-Modified и заголовок ETag .

Длина данных 2314 байтов. Обратите внимание на то, чего здесь нет: заголовок Content-encoding . В нашем запросе сказано, что мы принимаем только несжатые данные ( Accept-encoding: identity ), и, конечно же, этот ответ содержит несжатые данные.

Этот ответ также включает кэширующих заголовков , в которых указано, что этот канал может быть кэширован на срок до 6 часов (21600 секунд), и, наконец, загрузить фактические данные, вызвав response.read () . Как мы можем судить по функции len () , всего было получено 2314 байтов.

Как видим, этот код уже неэффективен: он запрашивал (и принимал) несжатые данные. Я точно знаю, что этот сервер поддерживает сжатие gzip, но сжатие http разрешено. Мы этого не просили, поэтому не получили. Это означает, что мы получаем 2314 байт, хотя могли получить меньше.

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

>>> response2 = urlopen ('http: // www.w3.org/2005/Atom ')
отправить: b'GET / 2005 / Atom HTTP / 1.1 \ r \ n
Accept-Encoding: идентификатор \ r \ n
Хост: www.w3.org \ r \ n
Подключение: закрыть \ r \ n
Пользовательский агент: Python-urllib / 3.2 \ r \ n \ r \ n '
ответ: 'HTTP / 1.1 200 ОК \ r \ n'
заголовок: Заголовок даты: Заголовок сервера: Заголовок Content-Location: Заголовок Vary:
Заголовок TCN: Заголовок Last-Modified: Заголовок ETag: Заголовок Accept-Ranges:
Заголовок Content-Length: Заголовок Cache-Control: Заголовок Expires: Заголовок P3P:
Заголовок соединения: Content-Type
...
 

Обратите внимание, что это не изменилось! Точно так же, как и в первом запросе.Отсутствуют признаки заголовков If-Modified-Since . Нет признаков If-None-Match headers. Никакого уважения к кэширует заголовки . По-прежнему нет сжатия .

httplib2

>>> импортировать httplib2
Отслеживание (последний вызов последний):
  Файл "", строка 1, в
    импортировать httplib2
ImportError: нет модуля с именем httplib2
# 37> 

Итак, нам нужно установить http://code.google.com/p/httplib2/.

Загрузки:

httplib2-0.7.7.tar.gz
httplib2-0.7.7.zip

Для установки:

$ python setup.py установить
 

Чтобы использовать httplib2 , создайте экземпляр класса httplib2.Http .

>>> импортировать httplib2
>>> h = httplib2.Http ('. cache')
>>> response, content = h.request ('http://www.w3.org/2005/Atom')
>>> response.status
200
>>> content [: 52]
b ' \ n >> len (содержание)
2314
 

Основным интерфейсом к httplib2 является объект Http .Мы всегда должны передавать имя каталога при создании объекта Http . Каталог не обязательно должен существовать; httplib2 при необходимости создаст его.

Как только у нас есть объект Http , получение данных так же просто, как вызов метода request () с адресом данных, которые мы хотим. Это выдаст запрос http GET для этого url .

Метод request () возвращает два значения:

  1. Первый - это httplib2.Объект Response , содержащий все заголовки http , возвращенные сервером. Например, код состояния 200 указывает, что запрос был успешным.
  2. Переменная content содержит фактические данные, которые были возвращены сервером http . Данные возвращаются в виде байтового объекта, а не строки. Если мы хотим, чтобы это была строка, нам нужно определить кодировку символов и преобразовать ее самостоятельно.

httplib2 - кеширование

Чтобы использовать кеширование, мы всегда должны создавать httplib2.Объект Http с именем каталога.

>>> response2, content2 = h.request ('http://www.w3.org/2005/Atom')
>>> response2.status
200
>>> content2 [: 52]
b ' \ n >> len (content2)
2314
 

Ничего необычного. Те же результаты, что и в предыдущем прогоне!

Однако, если мы запустим его после перезапуска оболочки Python, нас ждет сюрприз.

>>> импортировать httplib2
>>> httplib2.debuglevel = 1
>>> h = httplib2.Http ('. cache')
>>> response, content = h.request ('http://www.w3.org/2005/Atom')
>>> len (содержание)
2314
>>> response.status
200
>>> response.fromcache
Правда
 

Включили отладку. Это был эквивалент httplib2 включения отладки в http.client . httplib2 распечатает все данные, отправляемые на сервер, и некоторую ключевую информацию, отправляемую обратно.

Мы создали httplib2.Объект Http с тем же именем каталога , что и раньше. Затем запросил тот же URL , что и раньше. Точнее, на сервер ничего не отправлялось, и с сервера ничего не возвращалось. Никакой сетевой активности не было.

Однако мы получили некоторые данные - фактически, мы получили их все. Мы также получили код состояния http , указывающий, что запрос был успешным.

Фактически, этот ответ был сгенерирован из локального кеша httplib2 .Это имя каталога мы передали при создании объекта httplib2.Http - в этом каталоге содержится кеш httplib2 всех операций, которые он когда-либо выполнял.

Ранее мы запрашивали данные по этому адресу , . Этот запрос был успешным (статус: 200 ). Этот ответ включал не только данные канала, но и набор заголовков кэширования, которые сообщали всем, кто слушал, что они могут кэшировать этот ресурс на срок до 6 часов ( Cache-Control: max-age = 21600 , что является 6 часов в секундах). httplib2 понимает и уважает эти кэширующие заголовки, и он сохранил предыдущий ответ в каталоге .cache (который мы передали при создании объекта Http ). Срок действия этого кеша еще не истек, поэтому во второй раз мы запрашиваем данные по этому адресу url , httplib2 просто возвращает кешированный результат, не обращаясь к сети.

httplib2 автоматически обрабатывает HTTP-кеширование и по умолчанию .

Теперь предположим, что у нас есть кэшированные данные, но мы хотим обойти кеш и повторно запросить их с удаленного сервера.Браузеры иногда делают это, если пользователь специально этого требует. Например, нажатие F5 обновляет текущую страницу, но нажатие Ctrl + F5 обходит кеш и повторно запрашивает текущую страницу с удаленного сервера. Мы могли бы подумать, что : о, я просто удалю данные из своего локального кеша, а затем снова запрошу . Мы могли бы это сделать, но помните, что может быть задействовано больше сторон, чем только мы и удаленный сервер. А как насчет тех промежуточных прокси-серверов ? Они полностью вне нашего контроля, и у них все еще могут быть эти данные в кэше, и они с радостью вернут их нам, потому что (насколько они обеспокоены) их кеш все еще действителен.

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

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

>>> импортировать httplib2
>>> response2, content2 = h.request ('http: // www.w3.org/2005/Atom ',
заголовки = {'cache-control': 'no-cache'})
отправить: b'GET / 2005 / Atom HTTP / 1.1 \ r \ n
Хост: www.w3.org \ r \ n
пользовательский агент: Python-httplib2 / 0.7.7 (gzip) \ r \ n
accept-кодировка: gzip, deflate \ r \ n
кеш-контроль: без кеша \ r \ n \ r \ n '
ответ: 'HTTP / 1.1 200 ОК \ r \ n'
заголовок: Дата заголовок:
Заголовок сервера:
...
>>> response2.status
200
>>> response2.fromcache
Ложь
>>> print (dict (response2.items ()))
{
'status': '200',
'content-length': '2314',
'content-location': 'Atom.html',
'accept-range': 'байты',
'expires': 'Mon, 21 Jan 2013 12:55:46 GMT',
'варьировать': 'согласовать, принять-кодирование',
'сервер': 'Apache / 2',
'tcn': 'выбор',
'last-modified': 'Sat, 13 Oct 2007 02:19:32 GMT',
'соединение': 'закрыть',
'-content-encoding': 'gzip',
'etag': '"90a-43c56773a3500; 4bc4eec228980"',
'cache-control': 'max-age = 21600',
'date': 'Пн, 21 января 2013 г., 06:55:46 GMT',
'p3p': 'policyref = "http: // www.w3.org/2001/05/P3P/p3p.xml "',
"тип содержимого": "текст / html; charset = utf-8 '
}
 

httplib2 позволяет нам добавлять произвольные заголовки http к любому исходящему запросу. Чтобы обойти все кеши (не только наш локальный дисковый кеш, но и любые кэширующие прокси между нами и удаленным сервером), добавьте заголовок без кеширования в словарь заголовков.

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

Этот ответ не был создан из нашего локального кеша. Мы знали это, конечно, потому что видели отладочную информацию в исходящем запросе. Но приятно иметь это программно.

Запрос выполнен успешно; мы снова загрузили весь канал с удаленного сервера. Конечно, сервер также отправил обратно полный набор заголовков http вместе с данными канала.Это включает в себя кэширование заголовков, которые httplib2 использует для обновления своего локального кеша в надежде избежать доступа к сети в следующий раз, когда мы запросим этот канал. Все, что касается HTTP-кеширования , предназначено для максимального увеличения количества обращений к кешу и минимизации доступа к сети. Несмотря на то, что на этот раз мы обошли кеш, удаленный сервер был бы очень признателен, если бы мы кэшировали результат для следующего раза.

httplib2 - Last-Modified и ETag

Заголовки кэширования Cache-Control и Expires называются индикаторами свежести .Они недвусмысленно сообщают кешам, что мы можем полностью избежать любого доступа к сети, пока не истечет срок действия кеша. Учитывая индикатор свежести, httplib2 не генерирует ни одного байта сетевой активности для обслуживания кэшированных данных, если мы явно не обходим кеш.

Но как насчет случая, когда данные могли измениться, но не изменились? Для этой цели http определяет заголовки Last-Modified и Etag . Эти заголовки называются валидаторами .Если локальный кэш больше не является свежим, клиент может отправить проверяющим устройствам со следующим запросом, чтобы увидеть, действительно ли данные изменились. Если данные не изменились, сервер отправляет обратно код состояния 304 и никаких данных. Таким образом, по сети все еще существует круговой обход, но в итоге мы загружаем меньше байтов.

На этот раз вместо фида мы загрузим домашнюю страницу сайта, это html . Поскольку мы впервые запрашиваем эту страницу, httplib2 мало с чем можно работать, и он отправляет минимум заголовков с запросом.Ответ содержит множество заголовков http, но не содержит информации о кешировании. Однако он включает заголовок ETag и Last-Modified :

>>> импортировать httplib2
>>> httplib2.debuglevel = 1
>>> h = httplib2.Http ('. cache')
>>> response, content = h.request ('http://www.w3.org/')
отправить: b'GET / HTTP / 1.1 \ r \ n
Хост: www.w3.org \ r \ n
accept-кодировка: gzip, deflate \ r \ n
пользовательский агент: Python-httplib2 / 0.7.7 (gzip) \ r \ n
\ r \ n '
ответ: «HTTP / 1.1 200 ОК \ r \ n '
заголовок:
...
>>> print (dict (response.items ()))
{
'status': '200',
'content-length': '32868',
'content-location': 'Home.html',
'accept-range': 'байты',
'expires': 'Вт, 22 января 2013 г., 05:06:02 GMT',
'варьировать': 'договариваться, принимать',
'сервер': 'Apache / 2',
'
.

Использование веб-сервисов на Python - сервер машинного обучения

  • 4 минуты на чтение

В этой статье

Применимо к: Machine Learning Server

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

После публикации веб-службы любой аутентифицированный пользователь может составлять список, проверять и использовать эту веб-службу. Вы можете сделать это непосредственно в Python, используя функции из пакета azureml-model-management-sdk. Пакет azureml-model-management-sdk устанавливается вместе с сервером машинного обучения. Чтобы перечислить, изучить или использовать веб-службу за пределами Python, используйте API-интерфейсы RESTful, которые обеспечивают прямой программный доступ к жизненному циклу службы или на предпочтительном языке через Swagger.

По умолчанию операции с веб-сервисами доступны аутентифицированным пользователям. Однако ваш администратор также может назначать роли (RBAC) для дальнейшего управления разрешениями для веб-служб.

Важно

Определение веб-службы: В Machine Learning Server ваш код и модели R и Python можно развернуть как веб-службы. Эти модели и код, представленные как веб-службы, размещенные на сервере машинного обучения, могут быть доступны и использованы в R, Python, программно с использованием REST API или с помощью клиентских библиотек, созданных Swagger.Веб-сервисы можно развернуть с одной платформы и использовать на другой. Узнать больше ...

Требования

Прежде чем вы сможете использовать функции управления веб-сервисами в пакете Python azureml-model-management-sdk, вы должны:

Найти и перечислить веб-службы

Любой аутентифицированный пользователь может получить список веб-служб с помощью функции list_services в объекте DeployClient. Вы можете использовать аргументы для возврата конкретной веб-службы или всех помеченных версий данной веб-службы.

  ## - Возвращает метаданные для всех сервисов, размещенных на этом сервере.
client.list_services ()

## - Возвращать метаданные для всех версий сервиса myService
client.list_services ('myService')

## - Возвращает метаданные для конкретной версии "v1.0" службы "myService"
client.list_services ('myService', версия = 'v1.0')
  

Как только вы найдете нужную службу, используйте функцию get_service, чтобы получить объект службы для использования.

Получить и исследовать служебные объекты

Прошедшие проверку пользователи могут получить объект веб-службы, чтобы получить заглушку клиента для использования этой службы.Используйте функцию get_service из пакета azureml-model-management-sdk для получения объекта.

После возврата объекта вы можете использовать функцию справки для изучения опубликованной службы, например print (help (myServiceObject)) . Вы можете вызвать функцию справки для любых функций azureml-model-management-sdk, даже для тех, которые создаются динамически, чтобы узнать о них больше.

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

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

Пример кода:

  # - Список всех версий службы myService -
client.list_services ('myService')

# - Получить служебный объект для myService v2.0
svc = client.get_service ('myService', версия = 'v2.0')

# - Узнать больше об этой услуге.печать (помощь (svc))

# - Просмотр возможностей / схемы сервисного объекта
svc.capabilities ()
  

Примечание

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

Использование веб-сервисов

Веб-службы

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

Когда вы публикуете службу, вы можете сообщить людям, что она готова к использованию, сообщив им ее имя и версию. Веб-сервисы используют специалисты по обработке данных, инженеры по качеству и разработчики приложений. Их можно использовать в Python, R или через API.

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

Потребление в Python

После аутентификации с помощью Machine Learning Server пользователи также могут взаимодействовать со службами и использовать их, используя другие функции в пакете Python azureml-model-management-sdk.

Полный пример использования запроса-ответа см. В этой записной книжке Jupyter.

  # В этом сервисе вызовем функцию manualTransmission
res = svc.manualTransmission (120, 2.8)

# Вытащить указанный вывод ʻanswer`.
print (res.output ('ответ'))

# Получить `swagger.json`, определяющий службу.
cap = svc.capabilities ()
swagger_URL = cap ['swagger']
печать (swagger_URL)

# Распечатать содержимое файла swagger.
печать (svc.swagger ())
  

Делимся Swagger с разработчиками приложений

Разработчики приложений могут вызывать и интегрировать веб-службу в свои приложения с помощью файла JSON на основе Swagger для конкретной службы и путем предоставления любых необходимых входных данных для этой службы.Файл JSON на основе Swagger используется для создания клиентских библиотек для интеграции. Прочтите «Как интегрировать веб-службы и аутентификацию в ваше приложение» для получения более подробной информации.

Самый простой способ поделиться файлом Swagger с разработчиком приложения - использовать код, показанный в этой записной книжке Jupyter. В качестве альтернативы разработчик приложения может запросить файл как аутентифицированный пользователь с активным токеном носителя в заголовке запроса, используя этот API:

  GET / api / {{service-name}} / {{service-version}} / swagger.json
  

См. Также

.

Как развернуть модели Python как веб-службы - Сервер машинного обучения

  • 2 минуты на чтение

В этой статье

Применимо к: Machine Learning Server 9.x

Узнайте, как развернуть модель Python как веб-службу с помощью Machine Learning Server. Специалисты по обработке данных работают локально в своей любимой среде разработки Python и любимых инструментах управления версиями для создания сценариев и моделей.Используя пакет Python azureml-model-management-sdk, который поставляется с Machine Learning Server, вы можете разрабатывать, тестировать и, в конечном итоге, развертывать эту аналитику Python в качестве веб-сервисов в своей производственной среде.

В Machine Learning Server веб-служба - это модель и / или код, которые были развернуты и размещены на сервере. Каждая веб-служба уникально определяется именем и версией . При использовании служба состоит из выполнения кода на вычислительном узле.Узнайте больше о веб-сервисах.

Вы можете использовать функции из библиотеки Python azureml-model-management-sdk для управления жизненным циклом веб-службы из сценария Python. Также доступен набор RESTful API для обеспечения прямого программного доступа к жизненному циклу службы.

Оценка времени

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

Предварительные требования

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

Пример кода

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

В этом примере рассматривается развертывание модели Python в качестве веб-службы, размещенной на сервере машинного обучения. Мы построим простую линейную модель, используя функцию rx_lin_mod из пакета revoscalepy, установленного с сервером машинного обучения или локально на машине Windows. Этот пакет требует подключения к серверу машинного обучения.

В примере с записной книжкой показано, как:

  1. Создание и запуск линейной модели локально

  2. Аутентификация с сервером машинного обучения из вашего скрипта Python

  3. Опубликуйте модель как веб-службу Python на сервере машинного обучения

  4. Изучить, протестировать и использовать службу в одном сеансе

  5. Удалить услугу

Можете попробовать сами с ноутбуком.

Загрузите ноутбук Jupyter, чтобы попробовать его .

Следующие шаги

После развертывания веб-служба может иметь вид:

См. Также

В этом разделе представлен краткий обзор полезных ссылок для специалистов по обработке данных, которые хотят реализовать свою аналитику с помощью Machine Learning Server.

.

Как я могу использовать веб-службу WSDL (SOAP) в Python?

Переполнение стека
  1. Около
  2. Продукты
  3. Для команд
  1. Переполнение стека Общественные вопросы и ответы
  2. Переполнение стека для команд Где разработчики и технологи делятся частными знаниями с коллегами
  3. Вакансии Программирование и связанные с ним технические возможности карьерного роста
  4. Талант Нанимайте технических специалистов и создавайте свой бренд работодателя
  5. Реклама Обратитесь к разработчикам и технологам со всего мира
  6. О компании
.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Theme: Overlay by Kaira Extra Text
Cape Town, South Africa