Mercurial > personal > weather-server
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, |