Use Settings in Your App
Replace hardcoded values with settings from your configuration class
Writing code and entering commands is only available on desktop. Open this page on a larger screen to complete this chapter.
From hardcoded to configurable
You created a Settings class with data_file and app_title fields. Now you need to use those values instead of the hardcoded ones. Two lines in your code need to change:
DATA_FILE = Path("expenses.json")should read fromsettings.data_fileapp = FastAPI()should usesettings.app_titleas the title
After this change, you can control both values through environment variables or a .env file without touching the code.
The app title in Swagger
The title parameter in FastAPI() sets the heading that appears at the top of the Swagger documentation page at /docs. Right now it shows the default "FastAPI". Setting it to a descriptive name like "Expense Tracker API" makes the documentation easier to identify.
Instructions
Replace the hardcoded values with your settings.
- Change
DATA_FILE = Path("expenses.json")toDATA_FILE = Path(settings.data_file). - Change
app = FastAPI()toapp = FastAPI(title=settings.app_title).
import json
import os
import time
from datetime import datetime
from pathlib import Path
from fastapi import FastAPI, HTTPException
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel, Field, field_validator
from pydantic_settings import BaseSettings, SettingsConfigDict
from typing import Literal, Optional
# Step 2: Change FastAPI() to use settings.app_title
app = FastAPI()
app.add_middleware(CORSMiddleware, allow_origins=["*"], allow_methods=["*"], allow_headers=["*"])
class Expense(BaseModel):
description: str = Field(min_length=1)
amount: float = Field(gt=0)
category: Literal["food", "transport", "entertainment", "utilities", "other"]
date: Optional[str] = None
@field_validator("date")
@classmethod
def validate_date_format(cls, v):
if v is not None:
try:
datetime.strptime(v, "%Y-%m-%d")
except ValueError:
raise ValueError("Date must be in YYYY-MM-DD format")
return v
class ExpenseUpdate(BaseModel):
description: Optional[str] = Field(default=None, min_length=1)
amount: Optional[float] = Field(default=None, gt=0)
category: Optional[Literal["food", "transport", "entertainment", "utilities", "other"]] = None
class ExpenseOut(BaseModel):
id: int
description: str
amount: float
category: str
date: Optional[str] = None
class ErrorResponse(BaseModel):
detail: str
class Settings(BaseSettings):
data_file: str = "expenses.json"
app_title: str = "FastAPI"
model_config = SettingsConfigDict(env_file=".env")
settings = Settings()
# Step 1: Change DATA_FILE to use settings.data_file
DATA_FILE = Path("expenses.json")
def save_expenses():
temp_path = str(DATA_FILE) + ".tmp"
with open(temp_path, "w") as f:
json.dump({"counter": counter, "expenses": expenses}, f, indent=2)
os.replace(temp_path, str(DATA_FILE))
def load_expenses():
global expenses, counter
if DATA_FILE.exists():
data = json.loads(DATA_FILE.read_text())
expenses = {int(k): v for k, v in data["expenses"].items()}
counter = data["counter"]
expenses = {}
counter = 0
load_expenses()
@app.middleware("http")
async def log_requests(request, call_next):
start = time.time()
response = await call_next(request)
duration = (time.time() - start) * 1000
print(f"{request.method} {request.url.path} {response.status_code} {duration:.1f}ms")
return response
@app.post("/expenses", status_code=201, response_model=ExpenseOut)
def create_expense(expense: Expense):
global counter
counter += 1
expenses[counter] = {"id": counter, **expense.model_dump()}
save_expenses()
return expenses[counter]
@app.get("/expenses", response_model=list[ExpenseOut])
def list_expenses(category: str = None):
if category:
return [e for e in expenses.values() if e["category"] == category]
return list(expenses.values())
@app.get("/expenses/{expense_id}", response_model=ExpenseOut, responses={404: {"model": ErrorResponse}})
def get_expense(expense_id: int):
if expense_id not in expenses:
raise HTTPException(status_code=404, detail="Expense not found")
return expenses[expense_id]
@app.delete("/expenses/{expense_id}", response_model=ExpenseOut, responses={404: {"model": ErrorResponse}})
def delete_expense(expense_id: int):
if expense_id not in expenses:
raise HTTPException(status_code=404, detail="Expense not found")
save_expenses()
return expenses.pop(expense_id)
@app.patch("/expenses/{expense_id}", response_model=ExpenseOut, responses={404: {"model": ErrorResponse}})
def update_expense(expense_id: int, updates: ExpenseUpdate):
if expense_id not in expenses:
raise HTTPException(status_code=404, detail="Expense not found")
expenses[expense_id].update(updates.model_dump(exclude_unset=True))
save_expenses()
return expenses[expense_id]
@app.get("/summary")
def spending_summary():
summary = {}
for expense in expenses.values():
cat = expense["category"]
summary[cat] = summary.get(cat, 0) + expense["amount"]
return summary
Interactive Code Editor
Sign in to write and run code, track your progress, and unlock all chapters.
Sign In to Start Coding