2

I am trying to play around with fastapi by creating a simple mongo based rest api . Below is what i have done so far :

File : mongo.py This has all the code for getting the data from mongo db using motor framework:

"""Module for handling the motor mongo package code."""
from typing import Any, Dict, List

import motor.motor_asyncio

DB = "books"
COLLECTION = "books"


class MongoBackend:
    def __init__(self, uri: str) -> None:
        self._client = motor.motor_asyncio.AsyncIOMotorClient(uri)

    async def get_all_books(self) -> List[Dict[str, Any]]:
        cursor = self._client[DB][COLLECTION].find({}, {"_id": 0})
        return [doc async for doc in cursor]

    async def get_single_book(self, book_id: str) -> Dict[str, Any]:
        return await self._client[DB][COLLECTION].find_one(
            {"book_id": book_id}, {"_id": 0}
        )

    async def update_one_book(self, book_id: str, data: Dict[str, Any]) -> None:
        data["book_id"] = book_id
        await self._client[DB][COLLECTION].update(
            {"book_id": book_id}, data, upsert=True
        )

    async def insert_one_book(self, data: Dict[str, Any]) -> None:
        await self._client[DB][COLLECTION].insert_one(data)

    async def delete_one_book(self, book_id: str) -> None:
        await self._client[DB][COLLECTION].delete_one({"book_id": book_id})

My routers.py file looks like this :

"""Module for containing the routes for the application."""
import uuid
from typing import Dict, Any, Tuple, List

from fastapi import APIRouter, Depends
import application.models as models
import application.mongo as mongo
import os
import functools


router = APIRouter()


@functools.lru_cache()
def mongo_backend():
    return mongo.MongoBackend(uri=os.getenv("MONGODB_URI"))


@functools.lru_cache()
def base_uri():
    return os.getenv("BASE_URI")


def add_hyper_link_to_book(book: Dict[str, Any]) -> Dict[str, Any]:
    book_id = book.pop("book_id")
    book["_link"] = f"{base_uri()}book/{book_id}"
    return book


@router.get("/books")
async def get_all_books():
    backend = mongo_backend()
    print(id(backend))
    all_books = [
        add_hyper_link_to_book(book)
        for book in await backend.get_all_books()
    ]
    return all_books


@router.post("/books")
async def add_a_book(book: models.Book) -> Tuple[List[Dict[str, Any]], int]:
    backend = mongo_backend()
    book_to_insert = book.dict()
    book_to_insert["book_id"] = str(uuid.uuid1())
    await backend.insert_one_book(data=book_to_insert)
    return [
        add_hyper_link_to_book(book)
        for book in await backend.get_all_books()
    ], 201


@router.get("/book/{book_id}")
async def get_a_single_book(book_id: str, backend: mongo.MongoBackend = Depends(mongo_backend)):
    book = await backend.get_single_book(book_id=book_id)
    if book is None:
        return {"message": "No such book exist!!"}
    return add_hyper_link_to_book(book)

So as of now i have 3 routes and i want to dependency inject my backend object into the routes and i also i want it to be a singleton.

In order to show the difference between what works and what does not i have dependency injected into only one of the routes (/book) while for the rest of the routes (/books) i have just called the function to create the backend (mongo_backend) directly.

I do not quite understand what i am doing wrong because when i try to query the route in which i have dependency injected the backend object throws this error :

