Skip to content

Commit

Permalink
Adding more structure and database support
Browse files Browse the repository at this point in the history
  • Loading branch information
uittenbroekrobbert committed May 15, 2024
1 parent 32663ca commit b4257ba
Show file tree
Hide file tree
Showing 24 changed files with 263 additions and 171 deletions.
2 changes: 2 additions & 0 deletions tad/api/deps.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,7 @@ def get_db() -> Generator[Session, None, None]:
with Session(engine) as session:
yield session

session.get()


SessionDep = Annotated[Session, Depends(get_db)]
4 changes: 3 additions & 1 deletion tad/api/main.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
from fastapi import APIRouter

from tad.api.routes import health, root
from tad.api.routes import health, pages, root, tasks

api_router = APIRouter()
api_router.include_router(root.router)
api_router.include_router(health.router, prefix="/health", tags=["health"])
api_router.include_router(pages.router, prefix="/pages", tags=["pages"])
api_router.include_router(tasks.router, prefix="/tasks", tags=["tasks"])
21 changes: 21 additions & 0 deletions tad/api/routes/pages.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
from fastapi import APIRouter, Request
from fastapi.responses import HTMLResponse
from fastapi.templating import Jinja2Templates

from tad.services.statuses import StatusesService
from tad.services.tasks import TasksService

tasks_service = TasksService()
statuses_service = StatusesService()
router = APIRouter()
templates = Jinja2Templates(directory="tad/site/templates")


@router.get("/", response_class=HTMLResponse)
async def default_layout(request: Request):
context = {
"page_title": "This is the page title",
"tasks_service": tasks_service,
"statuses_service": statuses_service,
}
return templates.TemplateResponse(request=request, name="default_layout.jinja", context=context)
26 changes: 26 additions & 0 deletions tad/api/routes/tasks.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
from fastapi import APIRouter, Request
from fastapi.responses import HTMLResponse
from fastapi.templating import Jinja2Templates

from tad.services.tasks import TasksService

router = APIRouter()

tasks_service = TasksService()
templates = Jinja2Templates(directory="tad/site/templates")


@router.get("/")
async def test():
return [{"username": "Rick"}, {"username": "Morty"}]


@router.post("/move", response_class=HTMLResponse)
async def move_task(request: Request):
json = await request.json()

print(json)
task = tasks_service.move_task(
int(json["taskId"]), int(json["statusId"]), json["previousSiblingId"], json["nextSiblingId"]
)
return templates.TemplateResponse(request=request, name="task.jinja", context={"task": task})
7 changes: 6 additions & 1 deletion tad/core/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@
# Self type is not available in Python 3.10 so create our own with TypeVar
SelfSettings = TypeVar("SelfSettings", bound="Settings")

logger = logging.getLogger(__name__)

print("My name is " + __name__)
logger.info("Hallo ik ben een logOOOOOOOOger")


class Settings(BaseSettings):
# todo(berry): investigate yaml, toml or json file support for SettingsConfigDict
Expand Down Expand Up @@ -42,7 +47,7 @@ def server_host(self) -> str:
PROJECT_NAME: str = "TAD"
PROJECT_DESCRIPTION: str = "Transparency of Algorithmic Decision making"

STATIC_DIR: str = "tad/site/static"
STATIC_DIR: str = "tad/site/static/"
TEMPLATE_DIR: str = "tad/templates"

# todo(berry): create submodel for database settings
Expand Down
16 changes: 16 additions & 0 deletions tad/core/singleton.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
from typing import ClassVar


class Singleton(type):
"""The Singleton metaclass can be used to mark classes as singleton.
Based on https://stackoverflow.com/questions/6760685/what-is-the-best-way-of-implementing-singleton-in-python
Usage: class Classname(metaclass=Singleton):
"""

_instances = ClassVar[{}]

def __call__(cls, *args, **kwargs):
if cls not in cls._instances:
cls._instances[cls] = super().__call__(*args, **kwargs)
return cls._instances[cls]
12 changes: 8 additions & 4 deletions tad/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,11 @@
validation_exception_handler as tad_validation_exception_handler,
)
from tad.core.log import configure_logging
from tad.middleware.route_logging import RequestLoggingMiddleware
from tad.repositories.tasks import TasksRepository
from tad.utils.mask import Mask

from .routers import pages, tasks
from .middleware.route_logging import RequestLoggingMiddleware
from .repositories.statuses import StatusesRepository

configure_logging(settings.LOGGING_LEVEL, settings.LOGGING_CONFIG)

Expand Down Expand Up @@ -56,8 +57,6 @@ async def lifespan(app: FastAPI):

app.add_middleware(RequestLoggingMiddleware)
app.mount("/static", StaticFiles(directory=settings.STATIC_DIR), name="static")
app.include_router(tasks.router)
app.include_router(pages.router)


