Skip to content

Commit ebe1817

Browse files
committed
Merge branch '2025-dataclasses'
2 parents 3f96217 + 5ffb60d commit ebe1817

File tree

8 files changed

+589
-0
lines changed

8 files changed

+589
-0
lines changed

2025/dataclasses/books.db

12 KB
Binary file not shown.
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import secrets
2+
3+
import uvicorn
4+
from fastapi import FastAPI, HTTPException
5+
from pydantic import BaseModel
6+
7+
app = FastAPI()
8+
9+
10+
class Book(BaseModel):
11+
title: str
12+
author: str
13+
pages: int = 0
14+
15+
16+
books_db: dict[str, Book] = {}
17+
18+
19+
def generate_id() -> str:
20+
# Generate a 24-character hex string (like MongoDB ObjectID)
21+
return secrets.token_hex(12)
22+
23+
24+
@app.post("/books/")
25+
def create_book(book: Book):
26+
book_id = generate_id()
27+
books_db[book_id] = book
28+
return {"id": book_id, "book": book}
29+
30+
31+
@app.get("/books/{book_id}")
32+
def get_book(book_id: str):
33+
if book_id not in books_db:
34+
raise HTTPException(status_code=404, detail="Book not found")
35+
return {"id": book_id, "book": books_db[book_id]}
36+
37+
38+
@app.get("/books/")
39+
def list_books():
40+
return [{"id": book_id, "book": book} for book_id, book in books_db.items()]
41+
42+
43+
if __name__ == "__main__":
44+
uvicorn.run(app, host="0.0.0.0", port=8000)
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import secrets
2+
from dataclasses import dataclass, field
3+
4+
import uvicorn
5+
from fastapi import FastAPI, HTTPException
6+
from pydantic import BaseModel
7+
8+
app = FastAPI()
9+
10+
# --- DOMAIN MODEL ---
11+
12+
13+
def generate_id() -> str:
14+
return secrets.token_hex(12)
15+
16+
17+
@dataclass
18+
class Book:
19+
title: str
20+
author: str
21+
pages: int = 0
22+
id: str = field(default_factory=generate_id)
23+
24+
25+
# --- Pydantic MODELS for API ---
26+
27+
28+
class BookCreate(BaseModel):
29+
title: str
30+
author: str
31+
pages: int = 0
32+
33+
34+
class BookResponse(BaseModel):
35+
id: str
36+
title: str
37+
author: str
38+
pages: int
39+
40+
41+
# --- FAKE DB ---
42+
43+
books_db: dict[str, Book] = {}
44+
45+
46+
# --- ROUTES ---
47+
48+
49+
@app.post("/books/", response_model=BookResponse)
50+
def create_book(book_data: BookCreate):
51+
book = Book(**book_data.dict()) # ID is generated automatically
52+
books_db[book.id] = book
53+
return BookResponse(**book.__dict__)
54+
55+
56+
@app.get("/books/{book_id}", response_model=BookResponse)
57+
def get_book(book_id: str):
58+
book = books_db.get(book_id)
59+
if not book:
60+
raise HTTPException(status_code=404, detail="Book not found")
61+
return BookResponse(**book.__dict__)
62+
63+
64+
@app.get("/books/", response_model=list[BookResponse])
65+
def list_books():
66+
return [BookResponse(**book.__dict__) for book in books_db.values()]
67+
68+
69+
if __name__ == "__main__":
70+
uvicorn.run(app, host="0.0.0.0", port=8000)
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import secrets
2+
3+
import uvicorn
4+
from fastapi import FastAPI, HTTPException
5+
from pydantic import BaseModel, dataclasses
6+
7+
app = FastAPI()
8+
9+
# --- DOMAIN MODEL (Pydantic Dataclass) ---
10+
11+
12+
def generate_id() -> str:
13+
return secrets.token_hex(12)
14+
15+
16+
@dataclasses.dataclass
17+
class Book:
18+
title: str
19+
author: str
20+
pages: int = 0
21+
id: str = dataclasses.Field(default_factory=generate_id)
22+
23+
24+
# --- API MODELS (Pydantic BaseModels) ---
25+
26+
27+
class BookCreate(BaseModel):
28+
title: str
29+
author: str
30+
pages: int = 0
31+
32+
class Config:
33+
from_attributes = True
34+
35+
36+
class BookResponse(BaseModel):
37+
id: str
38+
title: str
39+
author: str
40+
pages: int
41+
42+
class Config:
43+
from_attributes = True
44+
45+
46+
# --- FAKE IN-MEMORY DB ---
47+
48+
books_db: dict[str, Book] = {}
49+
50+
# --- ROUTES ---
51+
52+
53+
@app.post("/books/", response_model=BookResponse)
54+
def create_book(book_data: BookCreate):
55+
book = Book(**book_data.model_dump())
56+
books_db[book.id] = book
57+
return BookResponse.model_validate(book)
58+
59+
60+
@app.get("/books/{book_id}", response_model=BookResponse)
61+
def get_book(book_id: str):
62+
book = books_db.get(book_id)
63+
if not book:
64+
raise HTTPException(status_code=404, detail="Book not found")
65+
return BookResponse.model_validate(book)
66+
67+
68+
@app.get("/books/", response_model=list[BookResponse])
69+
def list_books():
70+
return [BookResponse.model_validate(book) for book in books_db.values()]
71+
72+
73+
if __name__ == "__main__":
74+
uvicorn.run(app, host="0.0.0.0", port=8000)
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
import secrets
2+
3+
import uvicorn
4+
from fastapi import Depends, FastAPI, HTTPException
5+
from pydantic import BaseModel
6+
from sqlalchemy import Integer, String, create_engine
7+
from sqlalchemy.orm import (
8+
DeclarativeBase,
9+
Mapped,
10+
Session,
11+
mapped_column,
12+
sessionmaker,
13+
)
14+
15+
# --- Database setup ---
16+
17+
DATABASE_URL = "sqlite:///./books.db"
18+
engine = create_engine(DATABASE_URL, connect_args={"check_same_thread": False})
19+
20+
SessionLocal = sessionmaker(bind=engine, autoflush=False)
21+
22+
23+
# declarative base class
24+
class Base(DeclarativeBase):
25+
pass
26+
27+
28+
# --- SQLAlchemy ORM model ---
29+
30+
31+
class Book(Base):
32+
__tablename__ = "books"
33+
34+
id: Mapped[str] = mapped_column(
35+
primary_key=True, default=lambda: secrets.token_hex(12)
36+
)
37+
title: Mapped[str] = mapped_column(String, nullable=False)
38+
author: Mapped[str] = mapped_column(String, nullable=False)
39+
pages: Mapped[int] = mapped_column(Integer, default=0)
40+
41+
42+
# --- Pydantic models ---
43+
44+
45+
class BookCreate(BaseModel):
46+
title: str
47+
author: str
48+
pages: int = 0
49+
50+
51+
class BookResponse(BaseModel):
52+
id: str
53+
title: str
54+
author: str
55+
pages: int
56+
57+
class Config:
58+
from_attributes = True # allows conversion from ORM objects
59+
60+
61+
# --- Dependency to get a DB session ---
62+
63+
64+
def get_db():
65+
db = SessionLocal()
66+
try:
67+
yield db
68+
finally:
69+
db.close()
70+
71+
72+
# --- FastAPI setup ---
73+
74+
app = FastAPI()
75+
76+
# Create tables
77+
Base.metadata.create_all(bind=engine)
78+
79+
# --- Routes ---
80+
81+
82+
@app.post("/books/", response_model=BookResponse)
83+
def create_book(book_data: BookCreate, db: Session = Depends(get_db)):
84+
book = Book(**book_data.model_dump())
85+
db.add(book)
86+
db.commit()
87+
db.refresh(book)
88+
return book
89+
90+
91+
@app.get("/books/{book_id}", response_model=BookResponse)
92+
def get_book(book_id: str, db: Session = Depends(get_db)):
93+
book = db.get(Book, book_id)
94+
if not book:
95+
raise HTTPException(status_code=404, detail="Book not found")
96+
return book
97+
98+
99+
@app.get("/books/", response_model=list[BookResponse])
100+
def list_books(db: Session = Depends(get_db)):
101+
return db.query(Book).all()
102+
103+
104+
if __name__ == "__main__":
105+
uvicorn.run(app, host="0.0.0.0", port=8000)

