Skip to content

Commit

Permalink
Merge pull request #228 from BrewBlox/develop
Browse files Browse the repository at this point in the history
Edge release
  • Loading branch information
steersbob authored Jan 17, 2024
2 parents 8a20d42 + c83a433 commit 4ae6378
Show file tree
Hide file tree
Showing 36 changed files with 2,508 additions and 2,388 deletions.
2 changes: 2 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
.venv/
.vscode/
9 changes: 5 additions & 4 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ on:
branches: ["**"]
pull_request:
branches: [develop, edge]
workflow_dispatch: {}

env:
DOCKER_IMAGE: ghcr.io/brewblox/brewblox-history
Expand All @@ -20,7 +21,7 @@ jobs:
- uses: docker/setup-buildx-action@v2
- uses: actions/setup-python@v4
with:
python-version: "3.9"
python-version: "3.11"

- name: Get image metadata
id: meta
Expand All @@ -47,9 +48,9 @@ jobs:
poetry run pytest
poetry run flake8
- name: Run setup script
- name: Build
run: |
bash docker/before_build.sh
poetry run invoke build
- name: Build Docker image
uses: docker/build-push-action@v4
Expand All @@ -58,4 +59,4 @@ jobs:
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
platforms: linux/amd64,linux/arm/v7,linux/arm64/v8
context: docker
context: .
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: 3 additions & 2 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@
"python.defaultInterpreterPath": ".venv/bin/python",
"python.terminal.activateEnvInCurrentTerminal": true,
"python.terminal.activateEnvironment": true,
"python.linting.flake8Enabled": true,
"python.linting.pylintEnabled": false,
"python.testing.pytestArgs": [
"--no-cov",
"."
Expand All @@ -15,5 +13,8 @@
"**/.venv/*/**": true,
"victoria/**": true,
"redis/**": true
},
"[python]": {
"editor.defaultFormatter": "ms-python.autopep8"
}
}
42 changes: 42 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
FROM python:3.11-bookworm as base

ENV PIP_EXTRA_INDEX_URL=https://www.piwheels.org/simple
ENV PIP_FIND_LINKS=/wheeley
ENV VENV=/app/.venv
ENV PATH="$VENV/bin:$PATH"

COPY ./dist /app/dist

RUN <<EOF
set -ex

mkdir /wheeley
python3 -m venv $VENV
pip3 install --upgrade pip wheel setuptools
pip3 wheel --wheel-dir=/wheeley -r /app/dist/requirements.txt
pip3 wheel --wheel-dir=/wheeley /app/dist/*.tar.gz
EOF

FROM python:3.11-slim-bookworm
EXPOSE 5000
WORKDIR /app

ENV PIP_FIND_LINKS=/wheeley
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

python3 -m venv $VENV
pip3 install --no-index brewblox_history
pip3 freeze
rm -rf /wheeley
EOF


ENTRYPOINT ["bash", "./entrypoint.sh"]
33 changes: 1 addition & 32 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,34 +1,3 @@
# History Service

The history service is the gatekeeper for Brewblox databases. It writes data from history events, and offers REST interfaces for querying the InfluxDB and Redis databases.

## Features

### QueryClient ([influx.py](./brewblox_history/influx.py))

Handles directly querying InfluxDB. API functions eventually call this.

### InfluxWriter ([influx.py](./brewblox_history/influx.py))

Periodically writes scheduled data points to InfluxDB.

Publicly offers the `write_soon()` function, where data can be scheduled for writing.

### DataRelay ([relays.py](./brewblox_history/relays.py))

Subscribes to the `--history-topic` topic on the event bus, and schedules all received data for writing to the database.

### Datastore ([redis.py](./brewblox_history/redis.py))

Offers a simple wrapper around the Redis API.
Changes are broadcast to the `--datastore-topic` topic.

## REST API

### queries ([query_api.py](./brewblox_history/query_api.py))

REST API for the InfluxDB database. Also offers WebSocket endpoints for streamed values.

### datastore ([datastore_api](./brewblox_history/datastore_api.py))

REST API for the Redis datastore.
The history service is the gatekeeper for Brewblox databases. It writes data from history events, and offers REST interfaces for querying the Victoria and Redis databases.
87 changes: 0 additions & 87 deletions brewblox_history/__main__.py

This file was deleted.

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

from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse

from . import (datastore_api, mqtt, redis, relays, timeseries_api, utils,
victoria)
from .models import ErrorResponse

LOGGER = logging.getLogger(__name__)


def setup_logging(debug: bool):
level = logging.DEBUG if debug else logging.INFO
unimportant_level = logging.INFO if debug else logging.WARN
format = '%(asctime)s.%(msecs)03d [%(levelname).1s:%(name)s:%(lineno)d] %(message)s'
datefmt = '%Y/%m/%d %H:%M:%S'

logging.basicConfig(level=level, format=format, datefmt=datefmt)
logging.captureWarnings(True)

logging.getLogger('gmqtt').setLevel(unimportant_level)
logging.getLogger('httpx').setLevel(unimportant_level)
logging.getLogger('httpcore').setLevel(logging.WARN)
logging.getLogger('uvicorn.access').setLevel(unimportant_level)
logging.getLogger('uvicorn.error').disabled = True


def add_exception_handlers(app: FastAPI):
config = utils.get_config()
logger = logging.getLogger('history.error')

@app.exception_handler(Exception)
async def catchall_handler(request: Request, exc: Exception) -> JSONResponse:
short = utils.strex(exc)
details = utils.strex(exc, tb=config.debug)
content = ErrorResponse(error=str(exc),
details=details)

logger.error(f'[{request.url}] => {short}')
logger.debug(details)
return JSONResponse(content.model_dump(), status_code=500)


@asynccontextmanager
async def lifespan(app: FastAPI):
LOGGER.info(utils.get_config())
LOGGER.debug('ROUTES:\n' + pformat(app.routes))
LOGGER.debug('LOGGERS:\n' + pformat(logging.root.manager.loggerDict))

async with AsyncExitStack() as stack:
await stack.enter_async_context(mqtt.lifespan())
await stack.enter_async_context(redis.lifespan())
yield


def create_app() -> FastAPI:
config = utils.get_config()
setup_logging(config.debug)

# Call setup functions for modules
mqtt.setup()
redis.setup()
victoria.setup()
relays.setup()

# Create app
# OpenApi endpoints are set to /api/doc for backwards compatibility
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')

# Set standardized error response
add_exception_handlers(app)

# Include all endpoints declared by modules
app.include_router(datastore_api.router, prefix=prefix)
app.include_router(timeseries_api.router, prefix=prefix)

return app
Loading

0 comments on commit 4ae6378

Please sign in to comment.