mirror of
https://github.com/kennethreitz/pydantic.git
synced 2026-06-05 23:00:18 +00:00
9dee83a689
* better error message for TypeError on datetime parsing * add change, fix tests * reduce scope of errors caught
244 lines
10 KiB
Python
244 lines
10 KiB
Python
"""
|
|
Stolen from https://github.com/django/django/blob/master/tests/utils_tests/test_dateparse.py at
|
|
9718fa2e8abe430c3526a9278dd976443d4ae3c6
|
|
|
|
Changed to:
|
|
* use standard pytest layout
|
|
* parametrize tests
|
|
"""
|
|
from datetime import date, datetime, time, timedelta, timezone
|
|
|
|
import pytest
|
|
|
|
from pydantic import BaseModel, ValidationError, errors
|
|
from pydantic.datetime_parse import parse_date, parse_datetime, parse_duration, parse_time
|
|
|
|
|
|
def create_tz(minutes):
|
|
return timezone(timedelta(minutes=minutes))
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
'value,result',
|
|
[
|
|
# Valid inputs
|
|
('1494012444.883309', date(2017, 5, 5)),
|
|
(b'1494012444.883309', date(2017, 5, 5)),
|
|
(1_494_012_444.883_309, date(2017, 5, 5)),
|
|
('1494012444', date(2017, 5, 5)),
|
|
(1_494_012_444, date(2017, 5, 5)),
|
|
(0, date(1970, 1, 1)),
|
|
('2012-04-23', date(2012, 4, 23)),
|
|
(b'2012-04-23', date(2012, 4, 23)),
|
|
('2012-4-9', date(2012, 4, 9)),
|
|
(date(2012, 4, 9), date(2012, 4, 9)),
|
|
(datetime(2012, 4, 9, 12, 15), date(2012, 4, 9)),
|
|
# Invalid inputs
|
|
('x20120423', errors.DateError),
|
|
('2012-04-56', errors.DateError),
|
|
(19_999_999_999, date(2603, 10, 11)), # just before watershed
|
|
(20_000_000_001, date(1970, 8, 20)), # just after watershed
|
|
(1_549_316_052, date(2019, 2, 4)), # nowish in s
|
|
(1_549_316_052_104, date(2019, 2, 4)), # nowish in ms
|
|
(1_549_316_052_104_324, date(2019, 2, 4)), # nowish in μs
|
|
(1_549_316_052_104_324_096, date(2019, 2, 4)), # nowish in ns
|
|
],
|
|
)
|
|
def test_date_parsing(value, result):
|
|
if result == errors.DateError:
|
|
with pytest.raises(errors.DateError):
|
|
parse_date(value)
|
|
else:
|
|
assert parse_date(value) == result
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
'value,result',
|
|
[
|
|
# Valid inputs
|
|
('09:15:00', time(9, 15)),
|
|
('10:10', time(10, 10)),
|
|
('10:20:30.400', time(10, 20, 30, 400_000)),
|
|
(b'10:20:30.400', time(10, 20, 30, 400_000)),
|
|
('4:8:16', time(4, 8, 16)),
|
|
(time(4, 8, 16), time(4, 8, 16)),
|
|
(3610, time(1, 0, 10)),
|
|
(3600.5, time(1, 0, 0, 500000)),
|
|
(86400 - 1, time(23, 59, 59)),
|
|
# Invalid inputs
|
|
(86400, errors.TimeError),
|
|
('xxx', errors.TimeError),
|
|
('091500', errors.TimeError),
|
|
(b'091500', errors.TimeError),
|
|
('09:15:90', errors.TimeError),
|
|
],
|
|
)
|
|
def test_time_parsing(value, result):
|
|
if result == errors.TimeError:
|
|
with pytest.raises(errors.TimeError):
|
|
parse_time(value)
|
|
else:
|
|
assert parse_time(value) == result
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
'value,result',
|
|
[
|
|
# Valid inputs
|
|
# values in seconds
|
|
('1494012444.883309', datetime(2017, 5, 5, 19, 27, 24, 883_309, tzinfo=timezone.utc)),
|
|
(1_494_012_444.883_309, datetime(2017, 5, 5, 19, 27, 24, 883_309, tzinfo=timezone.utc)),
|
|
('1494012444', datetime(2017, 5, 5, 19, 27, 24, tzinfo=timezone.utc)),
|
|
(b'1494012444', datetime(2017, 5, 5, 19, 27, 24, tzinfo=timezone.utc)),
|
|
(1_494_012_444, datetime(2017, 5, 5, 19, 27, 24, tzinfo=timezone.utc)),
|
|
# values in ms
|
|
('1494012444000.883309', datetime(2017, 5, 5, 19, 27, 24, 883, tzinfo=timezone.utc)),
|
|
(1_494_012_444_000, datetime(2017, 5, 5, 19, 27, 24, tzinfo=timezone.utc)),
|
|
('2012-04-23T09:15:00', datetime(2012, 4, 23, 9, 15)),
|
|
('2012-4-9 4:8:16', datetime(2012, 4, 9, 4, 8, 16)),
|
|
('2012-04-23T09:15:00Z', datetime(2012, 4, 23, 9, 15, 0, 0, timezone.utc)),
|
|
('2012-4-9 4:8:16-0320', datetime(2012, 4, 9, 4, 8, 16, 0, create_tz(-200))),
|
|
('2012-04-23T10:20:30.400+02:30', datetime(2012, 4, 23, 10, 20, 30, 400_000, create_tz(150))),
|
|
('2012-04-23T10:20:30.400+02', datetime(2012, 4, 23, 10, 20, 30, 400_000, create_tz(120))),
|
|
('2012-04-23T10:20:30.400-02', datetime(2012, 4, 23, 10, 20, 30, 400_000, create_tz(-120))),
|
|
(b'2012-04-23T10:20:30.400-02', datetime(2012, 4, 23, 10, 20, 30, 400_000, create_tz(-120))),
|
|
(datetime(2017, 5, 5), datetime(2017, 5, 5)),
|
|
(0, datetime(1970, 1, 1, 0, 0, 0, tzinfo=timezone.utc)),
|
|
# Invalid inputs
|
|
('x20120423091500', errors.DateTimeError),
|
|
('2012-04-56T09:15:90', errors.DateTimeError),
|
|
(19_999_999_999, datetime(2603, 10, 11, 11, 33, 19, tzinfo=timezone.utc)), # just before watershed
|
|
(20_000_000_001, datetime(1970, 8, 20, 11, 33, 20, 1000, tzinfo=timezone.utc)), # just after watershed
|
|
(1_549_316_052, datetime(2019, 2, 4, 21, 34, 12, 0, tzinfo=timezone.utc)), # nowish in s
|
|
(1_549_316_052_104, datetime(2019, 2, 4, 21, 34, 12, 104_000, tzinfo=timezone.utc)), # nowish in ms
|
|
(1_549_316_052_104_324, datetime(2019, 2, 4, 21, 34, 12, 104_324, tzinfo=timezone.utc)), # nowish in μs
|
|
(1_549_316_052_104_324_096, datetime(2019, 2, 4, 21, 34, 12, 104_324, tzinfo=timezone.utc)), # nowish in ns
|
|
],
|
|
)
|
|
def test_datetime_parsing(value, result):
|
|
if result == errors.DateTimeError:
|
|
with pytest.raises(errors.DateTimeError):
|
|
parse_datetime(value)
|
|
else:
|
|
assert parse_datetime(value) == result
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
'delta',
|
|
[
|
|
timedelta(days=4, minutes=15, seconds=30, milliseconds=100), # fractions of seconds
|
|
timedelta(hours=10, minutes=15, seconds=30), # hours, minutes, seconds
|
|
timedelta(days=4, minutes=15, seconds=30), # multiple days
|
|
timedelta(days=1, minutes=00, seconds=00), # single day
|
|
timedelta(days=-4, minutes=15, seconds=30), # negative durations
|
|
timedelta(minutes=15, seconds=30), # minute & seconds
|
|
timedelta(seconds=30), # seconds
|
|
],
|
|
)
|
|
def test_parse_python_format(delta):
|
|
assert parse_duration(delta) == delta
|
|
assert parse_duration(str(delta)) == delta
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
'value,result',
|
|
[
|
|
# seconds
|
|
(timedelta(seconds=30), timedelta(seconds=30)),
|
|
('30', timedelta(seconds=30)),
|
|
(30, timedelta(seconds=30)),
|
|
(30.1, timedelta(seconds=30, milliseconds=100)),
|
|
# minutes seconds
|
|
('15:30', timedelta(minutes=15, seconds=30)),
|
|
('5:30', timedelta(minutes=5, seconds=30)),
|
|
# hours minutes seconds
|
|
('10:15:30', timedelta(hours=10, minutes=15, seconds=30)),
|
|
('1:15:30', timedelta(hours=1, minutes=15, seconds=30)),
|
|
('100:200:300', timedelta(hours=100, minutes=200, seconds=300)),
|
|
# days
|
|
('4 15:30', timedelta(days=4, minutes=15, seconds=30)),
|
|
('4 10:15:30', timedelta(days=4, hours=10, minutes=15, seconds=30)),
|
|
# fractions of seconds
|
|
('15:30.1', timedelta(minutes=15, seconds=30, milliseconds=100)),
|
|
('15:30.01', timedelta(minutes=15, seconds=30, milliseconds=10)),
|
|
('15:30.001', timedelta(minutes=15, seconds=30, milliseconds=1)),
|
|
('15:30.0001', timedelta(minutes=15, seconds=30, microseconds=100)),
|
|
('15:30.00001', timedelta(minutes=15, seconds=30, microseconds=10)),
|
|
('15:30.000001', timedelta(minutes=15, seconds=30, microseconds=1)),
|
|
(b'15:30.000001', timedelta(minutes=15, seconds=30, microseconds=1)),
|
|
# negative
|
|
('-4 15:30', timedelta(days=-4, minutes=15, seconds=30)),
|
|
('-172800', timedelta(days=-2)),
|
|
('-15:30', timedelta(minutes=-15, seconds=30)),
|
|
('-1:15:30', timedelta(hours=-1, minutes=15, seconds=30)),
|
|
('-30.1', timedelta(seconds=-30, milliseconds=-100)),
|
|
# iso_8601
|
|
('P4Y', errors.DurationError),
|
|
('P4M', errors.DurationError),
|
|
('P4W', errors.DurationError),
|
|
('P4D', timedelta(days=4)),
|
|
('P0.5D', timedelta(hours=12)),
|
|
('PT5H', timedelta(hours=5)),
|
|
('PT5M', timedelta(minutes=5)),
|
|
('PT5S', timedelta(seconds=5)),
|
|
('PT0.000005S', timedelta(microseconds=5)),
|
|
(b'PT0.000005S', timedelta(microseconds=5)),
|
|
],
|
|
)
|
|
def test_parse_durations(value, result):
|
|
if result == errors.DurationError:
|
|
with pytest.raises(errors.DurationError):
|
|
parse_duration(value)
|
|
else:
|
|
assert parse_duration(value) == result
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
'field, value, error_message',
|
|
[
|
|
('dt', [], 'invalid type; expected datetime, string, bytes, int or float'),
|
|
('dt', {}, 'invalid type; expected datetime, string, bytes, int or float'),
|
|
('dt', object, 'invalid type; expected datetime, string, bytes, int or float'),
|
|
('d', [], 'invalid type; expected date, string, bytes, int or float'),
|
|
('d', {}, 'invalid type; expected date, string, bytes, int or float'),
|
|
('d', object, 'invalid type; expected date, string, bytes, int or float'),
|
|
('t', [], 'invalid type; expected time, string, bytes, int or float'),
|
|
('t', {}, 'invalid type; expected time, string, bytes, int or float'),
|
|
('t', object, 'invalid type; expected time, string, bytes, int or float'),
|
|
('td', [], 'invalid type; expected timedelta, string, bytes, int or float'),
|
|
('td', {}, 'invalid type; expected timedelta, string, bytes, int or float'),
|
|
('td', object, 'invalid type; expected timedelta, string, bytes, int or float'),
|
|
],
|
|
)
|
|
def test_model_type_errors(field, value, error_message):
|
|
class Model(BaseModel):
|
|
dt: datetime = None
|
|
d: date = None
|
|
t: time = None
|
|
td: timedelta = None
|
|
|
|
with pytest.raises(ValidationError) as exc_info:
|
|
Model(**{field: value})
|
|
assert len(exc_info.value.errors()) == 1
|
|
error = exc_info.value.errors()[0]
|
|
assert error == {'loc': (field,), 'type': 'type_error', 'msg': error_message}
|
|
|
|
|
|
@pytest.mark.parametrize('field', ['dt', 'd', 't', 'dt'])
|
|
def test_unicode_decode_error(field):
|
|
class Model(BaseModel):
|
|
dt: datetime = None
|
|
d: date = None
|
|
t: time = None
|
|
td: timedelta = None
|
|
|
|
with pytest.raises(ValidationError) as exc_info:
|
|
Model(**{field: b'\x81'})
|
|
assert len(exc_info.value.errors()) == 1
|
|
error = exc_info.value.errors()[0]
|
|
assert error == {
|
|
'loc': (field,),
|
|
'type': 'value_error.unicodedecode',
|
|
'msg': "'utf-8' codec can't decode byte 0x81 in position 0: invalid start byte",
|
|
}
|