Skip to content

Commit

Permalink
Added API objects, endpoints and started work on persistence layer
Browse files Browse the repository at this point in the history
  • Loading branch information
max-pfeiffer committed Jan 23, 2024
1 parent d057b20 commit dd43380
Show file tree
Hide file tree
Showing 17 changed files with 846 additions and 25 deletions.
3 changes: 3 additions & 0 deletions backend/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Irrigation Pi Backend

A Python backend application using FastAPI.
4 changes: 2 additions & 2 deletions backend/app/api/v1/api.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"""API routers for API version 1.0."""
from fastapi import APIRouter

from app.api.v1.endpoints import schedules
from app.api.v1.endpoints import schedule

api_router = APIRouter()
api_router.include_router(schedules.router, prefix="/schedules", tags=["schedules"])
api_router.include_router(schedule.router, prefix="/schedule", tags=["schedule"])
43 changes: 43 additions & 0 deletions backend/app/api/v1/endpoints/schedule.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
"""API endpoints for Schedule objects."""
from fastapi import APIRouter

from app.api.v1.models import ScheduleCreate, ScheduleResponse, ScheduleUpdate
from app.services.schedule import (
service_create_schedule,
service_delete_schedule,
service_get_schedule,
service_get_schedules,
service_update_schedule,
)

router = APIRouter()


@router.get("/{primary_key}")
def get_schedule(primary_key: int) -> ScheduleResponse:
"""Get Schedule."""
return service_get_schedule(primary_key)


@router.get("/")
def get_schedules() -> list[ScheduleResponse]:
"""Get Schedules."""
return service_get_schedules()


@router.post("/")
def create_schedule(schedule_data: ScheduleCreate) -> int:
"""Create Schedule."""
return service_create_schedule(schedule_data)


@router.put("/")
def update_schedule(schedule_data: ScheduleUpdate):
"""Update Schedule."""
return service_update_schedule(schedule_data)


@router.delete("/{primary_key}")
def delete_schedule(primary_key: int):
"""Delete Schedule."""
return service_delete_schedule(primary_key)
10 changes: 0 additions & 10 deletions backend/app/api/v1/endpoints/schedules.py

This file was deleted.

43 changes: 43 additions & 0 deletions backend/app/api/v1/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
"""API models."""
from datetime import time
from typing import Union

from pydantic import BaseModel, Field, PositiveInt

from app.scheduling import Repeat


class ScheduleResponse(BaseModel):
"""Response schema for schedule."""

id: PositiveInt = Field(description="Primary key")
start_time: time = Field(description="Start time of the schedule")
duration: PositiveInt = Field(description="Duration in minutes")
repeat: Repeat = Field(description="Specifies how the schedule is repeated")
active: bool = Field(description="Whether the schedule is active")


class ScheduleCreate(BaseModel):
"""Creation schema for schedule."""

start_time: time = Field(description="Start time of the schedule")
duration: PositiveInt = Field(description="Duration in minutes")
repeat: Repeat = Field(description="Specifies how the schedule is repeated")
active: bool = Field(default=True, description="Whether the schedule is active")


class ScheduleUpdate(BaseModel):
"""Update schema for schedule."""

start_time: Union[time, None] = Field(
default=None, description="Start time of the schedule"
)
duration: Union[PositiveInt, None] = Field(
default=None, description="Duration in minutes"
)
repeat: Union[Repeat, None] = Field(
default=None, description="Specifies how the schedule is repeated"
)
active: Union[bool, None] = Field(
default=None, description="Whether the schedule is active"
)
20 changes: 20 additions & 0 deletions backend/app/config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
"""Application configuration."""
from pydantic import computed_field
from pydantic_settings import BaseSettings


class ApplicationSettings(BaseSettings):
"""Application settings."""

database_name: str = "sqlite"

@computed_field
def database_uri(self) -> str:
"""URI for database connection.
:return:
"""
return f"sqlite:///{self.database_name}.db"


