Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

prepping for yyyy-MM-DD datetime support #8404

Merged
merged 4 commits into from Dec 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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