squeaky-pl / japronto

Screaming-fast Python 3.5+ HTTP toolkit integrated with pipelining HTTP server based on uvloop and picohttpparser.
MIT License
8.61k stars 581 forks source link

Need suggestions to work with motor (MongoDB async driver) #97

Closed hieu-n closed 6 years ago

hieu-n commented 6 years ago

My app structure (simplified):

app/
   templates/
   __init__.py
manage.py

app/__init__.py

from japronto import Application
from motor.motor_asyncio

client = motor.motor_asyncio.AsyncIOMotorClient()
db = client.mydb

async def index(request):
    result = await db.find_one()
    print(result)
    return request.Response(text=str(result), mime_type='text/html')

def create_app():
   app = Application()
   app.router.add_route('/', index)

manage.py

from app import create_app

if __name__ == '__main__':
   app = create_app()
   app.run(debug=True)

When I ran it, I got an exception:

RuntimeError: Task <Task pending coro=<pkg() running at  .../app/__init__.py:64> cb=[Pipeline._task_done()]> got Future <Future pending cb=[_chain_future.<locals>._call_check_cancel() at /miniconda/envs/flecom/lib/python3.6/asyncio/futures.py:407]> attached to a different loop

I suspect putting client = motor.motor_asyncio.AsyncIOMotorClient() inside index would work, but that would create new connections for every request. Reusing connection would be more preferable.

vladz commented 6 years ago

Try this:

import asyncio
from japronto import Application
from motor.motor_asyncio

def create_app():
   app = Application()
   event_loop = asyncio.get_event_loop()
   client = motor.motor_asyncio.AsyncIOMotorClient(io_loop=event_loop)
   db = client.mydb
   async def index(request):
      result = await db.find_one()
      print(result)
      return request.Response(text=str(result), mime_type='text/html')
   app.router.add_route('/', index)
hieu-n commented 6 years ago

@vladz Thank you for your response. I tried and still got RuntimeError: Task ... got Future <Future pending cb=[_chain_future.<locals>._call_check_cancel() at ... attached to a different loop.

vladz commented 6 years ago

And if you change the code for this one?

from japronto import Application
from motor.motor_asyncio

def create_app():
   app = Application()
   event_loop = app.loop  # <<--
   client = motor.motor_asyncio.AsyncIOMotorClient(io_loop=event_loop)
   db = client.mydb
   async def index(request):
      result = await db.find_one()
      print(result)
      return request.Response(text=str(result), mime_type='text/html')
   app.router.add_route('/', index)
   app.run()

The idea is to catch a event_loop from japronto and use it in motor.

squeaky-pl commented 6 years ago

What you are doing will result in undefined behavior because Japronto is a pre-forking server and if you set your loop up in the parent process and then fork things will go terribly wrong. There is no official API to do it at the moment.

squeaky-pl commented 6 years ago

If you really want to hack you can override: https://github.com/squeaky-pl/japronto/blob/master/src/japronto/app/__init__.py#L159. This function is called from new process already, you can grab the loop after this is configured and setup your mongodb driver there.

vladz commented 6 years ago

@squeaky-pl Thank you for comments.