Use cases
Use cases implement and encapsulate all the application business rules.
If the use case wants to access a database (infrastructure layer), then the use case will use a data provider interface. The add_todo_use_case interacts with the infrastructure layer via TodoRepositoryInterface.
import uuid
from pydantic import BaseModel, Field
from features.todo.entities.todo_item import TodoItem
from features.todo.repository.todo_repository_interface import TodoRepositoryInterface
class AddTodoRequest(BaseModel):
    title: str = Field(
        title="The title of the item",
        max_length=300,
        min_length=1,
        json_schema_extra={
            "examples": ["Read about clean architecture"],
        },
    )
class AddTodoResponse(BaseModel):
    id: str = Field(
        json_schema_extra={
            "examples": ["vytxeTZskVKR7C7WgdSP3d"],
        }
    )
    title: str = Field(
        json_schema_extra={
            "examples": ["Read about clean architecture"],
        }
    )
    is_completed: bool = False
    @staticmethod
    def from_entity(todo_item: TodoItem) -> "AddTodoResponse":
        return AddTodoResponse(id=todo_item.id, title=todo_item.title, is_completed=todo_item.is_completed)
def add_todo_use_case(
    data: AddTodoRequest,
    user_id: str,
    todo_repository: TodoRepositoryInterface,
) -> AddTodoResponse:
    todo_item = TodoItem(id=str(uuid.uuid4()), title=data.title, user_id=user_id)
    todo_repository.create(todo_item)
    return AddTodoResponse.from_entity(todo_item)
Required- Each use case needs to have its own read and write model to handle custom requests inputs and outputs for each use case.
 
Optional- A repository interface describing necessary repository methods.
- The use case uses repositories for reading and writing to external systems like databases.
 
 
- A repository interface describing necessary repository methods.
 
info
Changes to use cases should not impact the entities.
The use-case should only know of the repository interface (abstract class) before run-time. The concrete implementation of a repository is injected (dependency injection) into the use-case at run-time.
Testing use cases
Use the todo_repository fixture as input to use_cases.
import pytest
from pydantic import ValidationError
from features.todo.repository.todo_repository_interface import TodoRepositoryInterface
from features.todo.use_cases.add_todo import AddTodoRequest, add_todo_use_case
def test_add_with_valid_title_should_return_todo(todo_repository: TodoRepositoryInterface):
    data = AddTodoRequest(title="new todo")
    result = add_todo_use_case(data, user_id="xyz", todo_repository=todo_repository)
    assert result.title == data.title
def test_add_with_empty_title_should_throw_validation_error(todo_repository: TodoRepositoryInterface):
    with pytest.raises(ValidationError):
        data = AddTodoRequest(title="")
        add_todo_use_case(data, user_id="xyz", todo_repository=todo_repository)