annotate weather_server/server.py @ 34:8d3f32455575

Bump version.
author Paul Fisher <paul@pfish.zone>
date Sat, 12 Jun 2021 20:24:30 +0000
parents 7def5611895b
children
Ignore whitespace changes - Everywhere: Within whitespace: At end of lines:
rev   line source
11
52ef21607b31 server: Create endpoint to get some recent readings.
Paul Fisher <paul@pfish.zone>
parents: 8
diff changeset
1 import datetime
4
e7c8dcc5fc15 Make the weather server pip-installable and locally runnable.
Paul Fisher <paul@pfish.zone>
parents: 3
diff changeset
2 import hmac
21
beb42c835c52 Make weather server handle arbitrary data:
Paul Fisher <paul@pfish.zone>
parents: 16
diff changeset
3 import pathlib
5
368f732f13d0 Actually make the server runnable.
Paul Fisher <paul@pfish.zone>
parents: 4
diff changeset
4 import sys
4
e7c8dcc5fc15 Make the weather server pip-installable and locally runnable.
Paul Fisher <paul@pfish.zone>
parents: 3
diff changeset
5
1
f66df122f18d get the skeleton of a webserver up?
Paul Fisher <paul@pfish.zone>
parents:
diff changeset
6 import bson
f66df122f18d get the skeleton of a webserver up?
Paul Fisher <paul@pfish.zone>
parents:
diff changeset
7 import flask
21
beb42c835c52 Make weather server handle arbitrary data:
Paul Fisher <paul@pfish.zone>
parents: 16
diff changeset
8 import pytz
1
f66df122f18d get the skeleton of a webserver up?
Paul Fisher <paul@pfish.zone>
parents:
diff changeset
9
f66df122f18d get the skeleton of a webserver up?
Paul Fisher <paul@pfish.zone>
parents:
diff changeset
10 from . import common
f66df122f18d get the skeleton of a webserver up?
Paul Fisher <paul@pfish.zone>
parents:
diff changeset
11 from . import locations
f66df122f18d get the skeleton of a webserver up?
Paul Fisher <paul@pfish.zone>
parents:
diff changeset
12
f66df122f18d get the skeleton of a webserver up?
Paul Fisher <paul@pfish.zone>
parents:
diff changeset
13
f66df122f18d get the skeleton of a webserver up?
Paul Fisher <paul@pfish.zone>
parents:
diff changeset
14 def build_app(root_directory: str) -> flask.Flask:
21
beb42c835c52 Make weather server handle arbitrary data:
Paul Fisher <paul@pfish.zone>
parents: 16
diff changeset
15 locs = locations.LocationFolder(pathlib.Path(root_directory))
1
f66df122f18d get the skeleton of a webserver up?
Paul Fisher <paul@pfish.zone>
parents:
diff changeset
16 app = flask.Flask(__name__)
8
d54155a199d8 Improve template; add stylesheet.
Paul Fisher <paul@pfish.zone>
parents: 5
diff changeset
17 app.config['SEND_FILE_MAX_AGE_DEFAULT'] = 0
1
f66df122f18d get the skeleton of a webserver up?
Paul Fisher <paul@pfish.zone>
parents:
diff changeset
18
f66df122f18d get the skeleton of a webserver up?
Paul Fisher <paul@pfish.zone>
parents:
diff changeset
19 @app.route('/favicon.ico')
f66df122f18d get the skeleton of a webserver up?
Paul Fisher <paul@pfish.zone>
parents:
diff changeset
20 def favicon():
f66df122f18d get the skeleton of a webserver up?
Paul Fisher <paul@pfish.zone>
parents:
diff changeset
21 return flask.send_file('static/favicon.ico')
f66df122f18d get the skeleton of a webserver up?
Paul Fisher <paul@pfish.zone>
parents:
diff changeset
22
f66df122f18d get the skeleton of a webserver up?
Paul Fisher <paul@pfish.zone>
parents:
diff changeset
23 @app.route('/')
f66df122f18d get the skeleton of a webserver up?
Paul Fisher <paul@pfish.zone>
parents:
diff changeset
24 def home():
26
7def5611895b Add support for an index page displaying all locations.
Paul Fisher <paul@pfish.zone>
parents: 23
diff changeset
25 locations = tuple(locs.locations().values())
7def5611895b Add support for an index page displaying all locations.
Paul Fisher <paul@pfish.zone>
parents: 23
diff changeset
26 return flask.render_template(
7def5611895b Add support for an index page displaying all locations.
Paul Fisher <paul@pfish.zone>
parents: 23
diff changeset
27 'index.html',
7def5611895b Add support for an index page displaying all locations.
Paul Fisher <paul@pfish.zone>
parents: 23
diff changeset
28 locations=locations,
7def5611895b Add support for an index page displaying all locations.
Paul Fisher <paul@pfish.zone>
parents: 23
diff changeset
29 )
1
f66df122f18d get the skeleton of a webserver up?
Paul Fisher <paul@pfish.zone>
parents:
diff changeset
30
f66df122f18d get the skeleton of a webserver up?
Paul Fisher <paul@pfish.zone>
parents:
diff changeset
31 @app.route('/_submit', methods=['POST'])
f66df122f18d get the skeleton of a webserver up?
Paul Fisher <paul@pfish.zone>
parents:
diff changeset
32 def submit():
f66df122f18d get the skeleton of a webserver up?
Paul Fisher <paul@pfish.zone>
parents:
diff changeset
33 req = flask.request
f66df122f18d get the skeleton of a webserver up?
Paul Fisher <paul@pfish.zone>
parents:
diff changeset
34 reader = bson.decode_file_iter(
f66df122f18d get the skeleton of a webserver up?
Paul Fisher <paul@pfish.zone>
parents:
diff changeset
35 req.stream, codec_options=common.BSON_OPTIONS)
3
b42c4bfe57c7 server: Use a "preamble" object in the POST to auth.
Paul Fisher <paul@pfish.zone>
parents: 2
diff changeset
36 try:
b42c4bfe57c7 server: Use a "preamble" object in the POST to auth.
Paul Fisher <paul@pfish.zone>
parents: 2
diff changeset
37 preamble = next(reader)
b42c4bfe57c7 server: Use a "preamble" object in the POST to auth.
Paul Fisher <paul@pfish.zone>
parents: 2
diff changeset
38 loc_name = preamble['location']
b42c4bfe57c7 server: Use a "preamble" object in the POST to auth.
Paul Fisher <paul@pfish.zone>
parents: 2
diff changeset
39 password = str(preamble['password'])
21
beb42c835c52 Make weather server handle arbitrary data:
Paul Fisher <paul@pfish.zone>
parents: 16
diff changeset
40 loc = locs.get(loc_name)
3
b42c4bfe57c7 server: Use a "preamble" object in the POST to auth.
Paul Fisher <paul@pfish.zone>
parents: 2
diff changeset
41 if not hmac.compare_digest(password, loc.password):
b42c4bfe57c7 server: Use a "preamble" object in the POST to auth.
Paul Fisher <paul@pfish.zone>
parents: 2
diff changeset
42 flask.abort(400)
21
beb42c835c52 Make weather server handle arbitrary data:
Paul Fisher <paul@pfish.zone>
parents: 16
diff changeset
43 entries = tuple(reader)
3
b42c4bfe57c7 server: Use a "preamble" object in the POST to auth.
Paul Fisher <paul@pfish.zone>
parents: 2
diff changeset
44 except (KeyError, bson.InvalidBSON):
b42c4bfe57c7 server: Use a "preamble" object in the POST to auth.
Paul Fisher <paul@pfish.zone>
parents: 2
diff changeset
45 flask.abort(400)
21
beb42c835c52 Make weather server handle arbitrary data:
Paul Fisher <paul@pfish.zone>
parents: 16
diff changeset
46 now = datetime.datetime.now(tz=pytz.UTC)
beb42c835c52 Make weather server handle arbitrary data:
Paul Fisher <paul@pfish.zone>
parents: 16
diff changeset
47 loc.record(entries, now)
2
cda47993a193 server: fix bugs and improve template.
Paul Fisher <paul@pfish.zone>
parents: 1
diff changeset
48 return flask.jsonify({'status': 'OK'})
1
f66df122f18d get the skeleton of a webserver up?
Paul Fisher <paul@pfish.zone>
parents:
diff changeset
49
f66df122f18d get the skeleton of a webserver up?
Paul Fisher <paul@pfish.zone>
parents:
diff changeset
50 @app.route('/<location>')
11
52ef21607b31 server: Create endpoint to get some recent readings.
Paul Fisher <paul@pfish.zone>
parents: 8
diff changeset
51 def show(location: str):
1
f66df122f18d get the skeleton of a webserver up?
Paul Fisher <paul@pfish.zone>
parents:
diff changeset
52 try:
21
beb42c835c52 Make weather server handle arbitrary data:
Paul Fisher <paul@pfish.zone>
parents: 16
diff changeset
53 loc = locs.get(location)
1
f66df122f18d get the skeleton of a webserver up?
Paul Fisher <paul@pfish.zone>
parents:
diff changeset
54 except KeyError:
f66df122f18d get the skeleton of a webserver up?
Paul Fisher <paul@pfish.zone>
parents:
diff changeset
55 flask.abort(404)
21
beb42c835c52 Make weather server handle arbitrary data:
Paul Fisher <paul@pfish.zone>
parents: 16
diff changeset
56 last_reading = loc.latest()
beb42c835c52 Make weather server handle arbitrary data:
Paul Fisher <paul@pfish.zone>
parents: 16
diff changeset
57 if last_reading:
2
cda47993a193 server: fix bugs and improve template.
Paul Fisher <paul@pfish.zone>
parents: 1
diff changeset
58 tz = loc.timezone()
1
f66df122f18d get the skeleton of a webserver up?
Paul Fisher <paul@pfish.zone>
parents:
diff changeset
59 date = tz.normalize(last_reading.sample_time.astimezone(tz))
f66df122f18d get the skeleton of a webserver up?
Paul Fisher <paul@pfish.zone>
parents:
diff changeset
60 else:
f66df122f18d get the skeleton of a webserver up?
Paul Fisher <paul@pfish.zone>
parents:
diff changeset
61 date = None
23
88249e451566 server: show date when last report was >12h ago.
Paul Fisher <paul@pfish.zone>
parents: 21
diff changeset
62 now = datetime.datetime.now(tz=pytz.UTC)
88249e451566 server: show date when last report was >12h ago.
Paul Fisher <paul@pfish.zone>
parents: 21
diff changeset
63 diff = (now - date) if date else None
88249e451566 server: show date when last report was >12h ago.
Paul Fisher <paul@pfish.zone>
parents: 21
diff changeset
64 is_recent = diff and diff < datetime.timedelta(hours=12)
1
f66df122f18d get the skeleton of a webserver up?
Paul Fisher <paul@pfish.zone>
parents:
diff changeset
65 return flask.render_template(
f66df122f18d get the skeleton of a webserver up?
Paul Fisher <paul@pfish.zone>
parents:
diff changeset
66 'location.html',
f66df122f18d get the skeleton of a webserver up?
Paul Fisher <paul@pfish.zone>
parents:
diff changeset
67 location=loc,
f66df122f18d get the skeleton of a webserver up?
Paul Fisher <paul@pfish.zone>
parents:
diff changeset
68 last_reading=last_reading,
23
88249e451566 server: show date when last report was >12h ago.
Paul Fisher <paul@pfish.zone>
parents: 21
diff changeset
69 date=date,
88249e451566 server: show date when last report was >12h ago.
Paul Fisher <paul@pfish.zone>
parents: 21
diff changeset
70 date_format=f'%H:%M' if is_recent else '%Y-%m-%d %H:%M')
1
f66df122f18d get the skeleton of a webserver up?
Paul Fisher <paul@pfish.zone>
parents:
diff changeset
71
11
52ef21607b31 server: Create endpoint to get some recent readings.
Paul Fisher <paul@pfish.zone>
parents: 8
diff changeset
72 @app.route('/<location>/recent')
52ef21607b31 server: Create endpoint to get some recent readings.
Paul Fisher <paul@pfish.zone>
parents: 8
diff changeset
73 def recent(location: str):
52ef21607b31 server: Create endpoint to get some recent readings.
Paul Fisher <paul@pfish.zone>
parents: 8
diff changeset
74 try:
21
beb42c835c52 Make weather server handle arbitrary data:
Paul Fisher <paul@pfish.zone>
parents: 16
diff changeset
75 loc = locs.get(location)
11
52ef21607b31 server: Create endpoint to get some recent readings.
Paul Fisher <paul@pfish.zone>
parents: 8
diff changeset
76 except KeyError:
52ef21607b31 server: Create endpoint to get some recent readings.
Paul Fisher <paul@pfish.zone>
parents: 8
diff changeset
77 flask.abort(404)
52ef21607b31 server: Create endpoint to get some recent readings.
Paul Fisher <paul@pfish.zone>
parents: 8
diff changeset
78 req = flask.request
52ef21607b31 server: Create endpoint to get some recent readings.
Paul Fisher <paul@pfish.zone>
parents: 8
diff changeset
79
52ef21607b31 server: Create endpoint to get some recent readings.
Paul Fisher <paul@pfish.zone>
parents: 8
diff changeset
80 try:
52ef21607b31 server: Create endpoint to get some recent readings.
Paul Fisher <paul@pfish.zone>
parents: 8
diff changeset
81 seconds = int(req.args['seconds'])
52ef21607b31 server: Create endpoint to get some recent readings.
Paul Fisher <paul@pfish.zone>
parents: 8
diff changeset
82 except (KeyError, ValueError):
52ef21607b31 server: Create endpoint to get some recent readings.
Paul Fisher <paul@pfish.zone>
parents: 8
diff changeset
83 flask.abort(400)
52ef21607b31 server: Create endpoint to get some recent readings.
Paul Fisher <paul@pfish.zone>
parents: 8
diff changeset
84
52ef21607b31 server: Create endpoint to get some recent readings.
Paul Fisher <paul@pfish.zone>
parents: 8
diff changeset
85 start = common.utc_now() - datetime.timedelta(seconds=seconds)
52ef21607b31 server: Create endpoint to get some recent readings.
Paul Fisher <paul@pfish.zone>
parents: 8
diff changeset
86
21
beb42c835c52 Make weather server handle arbitrary data:
Paul Fisher <paul@pfish.zone>
parents: 16
diff changeset
87 readings = [r for r in loc.entries if start < r['sample_time']]
11
52ef21607b31 server: Create endpoint to get some recent readings.
Paul Fisher <paul@pfish.zone>
parents: 8
diff changeset
88 resp = flask.Response()
52ef21607b31 server: Create endpoint to get some recent readings.
Paul Fisher <paul@pfish.zone>
parents: 8
diff changeset
89 resp.content_type = 'application/json'
16
9a609bcf0809 Port main script to TypeScript and prepare for serving it.
Paul Fisher <paul@pfish.zone>
parents: 11
diff changeset
90 resp.data = common.json_dumps({
9a609bcf0809 Port main script to TypeScript and prepare for serving it.
Paul Fisher <paul@pfish.zone>
parents: 11
diff changeset
91 'timezone': loc.tz_name,
9a609bcf0809 Port main script to TypeScript and prepare for serving it.
Paul Fisher <paul@pfish.zone>
parents: 11
diff changeset
92 'readings': readings,
9a609bcf0809 Port main script to TypeScript and prepare for serving it.
Paul Fisher <paul@pfish.zone>
parents: 11
diff changeset
93 })
11
52ef21607b31 server: Create endpoint to get some recent readings.
Paul Fisher <paul@pfish.zone>
parents: 8
diff changeset
94 return resp
52ef21607b31 server: Create endpoint to get some recent readings.
Paul Fisher <paul@pfish.zone>
parents: 8
diff changeset
95
1
f66df122f18d get the skeleton of a webserver up?
Paul Fisher <paul@pfish.zone>
parents:
diff changeset
96 return app
4
e7c8dcc5fc15 Make the weather server pip-installable and locally runnable.
Paul Fisher <paul@pfish.zone>
parents: 3
diff changeset
97
e7c8dcc5fc15 Make the weather server pip-installable and locally runnable.
Paul Fisher <paul@pfish.zone>
parents: 3
diff changeset
98
e7c8dcc5fc15 Make the weather server pip-installable and locally runnable.
Paul Fisher <paul@pfish.zone>
parents: 3
diff changeset
99 def main(argv):
e7c8dcc5fc15 Make the weather server pip-installable and locally runnable.
Paul Fisher <paul@pfish.zone>
parents: 3
diff changeset
100 """Main function for a simple local demo."""
5
368f732f13d0 Actually make the server runnable.
Paul Fisher <paul@pfish.zone>
parents: 4
diff changeset
101 app = build_app(argv[0])
4
e7c8dcc5fc15 Make the weather server pip-installable and locally runnable.
Paul Fisher <paul@pfish.zone>
parents: 3
diff changeset
102 app.run(host='0.0.0.0')
e7c8dcc5fc15 Make the weather server pip-installable and locally runnable.
Paul Fisher <paul@pfish.zone>
parents: 3
diff changeset
103
e7c8dcc5fc15 Make the weather server pip-installable and locally runnable.
Paul Fisher <paul@pfish.zone>
parents: 3
diff changeset
104
e7c8dcc5fc15 Make the weather server pip-installable and locally runnable.
Paul Fisher <paul@pfish.zone>
parents: 3
diff changeset
105 if __name__ == '__main__':
e7c8dcc5fc15 Make the weather server pip-installable and locally runnable.
Paul Fisher <paul@pfish.zone>
parents: 3
diff changeset
106 main(sys.argv[1:])