ERROR:    Exception in ASGI application
Traceback (most recent call last):
  File "/home/subhayan/anaconda3/envs/mongo-book-store-fastapi/lib/python3.8/site-packages/uvicorn/protocols/http/h11_impl.py", line 396, in run_asgi
    result = await app(self.scope, self.receive, self.send)
  File "/home/subhayan/anaconda3/envs/mongo-book-store-fastapi/lib/python3.8/site-packages/uvicorn/middleware/proxy_headers.py", line 45, in __call__
    return await self.app(scope, receive, send)
  File "/home/subhayan/anaconda3/envs/mongo-book-store-fastapi/lib/python3.8/site-packages/fastapi/applications.py", line 199, in __call__
    await super().__call__(scope, receive, send)
  File "/home/subhayan/anaconda3/envs/mongo-book-store-fastapi/lib/python3.8/site-packages/starlette/applications.py", line 111, in __call__
    await self.middleware_stack(scope, receive, send)
  File "/home/subhayan/anaconda3/envs/mongo-book-store-fastapi/lib/python3.8/site-packages/starlette/middleware/errors.py", line 181, in __call__
    raise exc from None
  File "/home/subhayan/anaconda3/envs/mongo-book-store-fastapi/lib/python3.8/site-packages/starlette/middleware/errors.py", line 159, in __call__
    await self.app(scope, receive, _send)
  File "/home/subhayan/anaconda3/envs/mongo-book-store-fastapi/lib/python3.8/site-packages/starlette/exceptions.py", line 82, in __call__
    raise exc from None
  File "/home/subhayan/anaconda3/envs/mongo-book-store-fastapi/lib/python3.8/site-packages/starlette/exceptions.py", line 71, in __call__
    await self.app(scope, receive, sender)
  File "/home/subhayan/anaconda3/envs/mongo-book-store-fastapi/lib/python3.8/site-packages/starlette/routing.py", line 566, in __call__
    await route.handle(scope, receive, send)
  File "/home/subhayan/anaconda3/envs/mongo-book-store-fastapi/lib/python3.8/site-packages/starlette/routing.py", line 227, in handle
    await self.app(scope, receive, send)
  File "/home/subhayan/anaconda3/envs/mongo-book-store-fastapi/lib/python3.8/site-packages/starlette/routing.py", line 41, in app
    response = await func(request)
  File "/home/subhayan/anaconda3/envs/mongo-book-store-fastapi/lib/python3.8/site-packages/fastapi/routing.py", line 191, in app
    solved_result = await solve_dependencies(
  File "/home/subhayan/anaconda3/envs/mongo-book-store-fastapi/lib/python3.8/site-packages/fastapi/dependencies/utils.py", line 550, in solve_dependencies
    solved = await run_in_threadpool(call, **sub_values)
  File "/home/subhayan/anaconda3/envs/mongo-book-store-fastapi/lib/python3.8/site-packages/starlette/concurrency.py", line 34, in run_in_threadpool
    return await loop.run_in_executor(None, func, *args)
  File "/home/subhayan/anaconda3/envs/mongo-book-store-fastapi/lib/python3.8/concurrent/futures/thread.py", line 57, in run
    result = self.fn(*self.args, **self.kwargs)
  File "./application/routers.py", line 17, in mongo_backend
    return mongo.MongoBackend(uri=os.getenv("MONGODB_URI"))
  File "./application/mongo.py", line 12, in __init__
    self._client = motor.motor_asyncio.AsyncIOMotorClient(uri)
  File "/home/subhayan/anaconda3/envs/mongo-book-store-fastapi/lib/python3.8/site-packages/motor/core.py", line 150, in __init__
    io_loop = self._framework.get_event_loop()
  File "/home/subhayan/anaconda3/envs/mongo-book-store-fastapi/lib/python3.8/site-packages/motor/frameworks/asyncio/__init__.py", line 42, in get_event_loop
    return asyncio.get_event_loop()
  File "/home/subhayan/anaconda3/envs/mongo-book-store-fastapi/lib/python3.8/asyncio/events.py", line 639, in get_event_loop
    raise RuntimeError('There is no current event loop in thread %r.'
RuntimeError: There is no current event loop in thread 'ThreadPoolExecutor-1_0'.

The rest of the two routes work fine. Can someone please help me with what needs to be done to make this work.

In case it helps this is my app.py:

"""Main module for the fastapi app."""
import pathlib

from dotenv import load_dotenv
from fastapi import FastAPI
import application.routers as routers

# load the environment from the file app.env in the project directory
basedir = pathlib.Path(__file__).parent.parent
load_dotenv(basedir / "app.env")

app = FastAPI()

app.include_router(routers.router)
Subhayan Bhattacharya
  • 5,407
  • 7
  • 42
  • 60

0 Answers0