I am writing APIs using stack FastAPI, Pydantic & SQL Alchemy and I have come across many cases where I had to query database to perform validations on payload values. Let's consider one example API, /forgot-password
. This API will accept email
in the payload and I need to validate the existence of the email in database. If the email exist in the database then necessary action like creating token and sending mail would be performed or else an error response against that field should be raise by Pydantic. The error responses must be the standard PydanticValueError
response. This is because all the validation errors would have consistent responses as it becomes easy to handle for the consumers.
Payload -
{
"email": "example@gmail.com"
}
In Pydantic this schema and the validation for email is implemented as -
class ForgotPasswordRequestSchema(BaseModel):
email: EmailStr
@validator("email")
def validate_email(cls, v):
# this is the db query I want to perform but
# I do not have access to the active session of this request.
user = session.get(Users, email=v)
if not user:
raise ValueError("Email does not exist in the database.")
return v
Now this can be easily handled if the we simple create an Alchemy session in the pydantic model like this.
class ForgotPasswordRequestSchema(BaseModel):
email: EmailStr
_session = get_db() # this will simply return the session of database.
_user = None
@validator("email")
def validate_email(cls, v):
# Here I want to query on Users's model to see if the email exist in the
# database. If the email does. not exist then I would like to raise a custom
# python exception as shown below.
user = cls._session.get(Users, email=v) # Here I can use session as I have
# already initialised it as a class variable.
if not user:
cls.session.close()
raise ValueError("Email does not exist in the database.")
cls._user = user # this is because we want to use user object in the request
# function.
cls.session.close()
return v
But it is not a right approach as through out the request only one session should be used. As you can see in above example we are closing the session so we won't be able to use the user object in request function as user = payload._user
. This means we will have to again query for the same row in request function. If we do not close the session then we are seeing alchemy exceptions like this - sqlalchemy.exc.PendingRollbackError
.
Now, the best approach is to be able to use the same session in the Pydantic model which is created at the start of request and is also closing at the end of the request.
So, I am basically looking for a way to pass that session to Pydantic as context. Session to my request function is provided as dependency.