from ecdsa.numbertheory import modular_exp
from fastapi import Depends, FastAPI, HTTPException, status, Request
from fastapi.middleware.cors import CORSMiddleware
from fastapi.security import OAuth2PasswordRequestForm
from fastapi.staticfiles import StaticFiles
from pydantic.tools import parse_obj_as
from sqlmodel import Session, select, func, text
from loguru import logger
import requests
from starlette.websockets import WebSocket, WebSocketDisconnect
import uvicorn
import json
from datetime import datetime, timedelta
from urllib import parse
from core import security, data_models, database, websockets, settings
from core.settings import get_settings, VERSION
from typing import List, Optional

# instantiate api.
logger.remove(0)
logger.add(f"./log/apilog_{datetime.now().strftime('%Y-%m-%d')}.log", rotation="1 day",
           colorize=False, format="{time:YYYY-MM-DD HH:mm:ss.SSS} | {level} | <level>{message}</level>")

app = FastAPI(title='Weatherstation API',
              version=VERSION, debug=False)
app.add_middleware(
    CORSMiddleware,
    allow_origins=['*'],
    allow_credentials=True,
    allow_methods=['*'],
    allow_headers=['*'],
)

# add middleware to capture logs
notifier = websockets.Notifier()


@app.middleware("http")
async def log_middle(request: Request, call_next):
    logger.debug(
        f" {request.client.host} | {request.method} {request.url}")

    logger.debug("Params:")
    for name, value in request.path_params.items():
        logger.debug(f"\t{name}: {value}")

    response = await call_next(request)
    return response


@app.on_event("startup")
async def startup_event():
    logger.debug("Starting logging.")
    await notifier.generator.asend(None)


# Posting weather data from station. not user endpoint.
# new function to reduce boilerplate on get request with large amount of params (why are professional companies creating devices that tranfer data in get requests...)
# parameters are mapped to an Observation model in Depends
@app.get("/weatherstation/updateweatherstation.php", status_code=status.HTTP_201_CREATED)
async def store(PASSWORD: str, observation: data_models.Observation = Depends(data_models.create_observation)):

    if PASSWORD == get_settings().ACCESSCTL:
        try:
            metric_obs = parse_obj_as(
                data_models.Metric_Observation, observation)
            logger.debug("Pushing observation to websocket")
            await notifier._notify(metric_obs.json())
            logger.debug("Done")
        except Exception as ex:
            logger.exception("error pushing websocket", ex)
            raise HTTPException(
                status_code=status.HTTP_500_INTERNAL_SERVER_ERROR)

        try:
            if get_settings().WINDY_ENABLED:
                url = "https://stations.windy.com/pws/update/%s?winddir=%s&windspeedmph=%s&windgustmph=%s&tempf=%s&rainin=%s&baromin=%s&dewptf=%s&humidity=%s&dateutc=%s" % (
                    parse.quote_plus(get_settings().WINDYKEY), observation.winddir, observation.windspeedmph, observation.windgustmph, observation.tempf, observation.rainin, observation.baromin, observation.dewptf, observation.humidity, observation.dateutc)
                requests.get(url)
        except Exception as ex:
            logger.exception(f"Error pushing data to windy", ex)

        try:
            with Session(database.engine) as session:
                session.add(observation)
                session.commit()
        except Exception as ex:
            logger.exception("Error saving observation to database", ex)
            raise HTTPException(
                status_code=status.HTTP_500_INTERNAL_SERVER_ERROR)
        return

    else:
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED
        )


