Skip to content

Add Pydantic Data Validation Examples and Fastapi example #17

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
3.11
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# Pydantic Data Validation Examples

This project demonstrates the use of Pydantic for data validation with nested models and custom validators.

## Features

- Nested model validation (User with multiple Addresses)
- Email validation using EmailStr
- Custom validators (e.g., name length validation)
- Error handling with ValidationError

## Setup

1. Install the required packages:
```bash
pip install pydantic email-validator
```

## Usage

The project includes examples of:
- Creating nested Pydantic models
- Validating data with custom rules
- Handling validation errors
- Using model_dump() instead of the deprecated dict() method

### Example Code

```python
from pydantic import BaseModel, EmailStr, validator, ValidationError
from typing import List

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

class UserWithAddress(BaseModel):
id: int
name: str
email: EmailStr
addresses: List[Address]

@validator("name")
def name_must_be_at_least_two_chars(cls, v):
if len(v) < 2:
raise ValueError("Name must be at least 2 characters long")
return v
```

## Important Notes

- Use `model_dump()` instead of the deprecated `dict()` method
- Import `ValidationError` from pydantic for error handling
- The `email-validator` package is required for email validation
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<!-- index.html -->
<!DOCTYPE html>
<html>
<head>
<title>Test CORS</title>
</head>
<body>
<h1>Test CORS with FastAPI</h1>
<button onclick="fetchData()">Fetch Data</button>
<pre id="result"></pre>

<script>
async function fetchData() {
const response = await fetch("http://localhost:8000/", {
method: "GET",
credentials: "include"
});
const data = await response.json();
document.getElementById("result").textContent = JSON.stringify(data, null, 2);
}
</script>
</body>
</html>
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
from fastapi import FastAPI, HTTPException, Depends
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel, EmailStr, Field
from typing import List, Optional
from datetime import datetime,UTC
from uuid import uuid4

# Initialize the FastAPI app
app = FastAPI(
title="DACA Chatbot API",
description="A FastAPI-based API for a chatbot in the DACA tutorial series",
version="0.1.0",
)

# Add CORS middleware
app.add_middleware(
CORSMiddleware,
allow_origins=["http://localhost:3000"], # Allow frontend origin (e.g., React app)
allow_credentials=True,
allow_methods=["*"], # Allow all HTTP methods
allow_headers=["*"], # Allow all headers
)

# Complex Pydantic models
class Metadata(BaseModel):
timestamp: datetime = Field(default_factory=lambda: datetime.now(UTC))
session_id: str = Field(default_factory=lambda: str(uuid4()))

class Message(BaseModel):
user_id: str
text: str
metadata: Metadata
tags: Optional[List[str]] = None # Optional list of tags

class Response(BaseModel):
user_id: str
reply: str
metadata: Metadata

# Simulate a database dependency
async def get_db():
return {"connection": "Mock DB Connection"}

# Root endpoint
@app.get("/")
async def root():
return {"message": "Welcome to the DACA Chatbot API! Access /docs for the API documentation."}

# GET endpoint with query parameters
@app.get("/users/{user_id}")
async def get_user(user_id: str, role: str | None = None):
user_info = {"user_id": user_id, "role": role if role else "guest"}
return user_info

# POST endpoint for chatting
@app.post("/chat/", response_model=Response)
async def chat(message: Message, db: dict = Depends(get_db)):
if not message.text.strip():
raise HTTPException(status_code=400, detail="Message text cannot be empty")
print(f"DB Connection: {db['connection']}")
reply_text = f"Hello, {message.user_id}! You said: '{message.text}'. How can I assist you today?"
return Response(
user_id=message.user_id,
reply=reply_text,
metadata=Metadata() # Auto-generate timestamp and session_id
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
from pydantic import BaseModel, EmailStr, validator, ValidationError
from typing import List

# Define a nested model
class Address(BaseModel):
street: str
city: str
zip_code: str

class UserWithAddress(BaseModel):
id: int
name: str
email: EmailStr # Built-in validator for email format
addresses: List[Address] # List of nested Address models

@validator("name")
def name_must_be_at_least_two_chars(cls, v):
if len(v) < 2:
raise ValueError("Name must be at least 2 characters long")
return v
''''
# Valid data with nested structure
user_data = {
"id": 2,
"name": "Bob",
"email": "[email protected]",
"addresses": [
{"street": "123 Main St", "city": "New York", "zip_code": "10001"},
{"street": "456 Oak Ave", "city": "Los Angeles", "zip_code": "90001"},
],
}
user = UserWithAddress(**user_data)
print(user.model_dump())
'''
# Test with invalid data
try:
invalid_user = UserWithAddress(
id=3,
name="A", # Too short
email="[email protected]",
addresses=[{"street": "789 Pine Rd", "city": "Chicago", "zip_code": "60601"}],
)
except ValidationError as e:
print(e)
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
[project]
name = "fastapi-daca-tutorial"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.11"
dependencies = [
"email-validator>=2.2.0",
"fastapi>=0.115.12",
"httpx>=0.28.1",
"pytest>=8.3.5",
"pytest-asyncio>=0.26.0",
"uvicorn>=0.34.0",
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
"""
Test package for the DACA Chatbot API.
"""
Binary file not shown.
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import pytest
from fastapi.testclient import TestClient
from main import app

# Create a test client
client = TestClient(app)

# Test the root endpoint
def test_root():
response = client.get("/")
assert response.status_code == 200
assert response.json() == {
"message": "Welcome to the DACA Chatbot API! Access /docs for the API documentation."
}

# Test the /users/{user_id} endpoint
def test_get_user():
response = client.get("/users/alice?role=admin")
assert response.status_code == 200
assert response.json() == {"user_id": "alice", "role": "admin"}

response = client.get("/users/bob")
assert response.status_code == 200
assert response.json() == {"user_id": "bob", "role": "guest"}

# Test the /chat/ endpoint (async test)
@pytest.mark.asyncio
async def test_chat():
# Valid request
request_data = {
"user_id": "alice",
"text": "Hello, how are you?",
"metadata": {
"timestamp": "2025-04-06T12:00:00Z",
"session_id": "123e4567-e89b-12d3-a456-426614174000"
},
"tags": ["greeting"]
}
response = client.post("/chat/", json=request_data)
assert response.status_code == 200
assert response.json()["user_id"] == "alice"
assert response.json()["reply"] == "Hello, alice! You said: 'Hello, how are you?'. How can I assist you today?"
assert "metadata" in response.json()

# Invalid request (empty text)
invalid_data = {
"user_id": "bob",
"text": "",
"metadata": {
"timestamp": "2025-04-06T12:00:00Z",
"session_id": "123e4567-e89b-12d3-a456-426614174001"
}
}
response = client.post("/chat/", json=invalid_data)
assert response.status_code == 400
assert response.json() == {"detail": "Message text cannot be empty"}
Loading