prettify emfit provider

This commit is contained in:
Dima Gerasimov 2020-05-03 13:36:54 +01:00
parent 2bf62e2db3
commit 78dbbd3c55
5 changed files with 28 additions and 35 deletions

View file

@ -1,7 +1,7 @@
from pathlib import Path from pathlib import Path
import functools import functools
import types import types
from typing import Union, Callable, Dict, Iterable, TypeVar, Sequence, List, Optional, Any, cast from typing import Union, Callable, Dict, Iterable, TypeVar, Sequence, List, Optional, Any, cast, Tuple
from . import init from . import init
@ -46,6 +46,7 @@ def the(l: Iterable[T]) -> T:
return first return first
# TODO more_itertools.bucket?
def group_by_key(l: Iterable[T], key: Callable[[T], K]) -> Dict[K, List[T]]: def group_by_key(l: Iterable[T], key: Callable[[T], K]) -> Dict[K, List[T]]:
res: Dict[K, List[T]] = {} res: Dict[K, List[T]] = {}
for i in l: for i in l:
@ -106,9 +107,11 @@ from .kython.klogging import setup_logger, LazyLogger
Paths = Union[Sequence[PathIsh], PathIsh] Paths = Union[Sequence[PathIsh], PathIsh]
def get_files(pp: Paths, glob: str, sort: bool=True) -> List[Path]: def get_files(pp: Paths, glob: str, sort: bool=True) -> Tuple[Path, ...]:
""" """
Helper function to avoid boilerplate. Helper function to avoid boilerplate.
Tuple as return type is a bit friendlier for hashing/caching, so hopefully makes sense
""" """
# TODO FIXME mm, some wrapper to assert iterator isn't empty? # TODO FIXME mm, some wrapper to assert iterator isn't empty?
sources: List[Path] = [] sources: List[Path] = []
@ -129,7 +132,7 @@ def get_files(pp: Paths, glob: str, sort: bool=True) -> List[Path]:
if sort: if sort:
paths = list(sorted(paths)) paths = list(sorted(paths))
return paths return tuple(paths)
def mcachew(*args, **kwargs): def mcachew(*args, **kwargs):

View file

