Bring everything up-to-date

This commit is contained in:
Scott Wallace 2025-02-25 18:19:38 +00:00
parent 0042d2367a
commit c4fbfe53a3
Signed by: scott
SSH key fingerprint: SHA256:+LJug6Dj01Jdg86CILGng9r0lJseUrpI0xfRqdW9Uws
2 changed files with 78 additions and 82 deletions

View file

@ -1,20 +1,19 @@
FROM python:3.8-slim-buster FROM python:3.11-alpine AS base
# Keeps Python from generating .pyc files in the container # Keeps Python from generating .pyc files in the container
ENV PYTHONDONTWRITEBYTECODE 1
# Turns off buffering for easier container logging
ENV PYTHONUNBUFFERED 1
FROM base AS build
ADD requirements.txt . ADD requirements.txt .
RUN python -m pip install -r requirements.txt RUN python -m pip install --user -r requirements.txt
FROM base AS app
RUN adduser -h /app -D appuser
COPY --from=build --chown=appuser /root/.local /app/.local
WORKDIR /app WORKDIR /app
COPY main.py /app COPY --chown=appuser main.py /app
RUN useradd appuser && chown -R appuser /app
USER appuser USER appuser
FROM app
ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONUNBUFFERED=1
EXPOSE 5000 EXPOSE 5000
ENTRYPOINT ["python", "main.py"] ENTRYPOINT ["python", "main.py"]

139
main.py
View file

@ -4,7 +4,7 @@ Present observational weather data to Prometheus
import json import json
import os import os
from typing import Dict, List, TypedDict from typing import TypedDict
import requests import requests
from flask import Flask, Response from flask import Flask, Response
@ -21,33 +21,28 @@ class PromData(TypedDict):
""" """
key: str key: str
labels: Dict[str, str] labels: dict[str, str]
type: str type: str
value: float value: float
def fetch_weather_data(location: int, apikey: str) -> Dict[str, Dict[str, float]]: def fetch_weather_data(location: str, apikey: str) -> dict[str, dict[str, float]]:
""" """
Fetch current data from the Met Office for the provided postcode Fetch current data from the Met Office for the provided postcode
""" """
obs_data = requests.get( obs_data = requests.get(f"http://api.weatherapi.com/v1/current.json?key={apikey}&q={location}&aqi=yes", timeout=30)
f'http://api.weatherapi.com/v1/current.json?'
f'key={apikey}&'
f'q={location}&'
f'aqi=yes'
)
return dict(json.loads(obs_data.content)) return dict(json.loads(obs_data.content))
@app.route('/metrics') @app.route("/metrics")
def metrics() -> Response: def metrics() -> Response:
""" """
Output Prometheus-style metrics Output Prometheus-style metrics
""" """
apikey = os.environ.get('WEATHER_APIKEY') apikey = os.environ.get("WEATHER_APIKEY")
location = os.environ.get('WEATHER_LOCATION') location = os.environ.get("WEATHER_LOCATION")
if not location: if not location:
return Response( return Response(
@ -55,88 +50,88 @@ def metrics() -> Response:
status=500, status=500,
) )
latest_data = fetch_weather_data(int(location), str(apikey))['current'] latest_data = fetch_weather_data(location, str(apikey))["current"]
ret_data: List[PromData] = [ ret_data: list[PromData] = [
{ {
'key': 'sensor_weather_outdoor_temperature_celsius', "key": "sensor_weather_outdoor_temperature_celsius",
'labels': { "labels": {
'location': location, "location": location,
}, },
'type': 'gauge', "type": "gauge",
'value': float(latest_data['temp_c']), "value": float(latest_data["temp_c"]),
}, },
{ {
'key': 'sensor_weather_outdoor_humidity_percent', "key": "sensor_weather_outdoor_humidity_percent",
'labels': { "labels": {
'location': location, "location": location,
}, },
'type': 'gauge', "type": "gauge",
'value': float(latest_data['humidity']), "value": float(latest_data["humidity"]),
}, },
{ {
'key': 'sensor_weather_outdoor_uv', "key": "sensor_weather_outdoor_uv",
'labels': { "labels": {
'location': location, "location": location,
}, },
'type': 'gauge', "type": "gauge",
'value': float(latest_data['uv']), "value": float(latest_data["uv"]),
}, },
{ {
'key': 'sensor_weather_outdoor_cloud_percent', "key": "sensor_weather_outdoor_cloud_percent",
'labels': { "labels": {
'location': location, "location": location,
}, },
'type': 'gauge', "type": "gauge",
'value': float(latest_data['cloud']), "value": float(latest_data["cloud"]),
}, },
{ {
'key': 'sensor_weather_outdoor_precip', "key": "sensor_weather_outdoor_precip",
'labels': { "labels": {
'location': location, "location": location,
}, },
'type': 'gauge', "type": "gauge",
'value': float(latest_data['precip_mm']), "value": float(latest_data["precip_mm"]),
}, },
{ {
'key': 'sensor_weather_outdoor_wind_speed', "key": "sensor_weather_outdoor_wind_speed",
'labels': { "labels": {
'location': location, "location": location,
}, },
'type': 'gauge', "type": "gauge",
'value': float(latest_data['wind_kph']), "value": float(latest_data["wind_kph"]),
}, },
{ {
'key': 'sensor_weather_outdoor_wind_gust', "key": "sensor_weather_outdoor_wind_gust",
'labels': { "labels": {
'location': location, "location": location,
}, },
'type': 'gauge', "type": "gauge",
'value': float(latest_data['gust_kph']), "value": float(latest_data["gust_kph"]),
}, },
{ {
'key': 'sensor_weather_outdoor_wind_direction', "key": "sensor_weather_outdoor_wind_direction",
'labels': { "labels": {
'location': location, "location": location,
}, },
'type': 'gauge', "type": "gauge",
'value': float(latest_data['wind_degree']), "value": float(latest_data["wind_degree"]),
}, },
{ {
'key': 'sensor_weather_outdoor_temp_feel_c', "key": "sensor_weather_outdoor_temp_feel_c",
'labels': { "labels": {
'location': location, "location": location,
}, },
'type': 'gauge', "type": "gauge",
'value': float(latest_data['feelslike_c']), "value": float(latest_data["feelslike_c"]),
}, },
{ {
'key': 'sensor_weather_outdoor_pressure', "key": "sensor_weather_outdoor_pressure",
'labels': { "labels": {
'location': location, "location": location,
}, },
'type': 'gauge', "type": "gauge",
'value': float(latest_data['pressure_mb']), "value": float(latest_data["pressure_mb"]),
}, },
] ]
@ -146,17 +141,19 @@ def metrics() -> Response:
ret_strs.append(f'# TYPE {item["key"]} {item["type"]}') ret_strs.append(f'# TYPE {item["key"]} {item["type"]}')
ret_strs.append( ret_strs.append(
str(item["key"]) str(item["key"])
+ '{' + "{"
+ " ".join([f'{key}="{val}"' for key, val in item["labels"].items()]) + " ".join([f'{key}="{val}"' for key, val in item["labels"].items()])
+ '} ' + "} "
+ str(item["value"]) + str(
item["value"],
)
) )
resp = Response('\n'.join(ret_strs)) resp = Response("\n".join(ret_strs))
resp.headers['Content-type'] = 'text/plain' resp.headers["Content-type"] = "text/plain"
return resp return resp
if __name__ == '__main__': if __name__ == "__main__":
app.run(host='0.0.0.0') app.run(host="0.0.0.0")