# Returns list of Observation objects based on input criteria.
# TODO: rework result_interval. Currently slices results and returns every Nth item.
# To get hourly intervals enter 12 as incoming observersions are stored every 5 minutes.
# Endpoint is public. Sharing is caring.
# NEW:
# https://chartio.com/resources/tutorials/how-to-execute-raw-sql-in-sqlalchemy/
# https://stackoverflow.com/questions/52952734/how-to-perform-date-trunc-query-in-postgres-using-sqlalchemy
# select *
# from (
#     select * ,
#            row_number() over (partition by date_trunc('hour', dateutc)) as r
#     from "Observations"
# ) as dt
# where r = 1
# and dateutc > (NOW() - interval '1 day')
# order by id desc;
@app.get("/weatherstation/getweather", status_code=status.HTTP_200_OK, response_model=List[data_models.Observation])
async def get_weather(day_delta: int = 1, result_interval: int = 12, imperial: Optional[bool] = False):

    if (day_delta > 31 or day_delta <= 0):
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="day_delta must be between 0 and 31"
        )
    if result_interval <= 0:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="result_interval must be a non-zero positive value"
        )

    time = datetime.utcnow() - timedelta(days=day_delta)

    try:
        with Session(database.engine) as session:
            statement = select(data_models.Observation
                               ).where(data_models.Observation.dateutc > time
                                       ).order_by(data_models.Observation.id.desc())

            results = session.exec(statement).all()
            # convert to metric via inheritance with validator decorators.
            # Don't return response_model as Metric_Observation as conversions will otherwise be applied twice as its passing through the application stack
            if imperial:  # switch between imperial and metric measurements. Imperial is standard form the source.
                return results[::result_interval]
            else:
                metric_results = parse_obj_as(
                    List[data_models.Metric_Observation], results[::result_interval])
                return metric_results
    except Exception as ex:
        logger.exception(
            f"failed weather lookup with day_delta: {day_delta}, result_interval {result_interval}", ex)
        raise HTTPException(
            status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
            detail="Database lookup failed"
        )


@app.get("/weatherstation/gethourly", status_code=status.HTTP_200_OK, response_model=List[data_models.Observation])
async def get_weather_new(day_delta: int = 1, imperial: Optional[bool] = False):

    if (day_delta > 31 or day_delta <= 0):
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="day_delta must be between 0 and 31"
        )
    time = datetime.utcnow() - timedelta(days=day_delta)

    try:
        with Session(database.engine) as session:

            statement = select(data_models.Observation).from_statement(text(f"""select *
                            from (
                                select * ,row_number() over (partition by date_trunc('hour', dateutc)) as r
                                from "public"."Observations"
                            ) as dt
                            where r = 1
                            and dateutc > (NOW() - interval ':x day')
                            order by id desc;"""))

            results = session.execute(statement, {"x": day_delta}).all()

            list = [i[0] for i in results]
            # convert to metric via inheritance with validator decorators.
            # Don't return response_model as Metric_Observation as conversions will otherwise be applied twice as its passing through the application stack
            if imperial:  # switch between imperial and metric measurements. Imperial is standard form the source.
                return list
            else:
                metric_results = parse_obj_as(
                    List[(data_models.Metric_Observation)], list)
                return metric_results
    except Exception as ex:
        logger.exception(
            f"failed weather lookup with day_delta: {day_delta}", ex)
        raise HTTPException(
            status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
            detail="Database lookup failed"
        )


@app.get("/weatherstation/getlatest", status_code=status.HTTP_200_OK, response_model=data_models.Observation)
async def get_latest(imperial: Optional[bool] = False):

    # speed optimization. the query will take longer if i dont limit the range due to the amount of data in the table
    time = datetime.utcnow() - timedelta(hours=1)

    try:
        with Session(database.engine) as session:
            statement = select(data_models.Observation
                               ).where(data_models.Observation.dateutc > time
                                       ).order_by(data_models.Observation.id.desc()
                                                  )

            result = session.exec(statement).first()

            # convert to metric via inheritance with validator decorators.
            # Don't return response_model as Metric_Observation as conversions will otherwise be applied twice as its passing through the application stack
            if imperial:  # switch between imperial and metric measurements. Imperial is standard form the source.
                return result
            else:
                metric_results = parse_obj_as(
                    data_models.Metric_Observation, result)
                return metric_results

    except Exception as ex:
        logger.exception(
            f"failed weather lookup", ex)
        raise HTTPException(
            status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
            detail="Database lookup failed"
        )


