Initial commit

This commit is contained in:
Me 2020-12-17 01:01:27 +01:00
commit b134f4c3b9
8 changed files with 299 additions and 0 deletions

2
.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
*.pyd
__pycache__

11
auto_test.sh Executable file
View file

@ -0,0 +1,11 @@
#!/bin/bash
# Start test if one of the files was modified
while true; do
NOTIFY=$(inotifywait -rq -e modify . | grep '\.py')
if [ _$? == _0 ]; then
pytest --capture=no test
echo ">>>>>>>> Test finished at: $(date)"
fi
done

10
energyDB.conf Normal file
View file

@ -0,0 +1,10 @@
# Configuration for the uvicorn server running the energyDB application
# Set HTTP port
HTTP_PORT=8444
# Bind to address
IP_ADDRESS=0.0.0.0
# More uvicorn command line arguments
UVICORN_ARGS=--reload

3
srv/__init__.py Normal file
View file

@ -0,0 +1,3 @@
from .energyDB import app
energyDB = app

117
srv/energyDB.py Normal file
View file

@ -0,0 +1,117 @@
from typing import Optional, List
from fastapi import FastAPI, Header, HTTPException
from pydantic import BaseModel
import datetime
import databases
import sqlalchemy
from sqlite3 import OperationalError
DATABASE_URL = "sqlite:///./energyDB.sqlite"
# DATABASE_URL = "sqlite://"
db = databases.Database(DATABASE_URL)
metadata = sqlalchemy.MetaData()
energy = sqlalchemy.Table(
"energy",
metadata,
sqlalchemy.Column("timestamp", sqlalchemy.DateTime, primary_key=True),
sqlalchemy.Column("channel_id", sqlalchemy.Integer(), nullable=False),
sqlalchemy.Column("value", sqlalchemy.Float),
sqlalchemy.UniqueConstraint("timestamp"),
)
engine = sqlalchemy.create_engine(
DATABASE_URL, connect_args={"check_same_thread": False}
)
metadata.create_all(engine)
class EnergyValue(BaseModel):
timestamp: datetime.datetime
value: float
class ChannelData(BaseModel):
channel_id: int
data: List[EnergyValue]
msg: Optional[str]
class BulkData(BaseModel):
bulk: List[ChannelData]
msg: Optional[str]
class BulkDataRequest(BaseModel):
channel_ids: List[int]
fromTime: datetime.datetime
tillTime: datetime.datetime = datetime.datetime.now()
app = FastAPI(debug=True)
@app.on_event("startup")
async def startup():
"""On startup connect to the database"""
await db.connect()
@app.on_event("shutdown")
async def shutdown():
"""On shutdow diconnect from the database"""
await db.disconnect()
# def _raiseHttpExceptionOnWrongToken(token : str):
# if token != fake_secret_token:
# raise HTTPException(status_code=400, detail="Invalid X-Token header")
@app.put("/energy/bulkData")
async def putBulkEnergyData(bulkData: BulkData):
valuesToInsert = []
for channelData in bulkData.bulk:
for measurement in channelData.data:
valuesToInsert.append({
"channel_id": channelData.channel_id,
"timestamp": measurement.timestamp,
"value": measurement.value})
query = energy.insert().values(valuesToInsert)
result = await db.execute(query)
return {
"valuesToInsert": valuesToInsert
}
@app.get("/energy/bulkData", response_model = BulkData)
async def getBulkEnergyData(bulkDataRequest: BulkDataRequest):
bulkData = []
for ch in bulkDataRequest.channel_ids:
query = sqlalchemy.select([energy.c.timestamp, energy.c.value]) \
.where(energy.c.channel_id == ch) \
.where(energy.c.timestamp >= bulkDataRequest.fromTime) \
.where(energy.c.timestamp <= bulkDataRequest.tillTime)
data = await db.fetch_all(query)
bulkData.append({"channel_id": ch, "data": data})
return {
"bulk": bulkData,
"msg": __name__ + " - " +str(query.compile())
}
@app.get("/energy/{channel_id}", response_model = ChannelData)
async def getChannelData(channel_id: int):
try:
query = sqlalchemy.select([energy.c.timestamp, energy.c.value]).where(energy.c.channel_id == channel_id)
result = await db.fetch_all(query)
return {
"channel_id": channel_id,
"data": result
}
except Exception as ex:
raise HTTPException(status_code=500, detail=f"Internal error: {type(ex)} - \"{ex}\"")
@app.put("/energy/{channel_id}") #, response_model = EnergyValue)
async def putChannelData(channel_id: int, data: EnergyValue):
query = energy.insert().values(
timestamp=data.timestamp,
channel_id=channel_id,
value=data.value)
result = await db.execute(query)
# # return await db.fetch_all(query)
return {
"timestamp": datetime.datetime.now(),
# "channel" : data.channel,
"value": data.value,
"msg": str(result)
}

14
start_server.sh Executable file
View file

@ -0,0 +1,14 @@
#!/bin/bash
# Start uvicorn server with application srv:energyDB
ENERGY_DB_CONF=./energyDB.conf
if [ -f $ENERGY_DB_CONF ]; then
. energyDB.conf
fi
ARG_HTTP_PORT=${HTTP_PORT:-8000}
ARG_IP_ADDRESS=${IP_ADDRESS:-127.0.0.1}
/usr/bin/env uvicorn --port $ARG_HTTP_PORT --host $ARG_IP_ADDRESS ${UVICORN_ARGS} srv:energyDB

0
test/__init__.py Normal file
View file

142
test/test_EnergyDB.py Normal file
View file

