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

# Validation and Fields

> Control what data is acceptable using Field constraints and validators.

Pydantic validates types, but you often need more:

* Email must be a valid format
* Age must be positive
* Username must be 3-20 characters
* Price can't be negative

The `Field()` function lets you add these constraints.

## The Field function

Import `Field` from pydantic and use it to add constraints:

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

class User(BaseModel):
    name: str = Field(min_length=1, max_length=100)
    age: int = Field(gt=0, le=120)
    email: str

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

Now `name` must be 1-100 characters, and `age` must be between 1 and 120.

## String constraints

Control string length and format:

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

class UserProfile(BaseModel):
    username: str = Field(min_length=3, max_length=20)
    bio: str = Field(max_length=500)
    website: str = Field(pattern=r"^https?://.*")

# Valid
profile = UserProfile(
    username="alice_dev",
    bio="Python developer",
    website="https://example.com"
)

# Invalid - username too short
profile = UserProfile(username="ab", bio="Hi", website="https://x.com")
# ValidationError: username must be at least 3 characters
```

String constraints:

* `min_length` - Minimum number of characters
* `max_length` - Maximum number of characters
* `pattern` - Regular expression pattern to match

## Numeric constraints

Control number ranges:

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

class Product(BaseModel):
    name: str
    price: float = Field(gt=0)           # Greater than 0
    quantity: int = Field(ge=0)          # Greater than or equal to 0
    discount: float = Field(ge=0, le=1)  # Between 0 and 1

product = Product(
    name="Widget",
    price=29.99,
    quantity=100,
    discount=0.15
)
```

Numeric constraints:

* `gt` - Greater than
* `ge` - Greater than or equal to
* `lt` - Less than
* `le` - Less than or equal to

## Default values with Field

Set defaults while also adding constraints:

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

class APIConfig(BaseModel):
    api_key: str
    model: str = Field(default="gpt-4")
    max_tokens: int = Field(default=1000, ge=1, le=4096)
    temperature: float = Field(default=0.7, ge=0, le=2)

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

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

## Field descriptions

Add descriptions for documentation:

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

class Order(BaseModel):
    order_id: str = Field(description="Unique order identifier")
    total: float = Field(gt=0, description="Order total in USD")
    items: int = Field(ge=1, description="Number of items in order")
```

Descriptions appear in generated JSON schemas and API documentation.

#### Another example

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

class Product(BaseModel):
    name: str = Field(..., min_length=2, max_length=50)
    price: float = Field(..., gt=0)
    stock: int = Field(default=0, ge=0)
    category: str | None = Field(None)
    product_name: str = Field(alias="name")
```

### Common Usage

| Syntax                      | Meaning                                                                                |
| --------------------------- | -------------------------------------------------------------------------------------- |
| `Field(...)`                | Required field. The client **must** provide a value.                                   |
| `Field(None)`               | Optional field. Defaults to `None` if omitted.                                         |
| `Field(default=value)`      | Uses the given default value when no input is provided.                                |
| `Field(alias="field_name")` | Accepts a different name in the input while using another attribute name in the model. |

## Custom validators (80/20 overview)

Sometimes built-in constraints aren't enough. Pydantic supports custom validators for business logic:

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

class User(BaseModel):
    username: str
    
    @field_validator("username")
    def validate_username(cls, v):
        if " " in v:
            raise ValueError("Username cannot contain spaces")
        return v.lower()  # Normalize to lowercase

user = User(username="AliceSmith")
print(user.username)  # alicesmith
```

The validator function receives `cls` (the class, since there's no instance yet during validation) and `v` (the value being validated). Return the value to accept it, or raise `ValueError` to reject it.

Custom validators let you:

* Add business-specific validation logic
* Transform values (like normalizing to lowercase)
* Validate things that built-in constraints can't handle

For most cases, built-in constraints and types are enough. Use custom validators only when you need specific business logic.

## Real-world example

Here's a model for a payment form:

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

class PaymentForm(BaseModel):
    card_number: str = Field(min_length=16, max_length=16)
    expiry_month: int = Field(ge=1, le=12)
    expiry_year: int = Field(ge=2024)
    cvv: str = Field(min_length=3, max_length=4)
    amount: float = Field(gt=0, description="Amount in USD")
    currency: str = Field(default="USD", min_length=3, max_length=3)

payment = PaymentForm(
    card_number="1234567890123456",
    expiry_month=12,
    expiry_year=2025,
    cvv="123",
    amount=99.99
)
```

## Common patterns

### Email validation

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

class User(BaseModel):
    email: EmailStr  # Built-in email validation
```

### URL validation

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

class Link(BaseModel):
    url: HttpUrl  # Must be valid HTTP/HTTPS URL
```

### Constrained lists

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

class Order(BaseModel):
    items: list[str] = Field(min_length=1)  # At least one item
```

## JSON Schema generation

Pydantic can generate JSON Schema from your models. This is useful for API documentation and integration with other tools:

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

class User(BaseModel):
    name: str = Field(min_length=1, description="User's full name")
    age: int = Field(ge=0, description="User's age in years")

print(User.model_json_schema())
```

Output:

```python theme={null}
{
    'properties': {
        'name': {'description': "User's full name", 'minLength': 1, 'type': 'string'},
        'age': {'description': "User's age in years", 'minimum': 0, 'type': 'integer'}
    },
    'required': ['name', 'age'],
    'title': 'User',
    'type': 'object'
}
```

FastAPI uses this to automatically generate API documentation.

## Learn more

