-
Notifications
You must be signed in to change notification settings - Fork 1
/
web.py
146 lines (123 loc) · 4.75 KB
/
web.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
import datetime
import json
import logging
import multiprocessing
import os
from dataclasses import dataclass
from typing import Optional, Dict, Final
import waitress
from flask import Flask, jsonify, request, make_response
from flask_cors import CORS
import lib_mpex
from log import LOG_DEFAULT_FMT
from ntfy import NtfyRecord, FeedbackNotification, FeedbackType
@dataclass
class WebConfig:
log_level: Optional[int] = logging.INFO
port: int = 5550
bind_to: str = "*"
liveness_tick_s: float = 30.0
class WebServer(lib_mpex.ChildProcess):
def __init__(
self,
config: WebConfig,
ntfy_share_ns,
ntfy_records: Dict[str, NtfyRecord],
ntfy_queue: multiprocessing.Queue,
health_share_ns,
):
self._config = config
self._ntfy_share_ns = ntfy_share_ns
self._ntfy_records = ntfy_records
self._ntfy_queue = ntfy_queue
self._health_share_ns = health_share_ns
def _run(self):
logging.getLogger("waitress").setLevel(self._config.log_level + 10)
logger = logging.getLogger(__name__)
logging.basicConfig(level=self._config.log_level, format=LOG_DEFAULT_FMT)
logger.info("configuring web server")
unhealthy_t: Final = datetime.timedelta(
seconds=2 * self._config.liveness_tick_s
)
app = Flask(__name__)
CORS(app)
@app.route("/health", methods=["GET"])
def health():
logger.debug(f"{request.remote_addr} {request.method} {request.path}")
if not self._health_share_ns.last_health_ping_at:
return jsonify(
{
"error": "no model health pings yet",
"status": "unhealthy",
}
), 503
if (
datetime.datetime.now(datetime.UTC)
- self._health_share_ns.last_health_ping_at
>= unhealthy_t
):
return jsonify(
{
"error": f"no model health pings in {unhealthy_t}",
"status": "unhealthy",
}
), 503
return jsonify({"status": "ok"})
@app.route("/mute", methods=["POST"])
def mute():
data = request.get_json()
logger.info(
f"{request.remote_addr} {request.method} {request.path} {json.dumps(data)}"
)
key = data.get("key", None)
if not key:
return jsonify({"error": "missing 'key' field"}), 403
if key not in self._ntfy_records:
return jsonify({"error": "bad key"}), 403
secs = data.get("s", None)
if secs is None:
return jsonify({"error": "missing 's' field"}), 400
try:
secs = int(secs)
except ValueError:
return jsonify({"error": "invalid 's' field"}), 400
utcnow = datetime.datetime.now(datetime.UTC)
if secs < 1:
mute_until = utcnow
resp_ntfy = FeedbackNotification(
type=FeedbackType.UNMUTED,
key=key,
)
else:
mute_until = utcnow + datetime.timedelta(
seconds=secs,
)
resp_ntfy = FeedbackNotification(
type=FeedbackType.MUTED,
key=key,
mute_seconds=secs,
)
self._ntfy_share_ns.mute_until = mute_until
logger.info(f"muted until {mute_until}")
self._ntfy_queue.put_nowait(resp_ntfy)
return jsonify({"status": "ok"})
@app.route("/photo/<fname>", methods=["GET"])
def photo(fname):
logger.info(f"{request.remote_addr} {request.method} {request.path}")
if not fname.endswith(".jpg"):
return jsonify({"error": "invalid filename"}), 400
key = fname[:-4]
photo_rec: Optional[NtfyRecord] = self._ntfy_records.get(key, None)
if photo_rec is None:
return jsonify({"error": "record not found"}), 404
if photo_rec.jpeg_image is None:
return jsonify({"error": "record has no photo"}), 404
response = make_response(photo_rec.jpeg_image)
response.headers.set("Content-Type", "image/jpeg")
return response
logger.info("starting web server")
listen = f"{self._config.bind_to}:{self._config.port}"
dm_docker_also_bind = os.getenv("DM_DOCKER_ALSO_BIND", None)
if dm_docker_also_bind:
listen = listen + " " + dm_docker_also_bind
waitress.serve(app, listen=listen)