Escreva aplicações web
assíncronas com Python3 +
Tornado
Wilson Júnior
Wilson Júnior
Há 3 anos trabalhando como
developer na Globo.com, apaixonado
por tecnologia e pessoas.
Entusiasta com python desde os 14
anos de idade.
Goiano, cerrado roots!
Entendendo as bases do
asyncronismo
Uma aplicação pode ser dividida
em 2 tempos
CPU Bound
CPU Bound
Tempo que sua aplicação está voltada
apenas para o uso do processador ...
CPU Bound
CPU Bound (exemplos)
● Cálculos matemáticos pesados
● Conversão de imagens
● Encoding de vídeo
IO Bound
IO Bound
Tempo que sua aplicação está voltada
apenas para comunicação com
interfaces de saída como:
IO Bound
● Comunicação com serviços
externos (banco de dados, HTTP,
redis, etc)
● Comunicação com dispositivos
locais (HD, Fita, DVD, pendrive, etc)
O que acontece quando fazemos
uma chamada que faz IO no
python
Exemplo de aplicação IO Bound
import requests
url = 'https://www.google.com.br'
response = requests.get(url, timeout=10)
Se este request HTTP demorar 10
segundos, o que acontece com a
aplicação ?
IOWait!!
O que vem acontecendo nos
últimos anos com nossas
aplicações ...
O número de clientes das
aplicações mudou!
O número de clientes das aplicações mudou!
Otimizar o tempo de IO para
alcançarmos maior número de
clientes.
Então temos o IOLoop!
Então temos o IOLoop!
● Funciona como um "for" global
● Troca o contexto e retorna quando o IO finalizar
● Simples e fácil uso
● Multi plataforma (select, epoll, poll, kqueue) Windows, Linux e BSD
O que um ioloop trouxe de
melhoria
As aplicações não ficam travadas
em IOWait!
Aviso sobre o conceito!
Não bloqueie o IOLoop com
operações que querem alto cpu
bound!
Features do python 3.6
Async functions!
Aprenda a separar IO bound de
CPU bound
Podemos fazer o seguinte
approach!
Funções que fazem IO são async!
Funções que não fazem IO são
tradicionais
Funções que fazem IO
async def async_func():
# do something async
E como pego o retorno de uma
função assíncrona ?!
await nela!
Funções que fazem IO
async def async_func():
# do something async
async def blah():
ret = await async_func()
Funções que fazem IO
async def async_func():
# do something async
async def blah():
ret = await async_func()
Só posso fazer await dentro de
outra função assíncrona!
Tornado web framework!
tornadoweb.org
Características
● Escalável, não bloqueante;
● Nasceu na friendfeed; que foi vendida para o Facebook; hoje é mantido
pela comunidade;
● Pode manter milhares de conexões abertas;
● Possui um IO não bloqueante
Vamos começar um projetinho
em tornado ?!
Primeiro step: crie um virtualenv
com python 3.6
Primeiro step: crie um virtualenv com python 3.6
$ which python3.6
/usr/local/bin/python3.6
$ mkvirtualenv myproject -p /usr/local/bin/python3.6
Segundo step: instale o tornado
$ pip install tornado
Segundo step: instale o tornado
from tornado.ioloop import IOLoop
from tornado.web import (
Application,
RequestHandler,
)
class HelloWorldHandler(RequestHandler):
def get(self):
self.write("Hello, world")
Segundo step: instale o tornado
app = Application([
(r"/", HelloWorldHandler),
])
app.listen(8888)
IOLoop.current().start()
Fazendo requests utilizando
tornado
Novos imports
import json
from tornado.ioloop import IOLoop
from tornado.web import (
Application,
RequestHandler,
)
from tornado.httpclient import AsyncHTTPClient
Realizando requests
async def get_http_result():
url = (
'https://raw.githubusercontent.com'
'/backstage/functions/master/package.json'
)
response = await AsyncHTTPClient().fetch(url,
validate_cert=False)
data = json.loads(response.body)
return {
'heey': data['name'],
}
Realizando requests
class HelloWorldHandler(RequestHandler):
async def get(self):
result = await get_http_result()
self.write(json.dumps(result))
Cuidado com execução de
tarefas com o uso de CPU alto
Cuidado com execução de tarefas com o uso de CPU alto
class HelloWorldHandler(RequestHandler):
async def get(self):
# processamento de imagens pesadas
# cálculos pesados
# etc.
Cuidado com execução de
tarefas que façam IO sem usar
IOLoop
Cuidado com execução de tarefas que façam IO sem usar IOLoop
class HelloWorldHandler(RequestHandler):
async def get(self):
requests.get('https://host.com')
Teste assíncronamente!
Teste assincronadamente
import unittest
from tornado.testing import AsyncTestCase, gen_test
from blah import get_http_result
class MyTest(AsyncTestCase):
@gen_test
async def test_ok(self):
result = await get_http_result()
self.assertEqual(result, {'heey': 'backstage-functions'})
Teste assincronadamente
$ pip install nose
$ nosetests test.py
Espere mais de uma operação
assíncrona
Espere mais de uma operação assíncrona
from tornado import gen
class HelloWorldHandler(RequestHandler):
async def get(self):
result01, result02 = await gen.multi([
get_http_result(),
get_http_result(),
])
self.write(json.dumps([result01, result02]))
Busque a lib não bloqueante para
o que você vai utilizar.
Banco de Dados
MySQL:
github.com/PyMySQL/Tornado-M
ySQL
MongoDB: motor.readthedocs.io
Redis:
github.com/aio-libs/aioredis
Brokers
Stomp:
github.com/wpjunior/torstomp
AMQP: pika.readthedocs.io
Alternativas para tarefas CPU
intensive
Use um ThreadPoolExecutor
Execute assincronamente via ThreadPoolExecutor
● Vantagens
○ Fica tudo auto-contido dentro de um
app
○ Único processo para manter de pé
● Desvantagens
○ Limitado ao número de threads
○ Processo caiu perdeu jobs
Use um ThreadPoolExecutor
import time
from tornado import gen
from tornado.ioloop import IOLoop
from tornado.web import (
Application,
RequestHandler,
)
from tornado.concurrent import run_on_executor
from concurrent.futures import ThreadPoolExecutor
Use um ThreadPoolExecutor
class ThreadPoolHandler(RequestHandler):
_thread_pool = ThreadPoolExecutor(10)
@gen.coroutine
def get(self):
result = yield self.sync_job()
self.write(result)
@run_on_executor(executor='_thread_pool')
def sync_job(self):
time.sleep(1)
return 'ok'
Use um ThreadPoolExecutor
if __name__ == "__main__":
app = Application([
(r"/", ThreadPoolHandler),
], debug=True)
app.listen(8888)
IOLoop.current().start()
Execute assincronamente via
barramento (noreply)
Execute assincronamente via barramento (noreply)
● Vantagens
○ Worker pode ser escrito em outra
linguaguem
○ Possibilidade de escalar em muitas
máquinas
○ Bufferização de tasks em uma fila
persistente
● Desvantagens
○ Mais um processo para manter
Execute assincronamente via barramento (noreply)
Tutorial: API Restful usando
motor + tornado
Tutorial: API Restful usando motor +
tornado
● instalaremos o mongo local
● $ pip install motor
Tutorial: API Restful usando motor + tornado
import json
import uuid
from tornado.ioloop import IOLoop
from tornado.web import (
Application,
RequestHandler,
)
from motor.motor_tornado import MotorClient
Tutorial: API Restful usando motor + tornado
class CollectionNameMixin(object):
@property
def collection_name(self):
raise NotImplementedError
def set_default_headers(self):
self.set_header('content-type', 'application/json')
@property
def collection(self):
return self.application.db[self.collection_name]
Tutorial: API Restful usando motor + tornado
class BaseCollectionHandler(
CollectionNameMixin, RequestHandler):
async def get(self):
result = []
async for document in self.collection.find({}):
result.append(document)
self.write(json.dumps(result))
Tutorial: API Restful usando motor + tornado
class BaseCollectionHandler(
CollectionNameMixin, RequestHandler):
async def post(self):
body = json.loads(self.request.body)
body['_id'] = str(uuid.uuid4())
await self.collection.insert_one(body)
self.write(json.dumps(body))
Tutorial: API Restful usando motor + tornado
class BaseResourceHandler(CollectionNameMixin,
RequestHandler):
async def get(self, resource_id):
where = {"_id": resource_id}
result = await self.collection.find_one(where)
if not result:
self.set_status(404)
self.write('{"error": "Not found"}')
return
self.write(json.dumps(result))
Tutorial: API Restful usando motor + tornado
class BaseResourceHandler(CollectionNameMixin,
RequestHandler):
async def put(self, resource_id):
body = json.loads(self.request.body)
result = await self.collection.update_one(
{'_id': resource_id},
{'$set': body}
)
await self.get(resource_id)
Tutorial: API Restful usando motor + tornado
class BaseResourceHandler(CollectionNameMixin,
RequestHandler):
async def delete(self, resource_id):
where = {'_id': resource_id}
result = await self.collection.delete_many(where)
if result.deleted_count == 0:
self.set_status(404)
self.write('{"error": "Not found"}')
return
self.set_status(204)
Tutorial: API Restful usando motor + tornado
class BaseResourceHandler(CollectionNameMixin,
RequestHandler):
async def delete(self, resource_id):
where = {'_id': resource_id}
result = await self.collection.delete_many(where)
if result.deleted_count == 0:
self.set_status(404)
self.write('{"error": "Not found"}')
return
self.set_status(204)
Tutorial: API Restful usando motor + tornado
class PeopleCollectionHandler(BaseCollectionHandler):
collection_name = 'people'
class PeopleResourceHandler(BaseResourceHandler):
collection_name = 'people'
Tutorial: API Restful usando motor + tornado
def get_app(db_name='blah'):
app = Application([
(r"/people", PeopleCollectionHandler),
(r"/people/(.*)", PeopleResourceHandler),
])
app.db = MotorClient()[db_name]
return app
if __name__ == "__main__":
get_app().listen(8888)
IOLoop.current().start()
Vamos testar nossa aplicação ?!
Vamos testar nossa aplicação ?!
import json
from tornado.testing import AsyncHTTPTestCase, gen_test
from rest_mongo import get_app
Vamos testar nossa aplicação ?!
class MyTest(AsyncHTTPTestCase):
def get_app(self):
return get_app('test-database')
def setUp(self):
super().setUp()
def drop_collection_result(*args):
self.stop()
self.get_app().db.drop_collection(
'people', callback=drop_collection_result)
self.wait()
Vamos testar nossa aplicação ?!
class MyTest(AsyncHTTPTestCase):
def test_not_found(self):
response = self.fetch('/people/1234')
self.assertEqual(response.code, 404)
self.assertEqual(response.headers['content-type'],
'application/json')
body = json.loads(response.body)
self.assertEqual(body, {'error': 'Not found'})
Vamos testar nossa aplicação ?!
class MyTest(AsyncHTTPTestCase):
def test_create_and_get(self):
# create
response = self.fetch('/people', method='POST',
body='{"name": "created"}')
self.assertEqual(response.code, 201)
self.assertEqual(response.headers['content-type'],
'application/json')
body = json.loads(response.body)
self.assertEqual(body['name'], 'created')
Vamos testar nossa aplicação ?!
class MyTest(AsyncHTTPTestCase):
def test_create_and_get(self):
...
# get
response = self.fetch('/people/%s' % body["_id"])
self.assertEqual(response.code, 200)
self.assertEqual(response.headers['content-type'],
'application/json')
body = json.loads(response.body)
self.assertEqual(body['name'], 'created')
Vamos testar então !?
Tutorial resolvido
github.com/wpjunior/python36-a
sync
Obrigado
github.com/wpjunior about.me/wpjunior
Temos projetos em tornado, venha trabalhar
na Globo.com
talentos.globo.com
Projetos