# Login endpoint, returns a token.
@app.post("/token", response_model=data_models.Token, status_code=status.HTTP_202_ACCEPTED)
async def login_for_access_token(form_data: OAuth2PasswordRequestForm = Depends()):

    try:
        user = security.authenticate_user(database.user_lookup(
            form_data.username), form_data.username, form_data.password)

    except Exception as ex:
        logger.exception(f"Problem logging in that isnt 401 related")
        raise HTTPException(
            status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
            detail="Incorrect username or password"
        )

    if not user:
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Incorrect username or password",
            headers={"WWW-Authenticate": "Bearer"}
        )
    access_token_expires = timedelta(
        minutes=get_settings().ACCESS_TOKEN_EXPIRE_MINUTES
    )
    access_token = security.create_access_token(
        data={"sub": user.username}, expires_delta=access_token_expires
    )

    return {
        "access_token": access_token,
        "token_type": "bearer",
        "expiry": datetime.now() + timedelta(minutes=get_settings().ACCESS_TOKEN_EXPIRE_MINUTES)
    }


@app.websocket_route("/ws")
async def websocket_endpoint(websocket: WebSocket):
    await notifier.connect(websocket)
    try:

        data = database.get_latest(imperial=False).json()
        # logger.debug(data)
        await websocket.send_text(data)
        while True:

            data = await websocket.receive_text()
            # await websocket.send_text(data)
    except WebSocketDisconnect:
        notifier.remove(websocket)


# test endpoint1 to be deactivated
@app.get("/users/me/", response_model=data_models.User, status_code=status.HTTP_200_OK)
async def read_users_me(current_user: data_models.User = Depends(security.get_current_active_user)):
    logger.info("getting user: {}",  current_user.username)
    return current_user


# Host wwwroot folder from /
app.mount("/", StaticFiles(directory="wwwroot",
          html=True), name="<NAME>")

# Dev mode launcher - not needed for prod
if __name__ == '__main__':
    uvicorn.run(app)

