Skip to content

Commit

Permalink
prepping for yyyy-MM-DD datetime support (#8404)
Browse files Browse the repository at this point in the history
  • Loading branch information
sydney-runkle committed Dec 19, 2023
1 parent 3bc5a52 commit c765f87
Show file tree
Hide file tree
Showing 5 changed files with 70 additions and 11 deletions.
3 changes: 3 additions & 0 deletions docs/concepts/models.md
Expand Up @@ -1427,6 +1427,9 @@ print(Model.y)

### Private model attributes

??? api "API Documentation"
[`pydantic.fields.PrivateAttr`][pydantic.fields.PrivateAttr]<br>

Attributes whose name has a leading underscore are not treated as fields by Pydantic, and are not included in the
model schema. Instead, these are converted into a "private attribute" which is not validated or even set during
calls to `__init__`, `model_validate`, etc.
Expand Down
33 changes: 30 additions & 3 deletions docs/errors/validation_errors.md
Expand Up @@ -365,6 +365,32 @@ except ValidationError as exc:

This error is also raised for strict fields when the input value is not an instance of `date`.

## `datetime_from_date_parsing`

!!! note
Support for this error, along with support for parsing datetimes from `yyyy-MM-DD` dates will be added in `v2.6.0`

This error is raised when the input value is a string that cannot be parsed for a `datetime` field:

<!-- uncomment with new pydantic-core version
```py test="skip"
from datetime import datetime
from pydantic import BaseModel, ValidationError
class Model(BaseModel):
x: datetime
try:
# there is no 13th month
Model(x='2023-13-01')
except ValidationError as exc:
print(repr(exc.errors()[0]['type']))
#> 'datetime_from_date_parsing'
``` -->

## `datetime_future`

This error is raised when the value provided for a `FutureDatetime` field is not in the future:
Expand Down Expand Up @@ -419,17 +445,18 @@ except ValidationError as exc:
This error is raised when the value is a string that cannot be parsed for a `datetime` field:

```py
import json
from datetime import datetime

from pydantic import BaseModel, ValidationError
from pydantic import BaseModel, Field, ValidationError


class Model(BaseModel):
x: datetime
x: datetime = Field(strict=True)


try:
Model(x='test')
Model.model_validate_json(json.dumps({'x': 'not a datetime'}))
except ValidationError as exc:
print(repr(exc.errors()[0]['type']))
#> 'datetime_parsing'
Expand Down
39 changes: 33 additions & 6 deletions tests/test_datetime.py
Expand Up @@ -176,18 +176,13 @@ class DatetimeModel(BaseModel):
(b'1494012444', datetime(2017, 5, 5, 19, 27, 24, tzinfo=timezone.utc)),
('1494012444000.883309', datetime(2017, 5, 5, 19, 27, 24, 883301, tzinfo=timezone.utc)),
('-1494012444000.883309', datetime(1922, 8, 29, 4, 32, 35, 999000, tzinfo=timezone.utc)),
# Invalid inputs
('2012-4-9 4:8:16', Err('Input should be a valid datetime, invalid character in month')),
('x20120423091500', Err('Input should be a valid datetime, invalid character in year')),
('2012-04-56T09:15:90', Err('Input should be a valid datetime, day value is outside expected range')),
('2012-04-23T11:05:00-25:00', Err('Input should be a valid datetime, timezone offset must be less than 24 ho')),
(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
# Invalid inputs
(1_549_316_052_104_324, Err('Input should be a valid datetime, dates after 9999')), # nowish in μs
(1_549_316_052_104_324_096, Err('Input should be a valid datetime, dates after 9999')), # nowish in ns
('infinity', Err('Input should be a valid datetime, input is too short')),
(float('inf'), Err('Input should be a valid datetime, dates after 9999')),
(float('-inf'), Err('Input should be a valid datetime, dates before 1600')),
(1e50, Err('Input should be a valid datetime, dates after 9999')),
Expand All @@ -202,6 +197,29 @@ def test_datetime_parsing(DatetimeModel, value, result):
assert DatetimeModel(dt=value).dt == result


@pytest.mark.xfail(reason='needs new pydantic-core version')
@pytest.mark.parametrize(
'value,result',
[
# Invalid inputs
('2012-4-9 4:8:16', Err('Input should be a valid datetime or date, invalid character in month')),
('x20120423091500', Err('Input should be a valid datetime or date, invalid character in year')),
('2012-04-56T09:15:90', Err('Input should be a valid datetime or date, day value is outside expected range')),
(
'2012-04-23T11:05:00-25:00',
Err('Input should be a valid datetime or date, unexpected extra characters at the end of the input'),
),
('infinity', Err('Input should be a valid datetime or date, input is too short')),
],
)
def test_datetime_parsing_from_str(DatetimeModel, value, result):
if isinstance(result, Err):
with pytest.raises(ValidationError, match=result.message_escaped()):
DatetimeModel(dt=value)
else:
assert DatetimeModel(dt=value).dt == result


def test_aware_datetime_validation_success(aware_datetime_type):
class Model(BaseModel):
foo: aware_datetime_type
Expand Down Expand Up @@ -609,3 +627,12 @@ class Model(BaseModel):

now = datetime.now(tz=m.value.tzinfo)
assert isinstance(now, datetime)


@pytest.mark.xfail(reason='needs new pydantic-core version')
def test_datetime_from_date_str():
class Model(BaseModel):
value: datetime

m = Model(value='2015-10-21')
assert m.value == datetime(2015, 10, 21, 0, 0, tzinfo=timezone.utc)
1 change: 1 addition & 0 deletions tests/test_docs.py
Expand Up @@ -233,6 +233,7 @@ def test_error_codes():
assert code_error_codes == documented_error_codes, 'Error codes in code and docs do not match'


@pytest.mark.xfail(reason='waiting on new pydantic-core version')
def test_validation_error_codes():
error_text = (DOCS_ROOT / 'errors/validation_errors.md').read_text()

Expand Down
5 changes: 3 additions & 2 deletions tests/test_types.py
Expand Up @@ -1472,15 +1472,16 @@ def test_datetime_successful(DatetimeModel):
assert m.duration == timedelta(minutes=15, seconds=30, microseconds=100)


@pytest.mark.xfail(reason='needs new pydantic-core version')
def test_datetime_errors(DatetimeModel):
with pytest.raises(ValueError) as exc_info:
DatetimeModel(dt='2017-13-05T19:47:07', date_='XX1494012000', time_='25:20:30.400', duration='15:30.0001broken')
# insert_assert(exc_info.value.errors(include_url=False))
assert exc_info.value.errors(include_url=False) == [
{
'type': 'datetime_parsing',
'type': 'datetime_from_date_parsing',
'loc': ('dt',),
'msg': 'Input should be a valid datetime, month value is outside expected range of 1-12',
'msg': 'Input should be a valid datetime or date, month value is outside expected range of 1-12',
'input': '2017-13-05T19:47:07',
'ctx': {'error': 'month value is outside expected range of 1-12'},
},
Expand Down

0 comments on commit c765f87

Please sign in to comment.