add Python server backend
Some checks failed
continuous-integration/drone/push Build is failing

This commit is contained in:
2024-05-11 14:06:39 +02:00
parent 14d63294a3
commit cd3afa4d61
22 changed files with 1896 additions and 119 deletions

View File

@@ -0,0 +1 @@
3.12.2

140
server-python/app.py Normal file
View File

@@ -0,0 +1,140 @@
import json
import os
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 dotenv import find_dotenv, load_dotenv
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
env_file = find_dotenv(".env")
load_dotenv("../.env")
IS_PROD = os.getenv("PRODUCTION", "") == "true"
class Config:
MONGO_SERVER: str = os.environ.get("MONGO_SERVER", "")
MONGO_DB: str = os.environ.get("MONGO_DB", "")
class Document(BaseModel):
date: int
value: float
@asynccontextmanager
async def lifespan(app: FastAPI):
table = PrettyTable()
table.field_names = ["VARIABLE", "VALUE"]
table.add_rows(
[
[env_variable, os.environ.get(env_variable)]
for env_variable in ["MONGO_SERVER", "MONGO_DB"]
]
)
print(table)
yield
app = FastAPI(openapi_url=("/openapi.json" if not IS_PROD else ""), lifespan=lifespan)
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
client = motor.motor_asyncio.AsyncIOMotorClient(Config.MONGO_SERVER, 27017)
db = client[Config.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": "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")

View File

@@ -0,0 +1,16 @@
import json
from app import app
from fastapi.openapi.utils import get_openapi
with open("openapi.json", "w") as f:
json.dump(
get_openapi(
title=app.title,
version=app.version,
openapi_version=app.openapi_version,
description=app.description,
routes=app.routes,
),
f,
)

View File

@@ -0,0 +1,81 @@
{
"openapi": "3.1.0",
"info": { "title": "FastAPI", "version": "0.1.0" },
"paths": {
"/type/{type}/startDate/{start_date}/endDate/{end_date}/sample/{sample}": {
"get": {
"tags": ["Retrieve sensor data"],
"summary": "Get Sensor Data",
"operationId": "get_sensor_data_type__type__startDate__start_date__endDate__end_date__sample__sample__get",
"parameters": [
{
"name": "type",
"in": "path",
"required": true,
"schema": { "enum": ["temperature", "humidity"], "type": "string", "title": "Type" }
},
{
"name": "start_date",
"in": "path",
"required": true,
"schema": { "type": "integer", "title": "Start Date" }
},
{ "name": "end_date", "in": "path", "required": true, "schema": { "type": "integer", "title": "End Date" } },
{ "name": "sample", "in": "path", "required": true, "schema": { "type": "integer", "title": "Sample" } }
],
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": { "$ref": "#/components/schemas/Document" },
"title": "Response Get Sensor Data Type Type Startdate Start Date Enddate End Date Sample Sample Get"
}
}
}
},
"422": {
"description": "Validation Error",
"content": { "application/json": { "schema": { "$ref": "#/components/schemas/HTTPValidationError" } } }
}
}
}
}
},
"components": {
"schemas": {
"Document": {
"properties": {
"date": { "type": "integer", "title": "Date" },
"value": { "type": "number", "title": "Value" }
},
"type": "object",
"required": ["date", "value"],
"title": "Document"
},
"HTTPValidationError": {
"properties": {
"detail": { "items": { "$ref": "#/components/schemas/ValidationError" }, "type": "array", "title": "Detail" }
},
"type": "object",
"title": "HTTPValidationError"
},
"ValidationError": {
"properties": {
"loc": {
"items": { "anyOf": [{ "type": "string" }, { "type": "integer" }] },
"type": "array",
"title": "Location"
},
"msg": { "type": "string", "title": "Message" },
"type": { "type": "string", "title": "Error Type" }
},
"type": "object",
"required": ["loc", "msg", "type"],
"title": "ValidationError"
}
}
}
}

1209
server-python/poetry.lock generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,22 @@
[tool.poetry]
name = "sensor-web-api"
version = "0.1.0"
description = ""
authors = ["Your Name <you@example.com>"]
readme = "README.md"
package-mode = false
[tool.poetry.dependencies]
python = "^3.12"
motor = "^3.4.0"
fastapi = "^0.111.0"
python-dotenv = "^1.0.1"
[tool.poetry.group.dev.dependencies]
ruff = "^0.4.4"
prettytable = "^3.10.0"
[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"