Pydantic field_validator: Normalize and Validate a Field
Normalize a handle before type validation while rejecting blank or non-string input.
Example check: Worked and guided code samples are checked against CPython 3.13. Practice uses Pyodide 0.29.5 (Python 3.13) in your browser. How we check lessons
Video lesson: Pydantic field_validator: Normalize and Validate a Field
The preview is stored on this site. YouTube loads only when you press Play. Watch on YouTube
Jump to a chapter
Read the video transcript
Select a timestamp to watch that moment on YouTube.
- 00:00
A user profile arrives with a handle that has spaces around it. Another request contains only spaces, and a third sends a number. We want one normalized handle for comparisons and a clear validation error for invalid input. In Pydantic V2, the field_validator decorator attaches a rule to a named field on a BaseModel. Our rule will trim whitespace, casefold text for case-insensitive matching, and reject blanks. The timing of the validator matters. We will use mode before, which means the method receives the raw object before Pydantic completes the field's ordinary string validation.
- 00:41
That raw object can be something other than a string. First we will make the mistake that happens when a before validator assumes every caller already sent text. The same model may be constructed from a form, a configuration file, or an API request. Centralizing the rule in the field validator helps those entry points agree. We will keep the rule small enough to audit and test with both good and bad examples. The broken function calls strip immediately. It works for a string and raises AttributeError for an integer. Inside a model validator, that would be an especially confusing way to report bad input because the intent is to tell the caller the handle must be text, not to
- 01:23
expose a string-method failure. A before validator cannot assume its argument has passed the annotated type check. The word before is literal: the raw input reaches our function before the normal field parser finishes. We should check isinstance first and raise ValueError for invalid types. A validator must also return the accepted value. If we normalize it but forget return, None flows into later validation and the model fails unexpectedly. Do not catch AttributeError and call that validation. It would also hide mistakes inside the validator implementation.
- 02:04
Check the expected type deliberately, then raise ValueError with a message that describes the input contract. The error location remains attached to handle. The diagnostic run contrasts the raw values and a guarded pure function. A string can be stripped; a number and None cannot. After checking the type, we test whether stripping leaves any visible characters. That rejects a blank handle without silently changing it to an empty string. casefold gives our chosen case-insensitive representation, including some Unicode cases that lower does not handle the same way. The policy is application-specific.
- 02:44
If stored handles already exist, changing their normalization could create collisions, so a real service would audit existing data before enforcing a new unique key. Now that the pure rule is clear, we can put it into a Pydantic model and observe validation at the field. Casefolding is a policy choice, not a universal requirement for display names. We apply it to a handle used as an identifier, while a user's preferred display spelling could be stored separately. Distinguish canonical comparison data from text shown back to a person. The fixed code imports BaseModel and field_validator from Pydantic V2.
- 03:25
We declare handle as a string field, place field_validator for handle in before mode above classmethod, and return the cleaned value. model_validate accepts the incoming mapping. The valid input with surrounding spaces becomes ada. This method belongs to the model's validation path, so every ordinary model construction applies the rule. A validator in after mode could also implement some rules, but it receives a value after normal type validation. We chose before because we want to inspect raw input and establish exactly how non- string values are rejected. Do not use the older V1 validator decorator syntax for this V2 example.
- 04:09
The decorator order in this example follows the documented Pydantic V2 style: field_validator wraps a classmethod. The method accepts cls and the candidate value. The returned string then continues through the model's ordinary validation and becomes the stored field value. The final run tests blank text, a number, and a valid Unicode handle. Pydantic wraps the ValueError in ValidationError, and the error location names the handle field. That location is useful for forms and API responses; avoid tests that depend on every word of the human error message. The browser practice on the lesson page runs the pure Python callback rule, while
- 04:53
the terminal demonstration uses the real Pydantic package. A browser Python console does not need to imitate an entire model library. If this model later becomes a FastAPI request body, FastAPI can report the field error at the HTTP boundary, but that is a separate integration step. The essential field-validator contract is simple: accept raw input carefully, raise a clear error for invalid values, and return the normalized value for valid ones. Try changing the input to None as another check. It should produce a model validation error at handle, not an unhandled string operation.
- 05:35
In production, log enough context to diagnose a bad record without exposing sensitive raw values in an error response.
Understand the concept
A Pydantic field validator attaches a custom rule to named model fields. In Pydantic V2, the @field_validator decorator lets this example trim a user handle, casefold it for comparison, and reject empty input. Put the decorator above @classmethod. Pydantic runs the method during model validation. An annotation such as handle: str alone does not express this normalization policy.
mode='before' receives raw input before the field's normal type validation. It could therefore receive a number or None rather than a str. Check isinstance(value, str) before calling strip. Return the normalized text so the usual string validation sees it. An after validator instead receives an already validated field value and is often simpler for rules that do not need raw input.
Raise ValueError for bad values instead of returning an empty handle or silently converting a number to text. Pydantic wraps that failure in a ValidationError whose location points to the field. Use model_validate when a mapping comes from an external source. Tests can assert the field location without depending on the exact wording of an error message.
The browser exercise implements the validator callback's pure Python normalization rule. The local block demonstrates the actual Pydantic model. The related FastAPI request-body lesson connects a model to a POST route; this page is focused on writing and testing a custom field rule in Pydantic V2.
- field_validator attaches a rule to a field
- before mode receives raw input
- return value continues through validation
- ValueError becomes field-level ValidationError
See it step by step
Read the code, predict the output, then compare it with the result.
01. Normalize raw text
def normalize(value):
if not isinstance(value, str) or not value.strip():
raise ValueError('handle must be nonempty text')
return value.strip().casefold()
print(normalize(' ADA '))adaThe pure function is the body of the model's before validator.
02. Validate with Pydantic V2
from pydantic import BaseModel, field_validator
class User(BaseModel):
handle: str
@field_validator('handle', mode='before')
@classmethod
def normalize(cls, value):
if not isinstance(value, str) or not value.strip():
raise ValueError('blank handle')
return value.strip().casefold()
print(User(handle=' Ada ').handle)adaThe decorator normalizes during model creation.
03. Inspect the field location
from pydantic import BaseModel, ValidationError, field_validator
class User(BaseModel):
handle: str
@field_validator('handle', mode='before')
@classmethod
def normalize(cls, value):
if not isinstance(value, str) or not value.strip(): raise ValueError('blank')
return value.strip().casefold()
try: User(handle=' ')
except ValidationError as error: print(error.errors()[0]['loc'])('handle',)Pydantic reports the failure at the relevant field.
Run the complete example locally
Run locally after python -m pip install 'pydantic>=2'. The browser task practices the pure Python rule because the page's Python console does not instantiate Pydantic.
from pydantic import BaseModel, ValidationError, field_validator
class User(BaseModel):
handle: str
@field_validator('handle', mode='before')
@classmethod
def normalize_handle(cls, value):
if not isinstance(value, str) or not value.strip():
raise ValueError('handle must be nonempty text')
return value.strip().casefold()
print(User.model_validate({'handle': ' Ada '}).handle)
try:
User.model_validate({'handle': ' '})
except ValidationError as error:
print(error.errors()[0]['loc'])ada
('handle',)A closer look
Follow the reasoning, inspect each result, then try the suggested changes in the console below.
Choose before or after validation
A Pydantic field validator in before mode sees the raw object from the caller. Use this mode when trimming or normalizing must happen before the field type check. Because the value may be None or a number, check its type before calling string methods. Test the same rule with a valid string, a blank string, a number, and None. Those examples reveal exactly which stage accepts raw input and make the chosen error behavior visible.
After validators receive a value that passed normal type validation. They are appropriate when the rule depends on a known parsed type. Choose the mode based on the required ordering.
for value in (' Ada ', 42):
print(type(value).__name__, isinstance(value, str))str True
int False- Pass text and a number through the same type check.
- Explain why the number must be rejected before strip.
Return normalized text
A validator's return value becomes the value for later validation. Trimming and casefolding gives this example one canonical handle for comparison. casefold is more aggressive than lower for case-insensitive text. A normalized identifier may collide with an existing identifier that differs only by case or spacing. Audit stored values before adding a uniqueness constraint based on the new canonical form.
A changed normalization policy may create uniqueness collisions in existing data. Check stored handles before enforcing a new normalized unique key.
raw = ' Straße '
print(raw.strip().casefold())
print(raw)strasse
Straße - Normalize visible text.
- Compare the result to the original string.
Inspect field errors
Raise ValueError when a handle is blank or not text. Pydantic converts that failure to a ValidationError whose location names handle. Tests should check the field location without relying on every word of an error message. Check the error location rather than the entire error sentence. Library versions may refine wording, while the field path is the contract a form or API client needs for feedback.
Run the local model code for one valid and one invalid input. The browser task checks the pure callback rule without loading Pydantic in the console.
from pydantic import BaseModel, ValidationError, field_validator
class User(BaseModel):
handle: str
@field_validator('handle', mode='before')
@classmethod
def normalize(cls, v):
if not isinstance(v, str) or not v.strip(): raise ValueError('blank')
return v.strip().casefold()
try: User(handle=' ')
except ValidationError as error: print(error.errors()[0]['loc'])('handle',)- Create a valid and invalid User locally.
- Inspect the first error location.
Try it in Python
Edit the example and run it. Python starts in your browser the first time you click Run.
Python console
Ready to runNeed input()? Add one value per line
Your output appears here.
Need a hint?
Check isinstance(value, str) before strip; reject not value.strip(); return value.strip().casefold().
Complete the task and select Check task to verify your code.
Quick quiz
Three questions. You can change your answers and try again.
Typical mistakes
Everyone meets these errors. See what causes them and how to fix them.
Assuming before means string
value.strip()Check isinstance(value, str) before strip.What happens: A number or None raises AttributeError inside the validator.
A before validator sees raw input.
Forgetting to return the cleaned value
value.strip().casefold() # no returnreturn value.strip().casefold()What happens: Validation receives None rather than a normalized handle.
Validator output becomes the field value for later validation.