Bird
Raised Fist0
FastAPIframework~15 mins

Custom validation with validator decorator in FastAPI - Deep Dive

Choose your learning style10 modes available
Overview - Custom validation with validator decorator
What is it?
Custom validation with the validator decorator in FastAPI allows you to add your own rules to check data before it is accepted. It is used to make sure the data fits your specific needs beyond basic types. This helps catch mistakes early and keeps your app safe and reliable. The validator decorator is part of Pydantic, which FastAPI uses to handle data validation.
Why it matters
Without custom validation, your app might accept wrong or harmful data, causing bugs or security issues. Custom validation lets you control exactly what data is allowed, making your app more trustworthy and user-friendly. It saves time by catching errors early and gives clear feedback to users about what is wrong.
Where it fits
Before learning custom validation, you should understand basic FastAPI routes and Pydantic models. After mastering this, you can explore more advanced data handling like complex nested models, dependency injection, and error handling in FastAPI.
Mental Model
Core Idea
Custom validation with the validator decorator lets you add special checks to your data models so only correct and meaningful data passes through.
Think of it like...
It's like having a security guard at a party who checks guests' invitations carefully to make sure only the right people get in.
Pydantic Model
┌─────────────────────────┐
│ Fields with basic types │
│ (e.g., str, int, float)  │
└──────────┬──────────────┘
           │
           ▼
┌─────────────────────────┐
│ @validator decorator    │
│ Custom check functions  │
└──────────┬──────────────┘
           │
           ▼
┌─────────────────────────┐
│ Validated data ready    │
│ for FastAPI use         │
└─────────────────────────┘
Build-Up - 7 Steps
1
FoundationUnderstanding Pydantic Models
🤔
Concept: Learn what Pydantic models are and how they define data shapes in FastAPI.
Pydantic models are Python classes that describe the shape and type of data your app expects. For example, a User model might have a name as a string and age as an integer. FastAPI uses these models to check incoming data automatically.
Result
You can create simple data models that FastAPI uses to validate input data types.
Understanding Pydantic models is key because custom validation builds on top of these models to add more specific rules.
2
FoundationBasic Field Validation
🤔
Concept: Learn how Pydantic automatically validates basic data types and required fields.
When you define a Pydantic model, FastAPI checks if the data matches the types you set. For example, if age is an int, sending a string will cause an error. Fields can also be optional or have default values.
Result
FastAPI rejects data that doesn't match the expected types or misses required fields.
Knowing what Pydantic validates automatically helps you see why custom validation is needed for rules beyond simple types.
3
IntermediateIntroducing the validator Decorator
🤔Before reading on: do you think the validator decorator runs before or after basic type checks? Commit to your answer.
Concept: The validator decorator lets you write custom functions that check or modify field values after basic validation.
You add a method to your Pydantic model and decorate it with @validator, specifying which field to check. This method receives the field value and can raise errors or change the value. For example, you can check if a username is not empty or if an age is positive.
Result
Your model now enforces custom rules, rejecting or adjusting data that doesn't meet them.
Understanding that validators run after basic checks lets you safely assume the data type is correct when writing your custom logic.
4
IntermediateWriting Multiple Validators
🤔Before reading on: can you use multiple validators on the same field? Commit to yes or no.
Concept: You can write several validator methods for different fields or even multiple validators for the same field to handle complex rules.
Each validator method targets one or more fields by name. They run in the order they appear in the code. This allows you to split checks into smaller, focused functions. For example, one validator checks format, another checks value range.
Result
Your model can handle complex validation logic cleanly and maintainably.
Knowing you can chain validators helps organize validation logic and avoid large, hard-to-read functions.
5
IntermediateUsing pre and each_item Options
🤔Before reading on: do you think validators run before or after parsing? Commit to your answer.
Concept: Validator decorators have options like pre=True to run before parsing and each_item=True to validate items in a list individually.
By default, validators run after Pydantic parses the data into the correct type. Setting pre=True runs the validator before parsing, useful for raw input checks. Using each_item=True applies the validator to each element in a list or set.
Result
You gain fine control over when and how validation happens, enabling checks on raw or complex data structures.
Understanding these options lets you handle edge cases and complex data shapes that default validation can't cover.
6
AdvancedRaising Custom Errors in Validators
🤔Before reading on: do you think raising ValueError or a custom exception is better for validation errors? Commit to your answer.
Concept: Validators raise errors to signal invalid data, and you can customize error messages for clarity.
Inside a validator method, if data is invalid, raise ValueError with a clear message. FastAPI catches this and returns a helpful error response to the client. You can also raise TypeError or AssertionError, but ValueError is most common.
Result
Users get precise feedback about what went wrong, improving user experience and debugging.
Knowing how to raise and customize errors in validators is crucial for building user-friendly APIs.
7
ExpertValidator Execution Order and Side Effects
🤔Before reading on: do you think validator methods run in the order they are defined or alphabetically? Commit to your answer.
Concept: Validator methods run in the order they appear in the code, and their results can affect each other, so side effects matter.
If multiple validators modify the same field, the output of one becomes the input of the next. This means order affects the final value. Also, validators should avoid side effects like changing unrelated state because they may run multiple times during model creation.
Result
You can write predictable, safe validators that compose well without unexpected bugs.
Understanding validator order and side effects prevents subtle bugs and helps design clean validation logic.
Under the Hood
When FastAPI receives data, it uses Pydantic to parse and validate it. Pydantic first checks basic types and required fields. Then it calls any validator methods decorated with @validator. These methods receive the field value and can modify it or raise errors. The final validated data is then passed to your FastAPI endpoint. This process happens synchronously and uses Python's class and method mechanisms internally.
Why designed this way?
Pydantic's validator decorator was designed to keep validation logic close to the data model, making code easier to read and maintain. It separates basic type validation from custom rules, allowing flexibility. Alternatives like external validation functions were less integrated and harder to manage. This design balances simplicity, power, and clarity.
Incoming Data
    │
    ▼
