Skip to main content

Inspecting Error Metadata Programmatically

In this tutorial, you will build a custom error reporting tool that transforms raw validation errors into localized or domain-specific messages. You will learn how to programmatically inspect ErrorDetails and use ErrorTypeInfo to discover the metadata available for every error type in the system.

Prerequisites

To follow this tutorial, you need pydantic and pydantic_core installed. You should be familiar with basic Pydantic models.

Step 1: Capturing Error Metadata

When a Pydantic model fails validation, it raises a ValidationError. This exception contains a list of ErrorDetails objects which provide programmatic access to what went wrong.

First, let's create a model and trigger an error:

from pydantic import BaseModel, ValidationError, Field

class UserProfile(BaseModel):
username: str = Field(min_length=3)
age: int

try:
UserProfile(username="ab", age="not-an-int")
except ValidationError as e:
# Get the list of ErrorDetails
errors = e.errors()
print(errors)

The e.errors() method returns a list of ErrorDetails dictionaries. Each dictionary contains:

  • type: A stable machine-readable identifier (e.g., string_too_short).
  • loc: A tuple showing the path to the error (e.g., ('username',)).
  • msg: The default human-readable message.
  • input: The actual value that failed validation.
  • ctx: An optional dictionary containing values used to generate the message.

Step 2: Building a Custom Error Reporter

Now, let's use the type and ctx fields to build a reporter that provides custom messages. This is useful for localization or providing more helpful hints to your users.

from typing import List
from pydantic_core import ErrorDetails
from pydantic import BaseModel, ValidationError

# Define custom templates for specific error types
CUSTOM_MESSAGES = {
'string_too_short': 'The name "{input}" is too short! It must have at least {min_length} characters.',
'int_parsing': 'We expected a number for the age, but you provided: "{input}".',
}

def format_custom_errors(errors: List[ErrorDetails]) -> List[str]:
formatted_messages = []
for error in errors:
err_type = error['type']
# Get the custom template or fall back to the default message
template = CUSTOM_MESSAGES.get(err_type)

if template:
# Merge context and input for formatting
ctx = error.get('ctx', {})
message = template.format(input=error['input'], **ctx)
else:
message = error['msg']

formatted_messages.append(f"Error at {'.'.join(map(str, error['loc']))}: {message}")

return formatted_messages

try:
UserProfile(username="ab", age="not-an-int")
except ValidationError as e:
custom_reports = format_custom_errors(e.errors())
for report in custom_reports:
print(report)

In this step, we accessed error['type'] to decide which template to use. We then used error.get('ctx', {}) to retrieve variables like min_length which Pydantic provides specifically for that error type.

Step 3: Discovering Error Types with ErrorTypeInfo

To build a comprehensive reporter, you need to know which error types exist and what variables are available in their ctx. You can use list_all_errors to inspect the metadata for every built-in error.

from pydantic_core import list_all_errors

# Retrieve all available error metadata
all_error_info = list_all_errors()

# Find metadata for a specific error type
string_too_short_info = next(
info for info in all_error_info if info['type'] == 'string_too_short'
)

print(f"Type: {string_too_short_info['type']}")
print(f"Template: {string_too_short_info['message_template_python']}")
print(f"Example Context: {string_too_short_info['example_context']}")

The list_all_errors() function returns a list of ErrorTypeInfo objects. Each ErrorTypeInfo provides:

  • message_template_python: The default template used for Python-based validation.
  • example_context: A sample of what the ctx dictionary will look like for this error.
  • example_message_python: A rendered example of the error message.

Complete Result

By combining these tools, you can create a robust error handling system that maps internal Pydantic states to your application's specific needs.

from pydantic import BaseModel, ValidationError, Field
from pydantic_core import list_all_errors, ErrorDetails

class Product(BaseModel):
name: str
price: float = Field(gt=0)

def custom_reporter(model_instance_data: dict):
try:
Product(**model_instance_data)
except ValidationError as e:
for error in e.errors():
# Programmatically inspect the error
if error['type'] == 'greater_than':
limit = error['ctx']['gt']
print(f"Validation Failed: {error['loc'][0]} must be more than {limit}")
else:
print(f"Validation Failed: {error['msg']}")

# Example usage
custom_reporter({"name": "Coffee", "price": -1.50})

You have now successfully implemented a system that inspects error metadata to provide targeted feedback. For next steps, consider using list_all_errors() to generate a complete translation file for your application's supported languages.