Skip to content

Commit c0657e5

Browse files
authored
Added support for pendulum Dates (#154)
* Added support for pydantic Dates * fixed linting * Fixed import sorting * Fixed tests * Fixed pre-commit * Modified tests to use Date explicitly * Split out tests into date/dt components * Fixed failing test; coverage 100%
1 parent f254b34 commit c0657e5

File tree

2 files changed

+95
-5
lines changed

2 files changed

+95
-5
lines changed

pydantic_extra_types/pendulum_dt.py

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
"""
55

66
try:
7+
from pendulum import Date as _Date
78
from pendulum import DateTime as _DateTime
89
from pendulum import parse
910
except ModuleNotFoundError: # pragma: no cover
@@ -72,3 +73,61 @@ def _validate(cls, value: Any, handler: core_schema.ValidatorFunctionWrapHandler
7273
except Exception as exc:
7374
raise PydanticCustomError('value_error', 'value is not a valid timestamp') from exc
7475
return handler(data)
76+
77+
78+
class Date(_Date):
79+
"""
80+
A `pendulum.Date` object. At runtime, this type decomposes into pendulum.Date automatically.
81+
This type exists because Pydantic throws a fit on unknown types.
82+
83+
```python
84+
from pydantic import BaseModel
85+
from pydantic_extra_types.pendulum_dt import Date
86+
87+
class test_model(BaseModel):
88+
dt: Date
89+
90+
print(test_model(dt='2021-01-01'))
91+
92+
#> test_model(dt=Date(2021, 1, 1))
93+
```
94+
"""
95+
96+
__slots__: List[str] = []
97+
98+
@classmethod
99+
def __get_pydantic_core_schema__(cls, source: Type[Any], handler: GetCoreSchemaHandler) -> core_schema.CoreSchema:
100+
"""
101+
Return a Pydantic CoreSchema with the Date validation
102+
103+
Args:
104+
source: The source type to be converted.
105+
handler: The handler to get the CoreSchema.
106+
107+
Returns:
108+
A Pydantic CoreSchema with the Date validation.
109+
"""
110+
return core_schema.no_info_wrap_validator_function(cls._validate, core_schema.date_schema())
111+
112+
@classmethod
113+
def _validate(cls, value: Any, handler: core_schema.ValidatorFunctionWrapHandler) -> Any:
114+
"""
115+
Validate the date object and return it.
116+
117+
Args:
118+
value: The value to validate.
119+
handler: The handler to get the CoreSchema.
120+
121+
Returns:
122+
The validated value or raises a PydanticCustomError.
123+
"""
124+
# if we are passed an existing instance, pass it straight through.
125+
if isinstance(value, _Date):
126+
return handler(value)
127+
128+
# otherwise, parse it.
129+
try:
130+
data = parse(value)
131+
except Exception as exc:
132+
raise PydanticCustomError('value_error', 'value is not a valid date') from exc
133+
return handler(data)

tests/test_pendulum_dt.py

Lines changed: 36 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,22 +2,35 @@
22
import pytest
33
from pydantic import BaseModel, ValidationError
44

5-
from pydantic_extra_types.pendulum_dt import DateTime
5+
from pydantic_extra_types.pendulum_dt import Date, DateTime
66

77

8-
class Model(BaseModel):
8+
class DtModel(BaseModel):
99
dt: DateTime
1010

1111

12+
class DateModel(BaseModel):
13+
d: Date
14+
15+
1216
def test_pendulum_dt_existing_instance():
1317
"""
1418
Verifies that constructing a model with an existing pendulum dt doesn't throw.
1519
"""
1620
now = pendulum.now()
17-
model = Model(dt=now)
21+
model = DtModel(dt=now)
1822
assert model.dt == now
1923

2024

25+
def test_pendulum_date_existing_instance():
26+
"""
27+
Verifies that constructing a model with an existing pendulum date doesn't throw.
28+
"""
29+
today = pendulum.today().date()
30+
model = DateModel(d=today)
31+
assert model.d == today
32+
33+
2134
@pytest.mark.parametrize(
2235
'dt', [pendulum.now().to_iso8601_string(), pendulum.now().to_w3c_string(), pendulum.now().to_iso8601_string()]
2336
)
@@ -26,14 +39,32 @@ def test_pendulum_dt_from_serialized(dt):
2639
Verifies that building an instance from serialized, well-formed strings decode properly.
2740
"""
2841
dt_actual = pendulum.parse(dt)
29-
model = Model(dt=dt)
42+
model = DtModel(dt=dt)
3043
assert model.dt == dt_actual
3144

3245

46+
def test_pendulum_date_from_serialized():
47+
"""
48+
Verifies that building an instance from serialized, well-formed strings decode properly.
49+
"""
50+
date_actual = pendulum.parse('2024-03-18').date()
51+
model = DateModel(d='2024-03-18')
52+
assert model.d == date_actual
53+
54+
3355
@pytest.mark.parametrize('dt', [None, 'malformed', pendulum.now().to_iso8601_string()[:5], 42])
3456
def test_pendulum_dt_malformed(dt):
3557
"""
3658
Verifies that the instance fails to validate if malformed dt are passed.
3759
"""
3860
with pytest.raises(ValidationError):
39-
Model(dt=dt)
61+
DtModel(dt=dt)
62+
63+
64+
@pytest.mark.parametrize('date', [None, 'malformed', pendulum.today().to_iso8601_string()[:5], 42])
65+
def test_pendulum_date_malformed(date):
66+
"""
67+
Verifies that the instance fails to validate if malformed date are passed.
68+
"""
69+
with pytest.raises(ValidationError):
70+
DateModel(d=date)

0 commit comments

Comments
 (0)