2025/dataclasses/pydantic_dc.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
from pydantic import BaseModel, ValidationError, validator
2+
from pydantic.dataclasses import dataclass
3+
4+
5+
@dataclass
6+
class Book:
7+
title: str
8+
pages: int
9+
10+
@validator("pages")
11+
def pages_must_be_positive(cls, v: int):
12+
if v < 0:
13+
raise ValueError("Pages must be a positive integer")
14+
return v
15+
16+
17+
class Author(BaseModel):
18+
name: str
19+
age: int
20+
21+
@validator("age")
22+
def age_must_be_positive(cls, v: int):
23+
if v < 0:
24+
raise ValueError("Age must be a positive integer")
25+
return v
26+
27+
28+
def main() -> None:
29+
# Valid input
30+
book = Book(title="1984", pages=328) # pages will be converted to int
31+
32+
# print(book.model_dump()) # {'title': '1984', 'pages': 328}
33+
34+
# Invalid input – will raise a ValidationError
35+
try:
36+
bad_book = Book(title="The Hobbit", pages="three hundred")
37+
except ValidationError as e:
38+
print(e)
39+
40+
author = Author(name="J.R.R. Tolkien", age=81)
41+
print(author.model_dump()) # {'name': 'J.R.R. Tolkien', 'age': 81}
42+
43+
44+
if __name__ == "__main__":
45+
main()

2025/dataclasses/pyproject.toml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
[project]
2+
name = "dataclasses_vs_pydantic"
3+
version = "0.1.0"
4+
requires-python = ">=3.13"
5+
dependencies = [
6+
"fastapi>=0.115.12",
7+
"pydantic>=2.11.4",
8+
"sqlalchemy>=2.0.41",
9+
"uvicorn>=0.34.2",
10+
]

0 commit comments

Comments
 (0)