Skip to main content

Quick Start

This guide will walk you through creating your first Pydantic model and demonstrate the core features of the library.

Your First Model

Let’s create a simple User model:
from pydantic import BaseModel

class User(BaseModel):
    id: int
    name: str
    email: str

# Create a user instance
user = User(id=123, name='John Doe', email='[email protected]')

print(user)
#> User id=123 name='John Doe' email='[email protected]'

print(user.id)
#> 123

print(user.model_dump())
#> {'id': 123, 'name': 'John Doe', 'email': '[email protected]'}
All Pydantic models inherit from BaseModel. Fields are defined using Python type annotations.

Automatic Type Validation

Pydantic automatically validates data types:
from pydantic import BaseModel

class Product(BaseModel):
    product_id: int
    name: str
    price: float
    in_stock: bool

# Pydantic converts compatible types automatically
product = Product(
    product_id='42',      # string → int
    name='Widget',
    price='19.99',        # string → float
    in_stock='yes'        # string → bool
)

print(product.product_id)  #> 42
print(type(product.product_id))  #> <class 'int'>

print(product.price)  #> 19.99
print(type(product.price))  #> <class 'float'>

Default Values

Fields can have default values:
from pydantic import BaseModel

class User(BaseModel):
    id: int
    name: str = 'John Doe'  # Default value
    is_active: bool = True   # Default value

user1 = User(id=1)
print(user1)
#> User id=1 name='John Doe' is_active=True

user2 = User(id=2, name='Jane Smith', is_active=False)
print(user2)
#> User id=2 name='Jane Smith' is_active=False

Optional Fields

Use Optional or None default for optional fields:
from typing import Optional
from pydantic import BaseModel

class User(BaseModel):
    id: int
    name: str
    email: Optional[str] = None
    phone: str | None = None  # Python 3.10+ syntax

user = User(id=1, name='John')
print(user)
#> User id=1 name='John' email=None phone=None

user_with_email = User(id=2, name='Jane', email='[email protected]')
print(user_with_email.email)
#> [email protected]

Validation Errors

When validation fails, Pydantic raises a ValidationError with detailed information:
from pydantic import BaseModel, ValidationError

class User(BaseModel):
    id: int
    name: str
    age: int

try:
    user = User(id='not-a-number', name='John', age='25')
except ValidationError as e:
    print(e)
    '''
    1 validation error for User
    id
      Input should be a valid integer, unable to parse string as an integer [type=int_parsing, input_value='not-a-number', input_type=str]
    '''
The error message tells you exactly which field failed (id), why it failed (unable to parse string as integer), and what the input was ('not-a-number').

Working with Complex Types

Pydantic handles complex nested types:
from datetime import datetime
from typing import Optional
from pydantic import BaseModel

class User(BaseModel):
    id: int
    name: str = 'John Doe'
    signup_ts: Optional[datetime] = None
    friends: list[int] = []
    tags: set[str] = set()
    metadata: dict[str, str] = {}

user_data = {
    'id': '123',
    'signup_ts': '2017-06-01 12:22',
    'friends': [1, '2', b'3'],  # Mixed types!
    'tags': ['python', 'coding', 'python'],  # Duplicates will be removed
    'metadata': {'source': 'api', 'version': '2.0'}
}

user = User(**user_data)

print(user.id)
#> 123

print(user.signup_ts)
#> 2017-06-01 12:22:00

print(user.friends)
#> [1, 2, 3]

print(user.tags)
#> {'python', 'coding'}

Field Constraints

Add validation constraints using Field:
from pydantic import BaseModel, Field

class Product(BaseModel):
    name: str = Field(min_length=1, max_length=100)
    description: str = Field(default='', max_length=500)
    price: float = Field(gt=0, le=1000000)  # Greater than 0, less than or equal to 1M
    quantity: int = Field(ge=0)  # Greater than or equal to 0

product = Product(
    name='Widget',
    description='A useful widget',
    price=29.99,
    quantity=100
)

try:
    invalid_product = Product(name='', price=-10, quantity=50)
except ValidationError as e:
    print(e)
    '''
    2 validation errors for Product
    name
      String should have at least 1 character [type=string_too_short, input_value='', input_type=str]
    price
      Input should be greater than 0 [type=greater_than, input_value=-10, input_type=int]
    '''

String Constraints

min_length, max_length, pattern

Numeric Constraints

gt, ge, lt, le, multiple_of

Container Constraints