Basic Type Checks (Pydantic)
    │
    ▼
@validator Methods Run
    │
    ├─ Modify or Check Field Value
    ├─ Raise Error if Invalid
    │
    ▼
Validated Model Instance
    │
    ▼
Passed to FastAPI Endpoint
Myth Busters - 4 Common Misconceptions
Quick: Does the validator decorator run before or after basic type checks? Commit to your answer.
Common Belief:Validators run before any type checking, so they receive raw input data.
Tap to reveal reality
Reality:Validators run after Pydantic has parsed and checked basic types, so they receive data already converted to the correct type.
Why it matters:If you assume validators get raw data, you might write checks that fail or behave incorrectly, causing bugs or missed errors.
Quick: Can you use the validator decorator to validate multiple fields at once? Commit yes or no.
Common Belief:Each validator can only check one field at a time.
Tap to reveal reality
Reality:Validators can target multiple fields by listing them in the decorator, allowing cross-field validation.
Why it matters:Believing validators are single-field only limits your ability to enforce rules involving relationships between fields.
Quick: Is it safe to have validators modify unrelated parts of the model or external state? Commit yes or no.
Common Belief:Validators can safely change anything, including external variables or other fields.
Tap to reveal reality
Reality:Validators should only modify their target field and avoid side effects because they may run multiple times or unpredictably.
Why it matters:Side effects in validators can cause hard-to-debug bugs and inconsistent data states.
Quick: Does the order of validator methods affect the final validated data? Commit yes or no.
Common Belief:Validator methods run in any order, so order does not matter.
Tap to reveal reality
Reality:Validator methods run in the order they are defined in the code, affecting the final data if multiple modify the same field.
Why it matters:Ignoring order can lead to unexpected validation results and bugs that are hard to trace.
Expert Zone
1
Validators with pre=True run before parsing, allowing raw input checks but require careful handling of types.
2
Using each_item=True on validators enables element-wise validation in collections, which is essential for nested or list data.
3
Validators can be class methods or static methods, but using class methods allows access to the whole model via values parameter for cross-field validation.
When NOT to use
Avoid using validator decorators for very complex validation logic involving multiple models or asynchronous checks. Instead, use FastAPI dependencies or custom request handlers for those cases.
Production Patterns
In production, validators are used to enforce business rules like password strength, date ranges, or unique constraints. They are combined with global error handlers to return consistent API error responses.
Connections
Data Validation in Database ORMs
Both enforce data correctness but at different layers; Pydantic validators check input before database interaction.
Understanding Pydantic validation helps grasp how ORMs also validate data but usually after input reaches the database layer.
Functional Programming Pure Functions
Validators ideally behave like pure functions without side effects, similar to functional programming principles.
Knowing pure function concepts helps write validators that are predictable and safe, avoiding bugs from side effects.
Quality Control in Manufacturing
Validator decorators act like quality inspectors checking each product (data) before it moves forward.
Seeing validation as quality control highlights its role in preventing defects early, saving time and resources.
Common Pitfalls
#1Raising generic exceptions instead of ValueError in validators.
Wrong approach:def check_age(cls, v): if v < 0: raise Exception('Age must be positive') return v
Correct approach:def check_age(cls, v): if v < 0: raise ValueError('Age must be positive') return v
Root cause:Using generic exceptions prevents FastAPI from properly formatting validation errors for clients.
#2Modifying unrelated fields or external state inside a validator.
Wrong approach:def check_name(cls, v): global some_flag some_flag = True return v
Correct approach:def check_name(cls, v): # Only validate or modify 'v', no side effects return v
Root cause:Misunderstanding that validators should be pure and only affect their own field.
#3Assuming validators run before type parsing and writing type-dependent code incorrectly.
Wrong approach:def check_date(cls, v): # v is expected as string, but it's already a date object if len(v) != 10: raise ValueError('Invalid date format') return v
Correct approach:def check_date(cls, v): # v is a date object, so check accordingly if v > date.today(): raise ValueError('Date cannot be in the future') return v
Root cause:Confusing validator timing leads to wrong assumptions about input data type.
Key Takeaways
Custom validation with the validator decorator extends basic type checks to enforce precise data rules.
Validators run after Pydantic parses data, so they receive correctly typed values to check or modify.
You can write multiple validators per model, control their execution order, and customize error messages.
Avoid side effects in validators to keep validation predictable and safe.
Understanding validator options like pre and each_item unlocks powerful validation for complex data.