application_settings = ApplicationSettings()
1 change: 1 addition & 0 deletions backend/app/database/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""Database module."""
7 changes: 7 additions & 0 deletions backend/app/database/config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
"""Database configuration."""
from sqlalchemy import Engine
from sqlmodel import create_engine

from app.config import application_settings

database_engine: Engine = create_engine(application_settings.database_uri)
22 changes: 22 additions & 0 deletions backend/app/database/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
"""Database models."""
from datetime import time
from typing import Optional

from sqlmodel import Field, SQLModel

from app.scheduling import Repeat


class BaseModel(SQLModel):
"""Base model."""

id: Optional[int] = Field(default=None, primary_key=True)


class Schedule(BaseModel, table=True):
"""Schedule."""

start_time: time
duration: int
repeat: Repeat
active: bool
16 changes: 14 additions & 2 deletions backend/app/main.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,28 @@
"""FastAPI application."""
from apscheduler.triggers.cron import CronTrigger
from fastapi import FastAPI, Request, status
from fastapi.middleware import Middleware
from fastapi.responses import RedirectResponse
from furl import furl

from app.api.v1.api import api_router
from app.scheduling import scheduler, SchedulerMiddleware
from fastapi.middleware import Middleware
from app.scheduling import SchedulerMiddleware, scheduler, switch_relays

middleware = [Middleware(SchedulerMiddleware, scheduler=scheduler)]
app = FastAPI(middleware=middleware)


@app.on_event("startup")
async def startup_event():
"""Startup event.
:return:
"""
await scheduler.add_schedule(
switch_relays, CronTrigger.from_crontab("* * * * *"), id="switch_relays"
)


@app.get("/", include_in_schema=False)
def redirect_to_autodocs(request: Request) -> RedirectResponse:
"""Home Page of the application.
Expand Down
39 changes: 35 additions & 4 deletions backend/app/scheduling.py
Original file line number Diff line number Diff line change
@@ -1,30 +1,61 @@
"""Scheduling for relay switches."""
from datetime import datetime
from enum import Enum

from apscheduler import AsyncScheduler
from apscheduler.triggers.cron import CronTrigger
from starlette.types import ASGIApp, Receive, Scope, Send


class Repeat(str, Enum):
"""Enumeration for repeat values."""

once = "once"
every_day = "every_day"
weekdays = "weekdays"
weekends = "weekends"
monday = "monday"
tuesday = "tuesday"
wednesday = "wednesday"
thursday = "thursday"
friday = "friday"
saturday = "saturday"
sunday = "sunday"


def switch_relays():
"""Trigger function to switch relays.
:return:
"""
print("Hello, the time is", datetime.now())


class SchedulerMiddleware:
"""Middleware to inject the scheduler instance."""

def __init__(
self,
app: ASGIApp,
scheduler: AsyncScheduler,
) -> None:
"""Initializer.
:param ASGIApp app:
:param AsyncScheduler scheduler:
"""
self.app = app
self.scheduler = scheduler

async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
"""Handles ASGI call.
:param Scope scope:
:param Receive receive:
:param Send send:
:return:
"""
if scope["type"] == "lifespan":
async with self.scheduler:
await self.scheduler.add_schedule(
switch_relays, CronTrigger.from_crontab("* * * * *"), id="switch_relays"
)
await self.scheduler.start_in_background()
await self.app(scope, receive, send)
else:
Expand Down
1 change: 1 addition & 0 deletions backend/app/services/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""Services module."""
File renamed without changes.
46 changes: 46 additions & 0 deletions backend/app/services/schedule.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
"""Services for handling persistence of Schedule objects."""
from app.api.v1.models import ScheduleCreate, ScheduleResponse, ScheduleUpdate


def service_get_schedule(primary_key: int) -> ScheduleResponse:
"""Service returns a Schedule object.
:param int primary_key: Primary key
:return ScheduleResponse:
"""
pass


def service_get_schedules() -> list[ScheduleResponse]:
"""Service returns a list of Schedule objects.
:return list[ScheduleResponse]:
"""
pass


def service_create_schedule(data: ScheduleCreate):
"""Services creates and persists a Schedule object.
:param ScheduleCreate data:
:return:
"""
pass


def service_update_schedule(data: ScheduleUpdate):
"""Service updates and persists a Schedule object.
:param ScheduleUpdate data:
:return:
"""
pass


def service_delete_schedule(primary_key: int):
"""Service deletes a persisted Schedule object.
:param int primary_key:
:return:
"""
pass
Loading

0 comments on commit dd43380

Please sign in to comment.