mongodbfastapitornado-motor

How can I use FastAPI Routers with FastAPI-Users and MongoDB?


I can use MongoDB with FastAPI either

  1. with a global client: motor.motor_asyncio.AsyncIOMotorClient object, or else
  2. by creating one during the startup event per this SO answer which refers to this "Real World Example".

However, I also want to use fastapi-users since it works nicely with MongoDB out of the box. The downside is it seems to only work with the first method of handling my DB client connection (ie global). The reason is that in order to configure fastapi-users, I have to have an active MongoDB client connection just so I can make the db object as shown below, and I need that db to then make the MongoDBUserDatabase object required by fastapi-users:

# main.py
app = FastAPI()


# Create global MongoDB connection 
DATABASE_URL = "mongodb://user:paspsword@localhost/auth_db"
client = motor.motor_asyncio.AsyncIOMotorClient(DATABASE_URL, uuidRepresentation="standard")
db = client["my_db"]

# Set up fastapi_users
user_db = MongoDBUserDatabase(UserDB, db["users"])

cookie_authentication = CookieAuthentication(secret='lame secret' , lifetime_seconds=3600, name='cookiemonster')

fastapi_users = FastAPIUsers(
    user_db,
    [cookie_authentication],
    User,
    UserCreate,
    UserUpdate,
    UserDB,
)

After that point in the code, I can import the fastapi_users Routers. However, if I want to break up my project into FastAPI Routers of my own, I'm hosed because:

  1. If I move the client creation to another module to be imported into both my app and my routers, then I have different clients in different event loops and get errors like 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 (touched on in this SO question)
  2. If I user the solutions of the "Real World Example", then I get stuck on where to build my fastapi_users object in my code example: I can't do it in main.py because there's no db object yet.

I considered making the MongoDBUserDatabase object as part of the startup event code (ie within async def connect_to_mongo() from the Real World Example), but I'm not able to get that to work either since I can't see how to make it work.

How can I either

  1. make a global MongoDB client and FastAPI-User object in a way that can be shared among my main app and several routers without "attached to a different loop" errors, or
  2. create fancy wrapper classes and functions to set up FastAPI users with the startup trigger?

Solution

  • The author (frankie567) of fastapi-users created a repl.it showing a solution of sorts. My discussion about this solution may provide more context but the key parts of the solution are:

    1. Don't bother using FastAPI startup trigger along with Depends for your MongDB connectivity management. Instead, create a separate file (ie db.py) to create your DB connection and client object. Import this db object whenever needed, like your Routers, and then use it as a global.
    2. Also create a separate users.py to do 2 things:
      1. Create globally used fastapi_users = FastAPIUsers(...) object for use with other Routers to handle authorization.
      2. Create a FastAPI.APIRouter() object and attach all the fastapi-user routers to it (router.include_router(...))
    3. In all your other Routers, import both db and fastapi_users from the above as needed
    4. Key: split your main code up into
      1. a main.py which only import uvicorn and serves app:app.
      2. an app.py which has your main FastAPI object (ie app) and which then attaches all our Routers, including the one from users.py with all the fastapi-users routers attached to it.

    By splitting up code per 4 above, you avoid the "attached to different loop" error.