diff --git a/aiogoogle/auth/utils.py b/aiogoogle/auth/utils.py index bed0c05..aa291ae 100644 --- a/aiogoogle/auth/utils.py +++ b/aiogoogle/auth/utils.py @@ -3,6 +3,9 @@ import hashlib import os import datetime +import sys + +from ..utils import _parse_isoformat def create_secret(bytes_length=1024): # pragma: no cover @@ -22,7 +25,11 @@ def _is_expired(expires_at): if expires_at is None: return True if not isinstance(expires_at, datetime.datetime): - expires_at = datetime.datetime.fromisoformat(expires_at) + # datetime.fromisoformat is 3.7+ + if sys.version_info[1] <= 6: + expires_at = _parse_isoformat(expires_at) + else: + expires_at = datetime.fromisoformat(expires_at) if datetime.datetime.utcnow() >= expires_at: return True else: diff --git a/aiogoogle/utils.py b/aiogoogle/utils.py index fe637f3..5f99587 100644 --- a/aiogoogle/utils.py +++ b/aiogoogle/utils.py @@ -1,5 +1,8 @@ __all__ = [] +import datetime +import re + def _safe_getitem(dct, *keys): for key in keys: @@ -40,3 +43,59 @@ def __delattr__(self, item): # pragma: no cover def __delitem__(self, key): # pragma: no cover super(_dict, self).__delitem__(key) del self.__dict__[key] + + +def _parse_time_components(tstr): + # supported format is HH[:MM[:SS[.fff[fff]]]] + if len(tstr) < 2: + raise ValueError("Invalid Isotime format") + hh = tstr[:2] + mm_ss = re.findall(r":(\d{2})", tstr) + ff = re.findall(r"\.(\d+)", tstr) + if ff and not len(ff[0]) in [3, 6]: + raise ValueError("Invalid Isotime format") + ff = ff[0] if ff else [] + + # ensure tstr was valid + if len(mm_ss) < 2 and ff: + raise ValueError("Invalid Isotime format") + parsed_str = hh + (":" + ":".join(mm_ss) if mm_ss else "") + \ + ("." + ff if ff else "") + if parsed_str != tstr: + raise ValueError("Invalid Isotime format") + components = [int(hh)] + if mm_ss: + components.extend(int(t) for t in mm_ss) + if ff: + components.append(int(ff.ljust(6, "0"))) + return components + [0] * (4 - len(components)) + + +def _parse_isoformat(dtstr): + # supported format is YYYY-mm-dd[THH[:MM[:SS[.fff[fff]]]]][+HH:MM[:SS[.ffffff]]] + dstr = dtstr[:10] + tstr = dtstr[11:] + try: + date = datetime.datetime.strptime(dstr, "%Y-%m-%d") + except ValueError as e: + raise ValueError("Invalid Isotime format") from e + + if tstr: + # check for time zone + tz_pos = (tstr.find("-") + 1 or tstr.find("+") + 1) + if tz_pos > 0: + tzsign = -1 if tstr[tz_pos - 1] == "-" else 1 + tz_comps = _parse_time_components(tstr[tz_pos:]) + tz = tzsign * datetime.timedelta( + hours=tz_comps[0], minutes=tz_comps[1], + seconds=tz_comps[2], microseconds=tz_comps[3]) + tstr = tstr[:tz_pos - 1] + else: + tz = datetime.timedelta(0) + time_comps = _parse_time_components(tstr) + date = date.replace(hour=time_comps[0], minute=time_comps[1], + second=time_comps[2], microsecond=time_comps[3]) + date -= tz + elif len(dtstr) == 11: + raise ValueError("Invalid Isotime format") + return date diff --git a/tests/test_units/test_dateutil.py b/tests/test_units/test_dateutil.py new file mode 100644 index 0000000..ebd91db --- /dev/null +++ b/tests/test_units/test_dateutil.py @@ -0,0 +1,66 @@ +import pytest + +from datetime import datetime +from aiogoogle.utils import _parse_isoformat + + +def test_iso_parser1(): + assert _parse_isoformat("2021-02-06") == datetime(2021, 2, 6) + + +def test_iso_parser2(): + assert _parse_isoformat("2021-02-06T14") == datetime(2021, 2, 6, 14) + + +def test_iso_parser3(): + assert _parse_isoformat("2021-02-06T14:52") == datetime(2021, 2, 6, 14, 52) + + +def test_iso_parser4(): + assert _parse_isoformat("2021-02-06T14:52:26") == \ + datetime(2021, 2, 6, 14, 52, 26) + + +def test_iso_parser5(): + assert _parse_isoformat("2021-02-06T14:52:26.123") == \ + datetime(2021, 2, 6, 14, 52, 26, 123000) + + +def test_iso_parser6(): + assert _parse_isoformat("2021-02-06T14:52:26.123456") == \ + datetime(2021, 2, 6, 14, 52, 26, 123456) + + +def test_iso_parser7(): + assert _parse_isoformat("2021-02-06T14:52:26.123456+01:30") == \ + datetime(2021, 2, 6, 13, 22, 26, 123456) + + +def test_iso_parser8(): + assert _parse_isoformat("2021-02-06T14:52:26.123456-01:00") == \ + datetime(2021, 2, 6, 15, 52, 26, 123456) + + +def test_iso_malformed1(): + with pytest.raises(ValueError): + _parse_isoformat("2021-02T14:52") + + +def test_iso_malformed2(): + with pytest.raises(ValueError): + _parse_isoformat("2021-02-06T") + + +def test_iso_malformed3(): + with pytest.raises(ValueError): + _parse_isoformat("2021-02-06T14.123") + + +def test_iso_malformed4(): + with pytest.raises(ValueError): + _parse_isoformat("2021-02-06T14:52.123456") + + +def test_iso_malformed5(): + with pytest.raises(ValueError): + _parse_isoformat("2021-02-06T14:52:26.1234")