Skip to content

Validation ​

A mutation can validate its input with a Pydantic model before writing to the database, and return the failures to the client as data.

Validating input ​

Declare a Pydantic model with @strawchemy.pydantic.create — or .pk_update/.filter_update for the other mutation shapes — and pass it as validation to the mutation field. The field's return type becomes a union of the mutated type and ValidationErrorType, so a validation failure comes back as data instead of raising:

python
from typing import Annotated

from pydantic import AfterValidator
from strawchemy import ValidationErrorType
from strawchemy.validation.pydantic import PydanticValidation


def _check_lower_case(value: str) -> str:
    if not value.islower():
        raise ValueError("Title must be lower cased")
    return value


@strawchemy.pydantic.create(Tag, include="all", exclude=["id"])
class TagCreateValidation:
    name: Annotated[str, AfterValidator(_check_lower_case)]


@strawchemy.pydantic.create(Post, include="all", exclude=["id"])
class PostCreateValidation:
    title: Annotated[str, AfterValidator(_check_lower_case)]
    tags: list[TagCreateValidation] | None = strawberry.UNSET


@strawberry.type
class Mutation:
    create_validated_post: PostType | ValidationErrorType = strawchemy.create(
        PostCreateInput, validation=PydanticValidation(PostCreateValidation)
    )

Both models exclude id because PostCreateInput never provides one. With include="all" and no exclusion, id would be a required pydantic field, and validation would reject every call with id: Field required before it checks title. tags mirrors the shape a relationship-carrying create input would send, but PostCreateInput has no tags field, so this mutation never reaches that branch.

WARNING

Add ValidationErrorType to the mutation field's return union, as in PostType | ValidationErrorType above. Without it, a validation failure still returns a ValidationErrorType, which the field's declared type cannot resolve: the client gets an execution error, such as 'ValidationErrorType' object has no attribute 'title', instead of the validation errors.

What the client receives ​

A failed validation returns a ValidationErrorType with the underlying pydantic error details instead of the created record:

graphql
mutation {
    createValidatedPost(data: { title: "Bad Title", content: "...", views: 0 }) {
        __typename
        ... on PostType {
            title
        }
        ... on ValidationErrorType {
            id
            errors {
                id
                loc
                message
                type
            }
        }
    }
}
json
{
  "data": {
    "createValidatedPost": {
      "__typename": "ValidationErrorType",
      "id": "ERROR",
      "errors": [
        {
          "id": "ERROR",
          "loc": ["title"],
          "message": "Value error, Title must be lower cased",
          "type": "value_error"
        }
      ]
    }
  }
}