core/compat: move fromisoformat to .core.compat module

This commit is contained in:
Dima Gerasimov 2023-11-19 22:45:02 +00:00 committed by karlicoss
parent 09e0f66892
commit a843407e40
8 changed files with 69 additions and 34 deletions

View file

@ -23,7 +23,7 @@ def inputs() -> Sequence[Path]:
from .core import dataclass, Json, PathIsh, datetime_aware
from .core.common import isoparse
from .core.compat import fromisoformat
@dataclass
@ -39,6 +39,7 @@ class Entry:
@property
def dt(self) -> datetime_aware:
# contains utc already
# TODO after python>=3.11, could just use fromisoformat
ds = self.json['date']
elen = 27
lds = len(ds)
@ -46,10 +47,10 @@ class Entry:
# ugh. sometimes contains less that 6 decimal points
ds = ds[:-1] + '0' * (elen - lds) + 'Z'
elif lds > elen:
# ahd sometimes more...
# and sometimes more...
ds = ds[:elen - 1] + 'Z'
return isoparse(ds)
return fromisoformat(ds)
@property
def active(self) -> Optional[str]:

View file

@ -313,20 +313,18 @@ class classproperty(Generic[_R]):
# def __get__(self) -> _R:
# return self.f()
# TODO deprecate in favor of datetime_aware
tzdatetime = datetime
# for now just serves documentation purposes... but one day might make it statically verifiable where possible?
# TODO e.g. maybe use opaque mypy alias?
datetime_naive = datetime
datetime_aware = datetime
# TODO doctests?
def isoparse(s: str) -> tzdatetime:
"""
Parses timestamps formatted like 2020-05-01T10:32:02.925961Z
"""
# TODO could use dateutil? but it's quite slow as far as I remember..
# TODO support non-utc.. somehow?
assert s.endswith('Z'), s
s = s[:-1] + '+00:00'
return datetime.fromisoformat(s)
# TODO deprecate
tzdatetime = datetime_aware
# TODO deprecate (although could be used in modules)
from .compat import fromisoformat as isoparse
import re
@ -590,12 +588,6 @@ def asdict(thing: Any) -> Json:
raise TypeError(f'Could not convert object {thing} to dict')
# for now just serves documentation purposes... but one day might make it statically verifiable where possible?
# TODO e.g. maybe use opaque mypy alias?
datetime_naive = datetime
datetime_aware = datetime
def assert_subpackage(name: str) -> None:
# can lead to some unexpected issues if you 'import cachew' which being in my/core directory.. so let's protect against it
# NOTE: if we use overlay, name can be smth like my.origg.my.core.cachew ...

View file

@ -76,3 +76,42 @@ if sys.version_info[:2] <= (3, 9):
return lo
else:
from bisect import bisect_left
from datetime import datetime
if sys.version_info[:2] >= (3, 11):
fromisoformat = datetime.fromisoformat
else:
def fromisoformat(date_string: str) -> datetime:
# didn't support Z as "utc" before 3.11
if date_string.endswith('Z'):
# NOTE: can be removed from 3.11?
# https://docs.python.org/3/library/datetime.html#datetime.datetime.fromisoformat
date_string = date_string[:-1] + '+00:00'
return datetime.fromisoformat(date_string)
def test_fromisoformat() -> None:
from datetime import timezone
# feedbin has this format
assert fromisoformat('2020-05-01T10:32:02.925961Z') == datetime(
2020, 5, 1, 10, 32, 2, 925961, timezone.utc,
)
# polar has this format
assert fromisoformat('2018-11-28T22:04:01.304Z') == datetime(
2018, 11, 28, 22, 4, 1, 304000, timezone.utc,
)
# stackexchange, runnerup has this format
assert fromisoformat('2020-11-30T00:53:12Z') == datetime(
2020, 11, 30, 0, 53, 12, 0, timezone.utc,
)
# arbtt has this format (sometimes less/more than 6 digits in milliseconds)
# TODO doesn't work atm, not sure if really should be supported...
# maybe should have flags for weird formats?
# assert isoparse('2017-07-18T18:59:38.21731Z') == datetime(
# 2017, 7, 18, 18, 59, 38, 217310, timezone.utc,
# )

