FastAPI, Peewee, and Building Blocks
With FastAPI and Peewee, it becomes quite simple to implement the tactical patterns required for this project.
1. Entity
Entities represent domain objects that have a unique identity. In the context of Peewee, entities are defined as models that connect to a table in the database.
Example:
from peewee import Model, CharField, SqliteDatabase
# Database connection
db = SqliteDatabase('users.db')
class User(Model):
name = CharField()
email = CharField(unique=True)
class Meta:
database = db # Defines the database associated with the model
2. Repository
The Repository acts as an intermediary between the application and the data persistence layer. It provides methods for manipulating entities.
Example:
class UserRepository:
@staticmethod
def create_user(name: str, email: str) -> User:
user = User.create(name=name, email=email)
return user
@staticmethod
def get_user(user_id: int) -> User:
return User.get_or_none(User.id == user_id)
@staticmethod
def get_all_users() -> list:
return list(User.select())
@staticmethod
def delete_user(user_id: int) -> None:
user = User.get_or_none(User.id == user_id)
if user:
user.delete_instance()
3. Service
The Service encapsulates the application's business logic, orchestrating the interactions between entities and repositories.
Example:
class UserService:
def __init__(self, user_repository: UserRepository):
self.user_repository = user_repository
def create_user(self, name: str, email: str) -> User:
return self.user_repository.create_user(name, email)
def get_user(self, user_id: int) -> User:
return self.user_repository.get_user(user_id)
def get_all_users(self) -> list:
return self.user_repository.get_all_users()
def delete_user(self, user_id: int) -> None:
self.user_repository.delete_user(user_id)
4. Controller
The Controller handles user requests, coordinating the interaction between the user interface and the services. FastAPI makes it easy to create APIs with routes and data handling.
Example:
from fastapi import FastAPI, HTTPException
app = FastAPI()
user_repository = UserRepository()
user_service = UserService(user_repository)
@app.post("/users/")
def create_user(name: str, email: str):
user = user_service.create_user(name, email)
return {"id": user.id, "name": user.name, "email": user.email}
@app.get("/users/{user_id}")
def get_user(user_id: int):
user = user_service.get_user(user_id)
if user:
return {"id": user.id, "name": user.name, "email": user.email}
raise HTTPException(status_code=404, detail="User not found")
@app.get("/users/")
def get_all_users():
users = user_service.get_all_users()
return [{"id": user.id, "name": user.name, "email": user.email} for user in users]
@app.delete("/users/{user_id}")
def delete_user(user_id: int):
user_service.delete_user(user_id)
return {"message": "User deleted successfully"}
5. TDD
We will use the TestClient provided by FastAPI. This approach allows for integrated testing of FastAPI endpoints, validating the application's functionality without the need for mocks, and ensuring that the system's behavior is as expected.
import pytest
from fastapi.testclient import TestClient
from your_module import app, UserRepository, UserService, User # Adjust the import as needed
# TestClient initialization
client = TestClient(app)
# Assuming you have appropriate UserRepository and UserService
@pytest.fixture(scope="module")
def setup_users():
# Cleanup and initial setup of the user repository
user_repository = UserRepository()
user_service = UserService(user_repository)
# Adding users for testing
user_service.create_user("Alice", "alice@example.com")
user_service.create_user("Bob", "bob@example.com")
yield # Allows the tests to run
# Cleanup after tests
user_repository.clear() # Assume you have a method to clear the data
def test_create_user():
response = client.post("/users/", json={"name": "Charlie", "email": "charlie@example.com"})
assert response.status_code == 200
data = response.json()
assert "id" in data
assert data["name"] == "Charlie"
assert data["email"] == "charlie@example.com"
def test_get_user(setup_users):
response = client.get("/users/1") # Assumes the first user's ID is 1
assert response.status_code == 200
data = response.json()
assert data["name"] == "Alice"
assert data["email"] == "alice@example.com"
def test_get_user_not_found():
response = client.get("/users/999") # ID that does not exist
assert response.status_code == 404
assert response.json() == {"detail": "User not found"}
def test_get_all_users(setup_users):
response = client.get("/users/")
assert response.status_code == 200
data = response.json()
assert len(data) == 2 # Expects 2 users created in the setup
assert data[0]["name"] == "Alice"
assert data[1]["name"] == "Bob"
def test_delete_user(setup_users):
response = client.delete("/users/1") # Delete the user with ID 1
assert response.status_code == 200
assert response.json() == {"message": "User deleted successfully"}
# Verify if the user was actually deleted
response = client.get("/users/1")
assert response.status_code == 404