We are using fastapi and pydantic to excellent effect. However, there are some validations that we perform that can not (should not?) be done in a pydantic validator. (Mostly database/world-state kinds of validations)
Examples:
- When creating a
Userwith ausername, the pydantic model has validations for length and allowed characters, but a uniqueness check requires a database call, and so should not be done in a pydantic validator.- Further, this should be enforced by the database, so it can be done atomically (create and check for error, rather than check if name exists and then try to create).
- Creating a
Documentobject with several "related to" links that mention other documents byid- These are foreign key relationships in the database, so the API endpoint must check if the linked ids are legitimate ids that are in use. (Again, should be done at the database layer)
The end result:
- The API endpoint itself implements a number of validations beyond those in the pydantic model (acceptable/expected)
- These validations are applied one at a time, so a user can only see one error message, fix it, and then go on to see the next one. (undesirable)
- It is difficult to return standardized error messages. (undesirable)
- pydantic's error messages are excellent: structured, describe individual fields, and can describe multiple errors at once.
{
"detail": [
{
"loc": [
"body",
"username"
],
"msg": "Username must be at least 5 characters long. Username cannot contain '-' character.",
"type": "value_error"
}
]
}
How can I:
- Return error messages with this same structure? Ideal solution would be something like:
try:
db.create_user(user)
except UserAlreadyExists:
raise pydantic.<something>(User.username, "This username is already in use.")
- Aggregate a number of errors at once: eg. Document name is not unique and "related to" links (a list) item #3 is not found.
I know that I can (and have) built error handling to reflect this same structure, and even tried to make it reasonably dynamic to handle different models/fields, but it is a lot of effort. If there were something provided by pydantic directly, that would be more convenient.
I did come across https://stackoverflow.com/a/76601052/15963311 which mentions ErrorWrapper from pydantic. At a glance, it seems to do what I want, but once I discovered that it is deprecated in pydantic V2, I didn't bother investigating it thoroughly.