feat: add FastAPI, Entity base class, and health check endpoint
This commit is contained in:
@@ -1,27 +1,18 @@
|
||||
# Plan: UserId Value Object
|
||||
# Plan: Add FastAPI to the Project
|
||||
|
||||
## Status: COMPLETED
|
||||
## Goal
|
||||
Add FastAPI as the web framework with a minimal health check endpoint.
|
||||
|
||||
## Objective
|
||||
Create a UserId value object in the marketplace domain with an immutable UUID value.
|
||||
## Changes
|
||||
|
||||
## Decisions Made
|
||||
- **Location**: `domain/marketplace/user_id.py`
|
||||
- **Class style**: `@dataclass(frozen=True)` for immutability
|
||||
- **Tests**: Yes
|
||||
### 1. Dependencies
|
||||
- Add `fastapi` and `uvicorn[standard]` to `pyproject.toml` under `[project.dependencies]`
|
||||
- Install dependencies into the virtual environment
|
||||
|
||||
## Value Object Structure
|
||||
```python
|
||||
@dataclass(frozen=True)
|
||||
class UserId:
|
||||
value: UUID
|
||||
```
|
||||
### 2. Application Entry Point
|
||||
- Create `main.py` with a FastAPI app instance
|
||||
- Add a health check endpoint (`GET /`) that returns `{"status": "ok"}`
|
||||
|
||||
## Files Created
|
||||
- `domain/marketplace/user_id.py`
|
||||
- `tests/test_user_id.py`
|
||||
|
||||
## Test Results
|
||||
- `test_user_id_creation` - PASSED
|
||||
- `test_user_id_is_immutable` - PASSED
|
||||
- `test_user_id_equality` - PASSED
|
||||
## Verification
|
||||
- `pytest tests/` — all tests pass
|
||||
- `uvicorn main:app --reload` — server starts and health endpoint responds
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -1,4 +1,4 @@
|
||||
env/
|
||||
.venv/
|
||||
__pycache__/
|
||||
*.pyc
|
||||
.pytest_cache/
|
||||
|
||||
0
domain/framework/__init__.py
Normal file
0
domain/framework/__init__.py
Normal file
18
domain/framework/entity.py
Normal file
18
domain/framework/entity.py
Normal file
@@ -0,0 +1,18 @@
|
||||
from abc import ABC
|
||||
from dataclasses import replace
|
||||
from typing import Self
|
||||
|
||||
from domain.marketplace.domain_event import DomainEvent
|
||||
|
||||
|
||||
class Entity(ABC):
|
||||
_events: tuple[DomainEvent, ...] = ()
|
||||
|
||||
def raise_event(self, event: DomainEvent) -> Self:
|
||||
return replace(self, _events=(*self._events, event))
|
||||
|
||||
def get_changes(self) -> tuple[DomainEvent, ...]:
|
||||
return self._events
|
||||
|
||||
def clear_changes(self) -> Self:
|
||||
return replace(self, _events=())
|
||||
8
main.py
Normal file
8
main.py
Normal file
@@ -0,0 +1,8 @@
|
||||
from fastapi import FastAPI
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
|
||||
@app.get("/")
|
||||
def health_check():
|
||||
return {"status": "ok"}
|
||||
@@ -6,6 +6,10 @@ build-backend = "setuptools.backends._legacy:_Backend"
|
||||
name = "python-fun"
|
||||
version = "0.1.0"
|
||||
requires-python = ">=3.12"
|
||||
dependencies = [
|
||||
"fastapi>=0.115.0",
|
||||
"uvicorn[standard]>=0.30.0",
|
||||
]
|
||||
|
||||
[tool.pytest.ini_options]
|
||||
pythonpath = ["."]
|
||||
79
tests/test_entity.py
Normal file
79
tests/test_entity.py
Normal file
@@ -0,0 +1,79 @@
|
||||
from dataclasses import dataclass
|
||||
from uuid import uuid4
|
||||
|
||||
from domain.framework.entity import Entity
|
||||
from domain.marketplace.domain_event import (
|
||||
ClassifiedAdCreated,
|
||||
ClassifiedAdSentForReview,
|
||||
DomainEvent,
|
||||
)
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class TestEntity(Entity):
|
||||
__test__ = False
|
||||
_events: tuple[DomainEvent, ...] = ()
|
||||
|
||||
|
||||
def test_entity_creation():
|
||||
entity = TestEntity()
|
||||
assert entity.get_changes() == ()
|
||||
|
||||
|
||||
def test_raise_event_returns_new_instance_with_event():
|
||||
entity = TestEntity()
|
||||
event = ClassifiedAdCreated(id=uuid4(), owner_id=uuid4())
|
||||
|
||||
new_entity = entity.raise_event(event)
|
||||
|
||||
assert new_entity.get_changes() == (event,)
|
||||
|
||||
|
||||
def test_raise_event_does_not_mutate_original():
|
||||
entity = TestEntity()
|
||||
event = ClassifiedAdCreated(id=uuid4(), owner_id=uuid4())
|
||||
|
||||
entity.raise_event(event)
|
||||
|
||||
assert entity.get_changes() == ()
|
||||
|
||||
|
||||
def test_multiple_events_accumulate():
|
||||
entity = TestEntity()
|
||||
event1 = ClassifiedAdCreated(id=uuid4(), owner_id=uuid4())
|
||||
event2 = ClassifiedAdSentForReview(id=uuid4())
|
||||
|
||||
entity = entity.raise_event(event1)
|
||||
entity = entity.raise_event(event2)
|
||||
|
||||
assert entity.get_changes() == (event1, event2)
|
||||
|
||||
|
||||
def test_clear_changes_returns_instance_with_empty_events():
|
||||
entity = TestEntity()
|
||||
event = ClassifiedAdCreated(id=uuid4(), owner_id=uuid4())
|
||||
entity = entity.raise_event(event)
|
||||
|
||||
cleared = entity.clear_changes()
|
||||
|
||||
assert cleared.get_changes() == ()
|
||||
|
||||
|
||||
def test_clear_changes_does_not_mutate_original():
|
||||
entity = TestEntity()
|
||||
event = ClassifiedAdCreated(id=uuid4(), owner_id=uuid4())
|
||||
entity = entity.raise_event(event)
|
||||
|
||||
entity.clear_changes()
|
||||
|
||||
assert entity.get_changes() == (event,)
|
||||
|
||||
|
||||
def test_get_changes_returns_tuple():
|
||||
entity = TestEntity()
|
||||
event = ClassifiedAdCreated(id=uuid4(), owner_id=uuid4())
|
||||
entity = entity.raise_event(event)
|
||||
|
||||
changes = entity.get_changes()
|
||||
|
||||
assert isinstance(changes, tuple)
|
||||
Reference in New Issue
Block a user