Skip to content

Commit

Permalink
Audit errors and clean up utils (#887)
Browse files Browse the repository at this point in the history
* Fix MAX_TIMESTAMP on Windows (#882)

* Fix max timestamp

* Fix scoping

* Catch OS Error also

* Fix comment

* Audit and improve error messages and remove total_seconds util function.
  • Loading branch information
jadchaar committed Nov 26, 2020
1 parent f885241 commit 1278a21
Show file tree
Hide file tree
Showing 9 changed files with 66 additions and 96 deletions.
78 changes: 35 additions & 43 deletions arrow/arrow.py
Expand Up @@ -573,7 +573,7 @@ def interval(cls, frame, start, end, interval=1, tz=None, bounds="[)"):
(<Arrow [2013-05-05T16:00:00+00:00]>, <Arrow [2013-05-05T17:59:59.999999+00:0]>)
"""
if interval < 1:
raise ValueError("interval has to be a positive integer")
raise ValueError("Interval must be a positive integer.")

spanRange = iter(cls.span_range(frame, start, end, tz, bounds=bounds))
while True:
Expand Down Expand Up @@ -775,9 +775,9 @@ def replace(self, **kwargs):
if key in self._ATTRS:
absolute_kwargs[key] = value
elif key in ["week", "quarter"]:
raise AttributeError(f"setting absolute {key} is not supported")
raise ValueError(f"Setting absolute {key} is not supported.")
elif key not in ["tzinfo", "fold"]:
raise AttributeError(f'unknown attribute: "{key}"')
raise ValueError(f"Unknown attribute: '{key}'.")

current = self._datetime.replace(**absolute_kwargs)

Expand Down Expand Up @@ -833,10 +833,9 @@ def shift(self, **kwargs):
if key in self._ATTRS_PLURAL or key in additional_attrs:
relative_kwargs[key] = value
else:
raise AttributeError(
"Invalid shift time frame. Please select one of the following: {}.".format(
", ".join(self._ATTRS_PLURAL + additional_attrs)
)
supported_attr = ", ".join(self._ATTRS_PLURAL + additional_attrs)
raise ValueError(
f"Invalid shift time frame. Please select one of the following: {supported_attr}."
)

# core datetime does not support quarters, translate to months.
Expand Down Expand Up @@ -966,16 +965,14 @@ def humanize(

else:
raise TypeError(
"Invalid 'other' argument of type '{}'. "
"Argument must be of type None, Arrow, or datetime.".format(
type(other).__name__
)
f"Invalid 'other' argument of type '{type(other).__name__}'. "
"Argument must be of type None, Arrow, or datetime."
)

if isinstance(granularity, list) and len(granularity) == 1:
granularity = granularity[0]

delta = int(round(util.total_seconds(self._datetime - dt)))
delta = int(round((self._datetime - dt).total_seconds()))
sign = -1 if delta < 0 else 1
diff = abs(delta)
delta = diff
Expand Down Expand Up @@ -1053,8 +1050,9 @@ def humanize(
elif granularity == "year":
delta = sign * delta / self._SECS_PER_YEAR
else:
raise AttributeError(
"Invalid level of granularity. Please select between 'second', 'minute', 'hour', 'day', 'week', 'month' or 'year'"
raise ValueError(
"Invalid level of granularity. "
"Please select between 'second', 'minute', 'hour', 'day', 'week', 'month' or 'year'."
)

if trunc(abs(delta)) != 1:
Expand Down Expand Up @@ -1098,7 +1096,7 @@ def humanize(
timeframes.append(["second", seconds])

if len(timeframes) < len(granularity):
raise AttributeError(
raise ValueError(
"Invalid level of granularity. "
"Please select between 'second', 'minute', 'hour', 'day', 'week', 'month' or 'year'."
)
Expand All @@ -1111,10 +1109,8 @@ def humanize(

except KeyError as e:
raise ValueError(
"Humanization of the {} granularity is not currently translated in the '{}' locale. "
"Please consider making a contribution to this locale.".format(
e, locale_name
)
f"Humanization of the {e} granularity is not currently translated in the '{locale_name}' locale. "
"Please consider making a contribution to this locale."
)

# query functions
Expand Down Expand Up @@ -1153,13 +1149,11 @@ def is_between(self, start, end, bounds="()"):

if not isinstance(start, Arrow):
raise TypeError(
"Can't parse start date argument type of '{}'".format(type(start))
f"Cannot parse start date argument type of '{type(start)}'."
)

if not isinstance(end, Arrow):
raise TypeError(
"Can't parse end date argument type of '{}'".format(type(end))
)
raise TypeError(f"Cannot parse end date argument type of '{type(start)}'.")

include_start = bounds[0] == "["
include_end = bounds[1] == "]"
Expand Down Expand Up @@ -1477,7 +1471,7 @@ def _get_tzinfo(tz_expr):
try:
return parser.TzinfoParser.parse(tz_expr)
except parser.ParserError:
raise ValueError(f"'{tz_expr}' not recognized as a timezone")
raise ValueError(f"'{tz_expr}' not recognized as a timezone.")

@classmethod
def _get_datetime(cls, expr):
Expand All @@ -1503,33 +1497,31 @@ def _get_frames(cls, name):
return "week", "weeks", 1
elif name in ["quarter", "quarters"]:
return "quarter", "months", 3

supported = ", ".join(
[
"year(s)",
"month(s)",
"day(s)",
"hour(s)",
"minute(s)",
"second(s)",
"microsecond(s)",
"week(s)",
"quarter(s)",
]
)
raise AttributeError(
"range/span over frame {} not supported. Supported frames: {}".format(
name, supported
else:
supported = ", ".join(
[
"year(s)",
"month(s)",
"day(s)",
"hour(s)",
"minute(s)",
"second(s)",
"microsecond(s)",
"week(s)",
"quarter(s)",
]
)
raise ValueError(
f"Range or span over frame {name} not supported. Supported frames: {supported}."
)
)

@classmethod
def _get_iteration_params(cls, end, limit):

if end is None:

if limit is None:
raise ValueError("one of 'end' or 'limit' is required")
raise ValueError("One of 'end' or 'limit' is required.")

return cls.max, limit

Expand Down
16 changes: 4 additions & 12 deletions arrow/factory.py
Expand Up @@ -207,9 +207,7 @@ def get(self, *args, **kwargs):
return self.type.fromdate(dt)

else:
raise TypeError(
"Can't parse single argument of type '{}'".format(type(arg))
)
raise TypeError(f"Cannot parse single argument of type '{type(arg)}'.")

elif arg_count == 2:

Expand All @@ -222,9 +220,7 @@ def get(self, *args, **kwargs):
return self.type.fromdatetime(arg_1, arg_2)
else:
raise TypeError(
"Can't parse two arguments of types 'datetime', '{}'".format(
type(arg_2)
)
f"Cannot parse two arguments of types 'datetime', '{type(arg_2)}'."
)

elif isinstance(arg_1, date):
Expand All @@ -234,9 +230,7 @@ def get(self, *args, **kwargs):
return self.type.fromdate(arg_1, tzinfo=arg_2)
else:
raise TypeError(
"Can't parse two arguments of types 'date', '{}'".format(
type(arg_2)
)
f"Cannot parse two arguments of types 'date', '{type(arg_2)}'."
)

# (str, format) -> parse.
Expand All @@ -250,9 +244,7 @@ def get(self, *args, **kwargs):

else:
raise TypeError(
"Can't parse two arguments of types '{}' and '{}'".format(
type(arg_1), type(arg_2)
)
f"Cannot parse two arguments of types '{type(arg_1)}' and '{type(arg_2)}'."
)

# 3+ args -> datetime-like via constructor.
Expand Down
4 changes: 2 additions & 2 deletions arrow/formatter.py
Expand Up @@ -2,7 +2,7 @@

from dateutil import tz as dateutil_tz

from arrow import locales, util
from arrow import locales

FORMAT_ATOM = "YYYY-MM-DD HH:mm:ssZZ"
FORMAT_COOKIE = "dddd, DD-MMM-YYYY HH:mm:ss ZZZ"
Expand Down Expand Up @@ -116,7 +116,7 @@ def _format_token(self, dt, token):
if token in ["ZZ", "Z"]:
separator = ":" if token == "ZZ" else ""
tz = dateutil_tz.tzutc() if dt.tzinfo is None else dt.tzinfo
total_minutes = int(util.total_seconds(tz.utcoffset(dt)) / 60)
total_minutes = int(tz.utcoffset(dt).total_seconds() / 60)

sign = "+" if total_minutes >= 0 else "-"
total_minutes = abs(total_minutes)
Expand Down
4 changes: 2 additions & 2 deletions arrow/locales.py
Expand Up @@ -14,7 +14,7 @@ def get_locale(name):
locale_cls = _locales.get(name.lower())

if locale_cls is None:
raise ValueError(f"Unsupported locale '{name}'")
raise ValueError(f"Unsupported locale '{name}'.")

return locale_cls()

Expand All @@ -29,7 +29,7 @@ def get_locale_by_class_name(name):
locale_cls = globals().get(name)

if locale_cls is None:
raise ValueError(f"Unsupported locale '{name}'")
raise ValueError(f"Unsupported locale '{name}'.")

return locale_cls()

Expand Down
25 changes: 11 additions & 14 deletions arrow/parser.py
Expand Up @@ -118,9 +118,8 @@ def parse_iso(self, datetime_string, normalize_whitespace=False):
num_spaces = datetime_string.count(" ")
if has_space_divider and num_spaces != 1 or has_t_divider and num_spaces > 0:
raise ParserError(
"Expected an ISO 8601-like string, but was given '{}'. Try passing in a format string to resolve this.".format(
datetime_string
)
f"Expected an ISO 8601-like string, but was given '{datetime_string}'. "
"Try passing in a format string to resolve this."
)

has_time = has_space_divider or has_t_divider
Expand Down Expand Up @@ -161,7 +160,8 @@ def parse_iso(self, datetime_string, normalize_whitespace=False):

if time_components is None:
raise ParserError(
"Invalid time component provided. Please specify a format or provide a valid time component in the basic or extended ISO 8601 time format."
"Invalid time component provided. "
"Please specify a format or provide a valid time component in the basic or extended ISO 8601 time format."
)

(
Expand Down Expand Up @@ -221,14 +221,14 @@ def parse(self, datetime_string, fmt, normalize_whitespace=False):
fmt_tokens, fmt_pattern_re = self._generate_pattern_re(fmt)
except re.error as e:
raise ParserMatchError(
f"Failed to generate regular expression pattern: {e}"
f"Failed to generate regular expression pattern: {e}."
)

match = fmt_pattern_re.search(datetime_string)

if match is None:
raise ParserMatchError(
f"Failed to match '{fmt}' when parsing '{datetime_string}'"
f"Failed to match '{fmt}' when parsing '{datetime_string}'."
)

parts = {}
Expand All @@ -242,9 +242,7 @@ def parse(self, datetime_string, fmt, normalize_whitespace=False):

if value is None:
raise ParserMatchError(
"Unable to find a match group for the specified token '{}'.".format(
token
)
f"Unable to find a match group for the specified token '{token}'."
)

self._parse_token(token, value, parts)
Expand Down Expand Up @@ -279,7 +277,7 @@ def _generate_pattern_re(self, fmt):
try:
input_re = self._input_re_map[token]
except KeyError:
raise ParserError(f"Unrecognized token '{token}'")
raise ParserError(f"Unrecognized token '{token}'.")
input_pattern = f"(?P<{token}>{input_re.pattern})"
tokens.append(token)
# a pattern doesn't have the same length as the token
Expand Down Expand Up @@ -548,10 +546,9 @@ def _parse_multiformat(self, string, formats):
pass

if _datetime is None:
supported_formats = ", ".join(formats)
raise ParserError(
"Could not match input '{}' to any of the following formats: {}".format(
string, ", ".join(formats)
)
f"Could not match input '{string}' to any of the following formats: {supported_formats}."
)

return _datetime
Expand Down Expand Up @@ -595,6 +592,6 @@ def parse(cls, tzinfo_string):
tzinfo = tz.gettz(tzinfo_string)

if tzinfo is None:
raise ParserError(f'Could not parse timezone expression "{tzinfo_string}"')
raise ParserError(f"Could not parse timezone expression '{tzinfo_string}'.")

return tzinfo
9 changes: 2 additions & 7 deletions arrow/util.py
Expand Up @@ -31,11 +31,6 @@ def next_weekday(start_date, weekday):
return rrule(freq=WEEKLY, dtstart=start_date, byweekday=weekday, count=1)[0]


def total_seconds(td):
"""Get total seconds for timedelta."""
return td.total_seconds()


def is_timestamp(value):
"""Check if value is a valid timestamp."""
if isinstance(value, bool):
Expand Down Expand Up @@ -85,8 +80,8 @@ def iso_to_gregorian(iso_year, iso_week, iso_day):
def validate_bounds(bounds):
if bounds != "()" and bounds != "(]" and bounds != "[)" and bounds != "[]":
raise ValueError(
'Invalid bounds. Please select between "()", "(]", "[)", or "[]".'
"Invalid bounds. Please select between '()', '(]', '[)', or '[]'."
)


__all__ = ["next_weekday", "total_seconds", "is_timestamp", "iso_to_gregorian"]
__all__ = ["next_weekday", "is_timestamp", "iso_to_gregorian"]

0 comments on commit 1278a21

Please sign in to comment.