0

I am writing an API using FastAPI when I run with uvicorn everything is normal, I get the error when I want to run a test using the FastAPI TestClient.

This is the error:

async def get_user_id(conn, user):
   collection = conn.CIA.get_collection("Employees_Info")
   user = await collection.find_one({'name':user},{"_id":1, "name":0, "password":0})
   TypeError: object dict can't be used in 'await' expression
db\db.py:12: TypeError

project structure:

APP
 |--__init__.py
 |--run.py
 |--main.py
 |--test
    |--test_app.py
 |--routes
    |--router.py
 |--models
    |--models.py
 |--db
    |--db_conn.py
    |--db.py
 |--auth_jwt
    |--jwt_auth.py
 |--auth
    |--auth.py

This is the code of the test, I am using mongomock, I don't know if this will be the root of the problem:

import collections
from fastapi.testclient import TestClient
from fastapi import status
from main import app
from mongoengine import connect, disconnect, get_connection
from db.db_conn import db


client = TestClient(app)

connect('mongoenginetest', host='mongomock://localhost', alias='testdb')
db.client = get_connection('testdb')
db.client["CIA"]
db.client["Employees_Info"]
db.client.CIA.Employees_Info.insert_one({"name": "user_Name","password": "week"})

def test_ping():
    response = client.get("/")
    assert response.status_code == status.HTTP_200_OK
    assert response.json() == {"message": "Conectado"}
   
def test_login():
    data = {"username":'user_name', 'password':'week'}
    response = client.post("/login", data=data)
    assert response.headers['Content-Type'] == 'application/json'
    assert response.status_code == status.HTTP_200_OK
    db.client.disconnect()

I tried performing the Async test according to the FastAPI documentation but it doesn't work either, if I use the "normal" database the test works.

router.py

@router.post("/login", tags=["user"], response_model=Token)
async def login_for_access_token(form_data: OAuth2PasswordRequestForm = Depends(), 
                                 db: AsyncIOMotorClient = Depends(get_database)):
    authenticate_user_id = await authenticate_user(db, form_data.username, form_data.password)
    if not authenticate_user_id:
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Incorrect username or password",
            headers={"WWW-Authenticate": "Bearer"},
        )
    access_token = create_access_token(data={"user_id": str(authenticate_user_id["_id"])})
    return {"access_token": access_token, "token_type": "bearer"}

auth.py

async def authenticate_user(conn:AsyncIOMotorClient, username, password):
    user_id = await verify_user(conn, username)
    if not user_id:
        return False
    
    if not await verify_password(conn, user_id, password):
        return False
    
    return user_id

async def verify_user(conn, user):
    return  await get_user_id(conn,user)

async def verify_password(conn, user_id, password):
    return pbkdf2_sha256.verify(password, await get_password(conn, user_id))

db.py

async def get_user_id(conn, user):
    collection = conn.CIA.get_collection("Employees_Info")
    user = await collection.find_one({'name':user},{"_id":1, "name":0, "password":0})
    print(type(user))
    if user:
        return user

async def get_password(conn, user_id):
    collection = conn.CIA.get_collection("Employees_Info")
    db = await collection.find_one(user_id)
    if db:
        return db['password']
  • 2
    Does this answer your question? [python-asyncio TypeError: object dict can't be used in 'await' expression](https://stackoverflow.com/questions/49822552/python-asyncio-typeerror-object-dict-cant-be-used-in-await-expression) – Michael Ruth Aug 31 '21 at 19:56
  • I'm guessing the mocked version doesn't support async (i.e. the `await` call is made on a regular returned value from the mock, instead of actually awaiting the method call). – MatsLindh Sep 01 '21 at 08:49

2 Answers2

0

Maybe you need install pytest-asyncio.Here more info https://fastapi.tiangolo.com/advanced/async-tests/

0

collection.find_one() is not a async function, so you are trying to await the result of the function which is a dict, that is why you are getting the error TypeError: object dict can't be used in 'await' expression you are awaiting a dict, not a coroutine which would be returned by an async function.

To fix you code just remove await from

db = await collection.find_one(user_id)

When you do that, you won't really need it to be a async function, so you can just define it regularly, but you will than have to change all the function calls and remove the await from them, otherwise you will get this error again

Full code:

db.py

def get_user_id(conn, user):
    collection = conn.CIA.get_collection("Employees_Info")
    user = collection.find_one({'name':user},{"_id":1, "name":0, "password":0})
    print(type(user))
    if user:
        return user


def get_password(conn, user_id):
    collection = conn.CIA.get_collection("Employees_Info")
    db = collection.find_one(user_id)
    if db:
        return db['password']

auth.py

def authenticate_user(conn:AsyncIOMotorClient, username, password):
    user_id = verify_user(conn, username)
    if not user_id:
        return False
    
    if not verify_password(conn, user_id, password):
        return False
    
    return user_id


def verify_user(conn, user):
    return get_user_id(conn,user)


def verify_password(conn, user_id, password):
    return pbkdf2_sha256.verify(password, get_password(conn, user_id))

router.py

# This probably has to stay as an async function, I'm not sure how the module works
@router.post("/login", tags=["user"], response_model=Token)
async def login_for_access_token(form_data: OAuth2PasswordRequestForm = Depends(), 
                                 db: AsyncIOMotorClient = Depends(get_database)):
    authenticate_user_id = authenticate_user(db, form_data.username, form_data.password)
    if not authenticate_user_id:
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Incorrect username or password",
            headers={"WWW-Authenticate": "Bearer"},
        )
    access_token = create_access_token(data={"user_id": str(authenticate_user_id["_id"])})
    return {"access_token": access_token, "token_type": "bearer"}
import collections
from fastapi.testclient import TestClient
from fastapi import status
from main import app
from mongoengine import connect, disconnect, get_connection
from db.db_conn import db


client = TestClient(app)

connect('mongoenginetest', host='mongomock://localhost', alias='testdb')
db.client = get_connection('testdb')
db.client["CIA"]
db.client["Employees_Info"]
db.client.CIA.Employees_Info.insert_one({"name": "user_Name","password": "week"})

def test_ping():
    response = client.get("/")
    assert response.status_code == status.HTTP_200_OK
    assert response.json() == {"message": "Conectado"}
   
def test_login():
    data = {"username":'user_name', 'password':'week'}
    response = client.post("/login", data=data)
    assert response.headers['Content-Type'] == 'application/json'
    assert response.status_code == status.HTTP_200_OK
    db.client.disconnect()
Bertik23
  • 401
  • 4
  • 9