Practice

(1/5)
1. What is the main purpose of using the @validator decorator in a Pydantic model within FastAPI?
easy
A. To define database relationships
B. To automatically generate API routes
C. To add custom validation logic for model fields
D. To format the output JSON response

Solution

  1. Step 1: Understand the role of @validator

    The @validator decorator is used inside Pydantic models to add custom validation logic for fields.
  2. Step 2: Differentiate from other functionalities

    It does not generate routes, define database relations, or format output JSON; those are handled elsewhere.
  3. Final Answer:

    To add custom validation logic for model fields -> Option C
  4. Quick Check:

    Custom validation = To add custom validation logic for model fields [OK]
Hint: Remember: @validator checks field values before use [OK]
Common Mistakes:
  • Confusing validation with route creation
  • Thinking it formats output JSON
  • Assuming it manages database relations
2. Which of the following is the correct syntax to create a validator for the field email in a Pydantic model?
easy
A. @validator['email']\ndef check_email(cls, value):\n return value
B. @validate('email')\ndef check_email(cls, value):\n return value
C. @validator(email)\ndef check_email(cls, value):\n return value
D. @validator('email')\ndef check_email(cls, value):\n return value

Solution

  1. Step 1: Identify correct decorator usage

    The correct decorator is @validator('field_name') with the field name as a string.
  2. Step 2: Check syntax correctness

    @validator('email')\ndef check_email(cls, value):\n return value uses @validator('email') correctly; others use wrong decorator names or syntax.
  3. Final Answer:

    @validator('email')\ndef check_email(cls, value):\n return value -> Option D
  4. Quick Check:

    Correct decorator syntax = @validator('email')\ndef check_email(cls, value):\n return value [OK]
Hint: Use quotes around field name in @validator [OK]
Common Mistakes:
  • Omitting quotes around field name
  • Using wrong decorator name like @validate
  • Using brackets [] instead of parentheses ()
3. Given the following Pydantic model, what will happen if you create an instance with age=15?
from pydantic import BaseModel, validator

class User(BaseModel):
    age: int

    @validator('age')
    def check_age(cls, v):
        if v < 18:
            raise ValueError('Must be at least 18')
        return v