View file

@ -24,7 +24,7 @@ from .query import (
ET,
)
from .common import isoparse
from .compat import fromisoformat
timedelta_regex = re.compile(r"^((?P<weeks>[\.\d]+?)w)?((?P<days>[\.\d]+?)d)?((?P<hours>[\.\d]+?)h)?((?P<minutes>[\.\d]+?)m)?((?P<seconds>[\.\d]+?)s)?$")
@ -78,7 +78,7 @@ def parse_datetime_float(date_str: str) -> float:
except ValueError:
pass
try:
return isoparse(ds).timestamp()
return fromisoformat(ds).timestamp()
except (AssertionError, ValueError):
pass

View file

@ -42,7 +42,7 @@ from typing import List, Dict, Iterable, NamedTuple, Sequence, Optional
import json
from .core import LazyLogger, Json, Res
from .core.common import isoparse
from .core.compat import fromisoformat
from .core.error import echain, sort_res_by
from .core.konsume import wrap, Zoomable, Wdict
@ -145,7 +145,7 @@ class Loader:
cmap[hlid] = ccs
ccs.append(Comment(
cid=cid.value,
created=isoparse(crt.value),
created=fromisoformat(crt.value),
text=html.value, # TODO perhaps coonvert from html to text or org?
))
v.consume()
@ -183,7 +183,7 @@ class Loader:
yield Highlight(
hid=hid,
created=isoparse(crt),
created=fromisoformat(crt),
selection=text,
comments=tuple(comments),
tags=tuple(htags),
@ -221,7 +221,7 @@ class Loader:
path = Path(config.polar_dir) / 'stash' / filename
yield Book(
created=isoparse(added),
created=fromisoformat(added),
uid=self.uid,
path=path,
title=title,

View file

@ -7,7 +7,8 @@ from my.config import feedbin as config
from pathlib import Path
from typing import Sequence
from ..core.common import listify, get_files, isoparse
from ..core.common import listify, get_files
from ..core.compat import fromisoformat
from .common import Subscription
@ -22,7 +23,7 @@ def parse_file(f: Path):
raw = json.loads(f.read_text())
for r in raw:
yield Subscription(
created_at=isoparse(r['created_at']),
created_at=fromisoformat(r['created_at']),
title=r['title'],
url=r['site_url'],
id=r['id'],

View file

@ -11,7 +11,8 @@ from pathlib import Path
from typing import Iterable
from .core import Res, get_files
from .core.common import isoparse, Json
from .core.common import Json
from .core.compat import fromisoformat
import tcxparser # type: ignore[import-untyped]
@ -44,7 +45,7 @@ def _parse(f: Path) -> Workout:
return {
'id' : f.name, # not sure?
'start_time' : isoparse(tcx.started_at),
'start_time' : fromisoformat(tcx.started_at),
'duration' : timedelta(seconds=tcx.duration),
'sport' : sport,
'heart_rate_avg': tcx.hr_avg,
@ -58,7 +59,7 @@ def _parse(f: Path) -> Workout:
# tcx.hr_values(),
# # todo cadence?
# ):
# t = isoparse(ts)
# t = fromisoformat(ts)
def workouts() -> Iterable[Res[Workout]]:

View file

@ -16,7 +16,8 @@ config = make_config(stackexchange)
# TODO just merge all of them and then filter?.. not sure
from ..core.common import Json, isoparse
from ..core.common import Json
from ..core.compat import fromisoformat
from typing import NamedTuple, Iterable
from datetime import datetime
class Vote(NamedTuple):
@ -25,7 +26,7 @@ class Vote(NamedTuple):
@property
def when(self) -> datetime:
return isoparse(self.j['eventTime'])
return fromisoformat(self.j['eventTime'])
# todo Url return type?
@property