mirror of
https://github.com/kennethreitz/pydantic.git
synced 2026-06-05 23:00:18 +00:00
adding datetime, date, time and timedelta validation
This commit is contained in:
+2
-1
@@ -9,7 +9,8 @@ Data validation and settings management using python 3.6 type hinting.
|
||||
|
||||
TODO:
|
||||
|
||||
* datetime types, enums
|
||||
* allow and disallow extra
|
||||
* enums
|
||||
* other types: regex, email field
|
||||
* exotic typing: Union, List, Dict
|
||||
* more info: alt names and descriptions
|
||||
|
||||
@@ -12,7 +12,7 @@ Stolen from https://raw.githubusercontent.com/django/django/master/django/utils/
|
||||
Changed to:
|
||||
* use standard python datetime types not django.utils.timezone
|
||||
* raise ValueError when regex doesn't match rather than returning None
|
||||
* support parsing unix timestamps
|
||||
* support parsing unix timestamps for dates and datetimes
|
||||
"""
|
||||
import re
|
||||
from datetime import date, datetime, time, timedelta, timezone
|
||||
@@ -42,8 +42,7 @@ standard_duration_re = re.compile(
|
||||
r'$'
|
||||
)
|
||||
|
||||
# Support the sections of ISO 8601 date representation that are accepted by
|
||||
# timedelta
|
||||
# Support the sections of ISO 8601 date representation that are accepted by timedelta
|
||||
iso8601_duration_re = re.compile(
|
||||
r'^(?P<sign>[-+]?)'
|
||||
r'P'
|
||||
|
||||
+10
-2
@@ -1,6 +1,9 @@
|
||||
from datetime import date, datetime, time, timedelta
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
|
||||
from .datetime_parse import parse_date, parse_datetime, parse_duration, parse_time
|
||||
|
||||
NoneType = type(None)
|
||||
|
||||
|
||||
@@ -73,7 +76,12 @@ VALIDATORS_LOOKUP = {
|
||||
Optional[bytes]: [bytes_validator, anystr_length_validator],
|
||||
bytes: [not_none_validator, bytes_validator, anystr_length_validator],
|
||||
|
||||
dict: [not_none_validator, dict_validator]
|
||||
dict: [not_none_validator, dict_validator],
|
||||
|
||||
# TODO list, List, Dict, Union, datetime, date, time, custom types
|
||||
date: [parse_date],
|
||||
time: [parse_time],
|
||||
datetime: [parse_datetime],
|
||||
timedelta: [parse_duration],
|
||||
|
||||
# TODO list, List, Dict, Union
|
||||
}
|
||||
|
||||
@@ -17,55 +17,72 @@ def create_tz(minutes):
|
||||
return timezone(timedelta(minutes=minutes))
|
||||
|
||||
|
||||
@pytest.mark.parametrize('func,value,result', [
|
||||
@pytest.mark.parametrize('value,result', [
|
||||
# Valid inputs
|
||||
(parse_date, '1494012444.883309', date(2017, 5, 5)),
|
||||
(parse_date, 1494012444.883309, date(2017, 5, 5)),
|
||||
(parse_date, '1494012444', date(2017, 5, 5)),
|
||||
(parse_date, 1494012444, date(2017, 5, 5)),
|
||||
(parse_date, '2012-04-23', date(2012, 4, 23)),
|
||||
(parse_date, '2012-4-9', date(2012, 4, 9)),
|
||||
('1494012444.883309', date(2017, 5, 5)),
|
||||
(1494012444.883309, date(2017, 5, 5)),
|
||||
('1494012444', date(2017, 5, 5)),
|
||||
(1494012444, date(2017, 5, 5)),
|
||||
('2012-04-23', date(2012, 4, 23)),
|
||||
('2012-4-9', date(2012, 4, 9)),
|
||||
# Invalid inputs
|
||||
(parse_date, 'x20120423', ValueError),
|
||||
(parse_date, '2012-04-56', ValueError),
|
||||
('x20120423', ValueError),
|
||||
('2012-04-56', ValueError),
|
||||
])
|
||||
def test_date_parsing(value, result):
|
||||
if result == ValueError:
|
||||
with pytest.raises(ValueError):
|
||||
parse_date(value)
|
||||
else:
|
||||
assert parse_date(value) == result
|
||||
|
||||
|
||||
@pytest.mark.parametrize('value,result', [
|
||||
# Valid inputs
|
||||
(parse_time, '09:15:00', time(9, 15)),
|
||||
(parse_time, '10:10', time(10, 10)),
|
||||
(parse_time, '10:20:30.400', time(10, 20, 30, 400000)),
|
||||
(parse_time, '4:8:16', time(4, 8, 16)),
|
||||
('09:15:00', time(9, 15)),
|
||||
('10:10', time(10, 10)),
|
||||
('10:20:30.400', time(10, 20, 30, 400000)),
|
||||
('4:8:16', time(4, 8, 16)),
|
||||
# Invalid inputs
|
||||
(parse_time, '091500', ValueError),
|
||||
(parse_time, '09:15:90', ValueError),
|
||||
('091500', ValueError),
|
||||
('09:15:90', ValueError),
|
||||
])
|
||||
def test_time_parsing(value, result):
|
||||
if result == ValueError:
|
||||
with pytest.raises(ValueError):
|
||||
parse_time(value)
|
||||
else:
|
||||
assert parse_time(value) == result
|
||||
|
||||
|
||||
@pytest.mark.parametrize('value,result', [
|
||||
# Valid inputs
|
||||
# values in seconds
|
||||
(parse_datetime, '1494012444.883309', datetime(2017, 5, 5, 19, 27, 24, 883309, tzinfo=timezone.utc)),
|
||||
(parse_datetime, 1494012444.883309, datetime(2017, 5, 5, 19, 27, 24, 883309, tzinfo=timezone.utc)),
|
||||
(parse_datetime, '1494012444', datetime(2017, 5, 5, 19, 27, 24, tzinfo=timezone.utc)),
|
||||
(parse_datetime, 1494012444, datetime(2017, 5, 5, 19, 27, 24, tzinfo=timezone.utc)),
|
||||
('1494012444.883309', datetime(2017, 5, 5, 19, 27, 24, 883309, tzinfo=timezone.utc)),
|
||||
(1494012444.883309, datetime(2017, 5, 5, 19, 27, 24, 883309, tzinfo=timezone.utc)),
|
||||
('1494012444', datetime(2017, 5, 5, 19, 27, 24, tzinfo=timezone.utc)),
|
||||
(1494012444, datetime(2017, 5, 5, 19, 27, 24, tzinfo=timezone.utc)),
|
||||
# values in ms
|
||||
(parse_datetime, '1494012444000.883309', datetime(2017, 5, 5, 19, 27, 24, 883, tzinfo=timezone.utc)),
|
||||
(parse_datetime, 1494012444000, datetime(2017, 5, 5, 19, 27, 24, tzinfo=timezone.utc)),
|
||||
('1494012444000.883309', datetime(2017, 5, 5, 19, 27, 24, 883, tzinfo=timezone.utc)),
|
||||
(1494012444000, datetime(2017, 5, 5, 19, 27, 24, tzinfo=timezone.utc)),
|
||||
|
||||
(parse_datetime, '2012-04-23T09:15:00', datetime(2012, 4, 23, 9, 15)),
|
||||
(parse_datetime, '2012-4-9 4:8:16', datetime(2012, 4, 9, 4, 8, 16)),
|
||||
(parse_datetime, '2012-04-23T09:15:00Z', datetime(2012, 4, 23, 9, 15, 0, 0, timezone.utc)),
|
||||
(parse_datetime, '2012-4-9 4:8:16-0320', datetime(2012, 4, 9, 4, 8, 16, 0, create_tz(-200))),
|
||||
(parse_datetime, '2012-04-23T10:20:30.400+02:30', datetime(2012, 4, 23, 10, 20, 30, 400000, create_tz(150))),
|
||||
(parse_datetime, '2012-04-23T10:20:30.400+02', datetime(2012, 4, 23, 10, 20, 30, 400000, create_tz(120))),
|
||||
(parse_datetime, '2012-04-23T10:20:30.400-02', datetime(2012, 4, 23, 10, 20, 30, 400000, create_tz(-120))),
|
||||
('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, 400000, create_tz(150))),
|
||||
('2012-04-23T10:20:30.400+02', datetime(2012, 4, 23, 10, 20, 30, 400000, create_tz(120))),
|
||||
('2012-04-23T10:20:30.400-02', datetime(2012, 4, 23, 10, 20, 30, 400000, create_tz(-120))),
|
||||
# Invalid inputs
|
||||
(parse_datetime, 'x20120423091500', ValueError),
|
||||
(parse_datetime, '2012-04-56T09:15:90', ValueError),
|
||||
('x20120423091500', ValueError),
|
||||
('2012-04-56T09:15:90', ValueError),
|
||||
])
|
||||
def test_parsing(func, value, result):
|
||||
if isinstance(result, type) and issubclass(result, BaseException):
|
||||
with pytest.raises(result):
|
||||
func(value)
|
||||
def test_datetime_parsing(value, result):
|
||||
if result == ValueError:
|
||||
with pytest.raises(ValueError):
|
||||
parse_datetime(value)
|
||||
else:
|
||||
print(repr(func(value)))
|
||||
assert func(value) == result
|
||||
assert parse_datetime(value) == result
|
||||
|
||||
|
||||
@pytest.mark.parametrize('delta', [
|
||||
@@ -121,8 +138,8 @@ def test_parse_python_format(delta):
|
||||
('PT0.000005S', timedelta(microseconds=5)),
|
||||
])
|
||||
def test_parse_durations(value, result):
|
||||
if isinstance(result, type) and issubclass(result, BaseException):
|
||||
with pytest.raises(result):
|
||||
if result == ValueError:
|
||||
with pytest.raises(ValueError):
|
||||
parse_duration(value)
|
||||
else:
|
||||
assert parse_duration(value) == result
|
||||
|
||||
+41
-1
@@ -1,4 +1,6 @@
|
||||
import os
|
||||
from collections import OrderedDict
|
||||
from datetime import date, datetime, time, timedelta
|
||||
|
||||
import pytest
|
||||
|
||||
@@ -123,10 +125,48 @@ class CheckModel(BaseModel):
|
||||
('float_check', '123', ValidationError),
|
||||
('float_check', b'123', ValidationError),
|
||||
])
|
||||
def test_bool_validation(field, value, result):
|
||||
def test_default_validators(field, value, result):
|
||||
kwargs = {field: value}
|
||||
if result == ValidationError:
|
||||
with pytest.raises(ValidationError):
|
||||
CheckModel(**kwargs)
|
||||
else:
|
||||
assert CheckModel(**kwargs).values[field] == result
|
||||
|
||||
|
||||
class DatetimeModel(BaseModel):
|
||||
dt: datetime = ...
|
||||
date_: date = ...
|
||||
time_: time = ...
|
||||
duration: timedelta = ...
|
||||
|
||||
|
||||
def test_datetime_successful():
|
||||
m = DatetimeModel(
|
||||
dt='2017-10-5T19:47:07',
|
||||
date_=1494012000,
|
||||
time_='10:20:30.400',
|
||||
duration='15:30.0001',
|
||||
)
|
||||
assert m.dt == datetime(2017, 10, 5, 19, 47, 7)
|
||||
assert m.date_ == date(2017, 5, 5)
|
||||
assert m.time_ == time(10, 20, 30, 400000)
|
||||
assert m.duration == timedelta(minutes=15, seconds=30, microseconds=100)
|
||||
|
||||
|
||||
def test_datetime_errors():
|
||||
with pytest.raises(ValueError) as exc_info:
|
||||
DatetimeModel(
|
||||
dt='2017-13-5T19:47:07',
|
||||
date_='XX1494012000',
|
||||
time_='25:20:30.400',
|
||||
duration='15:30.0001 broken',
|
||||
)
|
||||
assert exc_info.value.args[0].startswith('4 errors validating input:')
|
||||
assert exc_info.value.errors == OrderedDict(
|
||||
[
|
||||
('dt', {'type': 'ValueError', 'msg': 'month must be in 1..12', 'validator': 'parse_datetime'}),
|
||||
('date_', {'type': 'ValueError', 'msg': 'Invalid date format', 'validator': 'parse_date'}),
|
||||
('time_', {'type': 'ValueError', 'msg': 'hour must be in 0..23', 'validator': 'parse_time'}),
|
||||
('duration', {'type': 'ValueError', 'msg': 'Invalid duration format', 'validator': 'parse_duration'})
|
||||
])
|
||||
|
||||
Reference in New Issue
Block a user