Add table "channels" + move some of the db functionas to module db.py
This commit is contained in:
parent
f4d704efa0
commit
0c0d8992b1
3 changed files with 631 additions and 207 deletions
93
srv/db.py
Normal file
93
srv/db.py
Normal file
|
|
@ -0,0 +1,93 @@
|
||||||
|
import os
|
||||||
|
from typing import List
|
||||||
|
|
||||||
|
from databases import Database
|
||||||
|
from sqlalchemy import (Column, DateTime, Integer, Float, String,
|
||||||
|
MetaData, Table, UniqueConstraint, create_engine)
|
||||||
|
|
||||||
|
import sqlalchemy
|
||||||
|
from sqlalchemy.sql import func
|
||||||
|
|
||||||
|
class EnergyDB():
|
||||||
|
def __init__(self, db_url : str):
|
||||||
|
self._engine = create_engine(db_url, connect_args={"check_same_thread": False})
|
||||||
|
|
||||||
|
# self.database = Database(db_url)
|
||||||
|
self._conn = self._engine.connect()
|
||||||
|
|
||||||
|
self._metadata = MetaData(self._engine)
|
||||||
|
self._tables = {
|
||||||
|
"energy": Table(
|
||||||
|
"energy", self._metadata,
|
||||||
|
Column("timestamp", DateTime, primary_key=True),
|
||||||
|
Column("channel_id", Integer(), nullable=False),
|
||||||
|
Column("value", Float),
|
||||||
|
UniqueConstraint("timestamp"),
|
||||||
|
),
|
||||||
|
"channels": Table(
|
||||||
|
"channels", self._metadata,
|
||||||
|
# Column("id", Integer(), autoincrement = True),
|
||||||
|
Column("id", Integer()), #, primary_key = True),
|
||||||
|
Column("name", String(), primary_key = True),
|
||||||
|
),
|
||||||
|
}
|
||||||
|
self._metadata.create_all(self._engine)
|
||||||
|
|
||||||
|
def metadata(self):
|
||||||
|
return self._metadata
|
||||||
|
|
||||||
|
def url(self):
|
||||||
|
return self._engine.url
|
||||||
|
|
||||||
|
def engine(self):
|
||||||
|
return self._engine
|
||||||
|
|
||||||
|
def table(self, name : str) -> sqlalchemy.Table:
|
||||||
|
return self._tables[name]
|
||||||
|
|
||||||
|
def execute(self, cmd, values = None, **args):
|
||||||
|
if values is None:
|
||||||
|
return self._conn.execute(cmd, args)
|
||||||
|
else:
|
||||||
|
return self._conn.execute(cmd, values, args)
|
||||||
|
return self._conn.execute(cmd, args)
|
||||||
|
|
||||||
|
def addChannels(self, channelNames: List[str]):
|
||||||
|
result = []
|
||||||
|
try:
|
||||||
|
# query = self.table("channels").insert()
|
||||||
|
query = sqlalchemy.sql.text("INSERT INTO channels (name) VALUES(:name)")
|
||||||
|
# nameDicts = [ {"name": name } for name in channelNames]
|
||||||
|
# result = self.execute(query, name=channelNames)
|
||||||
|
for n in channelNames:
|
||||||
|
result.append({"n": n})
|
||||||
|
self.execute(query, name=n)
|
||||||
|
except Exception as e:
|
||||||
|
raise Exception(f"Database error in addChannels(): {type(e)} - {str(e)}")
|
||||||
|
return {
|
||||||
|
"query": str(query),
|
||||||
|
"result": result,
|
||||||
|
# "result": [str(r) for r in result.fetchall()]
|
||||||
|
}
|
||||||
|
|
||||||
|
def getChannels(self) -> dict:
|
||||||
|
try:
|
||||||
|
table_channels = self.table("channels")
|
||||||
|
query = sqlalchemy.select([table_channels.c.name, table_channels.c.id]).select_from(table_channels)
|
||||||
|
channels = [dict(r.items()) for r in self.execute(query).fetchall()]
|
||||||
|
except Exception as e:
|
||||||
|
raise Exception(f"Database error in getChannels(): {type(e)} - {str(e)}")
|
||||||
|
return {
|
||||||
|
"channels": channels,
|
||||||
|
"query": str(query),
|
||||||
|
}
|
||||||
|
|
||||||
|
def getChannelId(self, channelName : str) -> int:
|
||||||
|
try:
|
||||||
|
query = sqlalchemy.sql.text(f"SELECT _ROWID_, name FROM channels WHERE name == :name")
|
||||||
|
chId = self.execute(query, name=channelName).scalar()
|
||||||
|
except Exception as e:
|
||||||
|
raise Exception(f"Database error in getChannelId(): {type(e)} - {str(e)}")
|
||||||
|
if chId is None:
|
||||||
|
raise Exception(f"Database error in getChannelId(): channel '{channelName}' not found")
|
||||||
|
return chId
|
||||||
334
srv/energyDB.py
334
srv/energyDB.py
|
|
@ -1,42 +1,36 @@
|
||||||
from typing import Optional, List
|
from typing import Optional, List, Dict
|
||||||
from fastapi import FastAPI, Header, HTTPException
|
from fastapi import FastAPI, Header, HTTPException
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
import datetime
|
import datetime
|
||||||
import databases
|
|
||||||
import os
|
import os
|
||||||
import sqlalchemy
|
|
||||||
from sqlite3 import OperationalError
|
# import sqlalchemy
|
||||||
|
|
||||||
|
from .db import EnergyDB
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
API_VERSION_MAJOR = 1
|
API_VERSION_MAJOR = 1
|
||||||
API_VERSION_MINOR = 0
|
API_VERSION_MINOR = 0
|
||||||
|
|
||||||
REST_API_ROOT = f"/energy/v{API_VERSION_MAJOR}/"
|
DB_URL = os.getenv("DATABASE_URL") #, default="sqlite://")
|
||||||
|
if len(DB_URL) == 0:
|
||||||
|
raise Exception("Environment variable DATABASE_URL missed!")
|
||||||
|
print(f"DB URL: {DB_URL}")
|
||||||
|
db = EnergyDB(DB_URL)
|
||||||
|
|
||||||
metadata = sqlalchemy.MetaData()
|
app = FastAPI(debug=True)
|
||||||
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"),
|
|
||||||
)
|
|
||||||
|
|
||||||
# DATABASE_URL = "sqlite:///./energyDB.sqlite"
|
class InfoData(BaseModel):
|
||||||
DATABASE_URL = os.getenv("DATABASE_URL", default="sqlite://")
|
info: Dict
|
||||||
print(f"DB URL: {DATABASE_URL}")
|
|
||||||
db = databases.Database(DATABASE_URL)
|
|
||||||
engine = sqlalchemy.create_engine(
|
|
||||||
DATABASE_URL,
|
|
||||||
connect_args={"check_same_thread": False})
|
|
||||||
metadata.create_all(engine)
|
|
||||||
|
|
||||||
class EnergyValue(BaseModel):
|
class EnergyValue(BaseModel):
|
||||||
timestamp: datetime.datetime
|
timestamp: datetime.datetime
|
||||||
value: float
|
value: float
|
||||||
|
|
||||||
class ChannelData(BaseModel):
|
class ChannelData(BaseModel):
|
||||||
channel_id: int
|
channel_id: Optional[int]
|
||||||
|
channel: Optional[str]
|
||||||
data: List[EnergyValue]
|
data: List[EnergyValue]
|
||||||
msg: Optional[str]
|
msg: Optional[str]
|
||||||
|
|
||||||
|
|
@ -49,80 +43,246 @@ class BulkDataRequest(BaseModel):
|
||||||
fromTime: datetime.datetime
|
fromTime: datetime.datetime
|
||||||
tillTime: datetime.datetime = datetime.datetime.now()
|
tillTime: datetime.datetime = datetime.datetime.now()
|
||||||
|
|
||||||
app = FastAPI(debug=True)
|
class ChannelInfo(BaseModel):
|
||||||
|
name: str
|
||||||
|
|
||||||
@app.on_event("startup")
|
class Channels(BaseModel):
|
||||||
async def startup():
|
channels: List[ChannelInfo]
|
||||||
"""On startup connect to the database"""
|
|
||||||
await db.connect()
|
|
||||||
|
|
||||||
@app.on_event("shutdown")
|
def _restApiPath(subPath : str) -> str:
|
||||||
async def shutdown():
|
"""Return the full REST API path
|
||||||
"""On shutdow diconnect from the database"""
|
|
||||||
await db.disconnect()
|
|
||||||
|
|
||||||
# def _raiseHttpExceptionOnWrongToken(token : str):
|
Arguments:
|
||||||
# if token != fake_secret_token:
|
subPath {str} -- The sub-URL
|
||||||
# raise HTTPException(status_code=400, detail="Invalid X-Token header")
|
|
||||||
|
|
||||||
@app.put(REST_API_ROOT + "bulkData")
|
Returns:
|
||||||
async def putBulkEnergyData(bulkData: BulkData):
|
str -- The full REST API URL
|
||||||
valuesToInsert = []
|
"""
|
||||||
for channelData in bulkData.bulk:
|
REST_API_ROOT = f"/energy/v{API_VERSION_MAJOR}"
|
||||||
for measurement in channelData.data:
|
if (subPath.startswith("/")):
|
||||||
valuesToInsert.append({
|
return REST_API_ROOT + subPath
|
||||||
"channel_id": channelData.channel_id,
|
else:
|
||||||
"timestamp": measurement.timestamp,
|
return REST_API_ROOT + "/" + subPath
|
||||||
"value": measurement.value})
|
|
||||||
query = energy.insert().values(valuesToInsert)
|
@app.get(_restApiPath("/version"))
|
||||||
result = await db.execute(query)
|
async def restApiGetVersion() -> dict:
|
||||||
|
"""Return the version information
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict -- The version information of energyDB and SQLAlchemy
|
||||||
|
"""
|
||||||
return {
|
return {
|
||||||
"valuesToInsert": valuesToInsert
|
"version": f"{API_VERSION_MAJOR}.{API_VERSION_MINOR}",
|
||||||
}
|
}
|
||||||
|
|
||||||
@app.get(REST_API_ROOT + "bulkData", response_model = BulkData)
|
@app.get(_restApiPath("/info"), response_model = InfoData)
|
||||||
|
async def apiGetInfo():
|
||||||
|
info = {}
|
||||||
|
info["db_url"] = db.url()
|
||||||
|
|
||||||
|
e = sqlalchemy.Table("energy", db.metadata(), autoload=True, autoload_with=db.engine())
|
||||||
|
info["db_fields"] = [c.name for c in e.columns]
|
||||||
|
|
||||||
|
result = db.execute("select * from energy")
|
||||||
|
info["db_dir"] = str(dir(db))
|
||||||
|
info["result_type"] = str(type(result))
|
||||||
|
info["result_dir"] = str(dir(result))
|
||||||
|
info["result_count"] = str(result.rowcount)
|
||||||
|
info["db_contents"] = [str(row) for row in result.fetchall()]
|
||||||
|
result = db.execute("SELECT name FROM sqlite_master WHERE type='table'")
|
||||||
|
info["tables"] = [str(row) for row in result.fetchall()]
|
||||||
|
result = db.execute("SELECT sql FROM sqlite_master WHERE type='table'")
|
||||||
|
info["energy.sql"] = str(result.fetchone()[0]).replace("\n", "").replace("\t","")
|
||||||
|
result = db.execute("SELECT COUNT(*) FROM energy")
|
||||||
|
info["rows"] = str(result.fetchone())
|
||||||
|
# info["rows"] = [str(row) for row in result.fetchall()]
|
||||||
|
|
||||||
|
info["tables"] = [t.name for t in db.metadata().sorted_tables]
|
||||||
|
for t in db.metadata().sorted_tables:
|
||||||
|
# info[f"columns_{t}"] = str(dir(t))
|
||||||
|
info[f"columns_{t}"] = t.schema
|
||||||
|
|
||||||
|
return {
|
||||||
|
"info": info
|
||||||
|
}
|
||||||
|
|
||||||
|
@app.get(_restApiPath("/bulkData"), response_model = BulkData)
|
||||||
async def getBulkEnergyData(bulkDataRequest: BulkDataRequest):
|
async def getBulkEnergyData(bulkDataRequest: BulkDataRequest):
|
||||||
bulkData = []
|
bulkData = []
|
||||||
for ch in bulkDataRequest.channel_ids:
|
trace = []
|
||||||
query = sqlalchemy.select([energy.c.timestamp, energy.c.value]) \
|
exception = None
|
||||||
.where(energy.c.channel_id == ch) \
|
try:
|
||||||
.where(energy.c.timestamp >= bulkDataRequest.fromTime) \
|
for ch in bulkDataRequest.channel_ids:
|
||||||
.where(energy.c.timestamp <= bulkDataRequest.tillTime)
|
data = []
|
||||||
try:
|
table_energy = db.table("energy")
|
||||||
data = await db.fetch_all(query)
|
query = sqlalchemy.select([table_energy.c.timestamp, table_energy.c.value]) \
|
||||||
|
.select_from(table_energy) \
|
||||||
|
.where(sqlalchemy.sql.and_(
|
||||||
|
table_energy.c.channel_id == ch,
|
||||||
|
table_energy.c.timestamp >= bulkDataRequest.fromTime,
|
||||||
|
table_energy.c.timestamp <= bulkDataRequest.tillTime
|
||||||
|
)
|
||||||
|
)
|
||||||
|
for row in db.execute(query).fetchall():
|
||||||
|
data.append(dict(row.items()))
|
||||||
bulkData.append({"channel_id": ch, "data": data})
|
bulkData.append({"channel_id": ch, "data": data})
|
||||||
except OperationalError as e:
|
except Exception as e:
|
||||||
raise HTTPException(status_code=500, detail="Database error")
|
raise HTTPException(
|
||||||
|
status_code=404,
|
||||||
|
detail=f"Database error: {type(e)} - {str(e)}"
|
||||||
|
# detail=f"Database error: {str(e)}\nQuery: {str(query)}"
|
||||||
|
)
|
||||||
return {
|
return {
|
||||||
"bulk": bulkData,
|
"bulk": bulkData,
|
||||||
"msg": __name__ + " - " +str(query.compile())
|
"msg": None #__name__ + " - " + str(query)
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@app.put(_restApiPath("/bulkData"))
|
||||||
|
async def putBulkEnergyData(bulkData: BulkData):
|
||||||
|
valuesToInsert = []
|
||||||
|
result = "ok"
|
||||||
|
# rows_before = {}
|
||||||
|
# rows_after = {}
|
||||||
|
|
||||||
|
try:
|
||||||
|
# rowCounter = 0
|
||||||
|
# dbResult = db.execute( db.tables["energy"].select() )
|
||||||
|
# for row in dbResult.fetchall():
|
||||||
|
# rows_before[f"row_{rowCounter}"] = str(row)
|
||||||
|
# rowCounter += 1
|
||||||
|
|
||||||
|
for channelData in bulkData.bulk:
|
||||||
|
if channelData.channel_id is None:
|
||||||
|
try:
|
||||||
|
table_channels = db.table("channels")
|
||||||
|
channel_id = db.execute(
|
||||||
|
sqlalchemy.select([table_channels.c.id]) \
|
||||||
|
.select_from(table_channels) \
|
||||||
|
.where(table_channels.c.name == channelData.channel))
|
||||||
|
except:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code = 500,
|
||||||
|
detail = f"Database error: {type(ex)} - \"{ex}\""
|
||||||
|
)
|
||||||
|
for measurement in channelData.data:
|
||||||
|
valuesToInsert.append({
|
||||||
|
"channel_id": channelData.channel_id,
|
||||||
|
"timestamp": measurement.timestamp,
|
||||||
|
"value": measurement.value
|
||||||
|
})
|
||||||
|
db.execute(db.table("energy").insert(), valuesToInsert)
|
||||||
|
|
||||||
|
# rowCounter = 0
|
||||||
|
# dbResult = db.execute( db.tables["energy"].select() )
|
||||||
|
# for row in dbResult.fetchall():
|
||||||
|
# rows_after[f"row_{rowCounter}"] = str(row)
|
||||||
|
# rowCounter += 1
|
||||||
|
except Exception as e:
|
||||||
|
result = f"Exception \"{str(e)}\""
|
||||||
|
return {
|
||||||
|
"result": result,
|
||||||
|
# "rows_before": rows_before,
|
||||||
|
# "rows_after": rows_after,
|
||||||
|
}
|
||||||
|
|
||||||
|
@app.put(_restApiPath("/channels"))
|
||||||
|
async def putChannels(channel_info: Channels):
|
||||||
|
result = "ok"
|
||||||
|
query = "???"
|
||||||
|
rows_before = {}
|
||||||
|
rows_after = {}
|
||||||
|
|
||||||
|
try:
|
||||||
|
r = db.getChannels()
|
||||||
|
rows_before = r["channels"]
|
||||||
|
|
||||||
|
channelNames = [c.name for c in channel_info.channels]
|
||||||
|
res = db.addChannels(channelNames)
|
||||||
|
result = res["result"]
|
||||||
|
query = res["query"]
|
||||||
|
|
||||||
|
r = db.getChannels()
|
||||||
|
rows_after = r["channels"]
|
||||||
|
except Exception as e:
|
||||||
|
result = f"Exception \"{str(e)}\""
|
||||||
|
return {
|
||||||
|
"result": result,
|
||||||
|
"channels": str(channel_info.channels),
|
||||||
|
"channelNames": channelNames,
|
||||||
|
"query": query,
|
||||||
|
"rows_before": rows_before,
|
||||||
|
"rows_after": rows_after,
|
||||||
|
}
|
||||||
|
|
||||||
|
@app.get(_restApiPath("/channels"))
|
||||||
|
async def getChannels() -> dict:
|
||||||
|
"""Return a list of all channels
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
HTTPException: with status 500 on a database error
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict -- the list of all channels
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
result = db.getChannels()
|
||||||
|
return {"channels": [ch["name"] for ch in result["channels"]]}
|
||||||
|
except Exception as e:
|
||||||
|
raise HTTPException(status_code = 500, detail = str(e))
|
||||||
|
|
||||||
|
@app.get(_restApiPath("/channels/{channel}/id"))
|
||||||
|
async def getChannelId(channel: str): # -> dict:
|
||||||
|
try:
|
||||||
|
chId = db.getChannelId(channel)
|
||||||
|
return {
|
||||||
|
"channel": channel,
|
||||||
|
"id": chId,
|
||||||
|
# "query": str(r["query"])
|
||||||
|
}
|
||||||
|
except Exception as e:
|
||||||
|
raise HTTPException(status_code=500,detail=f"Database error: {type(e)} - {str(e)}")
|
||||||
|
|
||||||
|
|
||||||
|
# @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)
|
||||||
|
# }
|
||||||
|
|
||||||
|
@app.get(_restApiPath("/{channel_id}/count")) # response_model = ChannelData)
|
||||||
|
async def getChannelRowCount(channel_id: int):
|
||||||
|
info = {}
|
||||||
|
|
||||||
|
info["columns"] = str( db.table("energy").columns)
|
||||||
|
|
||||||
|
# countQuery = sqlalchemy.select([sqlalchemy.func.count()]).select_from(db.tables["energy"])
|
||||||
|
# info["stmt"] = str(countQuery)
|
||||||
|
# countResult = db.execute( countQuery )
|
||||||
|
# info["count"] = countResult.fetchone()[0]
|
||||||
|
|
||||||
|
# info["tables"] = [t.name for t in metadata.sorted_tables]
|
||||||
|
# for t in metadata.tables:
|
||||||
|
# info[f"columns_{t}"] = str(type(t))
|
||||||
|
# # info[f"columns_{t}"] = list(sqlalchemy.inspect(t).columns)
|
||||||
|
return { "info": info }
|
||||||
|
|
|
||||||
|
|
@ -9,143 +9,314 @@ import urllib.parse
|
||||||
#TODO Use in-memory DB to test the case that there is no table
|
#TODO Use in-memory DB to test the case that there is no table
|
||||||
#TODO Add helper function to fill the in-memory DB before test
|
#TODO Add helper function to fill the in-memory DB before test
|
||||||
|
|
||||||
os.environ["DATABASE_URL"] = "sqlite:///./energyDB.sqlite"
|
# os.environ["DATABASE_URL"] = "sqlite:///./testDB.sqlite"
|
||||||
|
os.environ["DATABASE_URL"] = "sqlite://"
|
||||||
|
|
||||||
from srv import energyDB
|
from srv import energyDB
|
||||||
|
|
||||||
class Test_energyDb:
|
class Test_energyDB:
|
||||||
restApiRoot = "/energy/v1/"
|
restApiRoot = "/energy/v1"
|
||||||
bulkTestData = [
|
testData = {
|
||||||
{
|
"channels": (
|
||||||
"channel_id": 1,
|
{"name": "dc_power1"},
|
||||||
"data": (
|
{"name": "daily_yield"},
|
||||||
{ "timestamp": "2020-12-11T12:00:22", "value": 1100.1 },
|
{"name": "total_yield"},
|
||||||
{ "timestamp": "2020-12-11T12:10:15", "value": 1109.2 },
|
),
|
||||||
{ "timestamp": "2020-12-11T12:20:13", "value": 1119.3 },
|
"bulkdata": (
|
||||||
{ "timestamp": "2020-12-11T12:30:21", "value": 1131.4 },
|
{
|
||||||
{ "timestamp": "2020-12-11T12:40:08", "value": 1143.5 },
|
"channel_id": 1,
|
||||||
{ "timestamp": "2020-12-11T12:50:13", "value": 1152.6 },
|
"data": (
|
||||||
{ "timestamp": "2020-12-11T13:00:11", "value": 1160.7 },
|
{ "timestamp": "2020-12-11T12:00:22", "value": 1100.1 },
|
||||||
{ "timestamp": "2020-12-11T13:10:09", "value": 1169.8 },
|
{ "timestamp": "2020-12-11T12:10:15", "value": 1109.2 },
|
||||||
{ "timestamp": "2020-12-11T13:20:10", "value": 1181.9 },
|
{ "timestamp": "2020-12-11T12:20:13", "value": 1119.3 },
|
||||||
{ "timestamp": "2020-12-11T13:30:17", "value": 1190.0 },
|
{ "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 },
|
||||||
"channel_id": 2,
|
{ "timestamp": "2020-12-11T13:10:09", "value": 1169.8 },
|
||||||
"data": [
|
{ "timestamp": "2020-12-11T13:20:10", "value": 1181.9 },
|
||||||
{ "timestamp": "2020-12-11T12:01:15", "value": 1200.1 },
|
{ "timestamp": "2020-12-11T13:30:17", "value": 1190.0 },
|
||||||
{ "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 },
|
"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)
|
# client = TestClient(energyDB)
|
||||||
|
|
||||||
# def setup(self):
|
def setup(self):
|
||||||
# self.client = TestClient(energyDB)
|
self.client = TestClient(energyDB)
|
||||||
|
|
||||||
# def teardown(self):
|
def teardown(self):
|
||||||
# self.client = None
|
self.client = None
|
||||||
|
|
||||||
def _test_bulkData_put(self):
|
# --- helper functions
|
||||||
# response = self.client.put("/energy/bulkData", json=self.bulkTestData);
|
|
||||||
|
def _apiUrl(self, sub_url : str):
|
||||||
|
if sub_url.startswith("/"):
|
||||||
|
return self.restApiRoot + sub_url
|
||||||
|
else:
|
||||||
|
return self.restApiRoot + "/" + sub_url
|
||||||
|
|
||||||
|
def _fillDatabase(self):
|
||||||
response = self.client.put(
|
response = self.client.put(
|
||||||
self.restApiRoot + "bulkData",
|
self._apiUrl("/channels"),
|
||||||
json={"bulk": self.bulkTestData})
|
json = {"channels": self.testData["channels"]}
|
||||||
# print(f"dir(response): {dir(response)}")
|
)
|
||||||
# print(f"dir(response.request): {dir(response.request)}")
|
# self._dumpRequestAndResponse("_fillDatabase(channels)", response)
|
||||||
# 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
|
assert response.status_code == 200
|
||||||
|
|
||||||
def test_bulkData_get(self):
|
response = self.client.put(
|
||||||
print(f"DB_URL: {os.getenv('DATABASE_URL')}")
|
self._apiUrl("/bulkData"),
|
||||||
# response = self.client.put("/energy/bulkData", json=self.bulkTestData);
|
json = {"bulk": self.testData["bulkdata"]}
|
||||||
fromTimestamp = datetime.fromisoformat("2020-12-11T12:30:00")
|
)
|
||||||
tillTimestamp = datetime.fromisoformat("2020-12-11T12:30:59")
|
# self._dumpRequestAndResponse("_fillDatabase(data)", response)
|
||||||
response = self.client.get(
|
assert response.status_code == 200
|
||||||
self.restApiRoot + "bulkData",
|
|
||||||
json = {
|
def _dumpRequestAndResponse(self, context : str, response):
|
||||||
"channel_ids": [1, 2, 3],
|
print("\n")
|
||||||
"fromTime": fromTimestamp.isoformat(),
|
print(f"---- request ({context})")
|
||||||
# "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.method: {response.request.method}")
|
||||||
print(f"response.request.url: {urllib.parse.unquote(response.request.url)}")
|
print(f"response.request.url: {urllib.parse.unquote(response.request.url)}")
|
||||||
print(f"response.request.headers: {response.request.headers}")
|
print(f"response.request.headers: {response.request.headers}")
|
||||||
print(f"dir(response.request): {dir(response.request)}")
|
try:
|
||||||
print(f"response.request.body: {response.request.body}")
|
requestJson = json.loads(response.request.body)
|
||||||
# requestJson = json.loads(response.request.body)
|
print(f"response.request.body(json): {json.dumps(requestJson, indent=2)}")
|
||||||
# print(f"response.request.body: {json.dumps(requestJson, indent=2)}")
|
except:
|
||||||
print("---- response")
|
print(f"response.request.body(plain): {response.request.body}")
|
||||||
|
print(f"---- response ({context})")
|
||||||
|
print(f"response.status_code: {response.status_code}")
|
||||||
print(f"response.reason: {response.reason}")
|
print(f"response.reason: {response.reason}")
|
||||||
responseJson = json.loads(response.text)
|
try:
|
||||||
print(f"response.text: {json.dumps(responseJson, indent=2)}")
|
responseJson = json.loads(response.text)
|
||||||
# print(f"response.text: {json.dumps(response.text, indent=2)}")
|
print(f"response.text(json): {json.dumps(responseJson, indent=2)}")
|
||||||
|
except:
|
||||||
|
print(f"response.text(plain): {response.text}")
|
||||||
|
|
||||||
|
# --- test functions
|
||||||
|
|
||||||
|
def test_invalidRoute(self):
|
||||||
|
response = self.client.get("/")
|
||||||
|
assert response.status_code == 404
|
||||||
|
|
||||||
|
def test_getVersion(self):
|
||||||
|
response = self.client.get(self._apiUrl("/version"))
|
||||||
|
# self._dumpRequestAndResponse("test_getVersion", response)
|
||||||
|
assert response.status_code == 200
|
||||||
|
assert response.json()["version"] == "1.0"
|
||||||
|
|
||||||
|
def _test_getInfo(self):
|
||||||
|
response = self.client.get( self._apiUrl("/info" ))
|
||||||
|
# self._dumpRequestAndResponse("test_getInfo", response)
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
|
|
||||||
@pytest.mark.skip(reason="Ignore me temporarily")
|
def _test_getChannelsOfEmptyTable(self):
|
||||||
def test_insert_energy(self):
|
response = self.client.get(self._apiUrl("/channels"))
|
||||||
energyData = {
|
# self._dumpRequestAndResponse("test_getChannelsOfEmptyTable", response)
|
||||||
"timestamp": datetime.now().isoformat(),
|
assert response.status_code == 200
|
||||||
"value": 234.5,
|
assert response.json()["channels"] == []
|
||||||
}
|
|
||||||
print(f"energyData: {energyData}")
|
def _test_getBulkDataOfEmptyTable(self):
|
||||||
# response = self.client.put("/energies/1", json=energyData) #, headers={"X-Token": "coneofsilence"})
|
response = self.client.get(
|
||||||
|
self._apiUrl("/bulkData"),
|
||||||
|
json = {
|
||||||
|
"channel_ids": [1],
|
||||||
|
"fromTime": "0001-01-01T00:00:00"
|
||||||
|
}
|
||||||
|
)
|
||||||
|
self._dumpRequestAndResponse("test_getBulkDataOfEmptyTable", response)
|
||||||
|
assert response.status_code == 200
|
||||||
|
assert "bulk" in response.json()
|
||||||
|
assert response.json()["bulk"] == [
|
||||||
|
{
|
||||||
|
"channel_id": 1,
|
||||||
|
"channel": None,
|
||||||
|
"data": [],
|
||||||
|
"msg": None
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
def test_fillDatabase(self):
|
||||||
|
self._fillDatabase()
|
||||||
|
response = self.client.get(self._apiUrl("/1/count"))
|
||||||
|
# self._dumpRequestAndResponse("test_fillDatabase", response)
|
||||||
|
assert response.status_code == 200
|
||||||
|
|
||||||
|
def test_getChannels(self):
|
||||||
|
response = self.client.get(self._apiUrl("/channels"))
|
||||||
|
# self._dumpRequestAndResponse("test_getChannels", response)
|
||||||
|
assert response.status_code == 200
|
||||||
|
|
||||||
|
def test_getChannelId(self):
|
||||||
|
response = self.client.get(self._apiUrl("/channels/total_yield/id"))
|
||||||
|
# self._dumpRequestAndResponse("test_getChannelId", response)
|
||||||
|
assert response.status_code == 200
|
||||||
|
|
||||||
|
response = self.client.get(self._apiUrl("/channels/dc_power1/id"))
|
||||||
|
# self._dumpRequestAndResponse("test_getChannelId", response)
|
||||||
|
assert response.status_code == 200
|
||||||
|
|
||||||
|
response = self.client.get(self._apiUrl("/channels/daily_yield/id"))
|
||||||
|
# self._dumpRequestAndResponse("test_getChannelId", response)
|
||||||
|
assert response.status_code == 200
|
||||||
|
|
||||||
|
def test_putChannels(self):
|
||||||
|
response = self.client.get(self._apiUrl("/channels/frequency/id"))
|
||||||
|
# self._dumpRequestAndResponse("test_getChannelId", response)
|
||||||
|
assert response.status_code == 500
|
||||||
|
|
||||||
response = self.client.put(
|
response = self.client.put(
|
||||||
self.restApiRoot + "2",
|
self._apiUrl("/channels"),
|
||||||
# params=energyData,
|
json = {"channels": [{"name": "frequency"}]}
|
||||||
json=energyData) #, headers={"X-Token": "coneofsilence"})
|
)
|
||||||
# print(f"dir(response): {dir(response)}")
|
# self._dumpRequestAndResponse("test_fillDatabase", 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.status_code == 200
|
||||||
# assert response.json()["msg"] == ""
|
|
||||||
|
|
||||||
@pytest.mark.skip(reason="Ignore me temporarily")
|
response = self.client.get(self._apiUrl("/channels/frequency/id"))
|
||||||
def test_get_energy(self):
|
# self._dumpRequestAndResponse("test_getChannelId", response)
|
||||||
response = self.client.get(self.restApiRoot + "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.status_code == 200
|
||||||
# assert response.json()["msg"] == ""
|
|
||||||
|
def _test_putBulkData(self):
|
||||||
|
response = self.client.put(
|
||||||
|
self._apiUrl("/bulkData"),
|
||||||
|
json = {"bulk": {
|
||||||
|
"channel_id": None,
|
||||||
|
"channel": "total_yield",
|
||||||
|
"data": [
|
||||||
|
{ "timestamp": "2020-12-11T12:01:20", "value": 120120.1 },
|
||||||
|
{ "timestamp": "2020-12-11T12:30:25", "value": 123025.2 },
|
||||||
|
]
|
||||||
|
}}
|
||||||
|
)
|
||||||
|
self._dumpRequestAndResponse("test_putBulkData", response)
|
||||||
|
assert response.status_code == 200
|
||||||
|
|
||||||
|
# def test_getRecordCount(self):
|
||||||
|
# response = self.client.get(self._apiUrl("/1/count"))
|
||||||
|
# self._dumpRequestAndResponse("test_getRecordCount", response)
|
||||||
|
# assert response.status_code == 200
|
||||||
|
# assert response.json()["info"] == {"columns"}
|
||||||
|
|
||||||
|
# def _test_bulkData_put(self):
|
||||||
|
# # response = self.client.put("/energy/bulkData", json=self.bulkTestData);
|
||||||
|
# response = self.client.put(
|
||||||
|
# self.restApiRoot + "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_getInfo2(self):
|
||||||
|
# response = self.client.get( self.restApiRoot + "info" )
|
||||||
|
# # 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("---- response")
|
||||||
|
# print(f"response.reason: {response.reason}")
|
||||||
|
# # print(f"response.text: {response.text}")
|
||||||
|
# responseJson = json.loads(response.text)
|
||||||
|
# print(f"response.text: {json.dumps(responseJson, indent=2)}")
|
||||||
|
# assert False #response.status_code == 404
|
||||||
|
|
||||||
|
# def _test_bulkData_get(self):
|
||||||
|
# print(f"DB_URL: {os.getenv('DATABASE_URL')}")
|
||||||
|
# # 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(
|
||||||
|
# self.restApiRoot + "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(
|
||||||
|
# self.restApiRoot + "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(self.restApiRoot + "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"] == ""
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue