Aiopg cursor doesn't return fetchone() - python

I am stuck into a problem with aiopg. There are a one problem when i async with engine and try to return a result to endpoint in FastApi. Look down below for my db class.
class DBEngine:
def __init__(self, connection_url: str) -> None:
self.dsn = connection_url
self.engine: Optional[Engine] = None
async def connect(self) -> None:
self.engine = await create_engine(dsn=self.dsn, maxsize=100)
#property
def client(self) -> Engine:
if self.engine:
return self.engine
raise Exception("Not connected to database")
async def close(self) -> None:
if self.engine:
self.engine.close()
await self.engine.wait_closed()
As you can see there is a property, i use it in a code of method of a model class:
class Products(Base):
__tablename__ = "products"
UUID = sa.Column(
UUID(as_uuid=True),
primary_key=True,
default=uuid.uuid4,
unique=True,
nullable=False,
)
product_name = sa.Column(sa.String())
#classmethod
async def create_loan(
cls,
*,
product_name: str
) -> "Products":
query = cls.insert_query(
product_name=product_name
)
async with db_engine.client.acquire() as conn:
cursor = await conn.execute(query)
return await cursor.fetchone()
And finally i'll show an endpoint in fastapi:
#routes.post("/api/v1/loans")
async def create_loan(loan: Loan):
product = await Products.create_loan(product_name=loan.product_name)
return {"loan_id": product.UUID}
ERROR LOG:
**Traceback**
...
aiopg.sa.exc.ResourceClosedError: This result object does not return rows. It has been closed automatically.
Please tell me what's wrong, i spent so much time to solve this problem. Feel free to answer, thanks.

Related

How to return an object after insert with sqlalchemy?

I would like to retrieve the object after inserting it into the database, by object I mean the Base class.
Some examples:
class EdaToken(Base):
__tablename__ = "eda_token"
"""id, primary key"""
id = Column(
Integer(),
primary_key=True
)
#... etc etc
this works, return an EdaToken object:
#classmethod
async def get_all(cls) -> List['EdaToken']:
"""
Get all records in database
"""
async with get_session() as conn:
result = await conn.execute(
select(EdaToken)
)
return result.scalars().all()
The problem is in the insert:
#various tests
#classmethod
async def create_eda_token(
cls,
token: EdaTokenInputOnCreate
) -> 'EdaToken':
"""
Create a token and returning its new id
"""
async with get_session() as conn:
result = await conn.execute(
insert(EdaToken).values(label=token.label,token=token.token)
)
return result.scalars().unique().first() #??
What I'd like to return is the new database entry as an EdaToken object.
Error:
<sqlalchemy.engine.cursor.CursorResult object at 0x7fedd7c24250>
'CursorResult' object has no attribute 'id'
Another test:
doesn't seem to work, though, it only allows me to enter a new token once, all new tokens are not entered and it always returns the previous token, the only one that is entered.
#classmethod
async def create_eda_token(cls, token: EdaTokenInputOnCreate) -> 'EdaToken':
"""
Create a token and returning its new id
"""
async with get_session() as conn:
result = await conn.execute(
insert(EdaToken).values(label=token.label,token=token.token).returning(EdaToken)
)
await conn.flush()
token_id = result.scalars().unique().first()
result = await conn.execute(
select(EdaToken).where(EdaToken.id == token_id)
)
return result.scalars().unique().first()
psycopg2==2.9.3
sqlalchemy==1.4.46
asyncpg==0.27.0
Probably the solution, although I don't like it.
Thanks to #MatsLindh
#classmethod
async def create_eda_token(cls, token: EdaTokenInputOnCreate) -> 'EdaToken':
"""
Create a token and returning a new EdaToken instance
"""
async with engine.begin() as conn:
result = await conn.execute(
insert(EdaToken).values(label=token.label,token=token.token).returning(EdaToken)
)
await conn.commit()
async with get_session() as conn:
token_id = result.scalars().unique().first()
result = await conn.execute(
select(EdaToken).where(EdaToken.id == token_id)
)
return result.scalars().unique().first()
New solution thanks to #GordThompson
#classmethod
async def create_eda_token(cls, token: EdaTokenInputOnCreate) -> 'EdaToken':
"""
Create a token and returning a new EdaToken instance <--
"""
async with get_session() as conn:
new_token_id = EdaToken(label=token.label,token=token.token)
conn.add(new_token_id)
await conn.commit()
return new_token_id

