Асинхронный Python для веб-разработки
Асинхронное программирование хорошо подходит для задач, которые включают в себя частое чтение и запись файлов или отправку данных туда и обратно с сервера. Асинхронные программы выполняют операции ввода-вывода неблокирующим образом, что означает, что они могут выполнять другие задачи, ожидая возвращения данных от клиента, а не ждать праздно, тратя ресурсы и время.
Python, как и многие другие языки, страдает от того, что по умолчанию не является асинхронным. К счастью, быстрые изменения в мире ИТ позволяют нам писать асинхронный код даже с использованием языков, которые изначально не предназначались для этого. На протяжении многих лет требования к скорости превышают аппаратные возможности, и компании по всему миру объединились с Реактивным манифестом , чтобы решить эту проблему.
Неблокирующее поведение асинхронных программ может привести к значительному повышению производительности в контексте веб-приложения, помогая решить проблему разработки реактивных приложений.
В Python 3 есть несколько мощных инструментов для написания асинхронных приложений. В этой статье мы рассмотрим некоторые из этих инструментов, особенно в том, что касается веб-разработки.
Мы будем разрабатывать простое реактивное aiohttp приложение для отображения текущих соответствующих координат неба планет Солнечной системы с учетом географических координат пользователя. Вы можете найти приложение здесь , а исходный код здесь .
В конце мы обсудим, как подготовить приложение к развертыванию в Хероку .
Введение в асинхронный Python
Для тех, кто знаком с написанием традиционного кода Python, переход к асинхронному коду может быть концептуально немного сложным. Асинхронный код в Python опирается на сопрограммы , которые в сочетании с циклом событий позволяют писать код, который, по-видимому, делает несколько вещей одновременно.
Сопрограммы можно рассматривать как функции, имеющие точки в коде, где они возвращают управление программой вызывающему контексту. Эти точки “выхода” позволяют приостанавливать и возобновлять выполнение сопрограммы, а также обмениваться данными между контекстами.
Цикл событий определяет, какой фрагмент кода выполняется в данный момент – он отвечает за приостановку, возобновление и обмен данными между сопрограммами. Это означает, что части различных сопрограмм могут в конечном итоге выполняться в порядке, отличном от того, в котором они были запланированы. Эта идея запуска различных фрагментов кода не по порядку называется параллелизм .
Размышления о параллелизме в контексте создания HTTP
запросов могут быть проясняющими. Представьте себе, что вы хотите сделать много независимых запросов к серверу. Например, мы можем запросить веб-сайт, чтобы получить статистику обо всех спортивных игроках в данном сезоне.
Мы могли бы сделать каждый запрос последовательно. Однако с каждым запросом мы можем себе представить, что код out может потратить некоторое время на ожидание запроса, который будет доставлен на сервер, и ответа, который будет отправлен обратно.
Иногда эти операции могут занять даже несколько секунд. Приложение может испытывать сетевое отставание из-за большого количества пользователей или просто из-за ограничений скорости данного сервера.
Что, если наш код может делать другие вещи, ожидая ответа от сервера? Более того, что, если он вернется к обработке данного запроса только после получения данных ответа? Мы могли бы сделать много запросов в быстрой последовательности, если бы нам не приходилось ждать окончания каждого отдельного запроса, прежде чем перейти к следующему в списке.
Сопрограммы с циклом событий позволяют нам писать код, который ведет себя именно так.
asyncio
asyncio , часть стандартной библиотеки Python, предоставляет цикл событий и набор инструментов для управления им. С помощью asyncio мы можем планировать выполнение сопрограмм и создавать новые сопрограммы (действительно asyncio.Task
objects, используя язык asyncio ), который завершит выполнение только после завершения выполнения составных сопрограмм.
В отличие от других асинхронных языков программирования, Python не заставляет нас использовать цикл событий, который поставляется вместе с языком. Как указывает Бретт Кэннон , сопрограммы Python представляют собой асинхронный API, с помощью которого мы можем использовать любой цикл событий. Существуют проекты, которые реализуют совершенно другой цикл событий , например curio , или позволяют отбрасывать другую политику цикла событий для asyncio (политика цикла событий-это то, что управляет циклом событий “за кулисами”), например uvloop .
Давайте посмотрим на фрагмент кода, который одновременно запускает две сопрограммы, каждая из которых печатает сообщение через одну секунду:
# example1.py import asyncio async def wait_around(n, name): for i in range(n): print(f"{name}: iteration {i}") await asyncio.sleep(1.0) async def main(): await asyncio.gather(*[ wait_around(2, "coroutine 0"), wait_around(5, "coroutine 1") ]) loop = asyncio.get_event_loop() loop.run_until_complete(main())
[email protected]:~$ time python example1.py coroutine 1: iteration 0 coroutine 0: iteration 0 coroutine 1: iteration 1 coroutine 0: iteration 1 coroutine 1: iteration 2 coroutine 1: iteration 3 coroutine 1: iteration 4 real 0m5.138s user 0m0.111s sys 0m0.019s
Этот код выполняется примерно за 5 секунд, так как сопрограмма asyncio.sleep
устанавливает точки, в которых цикл событий может перейти к выполнению другого кода. Более того, мы сказали циклу событий запланировать оба экземпляра wait_around
для одновременного выполнения с функцией asyncio.gather
.
asyncio.gather
принимает список “доступных” (т. е. сопрограмм или asyncio.Task
objects) и возвращает один asyncio.Task
объект, который завершается только тогда, когда все его составные задачи/сопрограммы завершены. Последние две строки являются asyncio
шаблоном для запуска данной сопрограммы до ее завершения.
Сопрограммы, в отличие от функций, не начинают выполняться сразу после их вызова. Ключевое слово await
указывает циклу событий запланировать выполнение сопрограммы.
Если мы уберем await
перед asyncio.sleep
, программа завершится (почти) мгновенно, так как мы не сказали циклу событий фактически выполнить сопрограмму, которая в данном случае говорит сопрограмме приостановиться на заданное количество времени.
Имея представление о том, как выглядит асинхронный код Python, давайте перейдем к асинхронной веб-разработке.
Установка aiohttp
aiohttp – это библиотека Python для выполнения асинхронных HTTP
запросов. Кроме того, он обеспечивает основу для объединения серверной части веб-приложения. Используя Python 3.5+ и pip, мы можем установить aiohttp :
pip install --user aiohttp
Клиентская сторона: Выполнение Запросов
Следующие примеры показывают, как мы можем загрузить HTML-содержимое “example.com” использование веб-сайта aiohttp :
# example2_basic_aiohttp_request.py import asyncio import aiohttp async def make_request(): url = "https://example.com" print(f"making request to {url}") async with aiohttp.ClientSession() as session: async with session.get(url) as resp: if resp.status == 200: print(await resp.text()) loop = asyncio.get_event_loop() loop.run_until_complete(make_request())
Несколько вещей, которые следует подчеркнуть:
- Очень похоже на
await asyncio.sleep
мы должны использоватьawait
withresp.text()
для того, чтобы получить HTML-содержимое страницы. Если бы мы оставили это, результат нашей программы был бы примерно следующим:
[email protected]:~$ python example2_basic_aiohttp_request.py
async with
– это контекстный менеджер, который работает с сопрограммами вместо функций. В обоих случаях, когда он используется, мы можем представить, что внутренне aiohttp закрывает соединения с серверами или иным образом освобождает ресурсы.aiohttp.ClientSession
имеет методы, соответствующие глаголам HTTP . Точно так же, как session.getделает
GET запрос, session.postсделает
POST запрос.
Этот пример сам по себе не дает никакого преимущества в производительности по сравнению с синхронными HTTP-запросами. Настоящая красота клиентской части aiohttp заключается в создании нескольких одновременных запросов:
# example3_multiple_aiohttp_request.py import asyncio import aiohttp async def make_request(session, req_n): url = "https://example.com" print(f"making request {req_n} to {url}") async with session.get(url) as resp: if resp.status == 200: await resp.text() async def main(): n_requests = 100 async with aiohttp.ClientSession() as session: await asyncio.gather( *[make_request(session, i) for i in range(n_requests)] ) loop = asyncio.get_event_loop() loop.run_until_complete(main())
Вместо того чтобы делать каждый запрос последовательно, мы просим asyncio
делать их одновременно с asyncio.gather
.
Веб-приложение Planet Tracker
В этом разделе я намерен продемонстрировать, как собрать приложение, которое сообщает текущие координаты планет на небе в местоположении пользователя (эфемериды).
Пользователь предоставляет свое местоположение с помощью web Geolocation API , который делает всю работу за нас.
В конце концов я покажу, как настроить профиль для развертывания приложения на Хероку . Если вы планируете следить за тем, как я работаю над созданием приложения, вам следует сделать следующее, предполагая, что у вас установлены Python 3.6 и pip:
[email protected]:~$ mkdir planettracker && cd planettracker [email protected]:~/planettracker$ pip install --user pipenv [email protected]:~/planettracker$ pipenv --python=3
Планета Эфемерид с Пиефемом
Эфемерида астрономического объекта-это его текущее положение в небе в заданном месте и времени на Земле. PyEphem – это библиотека Python, которая позволяет точно вычислять эфемериды.
Он особенно хорошо подходит для выполнения поставленной задачи, так как имеет общие астрономические объекты, приготовленные в библиотеке. Во-первых, давайте установим Пиефем :
[email protected]:~/planettracker$ pipenv install ephem
Получить текущие координаты Марса так же просто, как использовать экземпляр класса Observer
для вычисления
его координат:
import ephem import math convert = math.pi / 180. mars = ephem.Mars() greenwich = ephem.Observer() greenwich.lat = "51.4769" greenwich.lon = "-0.0005" mars.compute(observer) az_deg, alt_deg = mars.az*convert, mars.alt*convert print(f"Mars' current azimuth and elevation: {az_deg:.2f} {alt_deg:.2f}")
Чтобы упростить получение эфемерид планет, давайте создадим класс PlanetTracker
с методом, который возвращает текущий азимут и высоту данной планеты в градусах ( PyEphem по умолчанию использует радианы, а не градусы, для представления углов внутри):
# planet_tracker.py import math import ephem class PlanetTracker(ephem.Observer): def __init__(self): super(PlanetTracker, self).__init__() self.planets = { "mercury": ephem.Mercury(), "venus": ephem.Venus(), "mars": ephem.Mars(), "jupiter": ephem.Jupiter(), "saturn": ephem.Saturn(), "uranus": ephem.Uranus(), "neptune": ephem.Neptune() } def calc_planet(self, planet_name, when=None): convert = 180./math.pi if when is None: when = ephem.now() self.date = when if planet_name in self.planets: planet = self.planets[planet_name] planet.compute(self) return { "az": float(planet.az)*convert, "alt": float(planet.alt)*convert, "name": planet_name } else: raise KeyError(f"Couldn't find {planet_name} in planets dict")
Теперь мы можем получить любую из семи других планет Солнечной системы довольно легко:
from planet_tracker import PlanetTracker tracker = PlanetTracker() tracker.lat = "51.4769" tracker.lon = "-0.0005" tracker.calc_planet("mars")
Запуск этого фрагмента кода приведет к:
{'az': 92.90019644871396, 'alt': -23.146670983905302, 'name': 'mars'}
Серверный aiohttp: HTTP-маршруты
Учитывая некоторую широту и долготу, мы можем легко получить текущую эфемериду планеты в градусах. Теперь давайте настроим маршрут aiohttp , чтобы клиент мог получить эфемериды планеты с учетом геолокации пользователя.
Прежде чем мы начнем писать код, мы должны подумать о том, какие HTTP глаголы мы хотим связать с каждой из этих задач. Имеет смысл использовать POST для первой задачи, так как мы устанавливаем географические координаты наблюдателя. Учитывая, что мы получаем эфемериды, имеет смысл использовать GET для второй задачи:
# aiohttp_app.py from aiohttp import web from planet_tracker import PlanetTracker @routes.get("/planets/{name}") async def get_planet_ephmeris(request): planet_name = request.match_info['name'] data = request.query try: geo_location_data = { "lon": str(data["lon"]), "lat": str(data["lat"]), "elevation": float(data["elevation"]) } except KeyError as err: # default to Greenwich Observatory geo_location_data = { "lon": "-0.0005", "lat": "51.4769", "elevation": 0.0, } print(f"get_planet_ephmeris: {planet_name}, {geo_location_data}") tracker = PlanetTracker() tracker.lon = geo_location_data["lon"] tracker.lat = geo_location_data["lat"] tracker.elevation = geo_location_data["elevation"] planet_data = tracker.calc_planet(planet_name) return web.json_response(planet_data) app = web.Application() app.add_routes(routes) web.run_app(app, host="localhost", port=8000)
Здесь декоратор route.get
указывает, что мы хотим, чтобы сопрограмма get_planet_ephemeris
была обработчиком переменной GET
route.
Прежде чем мы запустим это, давайте установим aiohttp с pip env:
[email protected]:~/planettracker$ pipenv install aiohttp
Теперь мы можем запустить наше приложение:
[email protected]:~/planettracker$ pipenv run python aiohttp_app.py
Когда мы запускаем его, мы можем указать вашему браузеру на наши различные маршруты, чтобы увидеть данные, возвращаемые нашим сервером. Если я помещу localhost:8000/planets/mars
в адресную строку моего браузера, я должен увидеть следующий ответ:
{"az": 98.72414165963292, "alt": -18.720718647020792, "name": "mars"}
Это то же самое, что выполнить следующую команду curl :
[email protected]:~$ curl localhost:8000/planets/mars {"az": 98.72414165963292, "alt": -18.720718647020792, "name": "mars"}
Если вы не знакомы с curl , это удобный инструмент командной строки для тестирования ваших HTTP-маршрутов.
Мы можем предоставить GET URL to curl :
[email protected]:~$ curl localhost:8000/planets/mars {"az": 98.72414165963292, "alt": -18.720718647020792, "name": "mars"}
Это дает нам эфемериды Марса в Гринвичской обсерватории в Великобритании.
Мы можем закодировать координаты в URL-адресе запроса GET
, чтобы получить эфемериды Марса в других местах (обратите внимание на кавычки вокруг URL-адреса):
[email protected]:~$ curl "localhost:8000/planets/mars?lon=145.051&lat=-39.754&elevation=0" {"az": 102.30273048280189, "alt": 11.690380174890928, "name": "mars"
curl
также можно использовать для создания почтовых запросов:
[email protected]:~$ curl --header "Content-Type: application/x-www-form-urlencoded" --data "lat=48.93&lon=2.45&elevation=0" localhost:8000/geo_location {"lon": "2.45", "lat": "48.93", "elevation": 0.0}
Обратите внимание, что, предоставляя поле --data
, curl
автоматически предполагает, что мы делаем POST-запрос.
Прежде чем мы двинемся дальше, я должен отметить, что функция web.run_app
запускает наше приложение блокирующим образом. Это определенно не то, чего мы хотим достичь!
Чтобы запустить его одновременно, мы должны добавить немного больше кода:
# aiohttp_app.py import asyncio ... # web.run_app(app) async def start_app(): runner = web.AppRunner(app) await runner.setup() site = web.TCPSite( runner, parsed.host, parsed.port) await site.start() print(f"Serving up app on {parsed.host}:{parsed.port}") return runner, site loop = asyncio.get_event_loop() runner, site = loop.run_until_complete(start_async_app()) try: loop.run_forever() except KeyboardInterrupt as err: loop.run_until_complete(runner.cleanup())
Обратите внимание на наличие loop.run_forever
вместо вызова loop.run_until_complete
, который мы видели ранее. Вместо выполнения заданного количества сопрограмм мы хотим, чтобы наша программа запустила сервер , который будет обрабатывать запросы до тех пор, пока мы не выйдем с помощью ctrl+c
, после чего она изящно завершит работу сервера.
Клиент HTML/JavaScript
aiohttp позволяет нам обслуживать файлы HTML и JavaScript. Использование aiohttp для обслуживания “статических” ресурсов, таких как CSS и JavaScript, не рекомендуется, но для целей этого приложения это не должно быть проблемой.
Давайте добавим несколько строк к нашему aiohttp_app.py
файл для обслуживания HTML-файла, ссылающегося на файл JavaScript:
# aiohttp_app.py ... @routes.get('/') async def hello(request): return web.FileResponse("./index.html") app = web.Application() app.add_routes(routes) app.router.add_static("/", "./") ...
Сопрограмма hello
настраивает маршрут GET на localhost:8000/
, который обслуживает содержимое index.html
, расположенный в том же каталоге, из которого мы запускаем наш сервер.
Линия app.router.add_static
настраивает маршрут по адресу localhost:8000/
для обслуживания файлов в том же каталоге, из которого мы запускаем наш сервер. Это означает, что ваш браузер сможет найти файл JavaScript, на который мы ссылаемся в index.html
.
Примечание : В производстве имеет смысл переместить HTML, CSS и JS-файлы в отдельный каталог, который обслуживается сам по себе. Это делает его таким образом, что любопытный пользователь не может получить доступ к нашему серверному коду.
HTML файл довольно прост:
Planet Tracker
Тем не менее, файл JavaScript немного более вовлечен:
var App = function() { this.planetNames = [ "mercury", "venus", "mars", "jupiter", "saturn", "uranus", "neptune" ] this.geoLocationIds = [ "lon", "lat", "elevation" ] this.keyUpInterval = 500 this.keyUpTimer = null this.planetDisplayCreated = false this.updateInterval = 2000 // update very second and a half this.updateTimer = null this.geoLocation = null this.init = function() { this.getGeoLocation().then((position) => { var coords = this.processCoordinates(position) this.geoLocation = coords this.initGeoLocationDisplay() this.updateGeoLocationDisplay() return this.getPlanetEphemerides() }).then((planetData) => { this.createPlanetDisplay() this.updatePlanetDisplay(planetData) }).then(() => { return this.initUpdateTimer() }) } this.update = function() { if (this.planetDisplayCreated) { this.getPlanetEphemerides().then((planetData) => { this.updatePlanetDisplay(planetData) }) } } this.get = function(url, data) { var request = new XMLHttpRequest() if (data !== undefined) { url += `?${data}` } // console.log(`get: ${url}`) request.open("GET", url, true) return new Promise((resolve, reject) => { request.send() request.onreadystatechange = function(){ if (this.readyState === XMLHttpRequest.DONE && this.status === 200) { resolve(this) } } request.onerror = reject }) } this.processCoordinates = function(position) { var coordMap = { 'longitude': 'lon', 'latitude': 'lat', 'altitude': 'elevation' } var coords = Object.keys(coordMap).reduce((obj, name) => { var coord = position.coords[name] if (coord === null || isNaN(coord)) { coord = 0.0 } obj[coordMap[name]] = coord return obj }, {}) return coords } this.coordDataUrl = function (coords) { postUrl = Object.keys(coords).map((c) => { return `${c}=${coords[c]}` }) return postUrl } this.getGeoLocation = function() { return new Promise((resolve, reject) => { navigator.geolocation.getCurrentPosition(resolve) }) } this.getPlanetEphemeris = function(planetName) { var postUrlArr = this.coordDataUrl(this.geoLocation) return this.get(`/planets/${planetName}`, postUrlArr.join("&")).then((req) => { return JSON.parse(req.response) }) } this.getPlanetEphemerides = function() { return Promise.all( this.planetNames.map((name) => { return this.getPlanetEphemeris(name) }) ) } this.createPlanetDisplay = function() { var div = document.getElementById("app") var table = document.createElement("table") var header = document.createElement("tr") var headerNames = ["Name", "Azimuth", "Altitude"] headerNames.forEach((headerName) => { var headerElement = document.createElement("th") headerElement.textContent = headerName header.appendChild(headerElement) }) table.appendChild(header) this.planetNames.forEach((name) => { var planetRow = document.createElement("tr") headerNames.forEach((headerName) => { planetRow.appendChild( document.createElement("td") ) }) planetRow.setAttribute("id", name) table.appendChild(planetRow) }) div.appendChild(table) this.planetDisplayCreated = true } this.updatePlanetDisplay = function(planetData) { planetData.forEach((d) => { var content = [d.name, d.az, d.alt] var planetRow = document.getElementById(d.name) planetRow.childNodes.forEach((node, idx) => { var contentFloat = parseFloat(content[idx]) if (isNaN(contentFloat)) { node.textContent = content[idx] } else { node.textContent = contentFloat.toFixed(2) } }) }) } this.initGeoLocationDisplay = function() { this.geoLocationIds.forEach((id) => { var node = document.getElementById(id) node.childNodes[1].onkeyup = this.onGeoLocationKeyUp() }) var appNode = document.getElementById("app") var resetLocationButton = document.createElement("button") resetLocationButton.setAttribute("id", "reset-location") resetLocationButton.onclick = this.onResetLocationClick() resetLocationButton.textContent = "Reset Geo Location" appNode.appendChild(resetLocationButton) } this.updateGeoLocationDisplay = function() { Object.keys(this.geoLocation).forEach((id) => { var node = document.getElementById(id) node.childNodes[1].value = parseFloat( this.geoLocation[id] ).toFixed(2) }) } this.getDisplayedGeoLocation = function() { var displayedGeoLocation = this.geoLocationIds.reduce((val, id) => { var node = document.getElementById(id) var nodeVal = parseFloat(node.childNodes[1].value) val[id] = nodeVal if (isNaN(nodeVal)) { val.valid = false } return val }, {valid: true}) return displayedGeoLocation } this.onGeoLocationKeyUp = function() { return (evt) => { // console.log(evt.key, evt.code) var currentTime = new Date() if (this.keyUpTimer !== null){ clearTimeout(this.keyUpTimer) } this.keyUpTimer = setTimeout(() => { var displayedGeoLocation = this.getDisplayedGeoLocation() if (displayedGeoLocation.valid) { delete displayedGeoLocation.valid this.geoLocation = displayedGeoLocation console.log("Using user supplied geo location") } }, this.keyUpInterval) } } this.onResetLocationClick = function() { return (evt) => { console.log("Geo location reset clicked") this.getGeoLocation().then((coords) => { this.geoLocation = this.processCoordinates(coords) this.updateGeoLocationDisplay() }) } } this.initUpdateTimer = function () { if (this.updateTimer !== null) { clearInterval(this.updateTimer) } this.updateTimer = setInterval( this.update.bind(this), this.updateInterval ) return this.updateTimer } this.testPerformance = function(n) { var t0 = performance.now() var promises = [] for (var i=0; i{ var delta = (performance.now() - t0)/1000 console.log(`Took ${delta.toFixed(4)} seconds to do ${n} requests`) }) } } var app document.addEventListener("DOMContentLoaded", (evt) => { app = new App() app.init() })
Это приложение будет периодически (каждые 2 секунды) обновлять и отображать эфемериды планет. Мы можем предоставить наши собственные гео координаты или позволить веб-API геолокации определить наше текущее местоположение. Приложение обновляет геолокацию, если пользователь перестает печатать на полсекунды или больше.
Хотя это не учебник по JavaScript, я думаю, что полезно понять, что делают различные части скрипта:
create Planet Display
– это динамическое создание HTML-элементов и привязка их к объектной модели документа (DOM)update Planet Display
принимает данные, полученные с сервера, и заполняет элементы, созданные с помощьюcreate Planet Display
get
делает запрос GET на сервер. Объект XMLHttpRequest позволяет сделать это без перезагрузки страницы.post
делает POST-запрос к серверу. Как и в случае сget
это делается без перезагрузки страницы.get GeoLocation
использует Web Geolocation API для получения текущих географических координат пользователя. Это должно быть выполнено “в безопасном контексте” (то есть мы должны использоватьHTTPS
| не | HTTP ).get Planet Ephemeris
иget Planet Ephemerides
делают GET-запросы на сервер, чтобы получить эфемериды для конкретной планеты и получить эфемериды для всех планет соответственно.тест производительности
делаетn
запросов к серверу и определяет, сколько времени это займет.
Праймер по развертыванию в Heroku
Heroku – это сервис для легкого развертывания веб-приложений. Heroku заботится о настройке веб-компонентов приложения, таких как настройка обратных прокси-серверов или забота о балансировке нагрузки. Для приложений, обрабатывающих мало запросов и небольшое количество пользователей, Heroku-отличный бесплатный хостинг.
Развертывание приложений Python в Heroku в последние годы стало очень простым. По сути, мы должны создать два файла, которые перечисляют зависимости нашего приложения и сообщают Heroku, как запустить наше приложение.
A Pip file заботится о первом, в то время как a Procfile заботится о втором. Файл трубы поддерживается с помощью pipenv
– мы добавляем в ваш файл Pip (и файл трубы.lock) каждый раз, когда мы устанавливаем зависимость.
Чтобы запустить наше приложение на Heroku, мы должны добавить еще одну зависимость:
[email protected]:~/planettracker$ pipenv install gunicorn
Мы можем создать ваш собственный профиль, добавив в него следующую строку:
web: gunicorn aiohttp_app:app --worker-class aiohttp.GunicornWebWorker
В основном это говорит Heroku использовать Gunicorn для запуска нашего приложения, используя специальный aiohttp web worker.
Прежде чем вы сможете развернуться в Heroku, вам нужно будет начать отслеживать приложение с помощью Git:
[email protected]:~/planettracker$ git init [email protected]:~/planettracker$ git add . [email protected]:~/planettracker$ git commit -m "first commit"
Теперь вы можете следовать инструкциям на Heroku devcenter here для развертывания вашего приложения. Обратите внимание, что вы можете пропустить шаг “Подготовка приложения” этого руководства, так как у вас уже есть отслеживаемое приложение git.
Как только ваше приложение будет развернуто, вы можете перейти к выбранному URL-адресу Heroku в вашем браузере и просмотреть приложение, которое будет выглядеть примерно так:
Вывод
В этой статье мы погрузились в то, как выглядит асинхронная веб – разработка на Python- ее преимущества и использование. Затем мы построили простое реактивное приложение на основе aiohttp , которое динамически отображает текущие соответствующие координаты неба планет Солнечной системы с учетом географических координат пользователя.
После создания приложения мы подготовили его к развертыванию на Heroku.
Как уже упоминалось ранее, вы можете найти как исходный код , так и демо-версию приложения , если это необходимо.