loguru.logger.exception sqlmodel.text core.websockets.Notifier fastapi.HTTPException loguru.logger.remove core.security.create_access_token loguru.logger.info uvicorn.run pydantic.tools.parse_obj_as fastapi.FastAPI core.database.get_latest sqlmodel.select requests.get fastapi.staticfiles.StaticFiles datetime.datetime.now core.database.user_lookup sqlmodel.Session core.data_models.Observation.id.desc core.settings.get_settings datetime.datetime.utcnow loguru.logger.debug fastapi.Depends datetime.timedelta
[((723, 739), 'loguru.logger.remove', 'logger.remove', (['(0)'], {}), '(0)\n', (736, 739), False, 'from loguru import logger\n'), ((940, 1005), 'fastapi.FastAPI', 'FastAPI', ([], {'title': '"""Weatherstation API"""', 'version': 'VERSION', 'debug': '(False)'}), "(title='Weatherstation API', version=VERSION, debug=False)\n", (947, 1005), False, 'from fastapi import Depends, FastAPI, HTTPException, status, Request\n'), ((1210, 1231), 'core.websockets.Notifier', 'websockets.Notifier', ([], {}), '()\n', (1229, 1231), False, 'from core import security, data_models, database, websockets, settings\n'), ((1313, 1385), 'loguru.logger.debug', 'logger.debug', (['f""" {request.client.host} | {request.method} {request.url}"""'], {}), "(f' {request.client.host} | {request.method} {request.url}')\n", (1325, 1385), False, 'from loguru import logger\n'), ((1400, 1423), 'loguru.logger.debug', 'logger.debug', (['"""Params:"""'], {}), "('Params:')\n", (1412, 1423), False, 'from loguru import logger\n'), ((1638, 1671), 'loguru.logger.debug', 'logger.debug', (['"""Starting logging."""'], {}), "('Starting logging.')\n", (1650, 1671), False, 'from loguru import logger\n'), ((2157, 2196), 'fastapi.Depends', 'Depends', (['data_models.create_observation'], {}), '(data_models.create_observation)\n', (2164, 2196), False, 'from fastapi import Depends, FastAPI, HTTPException, status, Request\n'), ((10007, 10016), 'fastapi.Depends', 'Depends', ([], {}), '()\n', (10014, 10016), False, 'from fastapi import Depends, FastAPI, HTTPException, status, Request\n'), ((10756, 10854), 'core.security.create_access_token', 'security.create_access_token', ([], {'data': "{'sub': user.username}", 'expires_delta': 'access_token_expires'}), "(data={'sub': user.username}, expires_delta=\n    access_token_expires)\n", (10784, 10854), False, 'from core import security, data_models, database, websockets, settings\n'), ((11674, 11715), 'fastapi.Depends', 'Depends', (['security.get_current_active_user'], {}), '(security.get_current_active_user)\n', (11681, 11715), False, 'from fastapi import Depends, FastAPI, HTTPException, status, Request\n'), ((11722, 11776), 'loguru.logger.info', 'logger.info', (['"""getting user: {}"""', 'current_user.username'], {}), "('getting user: {}', current_user.username)\n", (11733, 11776), False, 'from loguru import logger\n'), ((11848, 11891), 'fastapi.staticfiles.StaticFiles', 'StaticFiles', ([], {'directory': '"""wwwroot"""', 'html': '(True)'}), "(directory='wwwroot', html=True)\n", (11859, 11891), False, 'from fastapi.staticfiles import StaticFiles\n'), ((11992, 12008), 'uvicorn.run', 'uvicorn.run', (['app'], {}), '(app)\n', (12003, 12008), False, 'import uvicorn\n'), ((1484, 1518), 'loguru.logger.debug', 'logger.debug', (['f"""\t{name}: {value}"""'], {}), "(f'\\t{name}: {value}')\n", (1496, 1518), False, 'from loguru import logger\n'), ((3707, 3762), 'fastapi.HTTPException', 'HTTPException', ([], {'status_code': 'status.HTTP_401_UNAUTHORIZED'}), '(status_code=status.HTTP_401_UNAUTHORIZED)\n', (3720, 3762), False, 'from fastapi import Depends, FastAPI, HTTPException, status, Request\n'), ((4768, 4872), 'fastapi.HTTPException', 'HTTPException', ([], {'status_code': 'status.HTTP_400_BAD_REQUEST', 'detail': '"""day_delta must be between 0 and 31"""'}), "(status_code=status.HTTP_400_BAD_REQUEST, detail=\n    'day_delta must be between 0 and 31')\n", (4781, 4872), False, 'from fastapi import Depends, FastAPI, HTTPException, status, Request\n'), ((4945, 5064), 'fastapi.HTTPException', 'HTTPException', ([], {'status_code': 'status.HTTP_400_BAD_REQUEST', 'detail': '"""result_interval must be a non-zero positive value"""'}), "(status_code=status.HTTP_400_BAD_REQUEST, detail=\n    'result_interval must be a non-zero positive value')\n", (4958, 5064), False, 'from fastapi import Depends, FastAPI, HTTPException, status, Request\n'), ((5106, 5123), 'datetime.datetime.utcnow', 'datetime.utcnow', ([], {}), '()\n', (5121, 5123), False, 'from datetime import datetime, timedelta\n'), ((5126, 5151), 'datetime.timedelta', 'timedelta', ([], {'days': 'day_delta'}), '(days=day_delta)\n', (5135, 5151), False, 'from datetime import datetime, timedelta\n'), ((6630, 6734), 'fastapi.HTTPException', 'HTTPException', ([], {'status_code': 'status.HTTP_400_BAD_REQUEST', 'detail': '"""day_delta must be between 0 and 31"""'}), "(status_code=status.HTTP_400_BAD_REQUEST, detail=\n    'day_delta must be between 0 and 31')\n", (6643, 6734), False, 'from fastapi import Depends, FastAPI, HTTPException, status, Request\n'), ((6775, 6792), 'datetime.datetime.utcnow', 'datetime.utcnow', ([], {}), '()\n', (6790, 6792), False, 'from datetime import datetime, timedelta\n'), ((6795, 6820), 'datetime.timedelta', 'timedelta', ([], {'days': 'day_delta'}), '(days=day_delta)\n', (6804, 6820), False, 'from datetime import datetime, timedelta\n'), ((8601, 8618), 'datetime.datetime.utcnow', 'datetime.utcnow', ([], {}), '()\n', (8616, 8618), False, 'from datetime import datetime, timedelta\n'), ((8621, 8639), 'datetime.timedelta', 'timedelta', ([], {'hours': '(1)'}), '(hours=1)\n', (8630, 8639), False, 'from datetime import datetime, timedelta\n'), ((10451, 10592), 'fastapi.HTTPException', 'HTTPException', ([], {'status_code': 'status.HTTP_401_UNAUTHORIZED', 'detail': '"""Incorrect username or password"""', 'headers': "{'WWW-Authenticate': 'Bearer'}"}), "(status_code=status.HTTP_401_UNAUTHORIZED, detail=\n    'Incorrect username or password', headers={'WWW-Authenticate': 'Bearer'})\n", (10464, 10592), False, 'from fastapi import Depends, FastAPI, HTTPException, status, Request\n'), ((2219, 2233), 'core.settings.get_settings', 'get_settings', ([], {}), '()\n', (2231, 2233), False, 'from core.settings import get_settings, VERSION\n'), ((2283, 2340), 'pydantic.tools.parse_obj_as', 'parse_obj_as', (['data_models.Metric_Observation', 'observation'], {}), '(data_models.Metric_Observation, observation)\n', (2295, 2340), False, 'from pydantic.tools import parse_obj_as\n'), ((2370, 2418), 'loguru.logger.debug', 'logger.debug', (['"""Pushing observation to websocket"""'], {}), "('Pushing observation to websocket')\n", (2382, 2418), False, 'from loguru import logger\n'), ((2485, 2505), 'loguru.logger.debug', 'logger.debug', (['"""Done"""'], {}), "('Done')\n", (2497, 2505), False, 'from loguru import logger\n'), ((5175, 5199), 'sqlmodel.Session', 'Session', (['database.engine'], {}), '(database.engine)\n', (5182, 5199), False, 'from sqlmodel import Session, select, func, text\n'), ((6104, 6223), 'loguru.logger.exception', 'logger.exception', (['f"""failed weather lookup with day_delta: {day_delta}, result_interval {result_interval}"""', 'ex'], {}), "(\n    f'failed weather lookup with day_delta: {day_delta}, result_interval {result_interval}'\n    , ex)\n", (6120, 6223), False, 'from loguru import logger\n'), ((6241, 6343), 'fastapi.HTTPException', 'HTTPException', ([], {'status_code': 'status.HTTP_500_INTERNAL_SERVER_ERROR', 'detail': '"""Database lookup failed"""'}), "(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=\n    'Database lookup failed')\n", (6254, 6343), False, 'from fastapi import Depends, FastAPI, HTTPException, status, Request\n'), ((6844, 6868), 'sqlmodel.Session', 'Session', (['database.engine'], {}), '(database.engine)\n', (6851, 6868), False, 'from sqlmodel import Session, select, func, text\n'), ((8069, 8143), 'loguru.logger.exception', 'logger.exception', (['f"""failed weather lookup with day_delta: {day_delta}"""', 'ex'], {}), "(f'failed weather lookup with day_delta: {day_delta}', ex)\n", (8085, 8143), False, 'from loguru import logger\n'), ((8171, 8273), 'fastapi.HTTPException', 'HTTPException', ([], {'status_code': 'status.HTTP_500_INTERNAL_SERVER_ERROR', 'detail': '"""Database lookup failed"""'}), "(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=\n    'Database lookup failed')\n", (8184, 8273), False, 'from fastapi import Depends, FastAPI, HTTPException, status, Request\n'), ((8663, 8687), 'sqlmodel.Session', 'Session', (['database.engine'], {}), '(database.engine)\n', (8670, 8687), False, 'from sqlmodel import Session, select, func, text\n'), ((9600, 9646), 'loguru.logger.exception', 'logger.exception', (['f"""failed weather lookup"""', 'ex'], {}), "(f'failed weather lookup', ex)\n", (9616, 9646), False, 'from loguru import logger\n'), ((9674, 9776), 'fastapi.HTTPException', 'HTTPException', ([], {'status_code': 'status.HTTP_500_INTERNAL_SERVER_ERROR', 'detail': '"""Database lookup failed"""'}), "(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=\n    'Database lookup failed')\n", (9687, 9776), False, 'from fastapi import Depends, FastAPI, HTTPException, status, Request\n'), ((10071, 10111), 'core.database.user_lookup', 'database.user_lookup', (['form_data.username'], {}), '(form_data.username)\n', (10091, 10111), False, 'from core import security, data_models, database, websockets, settings\n'), ((10203, 10264), 'loguru.logger.exception', 'logger.exception', (['f"""Problem logging in that isnt 401 related"""'], {}), "(f'Problem logging in that isnt 401 related')\n", (10219, 10264), False, 'from loguru import logger\n'), ((10279, 10389), 'fastapi.HTTPException', 'HTTPException', ([], {'status_code': 'status.HTTP_500_INTERNAL_SERVER_ERROR', 'detail': '"""Incorrect username or password"""'}), "(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=\n    'Incorrect username or password')\n", (10292, 10389), False, 'from fastapi import Depends, FastAPI, HTTPException, status, Request\n'), ((10966, 10980), 'datetime.datetime.now', 'datetime.now', ([], {}), '()\n', (10978, 10980), False, 'from datetime import datetime, timedelta\n'), ((2550, 2597), 'loguru.logger.exception', 'logger.exception', (['"""error pushing websocket"""', 'ex'], {}), "('error pushing websocket', ex)\n", (2566, 2597), False, 'from loguru import logger\n'), ((2616, 2680), 'fastapi.HTTPException', 'HTTPException', ([], {'status_code': 'status.HTTP_500_INTERNAL_SERVER_ERROR'}), '(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR)\n', (2629, 2680), False, 'from fastapi import Depends, FastAPI, HTTPException, status, Request\n'), ((2727, 2741), 'core.settings.get_settings', 'get_settings', ([], {}), '()\n', (2739, 2741), False, 'from core.settings import get_settings, VERSION\n'), ((3205, 3222), 'requests.get', 'requests.get', (['url'], {}), '(url)\n', (3217, 3222), False, 'import requests\n'), ((3267, 3319), 'loguru.logger.exception', 'logger.exception', (['f"""Error pushing data to windy"""', 'ex'], {}), "(f'Error pushing data to windy', ex)\n", (3283, 3319), False, 'from loguru import logger\n'), ((3351, 3375), 'sqlmodel.Session', 'Session', (['database.engine'], {}), '(database.engine)\n', (3358, 3375), False, 'from sqlmodel import Session, select, func, text\n'), ((3506, 3566), 'loguru.logger.exception', 'logger.exception', (['"""Error saving observation to database"""', 'ex'], {}), "('Error saving observation to database', ex)\n", (3522, 3566), False, 'from loguru import logger\n'), ((3585, 3649), 'fastapi.HTTPException', 'HTTPException', ([], {'status_code': 'status.HTTP_500_INTERNAL_SERVER_ERROR'}), '(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR)\n', (3598, 3649), False, 'from fastapi import Depends, FastAPI, HTTPException, status, Request\n'), ((5395, 5428), 'core.data_models.Observation.id.desc', 'data_models.Observation.id.desc', ([], {}), '()\n', (5426, 5428), False, 'from core import security, data_models, database, websockets, settings\n'), ((5930, 6008), 'pydantic.tools.parse_obj_as', 'parse_obj_as', (['List[data_models.Metric_Observation]', 'results[::result_interval]'], {}), '(List[data_models.Metric_Observation], results[::result_interval])\n', (5942, 6008), False, 'from pydantic.tools import parse_obj_as\n'), ((6953, 7380), 'sqlmodel.text', 'text', (['f"""select *\n                            from (\n                                select * ,row_number() over (partition by date_trunc(\'hour\', dateutc)) as r\n                                from "public"."Observations"\n                            ) as dt\n                            where r = 1\n                            and dateutc > (NOW() - interval \':x day\')\n                            order by id desc;"""'], {}), '(\n    f"""select *\n                            from (\n                                select * ,row_number() over (partition by date_trunc(\'hour\', dateutc)) as r\n                                from "public"."Observations"\n                            ) as dt\n                            where r = 1\n                            and dateutc > (NOW() - interval \':x day\')\n                            order by id desc;"""\n    )\n', (6957, 7380), False, 'from sqlmodel import Session, select, func, text\n'), ((7915, 7971), 'pydantic.tools.parse_obj_as', 'parse_obj_as', (['List[data_models.Metric_Observation]', 'list'], {}), '(List[data_models.Metric_Observation], list)\n', (7927, 7971), False, 'from pydantic.tools import parse_obj_as\n'), ((8883, 8916), 'core.data_models.Observation.id.desc', 'data_models.Observation.id.desc', ([], {}), '()\n', (8914, 8916), False, 'from core import security, data_models, database, websockets, settings\n'), ((9451, 9503), 'pydantic.tools.parse_obj_as', 'parse_obj_as', (['data_models.Metric_Observation', 'result'], {}), '(data_models.Metric_Observation, result)\n', (9463, 9503), False, 'from pydantic.tools import parse_obj_as\n'), ((10688, 10702), 'core.settings.get_settings', 'get_settings', ([], {}), '()\n', (10700, 10702), False, 'from core.settings import get_settings, VERSION\n'), ((11196, 11231), 'core.database.get_latest', 'database.get_latest', ([], {'imperial': '(False)'}), '(imperial=False)\n', (11215, 11231), False, 'from core import security, data_models, database, websockets, settings\n'), ((767, 781), 'datetime.datetime.now', 'datetime.now', ([], {}), '()\n', (779, 781), False, 'from datetime import datetime, timedelta\n'), ((6906, 6937), 'sqlmodel.select', 'select', (['data_models.Observation'], {}), '(data_models.Observation)\n', (6912, 6937), False, 'from sqlmodel import Session, select, func, text\n'), ((11001, 11015), 'core.settings.get_settings', 'get_settings', ([], {}), '()\n', (11013, 11015), False, 'from core.settings import get_settings, VERSION\n'), ((5236, 5267), 'sqlmodel.select', 'select', (['data_models.Observation'], {}), '(data_models.Observation)\n', (5242, 5267), False, 'from sqlmodel import Session, select, func, text\n'), ((8724, 8755), 'sqlmodel.select', 'select', (['data_models.Observation'], {}), '(data_models.Observation)\n', (8730, 8755), False, 'from sqlmodel import Session, select, func, text\n'), ((2968, 2982), 'core.settings.get_settings', 'get_settings', ([], {}), '()\n', (2980, 2982), False, 'from core.settings import get_settings, VERSION\n')]