I am writing a few apis with SQLAlchemy, Pydantic, and FastAPI and I'm wondering if the following approach would be discouraged and/ or considered an anti-pattern.
Output of python -c "import pydantic.utils; print(pydantic.utils.version_info())":
$ python -c "import pydantic.utils; print(pydantic.utils.version_info())"
pydantic version: 1.3
pydantic compiled: False
install path: /Users/anthcor/.pyenv/versions/3.7.5/lib/python3.7/site-packages/pydantic
python version: 3.7.5 (default, Dec 23 2019, 19:10:54) [Clang 11.0.0 (clang-1100.0.33.16)]
platform: Darwin-19.4.0-x86_64-i386-64bit
optional deps. installed: ['email-validator']
Here's an example:
from pydantic import BaseModel, EmailStr, validator
from typing import Optional
class User(BaseModel):
name: str
email: EmailStr
bio: Optional[str]
@validator('email')
def unique_email(cls: BaseModel, v: EmailStr) -> EmailStr:
db = next(get_db())
users = db.query(UserModel).all()
db.commit()
if str(v) in set(map(lambda u: u.email, users)):
raise ValueError('Email already in use.')
return v
What's great about this is getting the appropriate 422 and error message details on an invalid submission where the email is already in use, but having a database call in what's supposed to be a declaration of types is making me think again.
Thanks in advance for any advice! Cheers.
After some digging in issues I found this which notes that this is a discouraged practice.
Yes.
But it's not that hard to raise real pydantic errors from outside a validator, I'll give you an example tommorow when I'm at my desk, and one day we might have a utility for it.
Sounds awesome, thanks!
Howdy @samuelcolvin, from reading through some docs, I was thinking of writing decorators for these uniqueness checks similar to https://pydantic-docs.helpmanual.io/usage/validation_decorator/#raw-function.
However I'm thinking that raising pydantic validation errors in these sorts of situations may not be appropriate as I'm unsure of how to define/ assign a "unique" type to a parameter as that parameter would also need some context to evaluate the parameter. Feels like I'm trying to use pydantic for something that should just be raised as an HTTPException with FastAPI.
I did have an idea though, could you assign a "unique" type to a parameter via a context validator that would read in an iterable context parameter somehow? Is it possible to make a pydantic object context aware when being passed into a FastAPI route?
# pydantic ...
class NodeCreate(BaseModel):
id: int
name: str
@context_validator('name')
def unique_name(cls: BaseModel, v: str, context: Iterable = []) -> str:
if v in context:
raise ValueError('Node name is already taken!')
return v
# fastapi ...
def create_node(db: Session = Depends(get_db),
node_create: NodeCreate = Body(
..., example={
'id': 0,
'name': 'alpha'
})):
# set context
context = set(map(lambda n: n.name, db.query(Node).all()))
# value error is raised if name is in context?
node_create.context = context
# save result if node_create is valid w respect to context and move along
result = node_helper.save_node(db, node_create)
return result
Lmk if this sounds totally bonkers. Thanks again.
Most helpful comment
Yes.
But it's not that hard to raise real pydantic errors from outside a validator, I'll give you an example tommorow when I'm at my desk, and one day we might have a utility for it.