min_length, max_length for lists/sets

Custom Constraints

Build your own with validators

Custom Validators

Add custom validation logic with field_validator:
from pydantic import BaseModel, field_validator

class Product(BaseModel):
    name: str
    sku: str
    
    @field_validator('name')
    @classmethod
    def name_must_contain_space(cls, v: str) -> str:
        if ' ' not in v:
            raise ValueError('Product name must contain a space')
        return v
    
    @field_validator('sku')
    @classmethod
    def sku_format(cls, v: str) -> str:
        if not v.startswith('SKU-'):
            raise ValueError('SKU must start with "SKU-"')
        return v.upper()

product = Product(name='Super Widget', sku='sku-12345')
print(product.sku)
#> SKU-12345

try:
    invalid = Product(name='Widget', sku='12345')
except ValidationError as e:
    print(e)
    '''
    2 validation errors for Product
    name
      Value error, Product name must contain a space [type=value_error, input_value='Widget', input_type=str]
    sku
      Value error, SKU must start with "SKU-" [type=value_error, input_value='12345', input_type=str]
    '''
1

Define the validator

Use the @field_validator decorator on a classmethod
2

Receive the value

The validator receives the field value as its first argument
3

Validate and transform

Raise ValueError for invalid data, or return the (possibly modified) value

Serialization

Convert models to dictionaries or JSON:
from pydantic import BaseModel

class User(BaseModel):
    id: int
    name: str
    email: str

user = User(id=123, name='John Doe', email='[email protected]')

# To dictionary
user_dict = user.model_dump()
print(user_dict)
#> {'id': 123, 'name': 'John Doe', 'email': '[email protected]'}

# To JSON string
user_json = user.model_dump_json()
print(user_json)
#> {"id":123,"name":"John Doe","email":"[email protected]"}

# Pretty-printed JSON
user_json_pretty = user.model_dump_json(indent=2)
print(user_json_pretty)
'''
{
  "id": 123,
  "name": "John Doe",
  "email": "[email protected]"
}
'''

Exclude Fields

Exclude specific fields from serialization:
from pydantic import BaseModel, Field

class User(BaseModel):
    id: int
    name: str
    email: str
    password: str = Field(exclude=True)

user = User(id=1, name='John', email='[email protected]', password='secret123')

# Password is automatically excluded
print(user.model_dump())
#> {'id': 1, 'name': 'John', 'email': '[email protected]'}

# But it's still accessible on the instance
print(user.password)
#> secret123

Parsing JSON

Parse JSON strings directly into models:
from pydantic import BaseModel

class User(BaseModel):
    id: int
    name: str
    email: str

json_data = '{"id": 123, "name": "John Doe", "email": "[email protected]"}'

user = User.model_validate_json(json_data)
print(user)
#> User id=123 name='John Doe' email='[email protected]'

Nested Models

Models can contain other models:
from pydantic import BaseModel

class Address(BaseModel):
    street: str
    city: str
    country: str
    zip_code: str

class User(BaseModel):
    id: int
    name: str
    address: Address

user_data = {
    'id': 123,
    'name': 'John Doe',
    'address': {
        'street': '123 Main St',
        'city': 'Springfield',
        'country': 'USA',
        'zip_code': '12345'
    }
}

user = User(**user_data)
print(user.address.city)
#> Springfield

print(user.model_dump())
'''
{
    'id': 123,
    'name': 'John Doe',
    'address': {
        'street': '123 Main St',
        'city': 'Springfield',
        'country': 'USA',
        'zip_code': '12345'
    }
}
'''

Handling Errors

Access detailed error information:
from pydantic import BaseModel, ValidationError

class Model(BaseModel):
    a: float
    b: int

try:
    Model(a='x', b='x')
except ValidationError as e:
    # Get errors as list of dicts
    errors = e.errors()
    for error in errors:
        print(f"Field: {error['loc'][0]}")
        print(f"Error: {error['msg']}")
        print(f"Input: {error['input']}")
        print()
    
    # Get as JSON
    print(e.json())

Next Steps

Now that you understand the basics, explore more advanced features:

Models

Learn about model configuration, inheritance, and advanced features

Fields

Deep dive into field types, constraints, and customization

Validators

Master custom validation logic and transform data

Types

Explore built-in types for URLs, emails, dates, and more
Ready to use Pydantic in a real project? Check out the Examples section for common use cases like API validation, configuration management, and database models.

Build docs developers (and LLMs) love