@ -5,26 +5,21 @@
Consumes data exported by https://github.com/karlicoss/backup-emfit Consumes data exported by https://github.com/karlicoss/backup-emfit
""" """
import json import json
import logging
from collections import OrderedDict as odict
from dataclasses import dataclass from dataclasses import dataclass
from datetime import date, datetime, time, timedelta from datetime import date, datetime, time, timedelta
from itertools import groupby
from pathlib import Path from pathlib import Path
from typing import Dict, Iterator, List, NamedTuple, Any, cast from typing import Dict, Iterator, List, NamedTuple, Any, cast
import pytz import pytz
from more_itertools import bucket
from ..common import get_files, LazyLogger, cproperty, group_by_key, mcachew from ..common import get_files, LazyLogger, cproperty, mcachew
from my.config import emfit as config from my.config import emfit as config
logger = LazyLogger('my.emfit', level='info') logger = LazyLogger(__name__, level='info')
# TODO FIXME remove?
import kython
timed = lambda f: kython.timed(f, logger=logger)
def hhmm(minutes): def hhmm(minutes):
@ -35,13 +30,10 @@ AWAKE = 4
Sid = str Sid = str
# TODO use tz provider for that?
_TZ = pytz.timezone(config.tz)
# TODO use common tz thing? # TODO use common tz thing?
def fromts(ts) -> datetime: def fromts(ts) -> datetime:
dt = datetime.fromtimestamp(ts) dt = datetime.fromtimestamp(ts, tz=pytz.utc)
return _TZ.localize(dt) return dt
class Mixin: class Mixin:
@ -295,14 +287,14 @@ class Emfit(Mixin):
# TODO move to common? # TODO move to common?
def dir_hash(path: Path): def dir_hash(path: Path):
mtimes = tuple(p.stat().st_mtime for p in sorted(path.glob('*.json'))) mtimes = tuple(p.stat().st_mtime for p in get_files(path, glob='*.json'))
return mtimes return mtimes
# TODO take __file__ into account somehow?
@mcachew(cache_path=config.cache_path, hashf=dir_hash, logger=logger) @mcachew(cache_path=config.cache_path, hashf=dir_hash, logger=logger)
def iter_datas_cached(path: Path) -> Iterator[Emfit]: def iter_datas(path: Path=config.export_path) -> Iterator[Emfit]:
# TODO use get_files? for f in get_files(path, glob='*.json'):
for f in sorted(path.glob('*.json')):
sid = f.stem sid = f.stem
if sid in config.excluded_sids: if sid in config.excluded_sids:
continue continue
@ -311,20 +303,17 @@ def iter_datas_cached(path: Path) -> Iterator[Emfit]:
yield from Emfit.make(em) yield from Emfit.make(em)
def iter_datas(path=config.export_path) -> Iterator[Emfit]:
yield from iter_datas_cached(path)
def get_datas() -> List[Emfit]: def get_datas() -> List[Emfit]:
return list(sorted(iter_datas(), key=lambda e: e.start)) return list(sorted(iter_datas(), key=lambda e: e.start))
# TODO move away old entries if there is a diff?? # TODO move away old entries if there is a diff??
@timed
def by_night() -> Dict[date, Emfit]: def by_night() -> Dict[date, Emfit]:
res: Dict[date, Emfit] = odict() res: Dict[date, Emfit] = {}
# TODO shit. I need some sort of interrupted sleep detection? # TODO shit. I need some sort of interrupted sleep detection?
for dd, sleeps in group_by_key(get_datas(), key=lambda s: s.date).items(): grouped = bucket(get_datas(), key=lambda s: s.date)
for dd in grouped:
sleeps = list(grouped[dd])
if len(sleeps) > 1: if len(sleeps) > 1:
logger.warning("multiple sleeps per night, not handled yet: %s", sleeps) logger.warning("multiple sleeps per night, not handled yet: %s", sleeps)
continue continue

View file

@ -15,10 +15,10 @@ from .common import get_files, LazyLogger
from my.config import foursquare as config from my.config import foursquare as config
logger = LazyLogger(__package__) logger = LazyLogger(__name__)
def _get_exports() -> List[Path]: def inputs():
return get_files(config.export_path, '*.json') return get_files(config.export_path, '*.json')
@ -62,7 +62,7 @@ class Place:
def get_raw(fname=None): def get_raw(fname=None):
if fname is None: if fname is None:
fname = max(_get_exports()) fname = max(inputs())
j = json.loads(Path(fname).read_text()) j = json.loads(Path(fname).read_text())
assert isinstance(j, list) assert isinstance(j, list)

View file

@ -18,7 +18,7 @@ from my.config import rescuetime as config
log = LazyLogger(__package__, level='info') log = LazyLogger(__package__, level='info')
def _get_exports() -> List[Path]: def inputs():
return get_files(config.export_path, '*.json') return get_files(config.export_path, '*.json')
@ -28,7 +28,7 @@ Model = rescuexport.Model
# TODO cache? # TODO cache?
def get_model(last=0) -> Model: def get_model(last=0) -> Model:
return Model(_get_exports()[-last:]) return Model(inputs()[-last:])
def _without_errors(): def _without_errors():

View file

@ -4,8 +4,9 @@
from setuptools import setup, find_namespace_packages # type: ignore from setuptools import setup, find_namespace_packages # type: ignore
INSTALL_REQUIRES = [ INSTALL_REQUIRES = [
'appdirs', 'pytz', # even though it's not needed by the core, it's so common anyway...
'pytz', # even though it's not needed by the core, it's so common anyway... 'appdirs', # very common, and makes it portable
'more-itertools', # it's just too useful and very common anyway
] ]