user = User(age=15)
medium
A. A ValueError is raised with message 'Must be at least 18'
B. The user instance is created with age 15
C. The age is automatically set to 18
D. A TypeError is raised because age is int

Solution

  1. Step 1: Analyze the validator logic

    The validator checks if age is less than 18 and raises a ValueError if true.
  2. Step 2: Apply input value to validator

    Since age=15 is less than 18, the validator raises the error with the message.
  3. Final Answer:

    A ValueError is raised with message 'Must be at least 18' -> Option A
  4. Quick Check:

    Age < 18 triggers ValueError = A ValueError is raised with message 'Must be at least 18' [OK]
Hint: Validator raises error if condition fails [OK]
Common Mistakes:
  • Assuming instance is created despite validation error
  • Thinking age auto-corrects to 18
  • Confusing ValueError with TypeError
4. Identify the error in this validator code snippet:
from pydantic import BaseModel, validator

class Product(BaseModel):
    price: float

    @validator('price')
    def check_price(value):
        if value <= 0:
            raise ValueError('Price must be positive')
        return value
medium
A. Validator decorator should be @validate
B. Missing cls parameter in validator method
C. Return statement is missing
D. Field name in decorator should be price without quotes

Solution

  1. Step 1: Check validator method signature

    Validator methods must have cls as the first parameter, then the value.
  2. Step 2: Identify missing parameter

    The method only has value, missing cls, causing a TypeError.
  3. Final Answer:

    Missing cls parameter in validator method -> Option B
  4. Quick Check:

    Validator method needs cls first = Missing cls parameter in validator method [OK]
Hint: Validator methods always start with cls, then value [OK]
Common Mistakes:
  • Omitting cls parameter
  • Using wrong decorator name
  • Forgetting to return the value
5. You want to create a Pydantic model that validates a username field to ensure it is at least 3 characters long and contains only letters and numbers. Which validator implementation correctly achieves this?
hard
A. from pydantic import BaseModel, validator import re class User(BaseModel): username: str @validator('username') def valid_username(cls, v): if len(v) < 3 or not re.match(r'^[a-zA-Z0-9]+$', v): raise ValueError('Username must be 3+ chars and alphanumeric') return v
B. from pydantic import BaseModel, validator class User(BaseModel): username: str @validator('username') def valid_username(cls, v): if len(v) > 3 and v.isalnum(): raise ValueError('Username must be 3+ chars and alphanumeric') return v
C. from pydantic import BaseModel, validator class User(BaseModel): username: str @validator('username') def valid_username(cls, v): if len(v) < 3 or not v.isalpha(): raise ValueError('Username must be 3+ chars and letters only') return v
D. from pydantic import BaseModel, validator import re class User(BaseModel): username: str @validator('username') def valid_username(cls, v): if len(v) < 3 or not re.match(r'^[a-z]+$', v): raise ValueError('Username must be 3+ chars and lowercase letters only') return v

Solution

  1. Step 1: Check length and character conditions

    The username must be at least 3 characters and only letters or numbers.
  2. Step 2: Evaluate each option's validation logic

    from pydantic import BaseModel, validator import re class User(BaseModel): username: str @validator('username') def valid_username(cls, v): if len(v) < 3 or not re.match(r'^[a-zA-Z0-9]+$', v): raise ValueError('Username must be 3+ chars and alphanumeric') return v correctly checks length & uses regex to allow letters and numbers only. Others either check wrong conditions or restrict letters only.
  3. Final Answer:

    Option A correctly validates username length and alphanumeric characters -> Option A
  4. Quick Check:

    Length >=3 and alphanumeric = from pydantic import BaseModel, validator import re class User(BaseModel): username: str @validator('username') def valid_username(cls, v): if len(v) < 3 or not re.match(r'^[a-zA-Z0-9]+$', v): raise ValueError('Username must be 3+ chars and alphanumeric') return v [OK]
Hint: Use regex to check alphanumeric and length together [OK]
Common Mistakes:
  • Using isalpha() instead of isalnum()
  • Checking length incorrectly (greater than vs less than)
  • Restricting to letters only when numbers allowed
Recommended

Ready to prove it?

Pass the test and this pattern moves unknown strong while earning +1 Grip toward your certificate.

10 questions~5 mineasy hard