Skip to content

Commit

Permalink
Merge pull request #881 from forking-repos/value-error-messages
Browse files Browse the repository at this point in the history
Use custom ParserError class in parser exceptions
  • Loading branch information
pganssle committed Apr 23, 2019
2 parents 1180535 + f764c8a commit 833daf0
Show file tree
Hide file tree
Showing 5 changed files with 58 additions and 26 deletions.
1 change: 1 addition & 0 deletions AUTHORS.md
Expand Up @@ -112,6 +112,7 @@ switch, and thus all their contributions are dual-licensed.
- bachmann <bachmann.matt@MASKED>
- bjv <brandon.vanvaerenbergh@MASKED> (@bjamesvERT)
- gl <gl@MASKED>
- gfyoung <gfyoung17@gmail.com> **D**
- labrys <labrys@MASKED> (gh: @labrys) **R**
- ms-boom <ms-boom@MASKED>
- ryanss <ryanssdev@MASKED> (gh: @ryanss) **R**
Expand Down
3 changes: 3 additions & 0 deletions changelog.d/881.bugfix.rst
@@ -0,0 +1,3 @@
Parsing errors will now raise ``ParserError``, a subclass of ``ValueError``,
which has a nicer string representation.
Patch by @gfyoung (gh pr #881)
3 changes: 2 additions & 1 deletion dateutil/parser/__init__.py
@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
from ._parser import parse, parser, parserinfo
from ._parser import parse, parser, parserinfo, ParserError
from ._parser import DEFAULTPARSER, DEFAULTTZPARSER
from ._parser import UnknownTimezoneWarning

Expand All @@ -9,6 +9,7 @@

__all__ = ['parse', 'parser', 'parserinfo',
'isoparse', 'isoparser',
'ParserError',
'UnknownTimezoneWarning']


Expand Down
26 changes: 21 additions & 5 deletions dateutil/parser/_parser.py
Expand Up @@ -49,7 +49,7 @@
from .. import relativedelta
from .. import tz

__all__ = ["parse", "parserinfo"]
__all__ = ["parse", "parserinfo", "ParserError"]


# TODO: pandas.core.tools.datetimes imports this explicitly. Might be worth
Expand Down Expand Up @@ -626,7 +626,7 @@ def parse(self, timestr, default=None,
first element being a :class:`datetime.datetime` object, the second
a tuple containing the fuzzy tokens.
:raises ValueError:
:raises ParserError:
Raised for invalid or unknown string format, if the provided
:class:`tzinfo` is not in a valid format, or if an invalid date
would be created.
Expand All @@ -646,12 +646,15 @@ def parse(self, timestr, default=None,
res, skipped_tokens = self._parse(timestr, **kwargs)

if res is None:
raise ValueError("Unknown string format:", timestr)
raise ParserError("Unknown string format: %s", timestr)

if len(res) == 0:
raise ValueError("String does not contain a date:", timestr)
raise ParserError("String does not contain a date: %s", timestr)

ret = self._build_naive(res, default)
try:
ret = self._build_naive(res, default)
except ValueError as e:
six.raise_from(ParserError(e.args[0] + ": %s", timestr), e)

if not ignoretz:
ret = self._build_tzaware(ret, res, tzinfos)
Expand Down Expand Up @@ -1588,6 +1591,19 @@ def parse(self, tzstr):
def _parsetz(tzstr):
return DEFAULTTZPARSER.parse(tzstr)


class ParserError(ValueError):
"""Error class for representing failure to parse a datetime string."""
def __str__(self):
try:
return self.args[0] % self.args[1:]
except (TypeError, IndexError):
return super(ParserError, self).__str__()

def __repr__(self):
return "%s(%s)" % (self.__class__.__name__, str(self))


class UnknownTimezoneWarning(RuntimeWarning):
"""Raised when the parser finds a timezone it cannot parse into a tzinfo"""
# vim:ts=4:sw=4:et
51 changes: 31 additions & 20 deletions dateutil/test/test_parser.py
Expand Up @@ -9,6 +9,7 @@
from dateutil import tz
from dateutil.tz import tzoffset
from dateutil.parser import parse, parserinfo
from dateutil.parser import ParserError
from dateutil.parser import UnknownTimezoneWarning

from ._common import TZEnvContext
Expand Down Expand Up @@ -291,7 +292,7 @@ def test_strftime_formats_2003Sep25(self, fmt, dstr):

class TestInputTypes(object):
def test_empty_string_invalid(self):
with pytest.raises(ValueError):
with pytest.raises(ParserError):
parse('')

def test_none_invalid(self):
Expand Down Expand Up @@ -478,17 +479,17 @@ def testISOStrippedFormatStrip2(self):
tzinfo=tzoffset(None, 10800)))

def testAMPMNoHour(self):
with pytest.raises(ValueError):
with pytest.raises(ParserError):
parse("AM")

with pytest.raises(ValueError):
with pytest.raises(ParserError):
parse("Jan 20, 2015 PM")

def testAMPMRange(self):
with pytest.raises(ValueError):
with pytest.raises(ParserError):
parse("13:44 AM")

with pytest.raises(ValueError):
with pytest.raises(ParserError):
parse("January 25, 1921 23:13 PM")

def testPertain(self):
Expand Down Expand Up @@ -564,15 +565,15 @@ def testUnspecifiedDayFallbackFebLeapYear(self):
datetime(2008, 2, 29))