Can I use Gino[starlette] with SQLite?

I'm trying to make the Gino mock, but I keep seeing the error
gino.exceptions.UninitializedError: Gino engine is not initialized.
My code is formed like this:
# __init__.py
#functools.lru_cache
def get_db_service():
db = Gino(dsn=settings.get_settings().postgresql_conn_url)
return db
# model
_db = get_db_service()
class EdaTableInstance(_db.Model):
__tablename__ = "eda_table_instance"
#...
#classmethod
async def get_all(cls) -> List['EdaTableInstance']:
async with _db.acquire():
return await EdaTableInstance.query.gino.all()
How I'm writing the tests (various attempts):
# conftest.py
#pytest.fixture(autouse=True)
def mock_get_db_service(mocker):
db = Gino(dsn="sqlite//:memory:")
async_mock = AsyncMock(db)
mocker.patch("gateway_api.services.get_db_service", return_value=async_mock)
yield
or
# conftest.py
#pytest.fixture
async def db_initialize():
await db.set_bind('sqlite:///:memory:')
await db.gino.create_all()
await EdaTableInstance.create_eda_table_instance(
EdaTableInstanceInputOnCreate({"name":"table_server1", "host":"123.123.123.123"})
)
yield
or
# test_models.py
#pytest.fixture
def mock_gino_get_all(mocker):
mocker.patch("gino.api.GinoExecutor.all", return_value=[])
#pytest.mark.asyncio
#pytest.mark.parametrize("id, expected", [(None, None)])
async def test_01_table_instance_get_all(id, expected):
mock_cursor = MagicMock()
mock_cursor.configure_mock(
**{
"get_one.return_value":[id]
}
)
res = await EdaTableInstance().get_one(mock_cursor)
assert res == expected
I would like to use SQLite in memory, so I don't have to connect from a database, if you know better methods to mock the database, it would help me so much.

FastAPI, Pytest TypeError: object dict can't be used in 'await' expression

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']
Maybe you need install pytest-asyncio.Here more info https://fastapi.tiangolo.com/advanced/async-tests/
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()

FastAPI AttributeError: 'job_board' object has no attribute 'query'

