Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Future task attached to a different loop

I am trying to connect to mongodb in FastAPI. I am repeatedly getting this exception.

File - main.py

app = FastAPI(
    title=config.PROJECT_NAME, docs_url="/api/docs", openapi_url="/api"
)

@app.get("/api/testing")
async def testit():
    user_collection = readernetwork_db.get_collection("user_collection")
    all_users = await user_collection.find_one({"email": "sample_email"})
    print("all users --- ", all_users)
    return all_users

if __name__ == "__main__":
    uvicorn.run("main:app", host="0.0.0.0", reload=True, port=8888)

File - session.py

import motor.motor_asyncio
from app.core import config


print("here we go again....")
client = motor.motor_asyncio.AsyncIOMotorClient(
    config.MONGOATLAS_DATABASE_URI)
readernetwork_db = client.get_database("readernetwork")

Exception -:

all_users = await user_collection.find_one({"email": "sample_email"})

RuntimeError: Task <Task pending name='Task-4' coro=<RequestResponseCycle.run_asgi() running at /usr/local/lib/python3.8/site-packages/uvicorn/protocols/http/h11_impl.py:389> cb=[set.discard()]> got Future <Future pending cb=[_chain_future.<locals>._call_check_cancel() at /usr/local/lib/python3.8/asyncio/futures.py:360]> attached to a different loop

I don't know where I am getting this wrong. Should I specify a event loop to motor?

like image 575
Harsh Vardhan Sharma Avatar asked Jan 02 '21 17:01

Harsh Vardhan Sharma


3 Answers

Without the need for using global... You can access the application state from the request given to any route.

#main.py
async def open_db() -> AsyncIOMotorClient:
    app.state.mongodb = AsyncIOMotorClient(DB_URL)

async def close_db():
    app.state.mongodb.close()

app.add_event_handler('startup', open_db)
app.add_event_handler('shutdown', close_db)

Within each request to a given route you will have access to the app state. For example,

@app.route('/{username}')
async def index(request: Request, username: str):
    user = await request.app.state.mongodb['auth']['users'].find_one({"username" : username})

You can even make it easier by doing something like this in your open_db function. Specify a state value like 'users' to be a specific Collections instance.

async def open_db() -> AsyncIOMotorClient:
    app.state.mongodb = AsyncIOMotorClient(DB_URL)
    app.state.users = app.state.mongodb['auth']['users']

Now you can just do this,

@app.route('/{username}')
async def index(request: Request, username: str):
    user = await request.app.state.users.find_one({"username" : username})
like image 182
saeveritt Avatar answered Nov 20 '22 21:11

saeveritt


You can have mongodb motor client in the global scope, but creating and closing it should be done inside an async function. The most preferable way of doing that in startup and shutdown handler of the application. Like so:

# mongodb.py
from motor.motor_asyncio import AsyncIOMotorClient


db_client: AsyncIOMotorClient = None


async def get_db_client() -> AsyncIOMotorClient:
    """Return database client instance."""
    return db_client


async def connect_db():
    """Create database connection."""
    global db_client
    db_client = AsyncIOMotorClient(DB_URL)

async def close_db():
    """Close database connection."""
    db_client.close()
# main.py
app = FastAPI(title=PROJECT_NAME)
...
app.add_event_handler("startup", connect_db)
app.add_event_handler("shutdown", close_db)

Note that you need the line global db_client to modify the global variable defined beforehand.

like image 40
alex_noname Avatar answered Nov 20 '22 20:11

alex_noname


client = AsyncIOMotorClient()
client.get_io_loop = asyncio.get_event_loop

works for me.

like image 26
Deno Avatar answered Nov 20 '22 20:11

Deno