def testErrorType01(self):
with pytest.raises(ValueError):
with pytest.raises(ParserError):
parse('shouldfail')

def testCorrectErrorOnFuzzyWithTokens(self):
assertRaisesRegex(self, ValueError, 'Unknown string format',
assertRaisesRegex(self, ParserError, 'Unknown string format',
parse, '04/04/32/423', fuzzy_with_tokens=True)
assertRaisesRegex(self, ValueError, 'Unknown string format',
assertRaisesRegex(self, ParserError, 'Unknown string format',
parse, '04/04/04 +32423', fuzzy_with_tokens=True)
assertRaisesRegex(self, ValueError, 'Unknown string format',
assertRaisesRegex(self, ParserError, 'Unknown string format',
parse, '04/04/0d4', fuzzy_with_tokens=True)

def testIncreasingCTime(self):
Expand Down Expand Up @@ -713,43 +714,53 @@ def test_hmBY(self):
def test_validate_hour(self):
# See GH353
invalid = "201A-01-01T23:58:39.239769+03:00"
with pytest.raises(ValueError):
with pytest.raises(ParserError):
parse(invalid)

def test_era_trailing_year(self):
dstr = 'AD2001'
res = parse(dstr)
assert res.year == 2001, res

def test_includes_timestr(self):
timestr = "2020-13-97T44:61:83"

try:
parse(timestr)
except ParserError as e:
assert e.args[1] == timestr
else:
pytest.fail("Failed to raise ParserError")


class TestOutOfBounds(object):

def test_no_year_zero(self):
with pytest.raises(ValueError):
with pytest.raises(ParserError):
parse("0000 Jun 20")

def test_out_of_bound_day(self):
with pytest.raises(ValueError):
with pytest.raises(ParserError):
parse("Feb 30, 2007")

def test_day_sanity(self, fuzzy):
dstr = "2014-15-25"
with pytest.raises(ValueError):
with pytest.raises(ParserError):
parse(dstr, fuzzy=fuzzy)

def test_minute_sanity(self, fuzzy):
dstr = "2014-02-28 22:64"
with pytest.raises(ValueError):
with pytest.raises(ParserError):
parse(dstr, fuzzy=fuzzy)

def test_hour_sanity(self, fuzzy):
dstr = "2014-02-28 25:16 PM"
with pytest.raises(ValueError):
with pytest.raises(ParserError):
parse(dstr, fuzzy=fuzzy)

def test_second_sanity(self, fuzzy):
dstr = "2014-02-28 22:14:64"
with pytest.raises(ValueError):
with pytest.raises(ParserError):
parse(dstr, fuzzy=fuzzy)


Expand Down Expand Up @@ -802,7 +813,7 @@ def test_four_letter_day(self):
@pytest.mark.xfail
def test_non_date_number(self):
dstr = '1,700'
with pytest.raises(ValueError):
with pytest.raises(ParserError):
parse(dstr)

@pytest.mark.xfail
Expand Down Expand Up @@ -924,7 +935,7 @@ def test_rounding_floatlike_strings(dtstr, dt):

@pytest.mark.parametrize('value', ['1: test', 'Nan'])
def test_decimal_error(value):
# GH 632, GH 662 - decimal.Decimal raises some non-ValueError exception when
# constructed with an invalid value
with pytest.raises(ValueError):
# GH 632, GH 662 - decimal.Decimal raises some non-ParserError exception
# when constructed with an invalid value
with pytest.raises(ParserError):
parse(value)

0 comments on commit 833daf0

Please sign in to comment.