comparison weather_server/server.py @ 21:beb42c835c52

Make weather server handle arbitrary data: - Make logfile record arbitrary BSONs - Make server handlers OK with same - Make location type a normal class rather than attrs; have it handle its own logger. - Bump version number.
author Paul Fisher <paul@pfish.zone>
date Sat, 19 Oct 2019 18:40:48 -0400
parents 9a609bcf0809
children 88249e451566
comparison
equal deleted inserted replaced
20:a7fe635d1c88 21:beb42c835c52
1 import datetime 1 import datetime
2 import hmac 2 import hmac
3 import pathlib
3 import sys 4 import sys
4 5
5 import bson 6 import bson
6 import flask 7 import flask
8 import pytz
7 9
8 from . import common 10 from . import common
9 from . import locations 11 from . import locations
10 from . import types
11 12
12 13
13 def build_app(root_directory: str) -> flask.Flask: 14 def build_app(root_directory: str) -> flask.Flask:
14 locs = locations.Locations(root_directory) 15 locs = locations.LocationFolder(pathlib.Path(root_directory))
15 app = flask.Flask(__name__) 16 app = flask.Flask(__name__)
16 app.config['SEND_FILE_MAX_AGE_DEFAULT'] = 0 17 app.config['SEND_FILE_MAX_AGE_DEFAULT'] = 0
17 18
18 @app.route('/favicon.ico') 19 @app.route('/favicon.ico')
19 def favicon(): 20 def favicon():
30 req.stream, codec_options=common.BSON_OPTIONS) 31 req.stream, codec_options=common.BSON_OPTIONS)
31 try: 32 try:
32 preamble = next(reader) 33 preamble = next(reader)
33 loc_name = preamble['location'] 34 loc_name = preamble['location']
34 password = str(preamble['password']) 35 password = str(preamble['password'])
35 loc, logger = locs.get(loc_name) 36 loc = locs.get(loc_name)
36 if not hmac.compare_digest(password, loc.password): 37 if not hmac.compare_digest(password, loc.password):
37 flask.abort(400) 38 flask.abort(400)
38 entries = [ 39 entries = tuple(reader)
39 types.Reading.from_now(
40 sample_time=item['sample_time'],
41 temp_c=item['temp_c'],
42 rh_pct=item['rh_pct'],
43 )
44 for item in reader
45 ]
46 except (KeyError, bson.InvalidBSON): 40 except (KeyError, bson.InvalidBSON):
47 flask.abort(400) 41 flask.abort(400)
48 logger.write_rows(entries) 42 now = datetime.datetime.now(tz=pytz.UTC)
43 loc.record(entries, now)
49 return flask.jsonify({'status': 'OK'}) 44 return flask.jsonify({'status': 'OK'})
50 45
51 @app.route('/<location>') 46 @app.route('/<location>')
52 def show(location: str): 47 def show(location: str):
53 try: 48 try:
54 loc, logger = locs.get(location) 49 loc = locs.get(location)
55 except KeyError: 50 except KeyError:
56 flask.abort(404) 51 flask.abort(404)
57 data = logger.data 52 last_reading = loc.latest()
58 if data: 53 if last_reading:
59 last_reading = data[-1]
60 tz = loc.timezone() 54 tz = loc.timezone()
61 date = tz.normalize(last_reading.sample_time.astimezone(tz)) 55 date = tz.normalize(last_reading.sample_time.astimezone(tz))
62 else: 56 else:
63 last_reading = None
64 date = None 57 date = None
65 return flask.render_template( 58 return flask.render_template(
66 'location.html', 59 'location.html',
67 location=loc, 60 location=loc,
68 last_reading=last_reading, 61 last_reading=last_reading,
69 date=date) 62 date=date)
70 63
71 @app.route('/<location>/recent') 64 @app.route('/<location>/recent')
72 def recent(location: str): 65 def recent(location: str):
73 try: 66 try:
74 loc, logger = locs.get(location) 67 loc = locs.get(location)
75 except KeyError: 68 except KeyError:
76 flask.abort(404) 69 flask.abort(404)
77 req = flask.request 70 req = flask.request
78 71
79 try: 72 try:
81 except (KeyError, ValueError): 74 except (KeyError, ValueError):
82 flask.abort(400) 75 flask.abort(400)
83 76
84 start = common.utc_now() - datetime.timedelta(seconds=seconds) 77 start = common.utc_now() - datetime.timedelta(seconds=seconds)
85 78
86 readings = [ 79 readings = [r for r in loc.entries if start < r['sample_time']]
87 r.as_dict() for r in logger.data
88 if start < r.sample_time
89 ]
90 resp = flask.Response() 80 resp = flask.Response()
91 resp.content_type = 'application/json' 81 resp.content_type = 'application/json'
92 resp.data = common.json_dumps({ 82 resp.data = common.json_dumps({
93 'timezone': loc.tz_name, 83 'timezone': loc.tz_name,
94 'readings': readings, 84 'readings': readings,