@ -0,0 +1,142 @@
from fastapi.testclient import TestClient
import pytest
from datetime import datetime
import json
import urllib.parse
from srv import energyDB
class Test_energyDb:
bulkTestData = [
{
"channel_id": 1,
"data": (
{ "timestamp": "2020-12-11T12:00:22", "value": 1100.1 },
{ "timestamp": "2020-12-11T12:10:15", "value": 1109.2 },
{ "timestamp": "2020-12-11T12:20:13", "value": 1119.3 },
{ "timestamp": "2020-12-11T12:30:21", "value": 1131.4 },
{ "timestamp": "2020-12-11T12:40:08", "value": 1143.5 },
{ "timestamp": "2020-12-11T12:50:13", "value": 1152.6 },
{ "timestamp": "2020-12-11T13:00:11", "value": 1160.7 },
{ "timestamp": "2020-12-11T13:10:09", "value": 1169.8 },
{ "timestamp": "2020-12-11T13:20:10", "value": 1181.9 },
{ "timestamp": "2020-12-11T13:30:17", "value": 1190.0 },
)
},
{
"channel_id": 2,
"data": [
{ "timestamp": "2020-12-11T12:01:15", "value": 1200.1 },
{ "timestamp": "2020-12-11T12:21:28", "value": 1219.2 },
{ "timestamp": "2020-12-11T12:41:21", "value": 1243.3 },
{ "timestamp": "2020-12-11T13:01:16", "value": 1260.4 },
{ "timestamp": "2020-12-11T13:21:18", "value": 1281.5 },
]
}
]
client = TestClient(energyDB)
# def setup(self):
# self.client = TestClient(energyDB)
# def teardown(self):
# self.client = None
def _test_bulkData_put(self):
# response = self.client.put("/energy/bulkData", json=self.bulkTestData);
response = self.client.put("/energy/bulkData", json={"bulk": self.bulkTestData})
# print(f"dir(response): {dir(response)}")
# print(f"dir(response.request): {dir(response.request)}")
# print("---- request")
# print(f"response.request.method: {response.request.method}")
# print(f"response.request.url: {urllib.parse.unquote(response.request.url)}")
# print(f"response.request.headers: {response.request.headers}")
# requestJson = json.loads(response.request.body)
# print(f"response.request.body: {json.dumps(requestJson, indent=2)}")
print("---- response")
print(f"response.reason: {response.reason}")
responseJson = json.loads(response.text)
print(f"response.text: {json.dumps(responseJson, indent=2)}")
# print(f"response.text: {json.dumps(response.text, indent=2)}")
assert response.status_code == 200
def test_bulkData_get(self):
# response = self.client.put("/energy/bulkData", json=self.bulkTestData);
fromTimestamp = datetime.fromisoformat("2020-12-11T12:30:00")
tillTimestamp = datetime.fromisoformat("2020-12-11T12:30:59")
response = self.client.get(
"/energy/bulkData",
json = {
"channel_ids": [1, 2, 3],
"fromTime": fromTimestamp.isoformat(),
# "tillTime": tillTimestamp.isoformat()
})
# print(f"dir(response): {dir(response)}")
# print(f"dir(response.request): {dir(response.request)}")
print("---- request")
print(f"response.request.method: {response.request.method}")
print(f"response.request.url: {urllib.parse.unquote(response.request.url)}")
print(f"response.request.headers: {response.request.headers}")
print(f"dir(response.request): {dir(response.request)}")
print(f"response.request.body: {response.request.body}")
# requestJson = json.loads(response.request.body)
# print(f"response.request.body: {json.dumps(requestJson, indent=2)}")
print("---- response")
print(f"response.reason: {response.reason}")
responseJson = json.loads(response.text)
print(f"response.text: {json.dumps(responseJson, indent=2)}")
# print(f"response.text: {json.dumps(response.text, indent=2)}")
assert response.status_code == 200
@pytest.mark.skip(reason="Ignore me temporarily")
def test_insert_energy(self):
energyData = {
"timestamp": datetime.now().isoformat(),
"value": 234.5,
}
print(f"energyData: {energyData}")
# response = self.client.put("/energies/1", json=energyData) #, headers={"X-Token": "coneofsilence"})
response = self.client.put(
"/energy/2",
# params=energyData,
json=energyData) #, headers={"X-Token": "coneofsilence"})
# print(f"dir(response): {dir(response)}")
# print(f"dir(response.request): {dir(response.request)}")
# print("---- request")
# print(f"response.request.method: {response.request.method}")
# print(f"response.request.url: {urllib.parse.unquote(response.request.url)}")
# print(f"response.request.headers: {response.request.headers}")
# print(f"response.request.body: {json.loads(response.request.body)}")
# print("---- response")
# print(f"response.reason: {response.reason}")
# print(f"response.text: {json.loads(response.text)}")
# print(f"request.header: \"{response.request.header}\"")
# print(f"request.body: \"{response.request.body}\"")
assert response.status_code == 200
# assert response.json()["msg"] == ""
@pytest.mark.skip(reason="Ignore me temporarily")
def test_get_energy(self):
response = self.client.get("/energy/1")
# print(f"dir(response): {dir(response)}")
# print(f"dir(response.request): {dir(response.request)}")
# print("---- request")
# print(f"response.request.method: {response.request.method}")
# print(f"response.request.url: {urllib.parse.unquote(response.request.url)}")
# print(f"response.request.headers: {response.request.headers}")
# print("---- response")
# print(f"response.reason: {response.reason}")
responseJson = json.loads(response.text)
print(f"response.text: {json.dumps(responseJson, indent=2)}")
data = response.json()
# print(f"data: {type(data)}")
# for k,v in data.items():
# print(f"key: {k} -> value: {v}")
print(f"data of channel: {data['channel_id']}")
for r in data["data"]:
print(f"r: {r}")
assert response.status_code == 200
# assert response.json()["msg"] == ""