Back to Blog
Python

Handling Python Pydantic Validation Errors

python pydantic validation errors: Learn how to catch, inspect, and customize Python Pydantic validation errors, including error structure, manual raising, and FastAPI...

pydanticvalidationerror handlingfastapipython
A developer inspecting a Pydantic ValidationError with error details in a Python project

Consider a Pydantic model that expects a string and an integer:

from pydantic import BaseModel class User(BaseModel): name: str age: int

Passing invalid data raises a ValidationError:

try: User(name="Alice", age="twenty") except ValidationError as e: print(e)

The printed error shows the field, the invalid value, and the reason. This exception is the core of Python Pydantic validation errors, and this article explains its structure, how to handle it in code, and how to customize the messages it produces.

Understanding the ValidationError Structure

A ValidationError instance exposes two useful methods: .errors() and .json(). The .errors() method returns a list of dictionaries, each describing a single validation problem. Each dictionary contains four keys:

  • loc: a tuple of path segments, e.g., ('age',) for a field error.
  • msg: a human-readable message, e.g., "Input should be a valid integer".
  • type: a machine-readable error type, e.g., int_parsing.
  • input: the invalid value that was passed.

For example, the error from the earlier code has errors() returning:

[{'loc': ('age',), 'msg': 'Input should be a valid integer', 'type': 'int_parsing', 'input': 'twenty'}]

The loc tuple can be nested for models inside models. Accessing these fields programmatically lets you build custom error responses or log structured data.

Catching Validation Errors in Application Code

The most common way to handle a ValidationError is with a try/except block. Because the exception contains all validation problems, you can iterate over .errors() and decide how to react. For example, you might collect all error messages and return them to the caller:

from pydantic import ValidationError def create_user(data: dict): try: user = User(**data) except ValidationError as e: messages = [f"{'.'.join(str(x) for x in err['loc'])}: {err['msg']}" for err in e.errors()] raise ValueError("; ".join(messages)) from e return user

The from e preserves the original traceback, which is useful for debugging. In a web framework, you might convert the validation error to a 400 or 422 response instead of raising a new exception.

Customizing Validation Error Messages

Pydantic lets you control the message that appears in the msg field. The simplest approach is to use a field validator that raises a ValueError with your own text. For example, to enforce a minimum age:

from pydantic import BaseModel, field_validator class User(BaseModel): name: str age: int @field_validator('age') @classmethod def check_age(cls, v): if v < 18: raise ValueError('User must be at least 18 years old') return v

Now passing age=16 produces an error with msg equal to "User must be at least 18 years old". You can also use model_validator for cross-field checks. Custom messages are essential when the default messages are too generic for your domain.

Raising Validation Errors Manually

Sometimes you need to raise a ValidationError yourself, for example inside a custom validator or when re-validating data from an external source. You can construct a ValidationError with a list of error dictionaries. The constructor expects the model class and a list of error dicts:

from pydantic import ValidationError errors = [ { 'loc': ('name',), 'msg': 'Name is required', 'type': 'value_error', 'input': None, } ] raise ValidationError.from_exception_data('User', errors)

from_exception_data is a class method that builds a ValidationError from a model name and a list of error entries. This is useful when you want to raise a validation error that matches Pydantic's format, so downstream handlers can process it uniformly.

Validation Errors in FastAPI and Request Handling

FastAPI uses Pydantic models for request bodies and query parameters. When validation fails, FastAPI returns a 422 Unprocessable Entity response by default. The response body contains a detail list, each item having loc, msg, and type keys. You can customize this behavior by registering an exception handler for RequestValidationError:

from fastapi import FastAPI, Request from fastapi.exceptions import RequestValidationError from fastapi.responses import JSONResponse app = FastAPI() @app.exception_handler(RequestValidationError) async def validation_exception_handler(request: Request, exc: RequestValidationError): return JSONResponse( status_code=422, content={"errors": exc.errors()}, )

This gives you full control over the response format. Note that FastAPI's RequestValidationError is a subclass of Pydantic's ValidationError, so you can use the same .errors() method.

Debugging and Operational Considerations

In production, validation errors should be logged with enough context to diagnose the problem. The input field in each error dict contains the offending value, which is useful for debugging but may contain sensitive data. Consider redacting or truncating input before logging. Also, Pydantic's validation is CPU-bound; for high-throughput services, the overhead of repeated validation can be significant. If you validate the same data multiple times, cache the validated result when possible. Finally, keep your Pydantic version up to date, because error structures and messages can change between minor releases, which can affect code that parses errors() output.

python pydantic validation errors: Practical Usage and Code | RYUSLOG DEV