Skip to content

Commit

Permalink
proof of concept fastapi scaffolding
Browse files Browse the repository at this point in the history
  • Loading branch information
steersbob committed Nov 24, 2023
1 parent 488d231 commit d4a1adf
Show file tree
Hide file tree
Showing 14 changed files with 1,030 additions and 1,073 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -75,3 +75,4 @@ victoria/*

redis/*
!redis/.gitkeep
.appenv
5 changes: 4 additions & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ ENV VENV=/app/.venv
ENV PATH="$VENV/bin:$PATH"

COPY --from=base /wheeley /wheeley
COPY ./parse_appenv.py ./parse_appenv.py
COPY ./entrypoint.sh ./entrypoint.sh

RUN <<EOF
set -ex
Expand All @@ -36,4 +38,5 @@ RUN <<EOF
rm -rf /wheeley
EOF

ENTRYPOINT ["python3", "-m", "brewblox_history"]

ENTRYPOINT ["bash", "./entrypoint.sh"]
60 changes: 0 additions & 60 deletions brewblox_history/__main__.py

This file was deleted.

84 changes: 84 additions & 0 deletions brewblox_history/app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
from contextlib import AsyncExitStack, asynccontextmanager
from pprint import pformat

from fastapi import FastAPI

from . import datastore_api, redis, settings

LOGGER = settings.brewblox_logger(__name__)


@asynccontextmanager
async def lifespan(app: FastAPI):
LOGGER.info(settings.get_config())
LOGGER.debug('\n' + pformat(app.routes))
async with AsyncExitStack() as stack:
await stack.enter_async_context(redis.lifespan())
yield


def create_app():
settings.get_config.cache_clear()
settings.init_logging()
config = settings.get_config()

redis.client.set(redis.RedisClient())

prefix = f'/{config.name}'
app = FastAPI(lifespan=lifespan,
docs_url=f'{prefix}/api/doc',
redoc_url=f'{prefix}/api/redoc',
openapi_url=f'{prefix}/openapi.json')

app.include_router(datastore_api.router, prefix=prefix)

return app

# def create_parser():
# parser = service.create_parser('history')
# parser.add_argument('--ranges-interval',
# help='Interval (sec) between updates in live ranges. [%(default)s]',
# default=10,
# type=float)
# parser.add_argument('--metrics-interval',
# help='Interval (sec) between updates in live metrics. [%(default)s]',
# default=5,
# type=float)
# parser.add_argument('--redis-url',
# help='URL for the Redis database',
# default='redis://redis')
# parser.add_argument('--victoria-url',
# help='URL for the Victoria Metrics database',
# default='http://victoria:8428/victoria')
# parser.add_argument('--datastore-topic',
# help='Synchronization topic for datastore updates',
# default='brewcast/datastore')
# parser.add_argument('--minimum-step',
# help='Minimum period (sec) for range data downsampling',
# default=10,
# type=float)
# return parser


# def main():
# parser = create_parser()
# config = service.create_config(parser, model=ServiceConfig)
# app = service.create_app(config)

# async def setup():
# scheduler.setup(app)
# http.setup(app)
# mqtt.setup(app)
# socket_closer.setup(app)
# victoria.setup(app)
# timeseries_api.setup(app)
# redis.setup(app)
# datastore_api.setup(app)
# relays.setup(app)
# error_response.setup(app)

# service.run_app(app, setup())


# if __name__ == '__main__':
# main()
200 changes: 75 additions & 125 deletions brewblox_history/datastore_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,130 +2,80 @@
REST endpoints for datastore queries
"""

from aiohttp import web
from aiohttp_pydantic import PydanticView
from aiohttp_pydantic.oas.typing import r200
from brewblox_service import brewblox_logger

from brewblox_history import redis
from brewblox_history.models import (DatastoreDeleteResponse,
DatastoreMultiQuery,
DatastoreMultiValueBox,
DatastoreOptSingleValueBox,
DatastoreSingleQuery,
DatastoreSingleValueBox)
from fastapi import APIRouter, Response

from . import redis
from .models import (DatastoreDeleteResponse, DatastoreMultiQuery,
DatastoreMultiValueBox, DatastoreOptSingleValueBox,
DatastoreSingleQuery, DatastoreSingleValueBox)
from .settings import brewblox_logger

LOGGER = brewblox_logger(__name__)
routes = web.RouteTableDef()


class RedisView(PydanticView):
def __init__(self, request: web.Request) -> None:
super().__init__(request)
self.redis = redis.fget(request.app)


@routes.view('/datastore/ping')
class PingView(RedisView):
async def get(self) -> r200[dict]:
"""
Ping datastore, checking availability.
Tags: Datastore
"""
await self.redis.ping()
return web.json_response(
data={'ping': 'pong'},
headers={
'Cache-Control': 'no-cache',
'Pragma': 'no-cache',
'Expires': '0',
})


@routes.view('/datastore/get')
class GetView(RedisView):
async def post(self, args: DatastoreSingleQuery) -> r200[DatastoreOptSingleValueBox]:
"""
Get specific object from the datastore.
Tags: Datastore
"""
value = await self.redis.get(args.namespace, args.id)
return web.json_response(
DatastoreOptSingleValueBox(value=value).dict()
)


@routes.view('/datastore/mget')
class MGetView(RedisView):
async def post(self, args: DatastoreMultiQuery) -> r200[DatastoreMultiValueBox]:
"""
Get multiple objects from the datastore.
Tags: Datastore
"""
values = await self.redis.mget(args.namespace, args.ids, args.filter)
return web.json_response(
DatastoreMultiValueBox(values=values).dict()
)


@routes.view('/datastore/set')
class SetView(RedisView):
async def post(self, args: DatastoreSingleValueBox) -> r200[DatastoreSingleValueBox]:
"""
Create or update an object in the datastore.
Tags: Datastore
"""
value = await self.redis.set(args.value)
return web.json_response(
DatastoreSingleValueBox(value=value).dict()
)


@routes.view('/datastore/mset')
class MSetView(RedisView):
async def post(self, args: DatastoreMultiValueBox) -> r200[DatastoreMultiValueBox]:
"""
Create or update multiple objects in the datastore.
Tags: Datastore
"""
values = await self.redis.mset(args.values)
return web.json_response(
DatastoreMultiValueBox(values=values).dict()
)


@routes.view('/datastore/delete')
class DeleteView(RedisView):
async def post(self, args: DatastoreSingleQuery) -> r200[DatastoreDeleteResponse]:
"""
Remove a single object from the datastore.
Tags: Datastore
"""
count = await self.redis.delete(args.namespace, args.id)
return web.json_response(
DatastoreDeleteResponse(count=count).dict()
)


@routes.view('/datastore/mdelete')
class MDeleteView(RedisView):
async def post(self, args: DatastoreMultiQuery) -> r200[DatastoreDeleteResponse]:
"""
Remove multiple objects from the datastore.
Tags: Datastore
"""
count = await self.redis.mdelete(args.namespace, args.ids, args.filter)
return web.json_response(
DatastoreDeleteResponse(count=count).dict()
)


def setup(app: web.Application):
app.router.add_routes(routes)

router = APIRouter(prefix='/datastore', tags=['Datastore'])


@router.get('/ping')
async def ping(response: Response):
"""
Ping datastore, checking availability.
"""
response.headers['Cache-Control'] = 'no-cache, no-store, must-revalidate, proxy-revalidate, max-age=0'
response.headers['Pragma'] = 'no-cache'
response.headers['Expires'] = '0'
await redis.client.get().ping()
return {'ping': 'pong'}


@router.post('/get')
async def datastore_get(args: DatastoreSingleQuery) -> DatastoreOptSingleValueBox:
"""
Get a specific object from the datastore.
"""
value = await redis.client.get().get(args.namespace, args.id)
return DatastoreOptSingleValueBox(value=value)


@router.post('/mget')
async def datastore_mget(args: DatastoreMultiQuery) -> DatastoreMultiQuery:
"""
Get multiple objects from the datastore.
"""
values = await redis.client.get().mget(args.namespace, args.ids, args.filter)
return DatastoreMultiValueBox(values=values)


@router.post('/set')
async def datastore_set(args: DatastoreOptSingleValueBox) -> DatastoreSingleValueBox:
"""
Create or update an object in the datastore.
"""
value = await redis.client.get().set(args.value)
return DatastoreSingleValueBox(value=value)


@router.post('/mset')
async def datastore_mset(args: DatastoreMultiValueBox) -> DatastoreMultiValueBox:
"""
Create or update multiple objects in the datastore.
"""
values = await redis.client.get().mset(args.values)
return DatastoreMultiValueBox(values=values)


@router.post('/delete')
async def datastore_delete(args: DatastoreSingleQuery) -> DatastoreDeleteResponse:
"""
Remove a single object from the datastore.
"""
count = await redis.client.get().delete(args.namespace, args.id)
return DatastoreDeleteResponse(count=count)


@router.post('/mdelete')
async def datastore_mdelete(args: DatastoreMultiQuery) -> DatastoreDeleteResponse:
"""
Remove multiple objects from the datastore.
"""
count = await redis.client.get().mdelete(args.namespace, args.ids, args.filter)
return DatastoreDeleteResponse(count=count)
Loading

0 comments on commit d4a1adf

Please sign in to comment.