I'm trying to build a simple job board using Python, FastAPI and Async sqlalchemy by following the official FastAPI documentation.Problem occurring when I try to retrieve the job by the ID from the database, It's keep giving me this error AttributeError: 'job_board' object has no attribute 'query' when I hit the "/get/{id}"endpoint.
The Following is hopefully a minimum reproducible code segment:
schemas/jobs.py
from typing import Optional
from pydantic import BaseModel
from datetime import date, datetime
class JobBase(BaseModel):
title: Optional[str] = None
company_name: Optional[str] = None
company_url: Optional[str] = None
location: Optional[str] = "remote"
description: Optional[str] = None
date_posted: Optional[date] = datetime.now().date()
class JobCreate(JobBase):
title: str
company_name: str
location: str
description: str
class ShowJob(JobBase):
title: str
company_name: str
company_url: Optional[str]
location: str
date_posted: date
description: str
class Config():
orm_mode = True
routes/route_jobs.py
from fastapi import APIRouter, HTTPException, status
from fastapi import Depends
from sqlalchemy.orm.session import Session
from db.repository.job_board_dal import job_board
from db.models.jobs import Job as model_job
from schemas.jobs import JobCreate, ShowJob
from db.repository.job_board_dal import Job
from depends import get_db
router = APIRouter()
#router.post("/create-job",response_model=ShowJob)
async def create_user(Job: JobCreate, jobs: Job = Depends(get_db)):
owner_id = 1
return await jobs.create_new_job(Job, owner_id)
#router.get("/get/{id}")
def retreive_job_by_id(id:int, session: Session = Depends(get_db)):
#print(type(session))
job_id = job_board.retrieve_job(session, id=id)
if not job_id:
HTTPException(status_code=status.HTTP_404_NOT_FOUND,
detail=f"Job with id {id} does not exist")
return job_id
db/repository/job_board_dal.py
from sqlalchemy.orm import Session
from schemas.users import UserCreate
from schemas.jobs import JobCreate
from db.models.users import User
from db.models.jobs import Job
from core.hashing import Hasher
class job_board():
def __init__(self, db_session: Session):
self.db_session = db_session
async def register_user(self, user: UserCreate):
new_user = User(username=user.username,
email=user.email,
hashed_password=Hasher.get_password_hash(user.password),
is_active = False,
is_superuser=False
)
self.db_session.add(new_user)
await self.db_session.flush()
return new_user
async def create_new_job(self, job: JobCreate, owner_id: int):
new_job = Job(**job.dict(), owner_id = owner_id)
self.db_session.add(new_job)
await self.db_session.flush()
return new_job
def retrieve_job(db: Session, id:int):
item = db.query(Job).filter(Job.id == id).first()
return item
depends.py
from db.session import async_session
from db.repository.job_board_dal import job_board
async def get_db():
async with async_session() as session:
async with session.begin():
yield job_board(session)
I did try a lot of things I even try to retrieve with a separate async session which also gives me the AttributeError: 'AsyncSession' object has no attribute 'query'.
Any help would be much appreciated.
get_db doesnt return a session, it returns an instance of job_board. So when you do retreive_job_by_id(id:int, session: Session = Depends(get_db)) your are setting session to be a class of job_board. So when you call job_id = job_board.retrieve_job(session, id=id) you are passing session which is an instance of job_board. So when you do item = db.query(Job) db here is an instance of Job_board and job_board doesnt have a method called query. Instead you probably just want to update retrieve job to utilise the job_board class instance to access its session.
def retrieve_job(self, id:int):
item = self.db_session.query(Job).filter(Job.id == id).first()
return item
in your router you can make it more readable as
#router.get("/get/{id}")
def retreive_job_by_id(id:int, job_board = Depends(get_db)):
#print(type(session))
job_id = job_board.retrieve_job(job_board, id=id)
if not job_id:
HTTPException(status_code=status.HTTP_404_NOT_FOUND,
detail=f"Job with id {id} does not exist")
return job_id

Pass instance of object to FastAPI router

What's wrong with implementation of passing class instance as a dependency in FastAPI router or is it a bug?
1) I have defined router with dependency
app = FastAPI()
dbconnector_is = AsyncDBPool(conn=is_cnx, loop=None)
app.include_router(test_route.router, dependencies=[Depends(dbconnector_is)])
#app.on_event('startup')
async def startup():
app.logger = await AsyncLogger().getlogger(log)
await app.logger.warning('Webservice is starting up...')
await app.logger.info("Establishing RDBS Integration Pool Connection...")
await dbconnector_is.connect()
#app.on_event('startup')
async def startup():
await app.logger.getlogger(log)
await app.logger.warning('Webservice is starting up...')
await dbconnector_is.connect()
router
#router.get('/test')
async def test():
data = await dbconnector_is.callproc('is_processes_get', rows=-1, values=[None, None])
return Response(json.dumps(data, default=str))
custom class for passing it's instance as callable.
class AsyncDBPool:
def __init__(self, conn: dict, loop: None):
self.conn = conn
self.pool = None
self.connected = False
self.loop = loop
def __call__(self):
return self
async def connect(self):
while self.pool is None:
try:
self.pool = await aiomysql.create_pool(**self.conn, loop=self.loop, autocommit=True)
except aiomysql.OperationalError as e:
await asyncio.sleep(1)
continue
else:
return self.pool
And when I send request I receive this error.
data = await dbconnector_is.callproc('is_processes_get', rows=-1, values=[None, None])
NameError: name 'dbconnector_is' is not defined

Categories