-
Notifications
You must be signed in to change notification settings - Fork 15
Complete typing with strict type-checking #43
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,9 +1,19 @@ | ||
from .helpers import parse, DateMathException | ||
from __future__ import annotations | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This allows the use of |
||
def dm(expr, **kwargs): | ||
from datetime import datetime | ||
from typing import TYPE_CHECKING | ||
|
||
from arrow import Arrow | ||
|
||
if TYPE_CHECKING: | ||
from typing_extensions import Unpack | ||
|
||
from .helpers import ParseParams, parse as parse, DateMathException as DateMathException | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
def dm(expr: str | int, **kwargs: Unpack[ParseParams]) -> Arrow: | ||
''' does our datemath and returns an arrow object ''' | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. By using an unpacked |
||
return parse(expr, **kwargs) | ||
|
||
def datemath(expr, **kwargs): | ||
def datemath(expr: str | int, **kwargs: Unpack[ParseParams]) -> datetime: | ||
''' does our datemath and returns a datetime object ''' | ||
return parse(expr, **kwargs).datetime |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -38,16 +38,14 @@ | |||||
|
||||||
''' | ||||||
|
||||||
from __future__ import annotations | ||||||
|
||||||
import os | ||||||
import re | ||||||
from typing import TypedDict, cast | ||||||
|
||||||
import arrow | ||||||
from arrow import Arrow | ||||||
from datetime import datetime | ||||||
import re | ||||||
import os | ||||||
from dateutil import tz | ||||||
import dateutil | ||||||
import sys | ||||||
from pprint import pprint | ||||||
from typing import Any, Optional | ||||||
|
||||||
debug = True if os.environ.get('DATEMATH_DEBUG') else False | ||||||
|
||||||
|
@@ -78,7 +76,13 @@ def unitMap(c: str) -> str: | |||||
else: | ||||||
raise DateMathException("Not a valid timeunit: {0}".format(c)) | ||||||
|
||||||
def parse(expression: str, now: Any = None, tz: str = 'UTC', type: Any = None, roundDown: bool = True) -> Arrow: | ||||||
class ParseParams(TypedDict, total=False): | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. See comment in |
||||||
now: Arrow | None | ||||||
tz: str | ||||||
type: str | None | ||||||
roundDown: bool | ||||||
|
||||||
def parse(expression: str | int, now: Arrow | None = None, tz: str = 'UTC', type: str | None = None, roundDown: bool = True) -> Arrow: | ||||||
''' | ||||||
the main meat and potatoes of this this whole thing | ||||||
takes our datemath expression and does our date math | ||||||
|
@@ -101,15 +105,16 @@ def parse(expression: str, now: Any = None, tz: str = 'UTC', type: Any = None, r | |||||
if debug: print("parse() - will now convert tz to {0}".format(tz)) | ||||||
now = now.to(tz) | ||||||
|
||||||
expression = str(expression) | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Less runtime casting in some cases. But more importantly, this is now type-safe. |
||||||
if expression == 'now': | ||||||
if debug: print("parse() - Now, no dm: {0}".format(now)) | ||||||
if type: | ||||||
return getattr(now, type) | ||||||
return cast(Arrow, getattr(now, type)) | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In strict mode, mypy will whine about returning
Suggested change
|
||||||
else: | ||||||
return now | ||||||
elif re.match(r'\d{10,}', str(expression)): | ||||||
elif re.match(r'\d{10,}', expression): | ||||||
if debug: print('parse() - found an epoch timestamp') | ||||||
if len(str(expression)) == 13: | ||||||
if len(expression) == 13: | ||||||
raise DateMathException('Unable to parse epoch timestamps in millis, please convert to the nearest second to continue - i.e. 1451610061 / 1000') | ||||||
ts = arrow.get(int(expression)) | ||||||
ts = ts.replace(tzinfo=tz) | ||||||
|
@@ -142,7 +147,7 @@ def parse(expression: str, now: Any = None, tz: str = 'UTC', type: Any = None, r | |||||
rettime = evaluate(math, time, tz, roundDown) | ||||||
|
||||||
if type: | ||||||
return getattr(rettime, type) | ||||||
return cast(Arrow, getattr(rettime, type)) | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In strict mode, mypy will whine about returning
Suggested change
|
||||||
else: | ||||||
return rettime | ||||||
|
||||||
|
@@ -158,7 +163,7 @@ def parseTime(timestamp: str, timezone: str = 'UTC') -> Arrow: | |||||
if debug: print("parseTime() - timezone that came in = {}".format(timezone)) | ||||||
|
||||||
if ts.tzinfo: | ||||||
import dateutil | ||||||
import dateutil.tz | ||||||
if isinstance(ts.tzinfo, dateutil.tz.tz.tzoffset): | ||||||
# this means our TZ probably came in via our datetime string | ||||||
# then lets set our tz to whatever tzoffset is | ||||||
|
@@ -175,14 +180,14 @@ def parseTime(timestamp: str, timezone: str = 'UTC') -> Arrow: | |||||
if debug: print('parseTime() - Doesnt look like we have a valid timestamp, raise an exception. timestamp={}'.format(timestamp)) | ||||||
raise DateMathException('Valid length timestamp not provide, you gave me a timestamp of "{}", but I need something that has a len() >= 4'.format(timestamp)) | ||||||
|
||||||
def roundDate(now: Any, unit: str, tz: str = 'UTC', roundDown: bool = True) -> Arrow: | ||||||
def roundDate(now: Arrow, unit: str, tz: str = 'UTC', roundDown: bool = True) -> Arrow: | ||||||
''' | ||||||
rounds our date object | ||||||
''' | ||||||
if roundDown: | ||||||
now = now.floor(unit) | ||||||
now = now.floor(unit) # type: ignore[arg-type] | ||||||
else: | ||||||
now = now.ceil(unit) | ||||||
now = now.ceil(unit) # type: ignore[arg-type] | ||||||
Comment on lines
+188
to
+190
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||||||
if debug: print("roundDate() Now: {0}".format(now)) | ||||||
return now | ||||||
|
||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
# Marker file for PEP 561. The python-datemath package uses inline types. |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -9,8 +9,7 @@ docutils==0.15.2 | |
freezegun==1.2.2 | ||
idna==2.7 | ||
linecache2==1.0.0 | ||
mypy==1.5.1 | ||
mypy-extensions==1.0.0 | ||
mypy==1.7.1 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd personally bump mypy all the way to latest. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thank you for this. mypy is only used in this package to do the type checking so its probably good that its at the latest anyways. That got the tests to pass now |
||
packaging==16.8 | ||
pkginfo==1.4.2 | ||
Pygments==2.7.4 | ||
|
@@ -24,9 +23,10 @@ six==1.10.0 | |
tqdm==4.36.1 | ||
traceback2==1.4.0 | ||
twine==2.0.0 | ||
types-python-dateutil==2.8.19.14 | ||
types-python-dateutil==2.8.19.20240311 | ||
types-setuptools==73.0.0.20240822 | ||
types-pytz==2023.3.1.1 | ||
typing_extensions==4.7.1 | ||
tzdata==2024.1 | ||
unittest2==1.1.0 | ||
urllib3==1.24.3 | ||
webencodings==0.5.1 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Running on tests is important too. This ensures expected usage is correctly typed. In fact, it found that
parse
was missing theint
annotation on its first parameter.