From 26abd8589b24edd850f67cde4e1d55bd1437bb49 Mon Sep 17 00:00:00 2001 From: Alec Delaney Date: Thu, 3 Feb 2022 11:59:58 -0500 Subject: [PATCH 01/10] Add type annotations for non-classes --- adafruit_datetime.py | 37 +++++++++++++++++++++---------------- 1 file changed, 21 insertions(+), 16 deletions(-) diff --git a/adafruit_datetime.py b/adafruit_datetime.py index f3c442f..90ae439 100755 --- a/adafruit_datetime.py +++ b/adafruit_datetime.py @@ -32,6 +32,11 @@ import re as _re from micropython import const +try: + from typing import Any, Union, Optional, Tuple +except ImportError: + pass + __version__ = "0.0.0-auto.0" __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_DateTime.git" @@ -66,18 +71,18 @@ _INVALID_ISO_ERROR = "Invalid isoformat string: '{}'" # Utility functions - universal -def _cmp(obj_x, obj_y): +def _cmp(obj_x: Any, obj_y: Any) -> int: return 0 if obj_x == obj_y else 1 if obj_x > obj_y else -1 -def _cmperror(obj_x, obj_y): +def _cmperror(obj_x: Union["datetime", "timedelta"], obj_y: Union["datetime", "timedelta"]) -> None: raise TypeError( "can't compare '%s' to '%s'" % (type(obj_x).__name__, type(obj_y).__name__) ) # Utility functions - time -def _check_time_fields(hour, minute, second, microsecond, fold): +def _check_time_fields(hour: int, minute: int, second: int, microsecond: int, fold: int) -> None: if not isinstance(hour, int): raise TypeError("Hour expected as int") if not 0 <= hour <= 23: @@ -92,7 +97,7 @@ def _check_time_fields(hour, minute, second, microsecond, fold): raise ValueError("fold must be either 0 or 1", fold) -def _check_utc_offset(name, offset): +def _check_utc_offset(name: str, offset: "timedelta") -> None: assert name in ("utcoffset", "dst") if offset is None: return @@ -114,7 +119,7 @@ def _check_utc_offset(name, offset): # pylint: disable=invalid-name -def _format_offset(off): +def _format_offset(off: "timedelta") -> str: s = "" if off is not None: if off.days < 0: @@ -134,7 +139,7 @@ def _format_offset(off): # Utility functions - timezone -def _check_tzname(name): +def _check_tzname(name: Optional[str]) -> None: """"Just raise TypeError if the arg isn't None or a string.""" if name is not None and not isinstance(name, str): raise TypeError( @@ -142,18 +147,18 @@ def _check_tzname(name): ) -def _check_tzinfo_arg(time_zone): +def _check_tzinfo_arg(time_zone: Optional["tzinfo"]): if time_zone is not None and not isinstance(time_zone, tzinfo): raise TypeError("tzinfo argument must be None or of a tzinfo subclass") # Utility functions - date -def _is_leap(year): "year -> 1 if leap year, else 0." +def _is_leap(year: int) -> bool: return year % 4 == 0 and (year % 100 != 0 or year % 400 == 0) -def _days_in_month(year, month): +def _days_in_month(year: int, month: int) -> int: "year, month -> number of days in that month in that year." assert 1 <= month <= 12, month if month == 2 and _is_leap(year): @@ -161,7 +166,7 @@ def _days_in_month(year, month): return _DAYS_IN_MONTH[month] -def _check_date_fields(year, month, day): +def _check_date_fields(year: int, month: int, day: int) -> None: if not isinstance(year, int): raise TypeError("int expected") if not MINYEAR <= year <= MAXYEAR: @@ -173,19 +178,19 @@ def _check_date_fields(year, month, day): raise ValueError("day must be in 1..%d" % dim, day) -def _days_before_month(year, month): +def _days_before_month(year: int, month: int) -> int: "year, month -> number of days in year preceding first day of month." assert 1 <= month <= 12, "month must be in 1..12" return _DAYS_BEFORE_MONTH[month] + (month > 2 and _is_leap(year)) -def _days_before_year(year): +def _days_before_year(year: int) -> int: "year -> number of days before January 1st of year." year = year - 1 return year * 365 + year // 4 - year // 100 + year // 400 -def _ymd2ord(year, month, day): +def _ymd2ord(year: int, month: int, day: int) -> int: "year, month, day -> ordinal, considering 01-Jan-0001 as day 1." assert 1 <= month <= 12, "month must be in 1..12" dim = _days_in_month(year, month) @@ -194,7 +199,7 @@ def _ymd2ord(year, month, day): # pylint: disable=too-many-arguments -def _build_struct_time(tm_year, tm_month, tm_mday, tm_hour, tm_min, tm_sec, tm_isdst): +def _build_struct_time(tm_year: int, tm_month: int, tm_mday: int, tm_hour: int, tm_min: int, tm_sec: int, tm_isdst: int) -> _time.struct_time: tm_wday = (_ymd2ord(tm_year, tm_month, tm_mday) + 6) % 7 tm_yday = _days_before_month(tm_year, tm_month) + tm_mday return _time.struct_time( @@ -213,7 +218,7 @@ def _build_struct_time(tm_year, tm_month, tm_mday, tm_hour, tm_min, tm_sec, tm_i # pylint: disable=invalid-name -def _format_time(hh, mm, ss, us, timespec="auto"): +def _format_time(hh: int, mm: int, ss: int, us: int, timespec: str = "auto") -> str: if timespec != "auto": raise NotImplementedError("Only default timespec supported") if us: @@ -237,7 +242,7 @@ def _format_time(hh, mm, ss, us, timespec="auto"): assert _DI100Y == 25 * _DI4Y - 1 -def _ord2ymd(n): +def _ord2ymd(n: int) -> Tuple[int, int, int]: "ordinal -> (year, month, day), considering 01-Jan-0001 as day 1." # n is a 1-based index, starting at 1-Jan-1. The pattern of leap years From fe5f2ab8572d4d4fa305e084adcd9e7c73c20424 Mon Sep 17 00:00:00 2001 From: Alec Delaney Date: Thu, 3 Feb 2022 12:00:19 -0500 Subject: [PATCH 02/10] Update docstring for utility function with correct type annotation --- adafruit_datetime.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/adafruit_datetime.py b/adafruit_datetime.py index 90ae439..2bf8d3d 100755 --- a/adafruit_datetime.py +++ b/adafruit_datetime.py @@ -153,8 +153,8 @@ def _check_tzinfo_arg(time_zone: Optional["tzinfo"]): # Utility functions - date - "year -> 1 if leap year, else 0." def _is_leap(year: int) -> bool: + "year -> True if leap year, else False." return year % 4 == 0 and (year % 100 != 0 or year % 400 == 0) From 2292ecb4f0d578a58d230ebf717055cf3b691439 Mon Sep 17 00:00:00 2001 From: Alec Delaney Date: Thu, 3 Feb 2022 12:54:26 -0500 Subject: [PATCH 03/10] Add type annotations for timedelta --- adafruit_datetime.py | 65 ++++++++++++++++++++++---------------------- 1 file changed, 33 insertions(+), 32 deletions(-) diff --git a/adafruit_datetime.py b/adafruit_datetime.py index 2bf8d3d..851ef3f 100755 --- a/adafruit_datetime.py +++ b/adafruit_datetime.py @@ -33,7 +33,8 @@ from micropython import const try: - from typing import Any, Union, Optional, Tuple + from typing import Type, Any, Union, Optional, Tuple + NotImplementedType = Type[NotImplemented] except ImportError: pass @@ -311,14 +312,14 @@ class timedelta: # pylint: disable=too-many-arguments, too-many-locals, too-many-statements def __new__( cls, - days=0, - seconds=0, - microseconds=0, - milliseconds=0, - minutes=0, - hours=0, - weeks=0, - ): + days: int = 0, + seconds:int = 0, + microseconds: int = 0, + milliseconds: int = 0, + minutes: int = 0, + hours: int = 0, + weeks: int = 0, + ) -> "timedelta": # Check that all inputs are ints or floats. if not all( @@ -417,22 +418,22 @@ def __new__( # Instance attributes (read-only) @property - def days(self): + def days(self) -> int: """Days, Between -999999999 and 999999999 inclusive""" return self._days @property - def seconds(self): + def seconds(self) -> int: """Seconds, Between 0 and 86399 inclusive""" return self._seconds @property - def microseconds(self): + def microseconds(self) -> int: """Microseconds, Between 0 and 999999 inclusive""" return self._microseconds # Instance methods - def total_seconds(self): + def total_seconds(self) -> float: """Return the total number of seconds contained in the duration.""" # If the duration is less than a threshold duration, and microseconds # is nonzero, then the result is a float. Otherwise, the result is a @@ -444,7 +445,7 @@ def total_seconds(self): seconds += self._microseconds / 10 ** 6 return seconds - def __repr__(self): + def __repr__(self) -> str: args = [] if self._days: args.append("days=%d" % self._days) @@ -460,7 +461,7 @@ def __repr__(self): ", ".join(args), ) - def __str__(self): + def __str__(self) -> str: mm, ss = divmod(self._seconds, 60) hh, mm = divmod(mm, 60) s = "%d:%02d:%02d" % (hh, mm, ss) @@ -475,10 +476,10 @@ def plural(n): return s # Supported operations - def __neg__(self): + def __neg__(self) -> "timedelta": return timedelta(-self._days, -self._seconds, -self._microseconds) - def __add__(self, other): + def __add__(self, other: "timedelta") -> "timedelta": if isinstance(other, timedelta): return timedelta( self._days + other._days, @@ -487,7 +488,7 @@ def __add__(self, other): ) return NotImplemented - def __sub__(self, other): + def __sub__(self, other: "timedelta") -> "timedelta": if isinstance(other, timedelta): return timedelta( self._days - other._days, @@ -496,10 +497,10 @@ def __sub__(self, other): ) return NotImplemented - def _to_microseconds(self): + def _to_microseconds(self) -> int: return (self._days * (24 * 3600) + self._seconds) * 1000000 + self._microseconds - def __floordiv__(self, other): + def __floordiv__(self, other: Union[int, "timedelta"]) -> Union[int, "timedelta"]: if not isinstance(other, (int, timedelta)): return NotImplemented usec = self._to_microseconds() @@ -507,19 +508,19 @@ def __floordiv__(self, other): return usec // other._to_microseconds() return timedelta(0, 0, usec // other) - def __mod__(self, other): + def __mod__(self, other: "timedelta") -> "timedelta": if isinstance(other, timedelta): r = self._to_microseconds() % other._to_microseconds() return timedelta(0, 0, r) return NotImplemented - def __divmod__(self, other): + def __divmod__(self, other: "timedelta") -> "timedelta": if isinstance(other, timedelta): q, r = divmod(self._to_microseconds(), other._to_microseconds()) return q, timedelta(0, 0, r) return NotImplemented - def __mul__(self, other): + def __mul__(self, other: float) -> "timedelta": if isinstance(other, int): # for CPython compatibility, we cannot use # our __class__ here, but need a real timedelta @@ -536,45 +537,45 @@ def __mul__(self, other): __rmul__ = __mul__ # Supported comparisons - def __eq__(self, other): + def __eq__(self, other: "timedelta") -> bool: if not isinstance(other, timedelta): return False return self._cmp(other) == 0 - def __ne__(self, other): + def __ne__(self, other: "timedelta") -> bool: if not isinstance(other, timedelta): return True return self._cmp(other) != 0 - def __le__(self, other): + def __le__(self, other: "timedelta") -> bool: if not isinstance(other, timedelta): _cmperror(self, other) return self._cmp(other) <= 0 - def __lt__(self, other): + def __lt__(self, other: "timedelta") -> bool: if not isinstance(other, timedelta): _cmperror(self, other) return self._cmp(other) < 0 - def __ge__(self, other): + def __ge__(self, other: "timedelta") -> bool: if not isinstance(other, timedelta): _cmperror(self, other) return self._cmp(other) >= 0 - def __gt__(self, other): + def __gt__(self, other: "timedelta") -> bool: if not isinstance(other, timedelta): _cmperror(self, other) return self._cmp(other) > 0 # pylint: disable=no-self-use, protected-access - def _cmp(self, other): + def _cmp(self, other: "timedelta") -> int: assert isinstance(other, timedelta) return _cmp(self._getstate(), other._getstate()) - def __bool__(self): + def __bool__(self) -> bool: return self._days != 0 or self._seconds != 0 or self._microseconds != 0 - def _getstate(self): + def _getstate(self) -> Tuple[int, int, int]: return (self._days, self._seconds, self._microseconds) From 8d5d63317389401710998312eac5dd896ebc43eb Mon Sep 17 00:00:00 2001 From: Alec Delaney Date: Thu, 3 Feb 2022 12:57:36 -0500 Subject: [PATCH 04/10] Add type annotations for tzinfo --- adafruit_datetime.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/adafruit_datetime.py b/adafruit_datetime.py index 851ef3f..3563f3b 100755 --- a/adafruit_datetime.py +++ b/adafruit_datetime.py @@ -587,20 +587,20 @@ class tzinfo: """ - def utcoffset(self, dt): + def utcoffset(self, dt: "datetime") -> timedelta: """Return offset of local time from UTC, as a timedelta object that is positive east of UTC. """ raise NotImplementedError("tzinfo subclass must override utcoffset()") - def tzname(self, dt): + def tzname(self, dt: "datetime") -> str: """Return the time zone name corresponding to the datetime object dt, as a string.""" raise NotImplementedError("tzinfo subclass must override tzname()") # tzinfo is an abstract base class, disabling for self._offset # pylint: disable=no-member - def fromutc(self, dt): + def fromutc(self, dt: "datetime") -> "datetime": "datetime in UTC -> datetime in local time." if not isinstance(dt, datetime): From 9181f37ef408c1986dcba720976ae7637da218fd Mon Sep 17 00:00:00 2001 From: Alec Delaney Date: Thu, 3 Feb 2022 13:08:36 -0500 Subject: [PATCH 05/10] Add type annotations for date --- adafruit_datetime.py | 48 ++++++++++++++++++++++---------------------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/adafruit_datetime.py b/adafruit_datetime.py index 3563f3b..f609efa 100755 --- a/adafruit_datetime.py +++ b/adafruit_datetime.py @@ -621,7 +621,7 @@ class date: """ - def __new__(cls, year, month, day): + def __new__(cls, year: int, month: int, day: int) -> "date": """Creates a new date object. :param int year: Year within range, MINYEAR <= year <= MAXYEAR @@ -638,23 +638,23 @@ def __new__(cls, year, month, day): # Instance attributes (read-only) @property - def year(self): + def year(self) -> int: """Between MINYEAR and MAXYEAR inclusive.""" return self._year @property - def month(self): + def month(self) -> int: """Between 1 and 12 inclusive.""" return self._month @property - def day(self): + def day(self) -> int: """Between 1 and the number of days in the given month of the given year.""" return self._day # Class Methods @classmethod - def fromtimestamp(cls, t): + def fromtimestamp(cls, t: float) -> "date": """Return the local date corresponding to the POSIX timestamp, such as is returned by time.time(). """ @@ -662,7 +662,7 @@ def fromtimestamp(cls, t): return cls(tm_struct[0], tm_struct[1], tm_struct[2]) @classmethod - def fromordinal(cls, ordinal): + def fromordinal(cls, ordinal: int) -> "date": """Return the date corresponding to the proleptic Gregorian ordinal, where January 1 of year 1 has ordinal 1. @@ -673,7 +673,7 @@ def fromordinal(cls, ordinal): return cls(y, m, d) @classmethod - def fromisoformat(cls, date_string): + def fromisoformat(cls, date_string: str) -> "date": """Return a date object constructed from an ISO date format. Valid format is ``YYYY-MM-DD`` @@ -687,12 +687,12 @@ def fromisoformat(cls, date_string): raise ValueError(_INVALID_ISO_ERROR.format(date_string)) @classmethod - def today(cls): + def today(cls) -> "date": """Return the current local date.""" return cls.fromtimestamp(_time.time()) # Instance Methods - def replace(self, year=None, month=None, day=None): + def replace(self, year: Optional[int] = None, month: Optional[int] = None, day: Optional[int] = None): """Return a date with the same value, except for those parameters given new values by whichever keyword arguments are specified. If no keyword arguments are specified - values are obtained from @@ -701,36 +701,36 @@ def replace(self, year=None, month=None, day=None): """ raise NotImplementedError() - def timetuple(self): + def timetuple(self) -> _time.struct_time: """Return a time.struct_time such as returned by time.localtime(). The hours, minutes and seconds are 0, and the DST flag is -1. """ return _build_struct_time(self._year, self._month, self._day, 0, 0, 0, -1) - def toordinal(self): + def toordinal(self) -> int: """Return the proleptic Gregorian ordinal of the date, where January 1 of year 1 has ordinal 1. """ return _ymd2ord(self._year, self._month, self._day) - def weekday(self): + def weekday(self) -> int: """Return the day of the week as an integer, where Monday is 0 and Sunday is 6.""" return (self.toordinal() + 6) % 7 # ISO date - def isoweekday(self): + def isoweekday(self) -> int: """Return the day of the week as an integer, where Monday is 1 and Sunday is 7.""" return self.toordinal() % 7 or 7 - def isoformat(self): + def isoformat(self) -> str: """Return a string representing the date in ISO 8601 format, YYYY-MM-DD:""" return "%04d-%02d-%02d" % (self._year, self._month, self._day) # For a date d, str(d) is equivalent to d.isoformat() __str__ = isoformat - def __repr__(self): + def __repr__(self) -> str: """Convert to formal string, for repr().""" return "%s(%d, %d, %d)" % ( "datetime." + self.__class__.__name__, @@ -740,48 +740,48 @@ def __repr__(self): ) # Supported comparisons - def __eq__(self, other): + def __eq__(self, other: "date") -> bool: if isinstance(other, date): return self._cmp(other) == 0 return NotImplemented - def __le__(self, other): + def __le__(self, other: "date") -> bool: if isinstance(other, date): return self._cmp(other) <= 0 return NotImplemented - def __lt__(self, other): + def __lt__(self, other: "date") -> bool: if isinstance(other, date): return self._cmp(other) < 0 return NotImplemented - def __ge__(self, other): + def __ge__(self, other: "date") -> bool: if isinstance(other, date): return self._cmp(other) >= 0 return NotImplemented - def __gt__(self, other): + def __gt__(self, other: "date") -> bool: if isinstance(other, date): return self._cmp(other) > 0 return NotImplemented - def _cmp(self, other): + def _cmp(self, other: "date") -> int: assert isinstance(other, date) y, m, d = self._year, self._month, self._day y2, m2, d2 = other.year, other.month, other.day return _cmp((y, m, d), (y2, m2, d2)) - def __hash__(self): + def __hash__(self) -> int: if self._hashcode == -1: self._hashcode = hash(self._getstate()) return self._hashcode # Pickle support - def _getstate(self): + def _getstate(self) -> Tuple[bytes]: yhi, ylo = divmod(self._year, 256) return (bytes([yhi, ylo, self._month, self._day]),) - def _setstate(self, string): + def _setstate(self, string: bytes) -> None: yhi, ylo, self._month, self._day = string self._year = yhi * 256 + ylo From f4c4724555b93db1aed419d61ff9b550592662ed Mon Sep 17 00:00:00 2001 From: Alec Delaney Date: Thu, 3 Feb 2022 13:14:54 -0500 Subject: [PATCH 06/10] Add type annotations to timedelta --- adafruit_datetime.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/adafruit_datetime.py b/adafruit_datetime.py index f609efa..bb7f488 100755 --- a/adafruit_datetime.py +++ b/adafruit_datetime.py @@ -537,7 +537,7 @@ def __mul__(self, other: float) -> "timedelta": __rmul__ = __mul__ # Supported comparisons - def __eq__(self, other: "timedelta") -> bool: + def __eq__(self, other: Any) -> bool: if not isinstance(other, timedelta): return False return self._cmp(other) == 0 @@ -799,7 +799,7 @@ class timezone(tzinfo): # Sentinel value to disallow None _Omitted = object() - def __new__(cls, offset, name=_Omitted): + def __new__(cls, offset: timedelta, name: Union[str, object] = _Omitted) -> "timezone": if not isinstance(offset, timedelta): raise TypeError("offset must be a timedelta") if name is cls._Omitted: @@ -824,7 +824,7 @@ def __new__(cls, offset, name=_Omitted): # pylint: disable=protected-access, bad-super-call @classmethod - def _create(cls, offset, name=None): + def _create(cls, offset: timedelta, name: Optional[str] = None) -> "timezone": """High-level creation for a timezone object.""" self = super(tzinfo, cls).__new__(cls) self._offset = offset @@ -832,12 +832,12 @@ def _create(cls, offset, name=None): return self # Instance methods - def utcoffset(self, dt): + def utcoffset(self, dt: Optional["datetime"]) -> timedelta: if isinstance(dt, datetime) or dt is None: return self._offset raise TypeError("utcoffset() argument must be a datetime instance" " or None") - def tzname(self, dt): + def tzname(self, dt: Optional["datetime"]) -> str: if isinstance(dt, datetime) or dt is None: if self._name is None: return self._name_from_offset(self._offset) @@ -845,15 +845,15 @@ def tzname(self, dt): raise TypeError("tzname() argument must be a datetime instance" " or None") # Comparison to other timezone objects - def __eq__(self, other): + def __eq__(self, other: Any) -> bool: if not isinstance(other, timezone): return False return self._offset == other._offset - def __hash__(self): + def __hash__(self) -> int: return hash(self._offset) - def __repr__(self): + def __repr__(self) -> str: """Convert to formal string, for repr().""" if self is self.utc: return "datetime.timezone.utc" @@ -865,11 +865,11 @@ def __repr__(self): self._name, ) - def __str__(self): + def __str__(self) -> str: return self.tzname(None) @staticmethod - def _name_from_offset(delta): + def _name_from_offset(delta: timedelta) -> str: if delta < timedelta(0): sign = "-" delta = -delta From 0a14e5f431015abb17f097db9ffb8a5150fa1d2c Mon Sep 17 00:00:00 2001 From: Alec Delaney Date: Thu, 3 Feb 2022 13:24:33 -0500 Subject: [PATCH 07/10] Add type annotations for time --- adafruit_datetime.py | 48 ++++++++++++++++++++++---------------------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/adafruit_datetime.py b/adafruit_datetime.py index bb7f488..030ac07 100755 --- a/adafruit_datetime.py +++ b/adafruit_datetime.py @@ -33,7 +33,7 @@ from micropython import const try: - from typing import Type, Any, Union, Optional, Tuple + from typing import Type, Any, Union, Optional, Tuple, Sequence, List NotImplementedType = Type[NotImplemented] except ImportError: pass @@ -890,7 +890,7 @@ class time: """ # pylint: disable=redefined-outer-name - def __new__(cls, hour=0, minute=0, second=0, microsecond=0, tzinfo=None, *, fold=0): + def __new__(cls, hour: int = 0, minute: int = 0, second: int = 0, microsecond: int = 0, tzinfo: Optional[tzinfo] = None, *, fold: int = 0) -> "time": _check_time_fields(hour, minute, second, microsecond, fold) _check_tzinfo_arg(tzinfo) self = object.__new__(cls) @@ -905,39 +905,39 @@ def __new__(cls, hour=0, minute=0, second=0, microsecond=0, tzinfo=None, *, fold # Instance attributes (read-only) @property - def hour(self): + def hour(self) -> int: """In range(24).""" return self._hour @property - def minute(self): + def minute(self) -> int: """In range(60).""" return self._minute @property - def second(self): + def second(self) -> int: """In range(60).""" return self._second @property - def microsecond(self): + def microsecond(self) -> int: """In range(1000000).""" return self._microsecond @property - def fold(self): + def fold(self) -> int: """Fold.""" return self._fold @property - def tzinfo(self): + def tzinfo(self) -> Optional[tzinfo]: """The object passed as the tzinfo argument to the time constructor, or None if none was passed. """ return self._tzinfo @staticmethod - def _parse_iso_string(string_to_parse, segments): + def _parse_iso_string(string_to_parse: str, segments: Sequence[str]) -> List[int]: results = [] remaining_string = string_to_parse @@ -955,7 +955,7 @@ def _parse_iso_string(string_to_parse, segments): # pylint: disable=too-many-locals @classmethod - def fromisoformat(cls, time_string): + def fromisoformat(cls, time_string: str) -> "time": """Return a time object constructed from an ISO date format. Valid format is ``HH[:MM[:SS[.fff[fff]]]][+HH:MM[:SS[.ffffff]]]`` @@ -1027,7 +1027,7 @@ def fromisoformat(cls, time_string): # pylint: enable=too-many-locals # Instance methods - def isoformat(self, timespec="auto"): + def isoformat(self, timespec: str = "auto") -> str: """Return a string representing the time in ISO 8601 format, one of: HH:MM:SS.ffffff, if microsecond is not 0 @@ -1049,7 +1049,7 @@ def isoformat(self, timespec="auto"): # For a time t, str(t) is equivalent to t.isoformat() __str__ = isoformat - def utcoffset(self): + def utcoffset(self) -> timedelta: """Return the timezone offset in minutes east of UTC (negative west of UTC).""" if self._tzinfo is None: @@ -1058,7 +1058,7 @@ def utcoffset(self): _check_utc_offset("utcoffset", offset) return offset - def tzname(self): + def tzname(self) -> str: """Return the timezone name. Note that the name is 100% informational -- there's no requirement that @@ -1072,32 +1072,32 @@ def tzname(self): return name # Standard conversions and comparisons - def __eq__(self, other): + def __eq__(self, other: "time") -> bool: if not isinstance(other, time): return NotImplemented return self._cmp(other, allow_mixed=True) == 0 - def __le__(self, other): + def __le__(self, other: "time") -> bool: if not isinstance(other, time): return NotImplemented return self._cmp(other) <= 0 - def __lt__(self, other): + def __lt__(self, other: "time") -> bool: if not isinstance(other, time): return NotImplemented return self._cmp(other) < 0 - def __ge__(self, other): + def __ge__(self, other: "time") -> bool: if not isinstance(other, time): return NotImplemented return self._cmp(other) >= 0 - def __gt__(self, other): + def __gt__(self, other: "time") -> bool: if not isinstance(other, time): return NotImplemented return self._cmp(other) > 0 - def _cmp(self, other, allow_mixed=False): + def _cmp(self, other: "time", allow_mixed: bool = False) -> int: assert isinstance(other, time) mytz = self._tzinfo ottz = other.tzinfo @@ -1126,7 +1126,7 @@ def _cmp(self, other, allow_mixed=False): (othhmm, other.second, other.microsecond), ) - def __hash__(self): + def __hash__(self) -> int: """Hash.""" if self._hashcode == -1: t = self @@ -1146,7 +1146,7 @@ def __hash__(self): self._hashcode = hash((h, m, self.second, self.microsecond)) return self._hashcode - def _tzstr(self, sep=":"): + def _tzstr(self, sep: str = ":") -> Optional[str]: """Return formatted timezone offset (+xx:xx) or None.""" off = self.utcoffset() if off is not None: @@ -1162,12 +1162,12 @@ def _tzstr(self, sep=":"): off = "%s%02d%s%02d" % (sign, hh, sep, mm) return off - def __format__(self, fmt): + def __format__(self, fmt: str) -> str: if not isinstance(fmt, str): raise TypeError("must be str, not %s" % type(fmt).__name__) return str(self) - def __repr__(self): + def __repr__(self) -> str: """Convert to formal string, for repr().""" if self._microsecond != 0: s = ", %d, %d" % (self._second, self._microsecond) @@ -1187,7 +1187,7 @@ def __repr__(self): return s # Pickle support - def _getstate(self, protocol=3): + def _getstate(self, protocol: int = 3) -> Tuple[bytes]: us2, us3 = divmod(self._microsecond, 256) us1, us2 = divmod(us2, 256) h = self._hour From a0dca860daec1add4ce4a766eb18b323185f3440 Mon Sep 17 00:00:00 2001 From: Alec Delaney Date: Thu, 3 Feb 2022 16:01:13 -0500 Subject: [PATCH 08/10] Add type annotations for datetime --- adafruit_datetime.py | 116 +++++++++++++++++++++---------------------- 1 file changed, 58 insertions(+), 58 deletions(-) diff --git a/adafruit_datetime.py b/adafruit_datetime.py index 030ac07..8580546 100755 --- a/adafruit_datetime.py +++ b/adafruit_datetime.py @@ -1211,17 +1211,17 @@ class datetime(date): # pylint: disable=redefined-outer-name def __new__( cls, - year, - month, - day, - hour=0, - minute=0, - second=0, - microsecond=0, - tzinfo=None, + year: int, + month: int, + day: int, + hour: int = 0, + minute: int = 0, + second: int = 0, + microsecond: int = 0, + tzinfo: Optional[tzinfo] = None, *, - fold=0 - ): + fold: int = 0 + ) -> "datetime": _check_date_fields(year, month, day) _check_time_fields(hour, minute, second, microsecond, fold) _check_tzinfo_arg(tzinfo) @@ -1241,49 +1241,49 @@ def __new__( # Read-only instance attributes @property - def year(self): + def year(self) -> int: """Between MINYEAR and MAXYEAR inclusive.""" return self._year @property - def month(self): + def month(self) -> int: """Between 1 and 12 inclusive.""" return self._month @property - def day(self): + def day(self) -> int: """Between 1 and the number of days in the given month of the given year.""" return self._day @property - def hour(self): + def hour(self) -> int: """In range(24).""" return self._hour @property - def minute(self): + def minute(self) -> int: """In range (60)""" return self._minute @property - def second(self): + def second(self) -> int: """In range (60)""" return self._second @property - def microsecond(self): + def microsecond(self) -> int: """In range (1000000)""" return self._microsecond @property - def tzinfo(self): + def tzinfo(self) -> Optional[tzinfo]: """The object passed as the tzinfo argument to the datetime constructor, or None if none was passed. """ return self._tzinfo @property - def fold(self): + def fold(self) -> int: """Fold.""" return self._fold @@ -1291,7 +1291,7 @@ def fold(self): # pylint: disable=protected-access @classmethod - def _fromtimestamp(cls, t, utc, tz): + def _fromtimestamp(cls, t: float, utc: bool, tz: Optional["tzinfo"]) -> "datetime": """Construct a datetime from a POSIX timestamp (like time.time()). A timezone info object may be passed in as well. """ @@ -1330,11 +1330,11 @@ def _fromtimestamp(cls, t, utc, tz): ## pylint: disable=arguments-differ @classmethod - def fromtimestamp(cls, timestamp, tz=None): + def fromtimestamp(cls, timestamp: float, tz: Optional["tzinfo"] = None) -> "datetime": return cls._fromtimestamp(timestamp, tz is not None, tz) @classmethod - def fromisoformat(cls, date_string): + def fromisoformat(cls, date_string: str) -> "datetime": """Return a datetime object constructed from an ISO date format. Valid format is ``YYYY-MM-DD[*HH[:MM[:SS[.fff[fff]]]][+HH:MM[:SS[.ffffff]]]]`` @@ -1357,17 +1357,17 @@ def fromisoformat(cls, date_string): return cls.combine(dateval, timeval) @classmethod - def now(cls, timezone=None): + def now(cls, timezone: Optional["tzinfo"]=None) -> "datetime": """Return the current local date and time.""" return cls.fromtimestamp(_time.time(), tz=timezone) @classmethod - def utcfromtimestamp(cls, timestamp): + def utcfromtimestamp(cls, timestamp: float) -> "datetime": """Return the UTC datetime corresponding to the POSIX timestamp, with tzinfo None""" return cls._fromtimestamp(timestamp, True, None) @classmethod - def combine(cls, date, time, tzinfo=True): + def combine(cls, date: date, time: time, tzinfo: bool = True) -> "datetime": """Return a new datetime object whose date components are equal to the given date object’s, and whose time components are equal to the given time object’s. @@ -1391,7 +1391,7 @@ def combine(cls, date, time, tzinfo=True): ) # Instance methods - def _mktime(self): + def _mktime(self) -> int: """Return integer POSIX timestamp.""" epoch = datetime(1970, 1, 1) max_fold_seconds = 24 * 3600 @@ -1426,11 +1426,11 @@ def local(u): # a solution. This means t is in the gap. return (max, min)[self._fold](u1, u2) - def date(self): + def date(self) -> date: """Return date object with same year, month and day.""" return _date_class(self._year, self._month, self._day) - def time(self): + def time(self) -> time: """Return time object with same hour, minute, second, microsecond and fold. tzinfo is None. See also method timetz(). @@ -1439,7 +1439,7 @@ def time(self): self._hour, self._minute, self._second, self._microsecond, fold=self._fold ) - def dst(self): + def dst(self) -> Optional[timedelta]: """If tzinfo is None, returns None, else returns self.tzinfo.dst(self), and raises an exception if the latter doesn’t return None or a timedelta object with magnitude less than one day. @@ -1451,7 +1451,7 @@ def dst(self): _check_utc_offset("dst", offset) return offset - def timetuple(self): + def timetuple(self) -> _time.struct_time: """Return local time tuple compatible with time.localtime().""" dst = self.dst() if dst is None: @@ -1464,7 +1464,7 @@ def timetuple(self): self.year, self.month, self.day, self.hour, self.minute, self.second, dst ) - def utcoffset(self): + def utcoffset(self) -> Optional[timedelta]: """If tzinfo is None, returns None, else returns self.tzinfo.utcoffset(self), and raises an exception if the latter doesn’t return None or a timedelta object @@ -1477,22 +1477,22 @@ def utcoffset(self): _check_utc_offset("utcoffset", offset) return offset - def toordinal(self): + def toordinal(self) -> int: """Return the proleptic Gregorian ordinal of the date.""" return _ymd2ord(self._year, self._month, self._day) - def timestamp(self): + def timestamp(self) -> float: "Return POSIX timestamp as float" if not self._tzinfo is None: return (self - _EPOCH).total_seconds() s = self._mktime() return s + self.microsecond / 1e6 - def weekday(self): + def weekday(self) -> int: """Return the day of the week as an integer, where Monday is 0 and Sunday is 6.""" return (self.toordinal() + 6) % 7 - def ctime(self): + def ctime(self) -> str: "Return string representing the datetime." weekday = self.toordinal() % 7 or 7 return "%s %s %2d %02d:%02d:%02d %04d" % ( @@ -1505,7 +1505,7 @@ def ctime(self): self._year, ) - def __repr__(self): + def __repr__(self) -> str: """Convert to formal string, for repr().""" L = [ self._year, @@ -1527,7 +1527,7 @@ def __repr__(self): s = s[:-1] + ", tzinfo=%r" % self._tzinfo + ")" return s - def isoformat(self, sep="T", timespec="auto"): + def isoformat(self, sep: str = "T", timespec: str = "auto") -> str: """Return a string representing the date and time in ISO8601 format. @@ -1548,23 +1548,23 @@ def isoformat(self, sep="T", timespec="auto"): return s - def __str__(self): + def __str__(self) -> str: "Convert to string, for str()." return self.isoformat(sep=" ") def replace( self, - year=None, - month=None, - day=None, - hour=None, - minute=None, - second=None, - microsecond=None, - tzinfo=True, + year: Optional[int] = None, + month: Optional[str] = None, + day: Optional[str] = None, + hour: Optional[str] = None, + minute: Optional[str] = None, + second: Optional[str] = None, + microsecond: Optional[str] = None, + tzinfo: bool = True, *, - fold=None - ): + fold: Optional[int] = None + ) -> "datetime": """Return a datetime with the same attributes, except for those attributes given new values by whichever keyword arguments are specified. @@ -1593,32 +1593,32 @@ def replace( ) # Comparisons of datetime objects. - def __eq__(self, other): + def __eq__(self, other: Any) -> bool: if not isinstance(other, datetime): return False return self._cmp(other, allow_mixed=True) == 0 - def __le__(self, other): + def __le__(self, other: "datetime") -> bool: if not isinstance(other, datetime): _cmperror(self, other) return self._cmp(other) <= 0 - def __lt__(self, other): + def __lt__(self, other: "datetime") -> bool: if not isinstance(other, datetime): _cmperror(self, other) return self._cmp(other) < 0 - def __ge__(self, other): + def __ge__(self, other: "datetime") -> bool: if not isinstance(other, datetime): _cmperror(self, other) return self._cmp(other) >= 0 - def __gt__(self, other): + def __gt__(self, other: "datetime") -> bool: if not isinstance(other, datetime): _cmperror(self, other) return self._cmp(other) > 0 - def _cmp(self, other, allow_mixed=False): + def _cmp(self, other: "datetime", allow_mixed: bool = False) -> int: assert isinstance(other, datetime) mytz = self._tzinfo ottz = other.tzinfo @@ -1667,7 +1667,7 @@ def _cmp(self, other, allow_mixed=False): return -1 return 1 if diff else 0 - def __add__(self, other): + def __add__(self, other: timedelta) -> "datetime": "Add a datetime and a timedelta." if not isinstance(other, timedelta): return NotImplemented @@ -1690,7 +1690,7 @@ def __add__(self, other): __radd__ = __add__ - def __sub__(self, other): + def __sub__(self, other: Union["datetime", timedelta]) -> "datetime": "Subtract two datetimes, or a datetime and a timedelta." if not isinstance(other, datetime): if isinstance(other, timedelta): @@ -1714,7 +1714,7 @@ def __sub__(self, other): raise TypeError("cannot mix naive and timezone-aware time") return base + otoff - myoff - def __hash__(self): + def __hash__(self) -> int: if self._hashcode == -1: t = self tzoff = t.utcoffset() @@ -1728,7 +1728,7 @@ def __hash__(self): ) return self._hashcode - def _getstate(self): + def _getstate(self) -> Tuple[bytes]: protocol = 3 yhi, ylo = divmod(self._year, 256) us2, us3 = divmod(self._microsecond, 256) From 833a2bb2cbb7ca543d165b6513f2dfd90f31f1c0 Mon Sep 17 00:00:00 2001 From: Alec Delaney Date: Thu, 3 Feb 2022 16:02:10 -0500 Subject: [PATCH 09/10] Reformatted per pre-commit --- adafruit_datetime.py | 49 ++++++++++++++++++++++++++++++++++++-------- 1 file changed, 40 insertions(+), 9 deletions(-) diff --git a/adafruit_datetime.py b/adafruit_datetime.py index 8580546..399eb30 100755 --- a/adafruit_datetime.py +++ b/adafruit_datetime.py @@ -34,6 +34,7 @@ try: from typing import Type, Any, Union, Optional, Tuple, Sequence, List + NotImplementedType = Type[NotImplemented] except ImportError: pass @@ -76,14 +77,18 @@ def _cmp(obj_x: Any, obj_y: Any) -> int: return 0 if obj_x == obj_y else 1 if obj_x > obj_y else -1 -def _cmperror(obj_x: Union["datetime", "timedelta"], obj_y: Union["datetime", "timedelta"]) -> None: +def _cmperror( + obj_x: Union["datetime", "timedelta"], obj_y: Union["datetime", "timedelta"] +) -> None: raise TypeError( "can't compare '%s' to '%s'" % (type(obj_x).__name__, type(obj_y).__name__) ) # Utility functions - time -def _check_time_fields(hour: int, minute: int, second: int, microsecond: int, fold: int) -> None: +def _check_time_fields( + hour: int, minute: int, second: int, microsecond: int, fold: int +) -> None: if not isinstance(hour, int): raise TypeError("Hour expected as int") if not 0 <= hour <= 23: @@ -200,7 +205,15 @@ def _ymd2ord(year: int, month: int, day: int) -> int: # pylint: disable=too-many-arguments -def _build_struct_time(tm_year: int, tm_month: int, tm_mday: int, tm_hour: int, tm_min: int, tm_sec: int, tm_isdst: int) -> _time.struct_time: +def _build_struct_time( + tm_year: int, + tm_month: int, + tm_mday: int, + tm_hour: int, + tm_min: int, + tm_sec: int, + tm_isdst: int, +) -> _time.struct_time: tm_wday = (_ymd2ord(tm_year, tm_month, tm_mday) + 6) % 7 tm_yday = _days_before_month(tm_year, tm_month) + tm_mday return _time.struct_time( @@ -313,7 +326,7 @@ class timedelta: def __new__( cls, days: int = 0, - seconds:int = 0, + seconds: int = 0, microseconds: int = 0, milliseconds: int = 0, minutes: int = 0, @@ -692,7 +705,12 @@ def today(cls) -> "date": return cls.fromtimestamp(_time.time()) # Instance Methods - def replace(self, year: Optional[int] = None, month: Optional[int] = None, day: Optional[int] = None): + def replace( + self, + year: Optional[int] = None, + month: Optional[int] = None, + day: Optional[int] = None, + ): """Return a date with the same value, except for those parameters given new values by whichever keyword arguments are specified. If no keyword arguments are specified - values are obtained from @@ -799,7 +817,9 @@ class timezone(tzinfo): # Sentinel value to disallow None _Omitted = object() - def __new__(cls, offset: timedelta, name: Union[str, object] = _Omitted) -> "timezone": + def __new__( + cls, offset: timedelta, name: Union[str, object] = _Omitted + ) -> "timezone": if not isinstance(offset, timedelta): raise TypeError("offset must be a timedelta") if name is cls._Omitted: @@ -890,7 +910,16 @@ class time: """ # pylint: disable=redefined-outer-name - def __new__(cls, hour: int = 0, minute: int = 0, second: int = 0, microsecond: int = 0, tzinfo: Optional[tzinfo] = None, *, fold: int = 0) -> "time": + def __new__( + cls, + hour: int = 0, + minute: int = 0, + second: int = 0, + microsecond: int = 0, + tzinfo: Optional[tzinfo] = None, + *, + fold: int = 0 + ) -> "time": _check_time_fields(hour, minute, second, microsecond, fold) _check_tzinfo_arg(tzinfo) self = object.__new__(cls) @@ -1330,7 +1359,9 @@ def _fromtimestamp(cls, t: float, utc: bool, tz: Optional["tzinfo"]) -> "datetim ## pylint: disable=arguments-differ @classmethod - def fromtimestamp(cls, timestamp: float, tz: Optional["tzinfo"] = None) -> "datetime": + def fromtimestamp( + cls, timestamp: float, tz: Optional["tzinfo"] = None + ) -> "datetime": return cls._fromtimestamp(timestamp, tz is not None, tz) @classmethod @@ -1357,7 +1388,7 @@ def fromisoformat(cls, date_string: str) -> "datetime": return cls.combine(dateval, timeval) @classmethod - def now(cls, timezone: Optional["tzinfo"]=None) -> "datetime": + def now(cls, timezone: Optional["tzinfo"] = None) -> "datetime": """Return the current local date and time.""" return cls.fromtimestamp(_time.time(), tz=timezone) From bf5f62be65bc30729c8d475cc1520415ed68e9aa Mon Sep 17 00:00:00 2001 From: Alec Delaney Date: Thu, 3 Feb 2022 16:09:26 -0500 Subject: [PATCH 10/10] Remove NotImplementedType --- adafruit_datetime.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/adafruit_datetime.py b/adafruit_datetime.py index 399eb30..9d39e13 100755 --- a/adafruit_datetime.py +++ b/adafruit_datetime.py @@ -33,9 +33,7 @@ from micropython import const try: - from typing import Type, Any, Union, Optional, Tuple, Sequence, List - - NotImplementedType = Type[NotImplemented] + from typing import Any, Union, Optional, Tuple, Sequence, List except ImportError: pass