I have the following code to send result to browser based on the api call.
import tornado.ioloop
import tornado.web
from tornado import gen
from datetime import date
class GetGameByIdHandler(tornado.web.RequestHandler):
#gen.coroutine
def get(self, id):
response = { 'id': int(id),
'name': 'Crazy Game',
'release_date': date.today().isoformat() }
self.set_header('Content-Type', 'text/json')
self.write(response)
for i in range(10000000):
for j in range(10):
pass
print i
application = tornado.web.Application([
(r"/getgamebyid/([0-9]+)", GetGameByIdHandler),
], debug = True)
if __name__ == "__main__":
application.listen(8888)
tornado.ioloop.IOLoop.instance().start()
I want that the api should return result as soon as self.write is encountered. The for loop should be run after that. How can I get this done? Basically, I don't want to return the result immediately.
NOTE: The loop here has no real purpose except to demonstrate the sending of result is delayed just because of this extra thing in the get function.
A less abstract example:
import tornado.ioloop
import tornado.web
from tornado import gen
from datetime import date
class GetGameByIdHandler(tornado.web.RequestHandler):
#gen.coroutine
def get(self, id):
result_dict = GetResultsFromDB(id)
response = result_dict
self.set_header('Content-Type', 'text/json')
self.write(response)
# Basically i want to doSomething basedon results
# Generated from DB
for key in result_dict:
if result_dict[key] == None:
DoSomething()
application = tornado.web.Application([
(r"/getgamebyid/([0-9]+)", GetGameByIdHandler),
], debug = True)
if __name__ == "__main__":
application.listen(8888)
tornado.ioloop.IOLoop.instance().start()
if you need to run some code after writing all data to a socket, you can use tornado.web.RequestHandler.flush:
self.write(response)
self.flush(callback=lambda: DoSomethingWrapper(response))
Related
Let's say I have a web app driven by Uvicorn server, the app implements GraphQL API with a mutation that starts long calculations on the server-side and a query endpoint that checks the status of the server. Let's say we want to know how many tasks are running in the background.
I have a simplified code, which does not work:
import asyncio
import logging
import time
import ariadne
import ariadne.asgi
import uvicorn
import starlette as sl
import starlette.applications
query_t = ariadne.QueryType()
mutation_t = ariadne.MutationType()
FIFO = []
async def long_task():
print('Starting long task...')
global FIFO
FIFO = [1, *FIFO]
# mock long calc with 5sec sleep
time.sleep(5)
FIFO.pop()
print('Long task finished!')
#mutation_t.field('longTask')
async def resolve_long_task(_parent, info):
print('Start to resolve long task...')
asyncio.create_task(long_task())
print('Resolve finished!')
return {}
#query_t.field('ping')
async def resolve_ping(_parent, info):
return f'FIFO has {len(FIFO)} elements'
def main():
schema_str = ariadne.gql('''
type Mutation{
longTask: longTaskResponse
}
type longTaskResponse {
message: String
}
type Query {
ping: String
}
''')
schema = ariadne.make_executable_schema(schema_str, query_t, mutation_t)
gql_app = ariadne.asgi.GraphQL(schema)
app = sl.applications.Starlette(routes=[sl.routing.Mount('/graphql', gql_app)])
uvicorn.run(app,
host='0.0.0.0',
port=9002,
log_level='error')
if __name__ == '__main__':
main()
After running
$ python main.py
I send a mutation in the GraphQL GUI in the first tab:
mutation longTaskQueue{
longTask {
message
}
}
In the second tab, I try to retrieve the length of the FIFO:
query ping {
ping
}
It seems that it's possible to run 2 long_task, but ping is waiting until all long_task will be finished. My general question is how to run heavy code in the background and do not block GQL API?
After many attempts I made it, now I can put many tasks in the background and track their amount (API is not freezing on one long task). What is happening is
the blocking computations are run in a pool:
import asyncio
import logging
import time
import ariadne
import ariadne.asgi
import uvicorn
import starlette as sl
import starlette.applications
from concurrent.futures import ProcessPoolExecutor, ThreadPoolExecutor
query_t = ariadne.QueryType()
mutation_t = ariadne.MutationType()
FIFO = []
async def long_task():
print('Starting long task...')
global FIFO
FIFO = [1, *FIFO]
# mock long calc with 5sec sleep
time.sleep(5)
FIFO.pop()
print('Long task finished!')
def run(corofn, *args):
loop = asyncio.new_event_loop()
try:
coro = corofn(*args)
asyncio.set_event_loop(loop)
return loop.run_until_complete(coro)
finally:
loop.close()
#mutation_t.field('longTask')
async def resolve_long_task(_parent, info):
loop = asyncio.get_event_loop()
executor = ThreadPoolExecutor(max_workers=5)
loop.set_default_executor(ProcessPoolExecutor())
print('Start to resolve long task...')
loop.run_in_executor(executor, run, long_task)
print('Resolve finished!')
return {}
#query_t.field('ping')
async def resolve_ping(_parent, info):
return f'FIFO has {len(FIFO)} elements'
def main():
schema_str = ariadne.gql('''
type Mutation{
longTask: longTaskResponse
}
type longTaskResponse {
message: String
}
type Query {
ping: String
}
''')
schema = ariadne.make_executable_schema(schema_str, query_t, mutation_t)
gql_app = ariadne.asgi.GraphQL(schema)
app = sl.applications.Starlette(routes=[sl.routing.Mount('/graphql', gql_app)])
uvicorn.run(app,
host='0.0.0.0',
port=9002,
log_level='error')
if __name__ == '__main__':
main()
The solution inspired by this answer.
I have requirement to stream huge oracle record set in Python rest API. I am running flask on tornado server. when I use tornado streaming dosent work, whereas on flask native server(werkzeung) it works perfectly. can anyone help me tornado support streaming or not?
Here is a small sample of code just trying to stream by using yield.
import tornado.web
from tornado import gen, httpclient
import asyncio, json, time
class basicReuqestHandler(tornado.web.RequestHandler):
def get(self):
self.write("Helow World!")
class staticReuqestHandler(tornado.web.RequestHandler):
def get(self):
self.render("index.html")
class StreamingHandler(tornado.web.RequestHandler):
#gen.coroutine
def get(self):
self.write("starting ....")
def stream():
a = 1
for i in range(100):
a = a+i
print(i)
print(json.dumps(i))
yield json.dumps(i)
self.write(stream())
self.write("closing...")
self.finish()
if __name__=='__main__':
app = tornado.web.Application([
(r"/", basicReuqestHandler),
(r"/myPage",staticReuqestHandler ),
(r"/StreamTest", StreamingHandler),
])
app.listen(7000)
tornado.ioloop.IOLoop.current().start()
I got my mistake, so answering to my question here to help anyone having similar issue.
here is the code:
import tornado.web
from tornado import gen, httpclient
import asyncio, json, time
class basicReuqestHandler(tornado.web.RequestHandler):
def get(self):
self.write("Helow World!")
class staticReuqestHandler(tornado.web.RequestHandler):
def get(self):
self.render("index.html")
class StreamingHandler(tornado.web.RequestHandler):
#gen.coroutine
def get(self):
self.write("starting ....")
def stream():
for i in range(100):
print(i)
print(json.dumps(i))
self.write(json.dumps(i))
yield self.flush()
self.write("closing...")
self.finish()
if __name__=='__main__':
app = tornado.web.Application([
(r"/", basicReuqestHandler),
(r"/myPage",staticReuqestHandler ),
(r"/StreamTest", StreamingHandler),
])
app.listen(7000)
tornado.ioloop.IOLoop.current().start()
Let's say I have a very simple web app in python Tornado framework with a single endpoint. All I'm interested in is returning a value calculated before starting the server. Slightly modified example from https://www.tornadoweb.org/en/stable/index.html will do just fine.
handler.py
import tornado.web
class MainHandler(tornado.web.RequestHandler):
def get(self):
self.write('I want to return var `expensive_value`')
main.py
import tornado.ioloop
import tornado.web
def make_app():
return tornado.web.Application([
(r"/", MainHandler),
])
if __name__ == "__main__":
# calculate some var here before starting the server
expensive_value = 'value from long_calculation()'
app = make_app()
app.listen(8888)
tornado.ioloop.IOLoop.current().start()
When running python main.py and sending a request to the endpoint it returns only a string of course. But I'd like to return the actual value of expensive_value. Currently I'm aware of two solutions to the problem.
1. Using global variable in handler
handler.py
import tornado.web
global_variable = None
def setter(val):
global global_variable
global_variable = val
class MainHandler(tornado.web.RequestHandler):
def get(self):
self.write(global_variable)
main.py
import tornado.ioloop
import tornado.web
from handler import MainHandler, setter
def make_app():
return tornado.web.Application([
(r"/", MainHandler),
])
if __name__ == "__main__":
expensive_value = 'value from long_calculation()'
setter(expensive_value)
app = make_app()
app.listen(8888)
tornado.ioloop.IOLoop.current().start()
Having a global var and setting its value from some other module sounds like an antipattern to me.
2. Using initialize method in handler
handler.py
import tornado.web
class MainHandler(tornado.web.RequestHandler):
def initialize(self, expensive_value):
self.expensive_value = expensive_value
def get(self):
self.write(self.expensive_value)
main.py
import tornado.ioloop
import tornado.web
from handler import MainHandler
def make_app(parameter):
return tornado.web.Application([
(r"/", MainHandler, {'expensive_value': parameter}),
])
if __name__ == "__main__":
expensive_value = 'value from long_calculation()'
app = make_app(expensive_value)
app.listen(8888)
tornado.ioloop.IOLoop.current().start()
This solution is better. But initialize method is called for every request. I realize the overhead for that would be rather small but I think it might be misleading for potential reader of the code since expensive_value never changes.
Summary
Both of these solution work. But I don't like any of them and it seems like I'm missing some Tornado functionality. What would be a pythonic way to solve this?
For example I believe Flask has app.config dictionary that is accessible in handlers and it seems to be a nice solution to this as expensive_value is indeed a configuration to the app. But I'm not aware of anything similar in Tornado.
Handlers have access to self.application.settings which is a dictionary containing additional arguments passed to the Application constructor.
So you can pass expensive_value directly to the Application class like this:
def make_app(parameter):
return tornado.web.Application(
[
(r"/", MainHandler),
],
expensive_value=parameter
)
And access this value in any handler like this:
def initialize(self):
self.expensive_value = self.application.settings.get('expensive_value')
I want to perform 100 requests at the same time by easy way with yield [ list_of_futures ].
But this method performs only 10 requests at the time!
I prepared a short example which demostrates it, just run and you'll see requests which are performed by portions of 10 requests at the time.
Tested with debian stretch and ubuntu 16.04 with the same results.
Python 3.6.1,
tornado==4.5.1
from datetime import datetime
import tornado.ioloop
import tornado.gen
import tornado.web
from tornado.httpclient import AsyncHTTPClient
# the same for tornado and curl clients
# AsyncHTTPClient.configure('tornado.curl_httpclient.CurlAsyncHTTPClient')
http_client = AsyncHTTPClient()
class MainHandler(tornado.web.RequestHandler):
#tornado.gen.coroutine
def get(self, **kwargs):
yield self.write('<html><pre>')
yield tornado.gen.sleep(5)
yield self.finish('long page test</pre></html>')
def make_app():
return tornado.web.Application([
tornado.web.url('^/test', MainHandler),
])
#tornado.gen.coroutine
def long_request(n):
print('long_request {n} start'.format(n=n))
response = yield http_client.fetch('http://127.0.0.1:8000/test')
yield tornado.gen.sleep(5)
print('{date} long_request {n} finish, size {size}'.format(
date=datetime.now(), n=n, size=len(response.body))
)
#tornado.gen.coroutine
def requests_handler():
print('Requests handler started')
yield [long_request(n) for n in range(100)]
print('Requests handler finished')
app = make_app()
app.listen(8000, '127.0.0.1')
tornado.ioloop.IOLoop.current().add_callback(callback=requests_handler)
tornado.ioloop.IOLoop.current().start()
Oops, just found what it was.
Each client can perform only max_clients requests.
AsyncHTTPClient.configure(
'tornado.curl_httpclient.CurlAsyncHTTPClient',
max_clients=100
)
AsyncHTTPClient.configure(
'tornado.simple_httpclient.SimpleAsyncHTTPClient',
max_clients=100
)
But I suppose it's not an obvious behaviour, so I suppose it should be remained here for next googlers.
I'm trying to get a Tornado-Elasticsearch-Mongodb setup working right now. After Tornado.web.RequestHandler receives a search request, the query will be sent to the elasticsearch server through the REST api using Tornado.curl_httpclient.CurlAsyncHTTPClient.fetch(). fetch calls a callback with the results, so at this point, RequestHandler is out of scope so I can no longer write the HTTPResponse. I suppose I can nest the callback inside of my RequestHandler subclass, but I'm under the impression that this is not the best idea in asynchronous programming. Is there a magic python way to join my methods together? Here are the parts I want to join together:
from pymongo import Connection
from pymongo.objectID import ObjectId
from tornado.curl_httpclient import CurlAsyncHTTPClient
from tornado.httpclient import HTTPRequest
from tornado.web import RequestHandler, Application
from tornado.ioloop import IOLoop
import json
mongo_conn = Connection()
db = 'a_db'
collection = 'a_collection'
es_conn = CurlAsyncHTTPClient()
def get_mongo_data(id_list):
print list(mongo_conn[db][collection].find({"_id":{"$in":id_list}}))
# want to RequestHandler.write() here
def handle_search(response):
if response.error:
print "Error", response.error
else:
body = json.loads(response.body)
id_list = []
results = body['hits']['hits']
if len(results) > 0:
collection = results[0]['_type']
for r in results:
id_list.append(ObjectId(r['_id']))
if len(id_list) > 0:
get_mongo_data(id_list)
else:
# want RequestHandler.write("no matches found")
else:
# RequestHandler.write("no matches found")
def search(query):
url = '127.0.0.1:9250/%s/%s/_search' % (db, collection)
content = '{"query":{"term":{"field":"%s"}}}' % query
request = HTTPRequest(url, body=content, allow_nonstandard_methods=True)
es_conn.fetch(request, handle_search)
class SearchHandler(RequestHandler):
def get(self):
search('foo') # hack for now
if __name__=='__main__':
application = Application([
(r'/', SearchHandler),
])
application.listen(8888)
IOLoop.instance().start()