Mercurial > personal > weather-server
changeset 1:f66df122f18d
get the skeleton of a webserver up?
author | Paul Fisher <paul@pfish.zone> |
---|---|
date | Sun, 29 Sep 2019 00:52:13 -0400 |
parents | efe7a1eff167 |
children | cda47993a193 |
files | weather_server/locations.py weather_server/server.py weather_server/static/favicon.ico weather_server/templates/location.html |
diffstat | 3 files changed, 137 insertions(+), 0 deletions(-) [+] |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/weather_server/locations.py Sun Sep 29 00:52:13 2019 -0400 @@ -0,0 +1,54 @@ +"""Manages the directory containing the various logs.""" + +import configparser +import pathlib +import typing as t + +import attr +import pytz + +from . import logfile +from . import types + + +CONFIG_FILE = 'config.ini' +LOG = 'log.bson' + + +@attr.s(frozen=True, slots=True) +class LocationInfo: + name = attr.ib(type=str) + tz_name = attr.ib(type=str) + password = attr.ib(type=str) + + def timezone(self): + try: + return pytz.timezone(self.tz_name) + except pytz.UnknownTimeZoneError: + return pytz.UTC + + @classmethod + def load(cls, config_file: pathlib.Path) -> 'LocationInfo': + parser = configparser.ConfigParser(interpolation=None) + parser.read(config_file) + return LocationInfo( + name=parser.get('location', 'name', fallback='Weather station'), + tz_name=parser.get('location', 'timezone', fallback='UTC'), + password=parser.get('location', 'password')) + + +class Locations: + def __init__(self, base: str): + self._path = pathlib.Path(base) + + def paths(self) -> t.Tuple[str, ...]: + return tuple(sorted(f.name for f in self._path.iterdir())) + + def get(self, name) -> t.Tuple[LocationInfo, logfile.Logger]: + try: + directory = self._path / name + return ( + LocationInfo.load(directory / CONFIG_FILE), + logfile.Logger.create(str(directory / LOG))) + except OSError: + raise KeyError(name)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/weather_server/server.py Sun Sep 29 00:52:13 2019 -0400 @@ -0,0 +1,69 @@ +import bson +import flask + +from . import common +from . import locations +from . import types + + +def build_app(root_directory: str) -> flask.Flask: + locs = locations.Locations(root_directory) + app = flask.Flask(__name__) + + @app.route('/favicon.ico') + def favicon(): + return flask.send_file('static/favicon.ico') + + @app.route('/') + def home(): + return 'Weather server' + + @app.route('/_submit', methods=['POST']) + def submit(): + req = flask.request + target = req.args.get('location') + if not target: + flask.abort(404) + try: + target_loc = locs.get(target) + except KeyError: + flask.abort(404) + + password = req.args.get('password') + if password != target_loc.password: + flask.abort(401) + + reader = bson.decode_file_iter( + req.stream, codec_options=common.BSON_OPTIONS) + entries = [ + types.Reading.from_now( + sample_time=item['sample_time'], + temp_c=item['temp_c'], + rh_pct=item['rh_pct'], + ) + for item in reader + ] + target_loc.logger.write_rows(entries) + return {'status': 'OK'} + + @app.route('/<location>') + def show(location): + try: + loc, logger = locs.get(location) + except KeyError: + flask.abort(404) + data = logger.data + if data: + last_reading = data[-1] + tz = loc.timezone + date = tz.normalize(last_reading.sample_time.astimezone(tz)) + else: + last_reading = None + date = None + return flask.render_template( + 'location.html', + location=loc, + last_reading=last_reading, + date=date) + + return app
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/weather_server/templates/location.html Sun Sep 29 00:52:13 2019 -0400 @@ -0,0 +1,14 @@ +<!doctype html> +<html> + <head> + <title>Weather for {{ location.name }}</title> + </head> + <body> + {% if last_reading %} + Temperature: {{ last_reading.temp_f }}<br> + Dew point: {{ last_reading.dew_point_f }} + {% else %} + No weather yet + {% endif %} + </body> +</html>