@app.exception_handler(StarletteHTTPException)
Expand All @@ -71,3 +70,8 @@ async def validation_exception_handler(request: Request, exc: RequestValidationE


app.include_router(api_router)

tasks_repository = TasksRepository()
statuses_repository = StatusesRepository()

logger.info("Hallo ik ben een logger")
3 changes: 2 additions & 1 deletion tad/models/task.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,6 @@ class Task(SQLModel, table=True):
title: str
description: str
sort_order: float
status_id: int
status_id: int | None = Field(default=None, foreign_key="status.id")
user_id: int | None = Field(default=None, foreign_key="user.id")
# todo(robbert) Tasks probably are grouped (and sub-grouped), so we probably need a reference to a group_id
File renamed without changes.
45 changes: 45 additions & 0 deletions tad/repositories/statuses.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import logging
from collections.abc import Sequence

from sqlmodel import Session, select

from tad.core.db import engine
from tad.core.singleton import Singleton
from tad.models import Status

logger = logging.getLogger(__name__)


class StatusesRepository(metaclass=Singleton):
# TODO find out how to reuse Session

def __init__(self):
logger.info("Hello world from statuses repo")
statuses = self.find_all()
if len(statuses) == 0:
self.__add_test_statuses()

def __add_test_statuses(self):
with Session(engine) as session:
session.add(Status(id=1, name="todo", sort_order=1))
session.add(Status(id=2, name="in_progress", sort_order=2))
session.add(Status(id=3, name="review", sort_order=3))
session.add(Status(id=4, name="done", sort_order=4))
session.commit()

def find_all(self) -> Sequence[Status]:
with Session(engine) as session:
statement = select(Status)
return session.exec(statement).all()

def save(self, status) -> Status:
with Session(engine) as session:
session.add(status)
session.commit()
session.refresh(status)
return status

def find_by_id(self, status_id) -> Status:
with Session(engine) as session:
statement = select(Status).where(Status.id == status_id)
return session.exec(statement).one()
51 changes: 51 additions & 0 deletions tad/repositories/tasks.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
from collections.abc import Sequence

from sqlmodel import Session, select

from tad.core.db import engine
from tad.core.singleton import Singleton
from tad.models import Task


class TasksRepository(metaclass=Singleton):
def __init__(self):
tasks = self.find_all()
if len(tasks) == 0:
self.__add_test_tasks()

def __add_test_tasks(self):
with Session(engine) as session:
session.add(
Task(
status_id=1,
title="IAMA",
description="Impact Assessment Mensenrechten en Algoritmes",
sort_order=10,
)
)
session.add(Task(status_id=1, title="SHAP", description="SHAP", sort_order=20))
session.add(Task(status_id=1, title="This is title 3", description="This is description 3", sort_order=30))
session.commit()

def find_all(self) -> Sequence[Task]:
"""Returns all the tasks from the repository."""
with Session(engine) as session:
statement = select(Task)
return session.exec(statement).all()

def find_by_status_id(self, status_id) -> Sequence[Task]:
with Session(engine) as session:
statement = select(Task).where(Task.status_id == status_id).order_by(Task.sort_order)
return session.exec(statement).all()

def save(self, task) -> Task:
with Session(engine) as session:
session.add(task)
session.commit()
session.refresh(task)
return task

def find_by_id(self, task_id) -> Task:
with Session(engine) as session:
statement = select(Task).where(Task.id == task_id)
return session.exec(statement).one()
20 changes: 0 additions & 20 deletions tad/routers/pages.py

This file was deleted.

43 changes: 0 additions & 43 deletions tad/routers/tasks.py

This file was deleted.

20 changes: 20 additions & 0 deletions tad/services/statuses.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import logging

from tad.core.singleton import Singleton
from tad.repositories.statuses import StatusesRepository

logger = logging.getLogger(__name__)


class StatusesService(metaclass=Singleton):
__statuses_repository = StatusesRepository()

def __init__(self):
# TODO find out why logging is not visible
print("I am created, hello world")

def get_status(self, status_id):
return self.__statuses_repository.find_by_id(status_id)

def get_statuses(self) -> []:
return self.__statuses_repository.find_all()
58 changes: 58 additions & 0 deletions tad/services/tasks.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import logging

from tad.core.singleton import Singleton
from tad.models.task import Task
from tad.models.user import User
from tad.repositories.tasks import TasksRepository
from tad.services.statuses import StatusesService

logger = logging.getLogger(__name__)


class TasksService(metaclass=Singleton):
__tasks_repository = TasksRepository()
__statuses_service = StatusesService()

def __init__(self):
pass

def get_tasks(self, status_id):
return self.__tasks_repository.find_by_status_id(status_id)

def assign_task(self, task: Task, user: User):
task.user_id = user.id
self.__tasks_repository.save(task)

def move_task(self, task_id, status_id, previous_sibling_id, next_sibling_id) -> Task:
status = self.__statuses_service.get_status(status_id)
task = self.__tasks_repository.find_by_id(task_id)

if status.name == "done":
# TODO implement logic for done
logging.warning("Task is done, we need to update a system card")

# assign the task to the current user
if status.name == "in_progress":
task.user_id = 1

# update the status for the task (this may not be needed if the status has not changed)
task.status_id = status_id

# update order position of the card
if not previous_sibling_id and not next_sibling_id:
task.sort_order = 10
elif previous_sibling_id and next_sibling_id:
previous_task = self.__tasks_repository.find_by_id(int(previous_sibling_id))
next_task = self.__tasks_repository.find_by_id(int(next_sibling_id))
new_sort_order = previous_task.sort_order + ((next_task.sort_order - previous_task.sort_order) / 2)
task.sort_order = new_sort_order
elif previous_sibling_id and not next_sibling_id:
previous_task = self.__tasks_repository.find_by_id(int(previous_sibling_id))
task.sort_order = previous_task.sort_order + 10
elif not previous_sibling_id and next_sibling_id:
next_task = self.__tasks_repository.find_by_id(int(next_sibling_id))
task.sort_order = next_task.sort_order / 2

task = self.__tasks_repository.save(task)

return task
Loading

0 comments on commit b4257ba

Please sign in to comment.