Escreva aplicações web assíncronas com python3 + tornado

  • 1.
    Escreva aplicações web assíncronascom Python3 + Tornado Wilson Júnior
  • 2.
    Wilson Júnior Há 3anos trabalhando como developer na Globo.com, apaixonado por tecnologia e pessoas. Entusiasta com python desde os 14 anos de idade. Goiano, cerrado roots!
  • 3.
    Entendendo as basesdo asyncronismo
  • 4.
    Uma aplicação podeser dividida em 2 tempos
  • 5.
  • 6.
    CPU Bound Tempo quesua aplicação está voltada apenas para o uso do processador ...
  • 7.
  • 8.
    CPU Bound (exemplos) ●Cálculos matemáticos pesados ● Conversão de imagens ● Encoding de vídeo
  • 9.
  • 10.
    IO Bound Tempo quesua aplicação está voltada apenas para comunicação com interfaces de saída como:
  • 11.
    IO Bound ● Comunicaçãocom serviços externos (banco de dados, HTTP, redis, etc) ● Comunicação com dispositivos locais (HD, Fita, DVD, pendrive, etc)
  • 12.
    O que acontecequando fazemos uma chamada que faz IO no python
  • 13.
    Exemplo de aplicaçãoIO Bound import requests url = 'https://www.google.com.br' response = requests.get(url, timeout=10)
  • 14.
    Se este requestHTTP demorar 10 segundos, o que acontece com a aplicação ?
  • 15.
  • 16.
    O que vemacontecendo nos últimos anos com nossas aplicações ...
  • 17.
    O número declientes das aplicações mudou!
  • 18.
    O número declientes das aplicações mudou!
  • 19.
    Otimizar o tempode IO para alcançarmos maior número de clientes.
  • 20.
  • 21.
    Então temos oIOLoop! ● Funciona como um "for" global ● Troca o contexto e retorna quando o IO finalizar ● Simples e fácil uso ● Multi plataforma (select, epoll, poll, kqueue) Windows, Linux e BSD
  • 22.
    O que umioloop trouxe de melhoria
  • 23.
    As aplicações nãoficam travadas em IOWait!
  • 24.
    Aviso sobre oconceito!
  • 25.
    Não bloqueie oIOLoop com operações que querem alto cpu bound!
  • 26.
  • 27.
  • 28.
    Aprenda a separarIO bound de CPU bound
  • 29.
    Podemos fazer oseguinte approach!
  • 30.
    Funções que fazemIO são async!
  • 31.
    Funções que nãofazem IO são tradicionais
  • 32.
    Funções que fazemIO async def async_func(): # do something async
  • 33.
    E como pegoo retorno de uma função assíncrona ?!
  • 34.
  • 35.
    Funções que fazemIO async def async_func(): # do something async async def blah(): ret = await async_func()
  • 36.
    Funções que fazemIO async def async_func(): # do something async async def blah(): ret = await async_func()
  • 37.
    Só posso fazerawait dentro de outra função assíncrona!
  • 38.
  • 39.
    Características ● Escalável, nãobloqueante; ● Nasceu na friendfeed; que foi vendida para o Facebook; hoje é mantido pela comunidade; ● Pode manter milhares de conexões abertas; ● Possui um IO não bloqueante
  • 40.
    Vamos começar umprojetinho em tornado ?!
  • 41.
    Primeiro step: crieum virtualenv com python 3.6
  • 42.
    Primeiro step: crieum virtualenv com python 3.6 $ which python3.6 /usr/local/bin/python3.6 $ mkvirtualenv myproject -p /usr/local/bin/python3.6
  • 43.
    Segundo step: instaleo tornado $ pip install tornado
  • 44.
    Segundo step: instaleo tornado from tornado.ioloop import IOLoop from tornado.web import ( Application, RequestHandler, ) class HelloWorldHandler(RequestHandler): def get(self): self.write("Hello, world")
  • 45.
    Segundo step: instaleo tornado app = Application([ (r"/", HelloWorldHandler), ]) app.listen(8888) IOLoop.current().start()
  • 46.
  • 47.
    Novos imports import json fromtornado.ioloop import IOLoop from tornado.web import ( Application, RequestHandler, ) from tornado.httpclient import AsyncHTTPClient
  • 48.
    Realizando requests async defget_http_result(): url = ( 'https://raw.githubusercontent.com' '/backstage/functions/master/package.json' ) response = await AsyncHTTPClient().fetch(url, validate_cert=False) data = json.loads(response.body) return { 'heey': data['name'], }
  • 49.
    Realizando requests class HelloWorldHandler(RequestHandler): asyncdef get(self): result = await get_http_result() self.write(json.dumps(result))
  • 50.
    Cuidado com execuçãode tarefas com o uso de CPU alto
  • 51.
    Cuidado com execuçãode tarefas com o uso de CPU alto class HelloWorldHandler(RequestHandler): async def get(self): # processamento de imagens pesadas # cálculos pesados # etc.
  • 52.
    Cuidado com execuçãode tarefas que façam IO sem usar IOLoop
  • 53.
    Cuidado com execuçãode tarefas que façam IO sem usar IOLoop class HelloWorldHandler(RequestHandler): async def get(self): requests.get('https://host.com')
  • 54.
  • 55.
    Teste assincronadamente import unittest fromtornado.testing import AsyncTestCase, gen_test from blah import get_http_result class MyTest(AsyncTestCase): @gen_test async def test_ok(self): result = await get_http_result() self.assertEqual(result, {'heey': 'backstage-functions'})
  • 56.
    Teste assincronadamente $ pipinstall nose $ nosetests test.py
  • 57.
    Espere mais deuma operação assíncrona
  • 58.
    Espere mais deuma operação assíncrona from tornado import gen class HelloWorldHandler(RequestHandler): async def get(self): result01, result02 = await gen.multi([ get_http_result(), get_http_result(), ]) self.write(json.dumps([result01, result02]))
  • 59.
    Busque a libnão bloqueante para o que você vai utilizar.
  • 60.
  • 61.
  • 62.
  • 63.
  • 64.
  • 65.
  • 66.
  • 67.
  • 68.
  • 69.
    Execute assincronamente viaThreadPoolExecutor ● Vantagens ○ Fica tudo auto-contido dentro de um app ○ Único processo para manter de pé ● Desvantagens ○ Limitado ao número de threads ○ Processo caiu perdeu jobs
  • 70.
    Use um ThreadPoolExecutor importtime from tornado import gen from tornado.ioloop import IOLoop from tornado.web import ( Application, RequestHandler, ) from tornado.concurrent import run_on_executor from concurrent.futures import ThreadPoolExecutor
  • 71.
    Use um ThreadPoolExecutor classThreadPoolHandler(RequestHandler): _thread_pool = ThreadPoolExecutor(10) @gen.coroutine def get(self): result = yield self.sync_job() self.write(result) @run_on_executor(executor='_thread_pool') def sync_job(self): time.sleep(1) return 'ok'
  • 72.
    Use um ThreadPoolExecutor if__name__ == "__main__": app = Application([ (r"/", ThreadPoolHandler), ], debug=True) app.listen(8888) IOLoop.current().start()
  • 73.
  • 74.
    Execute assincronamente viabarramento (noreply) ● Vantagens ○ Worker pode ser escrito em outra linguaguem ○ Possibilidade de escalar em muitas máquinas ○ Bufferização de tasks em uma fila persistente ● Desvantagens ○ Mais um processo para manter
  • 75.
    Execute assincronamente viabarramento (noreply)
  • 76.
    Tutorial: API Restfulusando motor + tornado
  • 77.
    Tutorial: API Restfulusando motor + tornado ● instalaremos o mongo local ● $ pip install motor
  • 78.
    Tutorial: API Restfulusando motor + tornado import json import uuid from tornado.ioloop import IOLoop from tornado.web import ( Application, RequestHandler, ) from motor.motor_tornado import MotorClient
  • 79.
    Tutorial: API Restfulusando motor + tornado class CollectionNameMixin(object): @property def collection_name(self): raise NotImplementedError def set_default_headers(self): self.set_header('content-type', 'application/json') @property def collection(self): return self.application.db[self.collection_name]
  • 80.
    Tutorial: API Restfulusando motor + tornado class BaseCollectionHandler( CollectionNameMixin, RequestHandler): async def get(self): result = [] async for document in self.collection.find({}): result.append(document) self.write(json.dumps(result))
  • 81.
    Tutorial: API Restfulusando motor + tornado class BaseCollectionHandler( CollectionNameMixin, RequestHandler): async def post(self): body = json.loads(self.request.body) body['_id'] = str(uuid.uuid4()) await self.collection.insert_one(body) self.write(json.dumps(body))
  • 82.
    Tutorial: API Restfulusando motor + tornado class BaseResourceHandler(CollectionNameMixin, RequestHandler): async def get(self, resource_id): where = {"_id": resource_id} result = await self.collection.find_one(where) if not result: self.set_status(404) self.write('{"error": "Not found"}') return self.write(json.dumps(result))
  • 83.
    Tutorial: API Restfulusando motor + tornado class BaseResourceHandler(CollectionNameMixin, RequestHandler): async def put(self, resource_id): body = json.loads(self.request.body) result = await self.collection.update_one( {'_id': resource_id}, {'$set': body} ) await self.get(resource_id)
  • 84.
    Tutorial: API Restfulusando motor + tornado class BaseResourceHandler(CollectionNameMixin, RequestHandler): async def delete(self, resource_id): where = {'_id': resource_id} result = await self.collection.delete_many(where) if result.deleted_count == 0: self.set_status(404) self.write('{"error": "Not found"}') return self.set_status(204)
  • 85.
    Tutorial: API Restfulusando motor + tornado class BaseResourceHandler(CollectionNameMixin, RequestHandler): async def delete(self, resource_id): where = {'_id': resource_id} result = await self.collection.delete_many(where) if result.deleted_count == 0: self.set_status(404) self.write('{"error": "Not found"}') return self.set_status(204)
  • 86.
    Tutorial: API Restfulusando motor + tornado class PeopleCollectionHandler(BaseCollectionHandler): collection_name = 'people' class PeopleResourceHandler(BaseResourceHandler): collection_name = 'people'
  • 87.
    Tutorial: API Restfulusando motor + tornado def get_app(db_name='blah'): app = Application([ (r"/people", PeopleCollectionHandler), (r"/people/(.*)", PeopleResourceHandler), ]) app.db = MotorClient()[db_name] return app if __name__ == "__main__": get_app().listen(8888) IOLoop.current().start()
  • 88.
    Vamos testar nossaaplicação ?!
  • 89.
    Vamos testar nossaaplicação ?! import json from tornado.testing import AsyncHTTPTestCase, gen_test from rest_mongo import get_app
  • 90.
    Vamos testar nossaaplicação ?! class MyTest(AsyncHTTPTestCase): def get_app(self): return get_app('test-database') def setUp(self): super().setUp() def drop_collection_result(*args): self.stop() self.get_app().db.drop_collection( 'people', callback=drop_collection_result) self.wait()
  • 91.
    Vamos testar nossaaplicação ?! class MyTest(AsyncHTTPTestCase): def test_not_found(self): response = self.fetch('/people/1234') self.assertEqual(response.code, 404) self.assertEqual(response.headers['content-type'], 'application/json') body = json.loads(response.body) self.assertEqual(body, {'error': 'Not found'})
  • 92.
    Vamos testar nossaaplicação ?! class MyTest(AsyncHTTPTestCase): def test_create_and_get(self): # create response = self.fetch('/people', method='POST', body='{"name": "created"}') self.assertEqual(response.code, 201) self.assertEqual(response.headers['content-type'], 'application/json') body = json.loads(response.body) self.assertEqual(body['name'], 'created')
  • 93.
    Vamos testar nossaaplicação ?! class MyTest(AsyncHTTPTestCase): def test_create_and_get(self): ... # get response = self.fetch('/people/%s' % body["_id"]) self.assertEqual(response.code, 200) self.assertEqual(response.headers['content-type'], 'application/json') body = json.loads(response.body) self.assertEqual(body['name'], 'created')
  • 94.
  • 95.
  • 96.
  • 97.
    Temos projetos emtornado, venha trabalhar na Globo.com talentos.globo.com Projetos