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, |
