annotate weather_server/locations.py @ 39:b77c8e7d2742

Use zoneinfo rather than pytz.
author Paul Fisher <paul@pfish.zone>
date Tue, 01 Apr 2025 15:54:21 -0400
parents 2f3473416c11
children
Ignore whitespace changes - Everywhere: Within whitespace: At end of lines:
rev   line source
1
f66df122f18d get the skeleton of a webserver up?
Paul Fisher <paul@pfish.zone>
parents:
diff changeset
1 """Manages the directory containing the various logs."""
f66df122f18d get the skeleton of a webserver up?
Paul Fisher <paul@pfish.zone>
parents:
diff changeset
2
f66df122f18d get the skeleton of a webserver up?
Paul Fisher <paul@pfish.zone>
parents:
diff changeset
3 import configparser
21
beb42c835c52 Make weather server handle arbitrary data:
Paul Fisher <paul@pfish.zone>
parents: 9
diff changeset
4 import datetime
1
f66df122f18d get the skeleton of a webserver up?
Paul Fisher <paul@pfish.zone>
parents:
diff changeset
5 import pathlib
f66df122f18d get the skeleton of a webserver up?
Paul Fisher <paul@pfish.zone>
parents:
diff changeset
6 import typing as t
39
b77c8e7d2742 Use zoneinfo rather than pytz.
Paul Fisher <paul@pfish.zone>
parents: 35
diff changeset
7 import zoneinfo
1
f66df122f18d get the skeleton of a webserver up?
Paul Fisher <paul@pfish.zone>
parents:
diff changeset
8
f66df122f18d get the skeleton of a webserver up?
Paul Fisher <paul@pfish.zone>
parents:
diff changeset
9 from . import logfile
f66df122f18d get the skeleton of a webserver up?
Paul Fisher <paul@pfish.zone>
parents:
diff changeset
10 from . import types
f66df122f18d get the skeleton of a webserver up?
Paul Fisher <paul@pfish.zone>
parents:
diff changeset
11
f66df122f18d get the skeleton of a webserver up?
Paul Fisher <paul@pfish.zone>
parents:
diff changeset
12 CONFIG_FILE = 'config.ini'
f66df122f18d get the skeleton of a webserver up?
Paul Fisher <paul@pfish.zone>
parents:
diff changeset
13 LOG = 'log.bson'
f66df122f18d get the skeleton of a webserver up?
Paul Fisher <paul@pfish.zone>
parents:
diff changeset
14
f66df122f18d get the skeleton of a webserver up?
Paul Fisher <paul@pfish.zone>
parents:
diff changeset
15
21
beb42c835c52 Make weather server handle arbitrary data:
Paul Fisher <paul@pfish.zone>
parents: 9
diff changeset
16 class ConfigError(Exception):
beb42c835c52 Make weather server handle arbitrary data:
Paul Fisher <paul@pfish.zone>
parents: 9
diff changeset
17 """Raised when a location can't be loaded."""
beb42c835c52 Make weather server handle arbitrary data:
Paul Fisher <paul@pfish.zone>
parents: 9
diff changeset
18
beb42c835c52 Make weather server handle arbitrary data:
Paul Fisher <paul@pfish.zone>
parents: 9
diff changeset
19
beb42c835c52 Make weather server handle arbitrary data:
Paul Fisher <paul@pfish.zone>
parents: 9
diff changeset
20 class Location:
beb42c835c52 Make weather server handle arbitrary data:
Paul Fisher <paul@pfish.zone>
parents: 9
diff changeset
21
26
7def5611895b Add support for an index page displaying all locations.
Paul Fisher <paul@pfish.zone>
parents: 24
diff changeset
22 def __init__(self, root: pathlib.Path, key: str):
21
beb42c835c52 Make weather server handle arbitrary data:
Paul Fisher <paul@pfish.zone>
parents: 9
diff changeset
23 parser = configparser.ConfigParser(interpolation=None)
beb42c835c52 Make weather server handle arbitrary data:
Paul Fisher <paul@pfish.zone>
parents: 9
diff changeset
24 self.root = root
26
7def5611895b Add support for an index page displaying all locations.
Paul Fisher <paul@pfish.zone>
parents: 24
diff changeset
25 self.key = key
21
beb42c835c52 Make weather server handle arbitrary data:
Paul Fisher <paul@pfish.zone>
parents: 9
diff changeset
26 config_file = root / CONFIG_FILE
beb42c835c52 Make weather server handle arbitrary data:
Paul Fisher <paul@pfish.zone>
parents: 9
diff changeset
27 try:
beb42c835c52 Make weather server handle arbitrary data:
Paul Fisher <paul@pfish.zone>
parents: 9
diff changeset
28 with open(config_file, 'r') as infile:
beb42c835c52 Make weather server handle arbitrary data:
Paul Fisher <paul@pfish.zone>
parents: 9
diff changeset
29 parser.read_file(infile)
22
e229afdd447b locations.py: store location name into the right place.
Paul Fisher <paul@pfish.zone>
parents: 21
diff changeset
30 self.name = parser.get(
21
beb42c835c52 Make weather server handle arbitrary data:
Paul Fisher <paul@pfish.zone>
parents: 9
diff changeset
31 'location', 'name', fallback='Weather station')
beb42c835c52 Make weather server handle arbitrary data:
Paul Fisher <paul@pfish.zone>
parents: 9
diff changeset
32 self.tz_name = parser.get('location', 'timezone', fallback='UTC')
beb42c835c52 Make weather server handle arbitrary data:
Paul Fisher <paul@pfish.zone>
parents: 9
diff changeset
33 self.password = parser.get('location', 'password')
24
20c8ec56e447 logfile: Pull logfile thread out of Logger.
Paul Fisher <paul@pfish.zone>
parents: 22
diff changeset
34 self.logger = logfile.Logger(
21
beb42c835c52 Make weather server handle arbitrary data:
Paul Fisher <paul@pfish.zone>
parents: 9
diff changeset
35 str(root / LOG), sample_field='sample_time')
beb42c835c52 Make weather server handle arbitrary data:
Paul Fisher <paul@pfish.zone>
parents: 9
diff changeset
36 except (IOError, KeyError, configparser.Error):
beb42c835c52 Make weather server handle arbitrary data:
Paul Fisher <paul@pfish.zone>
parents: 9
diff changeset
37 raise ConfigError("Couldn't load location info.")
1
f66df122f18d get the skeleton of a webserver up?
Paul Fisher <paul@pfish.zone>
parents:
diff changeset
38
21
beb42c835c52 Make weather server handle arbitrary data:
Paul Fisher <paul@pfish.zone>
parents: 9
diff changeset
39 def record(
beb42c835c52 Make weather server handle arbitrary data:
Paul Fisher <paul@pfish.zone>
parents: 9
diff changeset
40 self,
beb42c835c52 Make weather server handle arbitrary data:
Paul Fisher <paul@pfish.zone>
parents: 9
diff changeset
41 entries: t.Iterable[t.Dict[str, object]],
beb42c835c52 Make weather server handle arbitrary data:
Paul Fisher <paul@pfish.zone>
parents: 9
diff changeset
42 timestamp: datetime.datetime,
beb42c835c52 Make weather server handle arbitrary data:
Paul Fisher <paul@pfish.zone>
parents: 9
diff changeset
43 ) -> None:
beb42c835c52 Make weather server handle arbitrary data:
Paul Fisher <paul@pfish.zone>
parents: 9
diff changeset
44 for e in entries:
beb42c835c52 Make weather server handle arbitrary data:
Paul Fisher <paul@pfish.zone>
parents: 9
diff changeset
45 e['ingest_time'] = timestamp
beb42c835c52 Make weather server handle arbitrary data:
Paul Fisher <paul@pfish.zone>
parents: 9
diff changeset
46 self.logger.write_rows(entries)
beb42c835c52 Make weather server handle arbitrary data:
Paul Fisher <paul@pfish.zone>
parents: 9
diff changeset
47
beb42c835c52 Make weather server handle arbitrary data:
Paul Fisher <paul@pfish.zone>
parents: 9
diff changeset
48 @property
beb42c835c52 Make weather server handle arbitrary data:
Paul Fisher <paul@pfish.zone>
parents: 9
diff changeset
49 def entries(self) -> t.Iterable[t.Dict[str, object]]:
31
9bc3687e1e5e logfile: Add an index, and don't keep everything in RAM.
Paul Fisher <paul@pfish.zone>
parents: 26
diff changeset
50 return self.logger.cached_data
21
beb42c835c52 Make weather server handle arbitrary data:
Paul Fisher <paul@pfish.zone>
parents: 9
diff changeset
51
beb42c835c52 Make weather server handle arbitrary data:
Paul Fisher <paul@pfish.zone>
parents: 9
diff changeset
52 def latest(self) -> t.Optional[types.Reading]:
31
9bc3687e1e5e logfile: Add an index, and don't keep everything in RAM.
Paul Fisher <paul@pfish.zone>
parents: 26
diff changeset
53 most_recent = reversed(self.logger.cached_data)
21
beb42c835c52 Make weather server handle arbitrary data:
Paul Fisher <paul@pfish.zone>
parents: 9
diff changeset
54 for entry in most_recent:
beb42c835c52 Make weather server handle arbitrary data:
Paul Fisher <paul@pfish.zone>
parents: 9
diff changeset
55 try:
beb42c835c52 Make weather server handle arbitrary data:
Paul Fisher <paul@pfish.zone>
parents: 9
diff changeset
56 return types.Reading.from_dict(entry)
beb42c835c52 Make weather server handle arbitrary data:
Paul Fisher <paul@pfish.zone>
parents: 9
diff changeset
57 except KeyError:
beb42c835c52 Make weather server handle arbitrary data:
Paul Fisher <paul@pfish.zone>
parents: 9
diff changeset
58 pass # go to the older one.
beb42c835c52 Make weather server handle arbitrary data:
Paul Fisher <paul@pfish.zone>
parents: 9
diff changeset
59 return None
beb42c835c52 Make weather server handle arbitrary data:
Paul Fisher <paul@pfish.zone>
parents: 9
diff changeset
60
beb42c835c52 Make weather server handle arbitrary data:
Paul Fisher <paul@pfish.zone>
parents: 9
diff changeset
61 def timezone(self) -> datetime.tzinfo:
1
f66df122f18d get the skeleton of a webserver up?
Paul Fisher <paul@pfish.zone>
parents:
diff changeset
62 try:
39
b77c8e7d2742 Use zoneinfo rather than pytz.
Paul Fisher <paul@pfish.zone>
parents: 35
diff changeset
63 return zoneinfo.ZoneInfo(self.tz_name)
b77c8e7d2742 Use zoneinfo rather than pytz.
Paul Fisher <paul@pfish.zone>
parents: 35
diff changeset
64 except zoneinfo.ZoneInfoNotFoundError:
b77c8e7d2742 Use zoneinfo rather than pytz.
Paul Fisher <paul@pfish.zone>
parents: 35
diff changeset
65 return datetime.UTC
1
f66df122f18d get the skeleton of a webserver up?
Paul Fisher <paul@pfish.zone>
parents:
diff changeset
66
21
beb42c835c52 Make weather server handle arbitrary data:
Paul Fisher <paul@pfish.zone>
parents: 9
diff changeset
67 def __repr__(self) -> str:
35
2f3473416c11 Fix format string in repr(Location).
Paul Fisher <paul@pfish.zone>
parents: 31
diff changeset
68 return f'<Location in {self.root!r}>'
1
f66df122f18d get the skeleton of a webserver up?
Paul Fisher <paul@pfish.zone>
parents:
diff changeset
69
f66df122f18d get the skeleton of a webserver up?
Paul Fisher <paul@pfish.zone>
parents:
diff changeset
70
21
beb42c835c52 Make weather server handle arbitrary data:
Paul Fisher <paul@pfish.zone>
parents: 9
diff changeset
71 class LocationFolder:
1
f66df122f18d get the skeleton of a webserver up?
Paul Fisher <paul@pfish.zone>
parents:
diff changeset
72
21
beb42c835c52 Make weather server handle arbitrary data:
Paul Fisher <paul@pfish.zone>
parents: 9
diff changeset
73 def __init__(self, root: pathlib.Path):
beb42c835c52 Make weather server handle arbitrary data:
Paul Fisher <paul@pfish.zone>
parents: 9
diff changeset
74 self.root = root
beb42c835c52 Make weather server handle arbitrary data:
Paul Fisher <paul@pfish.zone>
parents: 9
diff changeset
75 # locations, mtime
beb42c835c52 Make weather server handle arbitrary data:
Paul Fisher <paul@pfish.zone>
parents: 9
diff changeset
76 self.info: t.Tuple[t.Dict[str, Location], t.Optional[int]] = ({}, None)
beb42c835c52 Make weather server handle arbitrary data:
Paul Fisher <paul@pfish.zone>
parents: 9
diff changeset
77 self._maybe_reload()
beb42c835c52 Make weather server handle arbitrary data:
Paul Fisher <paul@pfish.zone>
parents: 9
diff changeset
78
beb42c835c52 Make weather server handle arbitrary data:
Paul Fisher <paul@pfish.zone>
parents: 9
diff changeset
79 def get(self, name: str) -> Location:
beb42c835c52 Make weather server handle arbitrary data:
Paul Fisher <paul@pfish.zone>
parents: 9
diff changeset
80 self._maybe_reload()
beb42c835c52 Make weather server handle arbitrary data:
Paul Fisher <paul@pfish.zone>
parents: 9
diff changeset
81 locs, _ = self.info
beb42c835c52 Make weather server handle arbitrary data:
Paul Fisher <paul@pfish.zone>
parents: 9
diff changeset
82 return locs[name]
1
f66df122f18d get the skeleton of a webserver up?
Paul Fisher <paul@pfish.zone>
parents:
diff changeset
83
26
7def5611895b Add support for an index page displaying all locations.
Paul Fisher <paul@pfish.zone>
parents: 24
diff changeset
84 def locations(self) -> t.Dict[str, Location]:
7def5611895b Add support for an index page displaying all locations.
Paul Fisher <paul@pfish.zone>
parents: 24
diff changeset
85 return self.info[0]
7def5611895b Add support for an index page displaying all locations.
Paul Fisher <paul@pfish.zone>
parents: 24
diff changeset
86
21
beb42c835c52 Make weather server handle arbitrary data:
Paul Fisher <paul@pfish.zone>
parents: 9
diff changeset
87 def _maybe_reload(self) -> None:
beb42c835c52 Make weather server handle arbitrary data:
Paul Fisher <paul@pfish.zone>
parents: 9
diff changeset
88 new_mtime = self.root.stat().st_mtime_ns
beb42c835c52 Make weather server handle arbitrary data:
Paul Fisher <paul@pfish.zone>
parents: 9
diff changeset
89 _, old_mtime = self.info
beb42c835c52 Make weather server handle arbitrary data:
Paul Fisher <paul@pfish.zone>
parents: 9
diff changeset
90 if old_mtime == new_mtime:
beb42c835c52 Make weather server handle arbitrary data:
Paul Fisher <paul@pfish.zone>
parents: 9
diff changeset
91 return
beb42c835c52 Make weather server handle arbitrary data:
Paul Fisher <paul@pfish.zone>
parents: 9
diff changeset
92 locations = {}
beb42c835c52 Make weather server handle arbitrary data:
Paul Fisher <paul@pfish.zone>
parents: 9
diff changeset
93 for child in self.root.iterdir():
beb42c835c52 Make weather server handle arbitrary data:
Paul Fisher <paul@pfish.zone>
parents: 9
diff changeset
94 try:
26
7def5611895b Add support for an index page displaying all locations.
Paul Fisher <paul@pfish.zone>
parents: 24
diff changeset
95 locations[child.name] = Location(child, child.name)
21
beb42c835c52 Make weather server handle arbitrary data:
Paul Fisher <paul@pfish.zone>
parents: 9
diff changeset
96 except ConfigError:
beb42c835c52 Make weather server handle arbitrary data:
Paul Fisher <paul@pfish.zone>
parents: 9
diff changeset
97 pass # It's OK. Skip this.
beb42c835c52 Make weather server handle arbitrary data:
Paul Fisher <paul@pfish.zone>
parents: 9
diff changeset
98 self.info = locations, new_mtime