import json from contextlib import asynccontextmanager from datetime import datetime, timezone from typing import Literal import motor.motor_asyncio from bson import json_util from bson.objectid import ObjectId from fastapi import FastAPI, Request from fastapi.middleware.cors import CORSMiddleware from fastapi.responses import FileResponse from fastapi.staticfiles import StaticFiles from prettytable import PrettyTable from pydantic import BaseModel, Field from pydantic_settings import BaseSettings, SettingsConfigDict class Settings(BaseSettings): model_config = SettingsConfigDict(env_file="../.env") is_prod: bool = Field(default=False) mongo_server: str = Field(default="") mongo_db: str = Field(default="") class Document(BaseModel): date: int value: float @asynccontextmanager async def lifespan(app: FastAPI): table = PrettyTable() table.field_names = ["VARIABLE", "VALUE"] rows = [list(item) for item in Settings().__dict__.items()] table.add_rows(rows) print(table) yield app = FastAPI( openapi_url=("/openapi.json" if not Settings().is_prod else ""), lifespan=lifespan ) app.add_middleware( CORSMiddleware, allow_origins=["*"], allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) client = motor.motor_asyncio.AsyncIOMotorClient(Settings().mongo_server, 27017) db = client[Settings().mongo_db] def object_id_from_date(date: int) -> ObjectId: timestamp = datetime.fromtimestamp(date, tz=timezone.utc) object_id = ObjectId.from_datetime(timestamp) return object_id @app.get( "/type/{type}/startDate/{start_date}/endDate/{end_date}/sample/{sample}", tags=["Retrieve sensor data"], ) async def get_sensor_data( type: Literal["temperature", "humidity"], start_date: int, end_date: int, sample: int, ) -> list[Document]: collection = db["dht22"] pipeline = [ { "$match": { "$and": [ { "_id": { "$gt": object_id_from_date(start_date), "$lt": object_id_from_date(end_date), } }, {"type": type}, {"value": {"$ne": float("nan")}}, ] } }, { "$group": { "_id": { "$toDate": { "$subtract": [ {"$toLong": {"$toDate": "$_id"}}, { "$mod": [ {"$toLong": {"$toDate": "$_id"}}, 1000 * 60 * sample, ] }, ] } }, "value": {"$avg": "$value"}, "date": {"$min": {"$toDate": "$_id"}}, } }, { "$project": { "_id": 0, "value": {"$round": ["$value", 1]}, "date": {"$toLong": "$date"}, } }, {"$sort": {"date": 1}}, ] docs = [doc async for doc in collection.aggregate(pipeline)] return json.loads(json_util.dumps(docs)) index = FileResponse(path="../dist/index.html", headers={"Cache-Control": "no-cache"}) @app.middleware("http") async def add_default_404(request: Request, call_next): response = await call_next(request) if response.status_code == 404: return index else: return response app.mount("/", StaticFiles(directory="../dist", html=True), name="dist")