From da8f541cdc0f40c0919499f1d3a7f64180ebd86d Mon Sep 17 00:00:00 2001 From: Sean Breckenridge Date: Tue, 21 Feb 2023 19:37:27 -0800 Subject: [PATCH] convert fallback location estimators to be iterators --- my/location/fallback/all.py | 17 ++++++++++++----- my/location/fallback/common.py | 21 ++++++++++++--------- my/location/fallback/via_home.py | 12 +++++++----- my/time/tz/via_location.py | 2 +- 4 files changed, 32 insertions(+), 20 deletions(-) diff --git a/my/location/fallback/all.py b/my/location/fallback/all.py index ae7b333..f645eb7 100644 --- a/my/location/fallback/all.py +++ b/my/location/fallback/all.py @@ -1,17 +1,24 @@ # TODO: add config here which passes kwargs to estimate_from (under_accuracy) # overwritable by passing the kwarg name here to the top-level estimate_location -from typing import Union -from datetime import datetime +from typing import Iterator -from my.location.fallback.common import estimate_from, FallbackLocation +from my.core.source import import_source +from my.location.fallback.common import estimate_from, FallbackLocation, DateExact -def estimate_location(dt: Union[datetime, float, int]) -> FallbackLocation: +# note: the import_source returns an iterator +@import_source(module_name="my.location.fallback.via_home") +def _home_estimate(dt: DateExact) -> Iterator[FallbackLocation]: from my.location.fallback.via_home import estimate_location as via_home + yield from via_home(dt) + + + +def estimate_location(dt: DateExact) -> FallbackLocation: loc = estimate_from( dt, - estimators=(via_home,) + estimators=(_home_estimate,) ) if loc is None: raise ValueError("Could not estimate location") diff --git a/my/location/fallback/common.py b/my/location/fallback/common.py index 8c602d5..b7d96b5 100644 --- a/my/location/fallback/common.py +++ b/my/location/fallback/common.py @@ -1,10 +1,10 @@ from __future__ import annotations from dataclasses import dataclass from typing import Optional, Callable, Sequence, Iterator, List, Union -from datetime import datetime, timedelta +from datetime import datetime, timedelta, date from ..common import LocationProtocol, Location -DateIsh = Union[datetime, float, int] +DateExact = Union[datetime, float, int] # float/int as epoch timestamps @dataclass class FallbackLocation(LocationProtocol): @@ -62,28 +62,31 @@ class FallbackLocation(LocationProtocol): ) -LocationEstimator = Callable[[DateIsh], Optional[FallbackLocation]] +# a location estimator can return multiple fallbacks, incase there are +# differing accuracies/to allow for possible matches to be computed +# iteratively +LocationEstimator = Callable[[DateExact], Iterator[FallbackLocation]] LocationEstimators = Sequence[LocationEstimator] # helper function, instead of dealing with datetimes while comparing, just use epoch timestamps -def _datetime_timestamp(dt: DateIsh) -> float: +def _datetime_timestamp(dt: DateExact) -> float: if isinstance(dt, datetime): return dt.timestamp() return float(dt) def _iter_estimate_from( - dt: DateIsh, + dt: DateExact, estimators: LocationEstimators, ) -> Iterator[FallbackLocation]: for est in estimators: - loc = est(dt) - if loc is None: + loc = list(est(dt)) + if not loc: continue - yield loc + yield from loc def estimate_from( - dt: DateIsh, + dt: DateExact, estimators: LocationEstimators, *, first_match: bool = False, diff --git a/my/location/fallback/via_home.py b/my/location/fallback/via_home.py index 1ce7ab1..df18878 100644 --- a/my/location/fallback/via_home.py +++ b/my/location/fallback/via_home.py @@ -5,12 +5,12 @@ Simple location provider, serving as a fallback when more detailed data isn't av from dataclasses import dataclass from datetime import datetime, time, timezone from functools import lru_cache -from typing import Sequence, Tuple, Union, cast, List +from typing import Sequence, Tuple, Union, cast, List, Iterator from my.config import location as user_config from my.location.common import LatLon, DateIsh -from my.location.fallback.common import FallbackLocation +from my.location.fallback.common import FallbackLocation, DateExact @dataclass class Config(user_config): @@ -85,24 +85,26 @@ def homes_cached() -> List[Tuple[datetime, LatLon]]: return list(config._history) -def estimate_location(dt: Union[datetime, int, float]) -> FallbackLocation: +def estimate_location(dt: DateExact) -> Iterator[FallbackLocation]: from my.location.fallback.common import _datetime_timestamp d: float = _datetime_timestamp(dt) hist = list(reversed(homes_cached())) for pdt, (lat, lon) in hist: if d >= pdt.timestamp(): - return FallbackLocation( + yield FallbackLocation( lat=lat, lon=lon, accuracy=config.home_accuracy, dt=datetime.fromtimestamp(d, timezone.utc), datasource='via_home') + return else: # I guess the most reasonable is to fallback on the first location lat, lon = hist[-1][1] - return FallbackLocation( + yield FallbackLocation( lat=lat, lon=lon, accuracy=config.home_accuracy, dt=datetime.fromtimestamp(d, timezone.utc), datasource='via_home') + return diff --git a/my/time/tz/via_location.py b/my/time/tz/via_location.py index 42bfb0b..89b2551 100644 --- a/my/time/tz/via_location.py +++ b/my/time/tz/via_location.py @@ -199,7 +199,7 @@ def _get_day_tz(d: date) -> Optional[pytz.BaseTzInfo]: # ok to cache, there are only a few home locations? @lru_cache(maxsize=None) -def _get_home_tz(loc) -> Optional[pytz.BaseTzInfo]: +def _get_home_tz(loc: LatLon) -> Optional[pytz.BaseTzInfo]: (lat, lng) = loc finder = _timezone_finder(fast=False) # ok to use slow here for better precision zone = finder.timezone_at(lat=lat, lng=lng)