* [Fields documentation](https://docs.pydantic.dev/latest/concepts/fields/)
* [Validators documentation](https://docs.pydantic.dev/latest/concepts/validators/)
* [JSON Schema documentation](https://docs.pydantic.dev/latest/concepts/json_schema/)

\##Class Attributes in Dataclasses and Pydantic Models

### Instance Attributes vs Class Attributes

| Instance Attribute                              | Class Attribute                                |
| ----------------------------------------------- | ---------------------------------------------- |
| Belongs to each object                          | Shared by all objects                          |
| Stored separately for every instance            | Stored only once in the class                  |
| Included in the constructor                     | Not included in the constructor                |
| Can have different values for different objects | Same value for all objects (unless overridden) |

## Instance Attributes (Fields)

These are the attributes that represent the data of each object.

### Dataclass

```python theme={null}
from dataclasses import dataclass

@dataclass
class Student:
    name: str
    age: int
```

Usage:

```python theme={null}
student = Student("John", 20)

print(student.name)
print(student.age)
```

Here, `name` and `age` are **instance attributes**.

### Pydantic Model

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

class Student(BaseModel):
    name: str
    age: int
```

Usage:

```python theme={null}
student = Student(name="John", age=20)

print(student.name)
print(student.age)
```

Again, `name` and `age` are **instance attributes** (also called **model fields** in Pydantic).

## Class Attributes

Class attributes belong to the class itself rather than individual objects.

For both **dataclasses** and **Pydantic models**, use `ClassVar` from the `typing` module to declare class attributes.

```python theme={null}
from typing import ClassVar
```

### Class Attributes in Dataclasses

```python theme={null}
from dataclasses import dataclass
from typing import ClassVar

@dataclass
class Student:
    school: ClassVar[str] = "ABC School"
    name: str
    age: int
```

Usage:

```python theme={null}
student = Student("John", 20)

print(student.school)
print(Student.school)
```

Output

```text theme={null}
ABC School
ABC School
```

Notice that `school` is **not** part of the constructor.

```python theme={null}
Student("John", 20)
```

Not

```python theme={null}
Student("John", 20, "XYZ School")
```

### Class Attributes in Pydantic

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

class Student(BaseModel):
    school: ClassVar[str] = "ABC School"
    name: str
    age: int
```

Usage

```python theme={null}
student = Student(name="John", age=20)

print(student.school)
print(Student.school)
```

Output

```text theme={null}
ABC School
ABC School
```

The class attribute is **not** included in the model fields.

```python theme={null}
print(student.model_dump())
```

Output

```python theme={null}
{
    "name": "John",
    "age": 20
}
```

## What Happens Without `ClassVar`?

If you omit `ClassVar`, the attribute becomes an **instance attribute (field)**.

### Dataclass

```python theme={null}
from dataclasses import dataclass

@dataclass
class Student:
    school: str = "ABC School"
    name: str = ""
```

Now `school` becomes part of every object.

```python theme={null}
student = Student()

print(student.school)
```

It also appears in the constructor.

### Pydantic

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

class Student(BaseModel):
    school: str = "ABC School"
    name: str
```

```python theme={null}
student = Student(name="John")

print(student.model_dump())
```

Output

```python theme={null}
{
    "school": "ABC School",
    "name": "John"
}
```

Since `school` is a model field, it is included in serialization.

## Summary

| Declaration                            | Meaning                                 |
| -------------------------------------- | --------------------------------------- |
| `name: str`                            | Instance attribute (field)              |
| `age: int = 18`                        | Instance attribute with a default value |
| `school: ClassVar[str] = "ABC School"` | Class attribute shared by all instances |

## Key Takeaways

* **Instance attributes** store data for each object.
* **Class attributes** are shared across all objects.
* In both **dataclasses** and **Pydantic**, use `ClassVar` to declare class attributes.
* Attributes declared with `ClassVar`:
  * Are **not** included in the constructor.
  * Are **not** serialized.
  * Are shared by all instances.
* Without `ClassVar`, both dataclasses and Pydantic treat the attribute as an **instance field**.

## Rule of Thumb

* Use **normal type annotations** (`name: str`) for object data.
* Use **`ClassVar`** for constants or values shared across all instances.

## Practice & Exercises

To reinforce what you've learned, practice with these interactive notebooks:

<CardGroup cols={2}>
  <Card title="Follow-Along Practice" icon="laptop-code">
    Practice setting up numeric constraints, string constraints, descriptions, defaults using Field(), and implementing custom validators with @field\_validator.

    [💻 VS Code](vscode://file/Users/sivaprasad/Downloads/python%20material/python4ai/public/notebooks/basics_exercises/Validation_and_Fields_Practice.ipynb) | [🚀 Colab](https://colab.research.google.com/github/prasad230776/python4ai/blob/master/public/notebooks/basics_exercises/Validation_and_Fields_Practice.ipynb) | <a href="/public/notebooks/basics_exercises/Validation_and_Fields_Practice.ipynb" download>📥 Download</a>
  </Card>

  <Card title="Practice Exercises" icon="pen-to-square">
    Test your knowledge with hands-on exercises defining Field value ranges, metadata descriptions, and writing custom validation conditions.

    [💻 VS Code](vscode://file/Users/sivaprasad/Downloads/python%20material/python4ai/public/notebooks/basics_exercises/Validation_and_Fields_Exercises.ipynb) | [🚀 Colab](https://colab.research.google.com/github/prasad230776/python4ai/blob/master/public/notebooks/basics_exercises/Validation_and_Fields_Exercises.ipynb) | <a href="/public/notebooks/basics_exercises/Validation_and_Fields_Exercises.ipynb" download>📥 Download</a>
  </Card>
</CardGroup>

***

## What's next?

You know how to validate individual fields. Next, let's learn how to handle complex data with nested models.

<Card title="Nested Models" icon="arrow-right" href="/pydantic/nested-models">
  Learn how to handle complex, nested data structures.
</Card>
