> ## Documentation Index
> Fetch the complete documentation index at: https://fastapi2day.codewithsiva.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Your First Pydantic Model: A BaseModel Basics Guide

> Learn to define a Pydantic BaseModel subclass, declare typed fields, create instances, handle validation errors, and convert models to dicts and JSON.

A Pydantic model is a Python class that describes the structure of a piece of data — what fields it has, what types those fields must be, and which fields are optional versus required. You define the model once, and Pydantic takes care of validating every value that flows into it. This means that wherever you create or receive a model instance in your code, you can trust that the data inside it is exactly what you declared. That guarantee is what makes Pydantic so powerful in APIs, AI pipelines, and any other context where data comes from outside your application.

## Defining a model

Inherit from `BaseModel` and declare fields using Python type annotations:

```python theme={null}
from pydantic import BaseModel

class User(BaseModel):
    name: str
    email: str
    age: int
```

This model says: *a `User` has a `name` (string), an `email` (string), and an `age` (integer).* That's it. No `__init__` method, no boilerplate.

<Note>
  Type annotations are not just documentation here — Pydantic reads them at runtime and uses them to validate data. Missing a type annotation means Pydantic ignores the field entirely.
</Note>

## Pydantic vs Python dataclasses

You may have seen Python's built-in `dataclass` decorator. It looks similar:

<Tabs>
  <Tab title="dataclass (no validation)">
    ```python theme={null}
    from dataclasses import dataclass

    @dataclass
    class User:
        name: str
        email: str
        age: int

    user = User(name="Alice", email="alice@example.com", age="not a number")
    print(user.age)   # "not a number" — no error raised!
    ```
  </Tab>

  <Tab title="Pydantic BaseModel (validated)">
    ```python theme={null}
    from pydantic import BaseModel

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

    user = User(name="Alice", email="alice@example.com", age="not a number")
    # ValidationError: Input should be a valid integer
    ```
  </Tab>
</Tabs>

Dataclasses generate an `__init__` method from your type hints but perform **no validation at all** — the hints are pure documentation. Pydantic enforces them at runtime. For most projects, reach for Pydantic directly: you get validation, serialization, and JSON Schema generation with almost no extra effort.

## Creating instances

Pass your data as keyword arguments:

```python theme={null}
from pydantic import BaseModel

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

user = User(name="Alice", email="alice@example.com", age=30)

print(user.name)   # Alice
print(user.email)  # alice@example.com
print(user.age)    # 30
```

Pydantic validates the data the moment you call `User(...)`. If anything is wrong, you get a `ValidationError` right there — not somewhere downstream in your code.

## Automatic type coercion

Pydantic is smart about compatible types. If a value can be safely converted to the declared type, it will be:

```python theme={null}
from pydantic import BaseModel

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

user = User(name="Alice", age="25")   # "25" is a string
print(user.age)         # 25
print(type(user.age))   # <class 'int'>
```

This is especially useful when parsing form data or API responses where numbers often arrive as strings.

<Warning>
  Coercion is not unlimited. The string `"twenty-five"` cannot be coerced to an `int` and will raise a `ValidationError`. If you need strict mode (no coercion at all), see the section below.
</Warning>

## Handling validation errors

When data is invalid, Pydantic raises a `ValidationError` with a precise message:

```python theme={null}
from pydantic import BaseModel, ValidationError

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

try:
    user = User(name="Alice", email="alice@example.com", age="thirty")
except ValidationError as e:
    print(e)
```

Output:

```text theme={null}
1 validation error for User
age
  Input should be a valid integer, unable to parse string as an integer
    [type=int_parsing, input_value='thirty', input_url=...]
```

The error tells you exactly which field failed and why — making debugging fast and clear.

## Required vs optional fields

Fields with no default value are **required**. Fields with a default are **optional**:

```python theme={null}
from pydantic import BaseModel

class User(BaseModel):
    name: str              # Required
    email: str             # Required
    age: int | None = None # Optional — defaults to None

# Works — age is omitted
user = User(name="Alice", email="alice@example.com")
print(user.age)   # None

# Also works — age is provided
user = User(name="Bob", email="bob@example.com", age=25)
print(user.age)   # 25
```

## Default values

Set a default for fields that usually have a common value:

```python theme={null}
from pydantic import BaseModel

class APIConfig(BaseModel):
    api_key: str
    model: str = "gpt-4"
    max_tokens: int = 1000
    temperature: float = 0.7

# Only api_key is required
config = APIConfig(api_key="sk-abc123")

print(config.model)        # gpt-4
print(config.max_tokens)   # 1000
print(config.temperature)  # 0.7
```

<Tip>
  Default values in Pydantic models are safe to use with mutable types like lists. Each instance gets its own copy — unlike regular Python class attributes where `= []` is shared across all instances.
</Tip>

## Converting a model to a dictionary

Use `model_dump()` to export a model's data as a plain Python dictionary:

```python theme={null}
from pydantic import BaseModel

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

user = User(name="Alice", email="alice@example.com", age=30)

data = user.model_dump()
print(data)
# {'name': 'Alice', 'email': 'alice@example.com', 'age': 30}
```

You'll use `model_dump()` constantly — when storing data in a database, sending it to another service, or logging it.

## Converting a model to JSON

Use `model_dump_json()` to get a JSON-encoded string:

```python theme={null}
from pydantic import BaseModel

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

user = User(name="Alice", email="alice@example.com", age=30)

json_str = user.model_dump_json()
print(json_str)
# {"name":"Alice","email":"alice@example.com","age":30}
```

## Creating a model from a dictionary

When you receive data as a dictionary (from an API response, a database row, or a parsed JSON file), you have two equally valid ways to create a model:

```python theme={null}
from pydantic import BaseModel

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

data = {"name": "Alice", "email": "alice@example.com", "age": 30}

# Option 1 — unpack the dict (concise, most common)
user = User(**data)

# Option 2 — model_validate (explicit, supports extra options)
user = User.model_validate(data)

print(user.name)  # Alice
```

Both approaches validate the incoming data. Use `**data` for everyday use and `model_validate()` when you need options like `strict=True`.

## Using models as type hints

Pydantic models work naturally as type hints in function signatures, giving you IDE autocomplete and making your intent clear:

```python theme={null}
from pydantic import BaseModel

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

def greet(user: User) -> str:
    return f"Hello, {user.name}!"

def load_user(data: dict) -> User:
    return User.model_validate(data)

user = load_user({"name": "Alice", "email": "alice@example.com"})
print(greet(user))  # Hello, Alice!
```

This makes your code self-documenting: any function that accepts `user: User` communicates exactly what data it needs.

## Strict mode

By default, Pydantic coerces compatible types. If you need exact-type matching, enable strict mode:

```python theme={null}
from pydantic import BaseModel, ConfigDict

class StrictUser(BaseModel):
    model_config = ConfigDict(strict=True)

    name: str
    age: int

# Raises ValidationError — coercion is disabled
user = StrictUser(name="Alice", age="25")
# ValidationError: Input should be a valid integer [type=int_type]
```

`ConfigDict` is a special configuration object Pydantic looks for on the `model_config` class attribute. For most use cases, the default lax mode is what you want.

<Accordion title="Real-world example: parsing an API response">
  ```python theme={null}
  from pydantic import BaseModel

  class WeatherResponse(BaseModel):
      city: str
      temperature: float
      humidity: int
      description: str

  # Simulating a parsed API response
  api_data = {
      "city": "Amsterdam",
      "temperature": 18.5,
      "humidity": 75,
      "description": "Partly cloudy",
  }

  weather = WeatherResponse.model_validate(api_data)

  print(f"Weather in {weather.city}: {weather.temperature}°C")
  print(f"Humidity: {weather.humidity}%")
  print(f"Conditions: {weather.description}")
  # Weather in Amsterdam: 18.5°C
  # Humidity: 75%
  # Conditions: Partly cloudy
  ```
</Accordion>

## Learn more

* [Pydantic Models documentation](https://docs.pydantic.dev/latest/concepts/models/)

***

## What's next?

You can now create and instantiate basic models. Next, you'll learn how to add constraints to fields — minimum lengths, numeric ranges, regex patterns, and custom validation logic.

<Card title="Validation and Fields" icon="arrow-right" href="/pydantic/validation-and-fields">
  Learn how to add validation rules and constraints to your Pydantic fields.
</Card>
