my.location.home: simplify config format, make it a bit more robust + tests

This commit is contained in:
Dima Gerasimov 2020-10-11 19:57:14 +01:00 committed by karlicoss
parent d8ed780e36
commit 4666378f7e
3 changed files with 68 additions and 34 deletions

View file

@ -33,6 +33,12 @@ class bluemaestro:
class google: class google:
takeout_path: Paths = '' takeout_path: Paths = ''
from typing import Sequence, Union, Tuple
from datetime import datetime, date
DateIsh = Union[datetime, date, str]
LatLon = Tuple[float, float]
class location: class location:
class home: # todo ugh, need to think about it... mypy wants the type here to be general, otherwise it can't deduce
current = (1.0, -1.0) # and we can't import the types from the module itself, otherwise would be circular. common module?
home: Union[LatLon, Sequence[Tuple[DateIsh, LatLon]]] = (1.0, -1.0)

View file

@ -2,52 +2,63 @@
Simple location provider, serving as a fallback when more detailed data isn't available Simple location provider, serving as a fallback when more detailed data isn't available
''' '''
from dataclasses import dataclass from dataclasses import dataclass
from datetime import datetime, date from datetime import datetime, date, time, timezone
from functools import lru_cache from functools import lru_cache
from typing import Optional, Sequence, Tuple, Union from typing import Optional, Sequence, Tuple, Union, cast
from ..core.common import fromisoformat from ..core.common import fromisoformat
from my.config import location as L from my.config import location as user_config
user_config = L.home
DateIsh = Union[datetime, str] DateIsh = Union[datetime, date, str]
# todo hopefully reasonable? might be nice to add name or something too # todo hopefully reasonable? might be nice to add name or something too
LatLon = Tuple[float, float] LatLon = Tuple[float, float]
@dataclass @dataclass
class home(user_config): class Config(user_config):
home: Union[
LatLon, # either single, 'current' location
Sequence[Tuple[ # or, a sequence of location history
DateIsh, # date when you moved to
LatLon, # the location
]]
]
# TODO could make current Optional and somehow determine from system settings? # TODO could make current Optional and somehow determine from system settings?
# todo possibly also could be core config.. but not sure
current: LatLon
'''
First element is location, the second is the date when you left it (datetime/ISO string)
'''
past: Sequence[Tuple[LatLon, DateIsh]] = ()
# todo test for proper localized/not localized handling as well
# todo make sure they are increasing
@property @property
def _past(self) -> Sequence[Tuple[LatLon, datetime]]: def _history(self) -> Sequence[Tuple[datetime, LatLon]]:
home1 = self.home
# tood ugh, can't test for isnstance LatLon, it's a tuple itself
home2: Sequence[Tuple[DateIsh, LatLon]]
if isinstance(home1[0], tuple):
# already a sequence
home2 = cast(Sequence[Tuple[DateIsh, LatLon]], home1)
else:
# must be a pair of coordinates. also doesn't really matter which date to pick?
loc = cast(LatLon, home1)
home2 = [(datetime.min, loc)]
# todo cache? # todo cache?
res = [] res = []
for loc, x in self.past: for x, loc in home2:
dt: datetime dt: datetime
if isinstance(x, str): if isinstance(x, str):
dt = fromisoformat(x) dt = fromisoformat(x)
else: elif isinstance(x, datetime):
dt = x dt = x
res.append((loc, dt)) else:
dt = datetime.combine(x, time.min)
# todo not sure about doing it here, but makes it easier to compare..
if dt.tzinfo is None:
dt = dt.replace(tzinfo=timezone.utc)
res.append((dt, loc))
res = list(sorted(res, key=lambda p: p[0]))
return res return res
from ..core.cfg import make_config from ..core.cfg import make_config
config = make_config(home) config = make_config(Config)
@lru_cache(maxsize=None) @lru_cache(maxsize=None)
@ -55,7 +66,12 @@ def get_location(dt: datetime) -> LatLon:
''' '''
Interpolates the location at dt Interpolates the location at dt
''' '''
for loc, pdt in config._past: if dt.tzinfo is None:
if dt <= pdt: dt = dt.replace(tzinfo=timezone.utc)
hist = list(reversed(config._history))
for pdt, loc in hist:
if dt >= pdt:
return loc return loc
return config.current else:
# I guess the most reasonable is to fallback on the first location
return hist[-1][1]

View file

@ -1,4 +1,4 @@
from datetime import datetime, timedelta from datetime import datetime, timedelta, date, timezone
from pathlib import Path from pathlib import Path
import sys import sys
@ -44,6 +44,14 @@ def test_tz() -> None:
assert tz is not None assert tz is not None
assert tz.zone == 'Europe/Rome' assert tz.zone == 'Europe/Rome'
tz = LTZ._get_tz(D('20201001 14:15:16'))
assert tz is not None
assert tz.zone == 'Europe/Moscow'
tz = LTZ._get_tz(datetime.min)
assert tz is not None
assert tz.zone == 'America/New_York'
def D(dstr: str) -> datetime: def D(dstr: str) -> datetime:
return datetime.strptime(dstr, '%Y%m%d %H:%M:%S') return datetime.strptime(dstr, '%Y%m%d %H:%M:%S')
@ -72,11 +80,15 @@ def prepare(tmp_path: Path):
config.google = google # type: ignore config.google = google # type: ignore
class location: class location:
class home: home = (
current = (42.697842, 23.325973) # Bulgaria, Sofia # supports ISO strings
past = [ ('2005-12-04' , (42.697842, 23.325973)), # Bulgaria, Sofia
((40.7128, -74.0060), '2005-12-04'), # NY # supports date/datetime objects
] (date(year=1980, month=2, day=15) , (40.7128 , -74.0060 )), # NY
# check tz handling..
(datetime.fromtimestamp(1600000000, tz=timezone.utc), (55.7558 , 37.6173 )), # Moscow, Russia
)
# note: order doesn't matter, will be sorted in the data provider
config.location = location # type: ignore config.location = location # type: ignore
yield yield