My question is pretty simple and I'm surprised no one has asked it so far:
How can I validate dates in pydantic?
For example, I only want to accept dates in the range 1980.1.1-2000.1.1.
validator for datetime
field is what you want. You can use it as following:
from pydantic import BaseModel, validator
from datetime import datetime
class DModel(BaseModel):
dt: datetime
@validator("dt")
def ensure_date_range(cls, v):
if not datetime(year=1980, month=1, day=1) <= v < datetime(year=2000, month=1, day=1):
raise ValueError("Must be in range")
return v
DModel.parse_obj({"dt": "1995-01-01T00:00"})
DModel.parse_obj({"dt": "2001-01-01T00:00"}) # validation error
One common use case, possibly hinted at by the OP's use of "dates" in the plural, is the validation of multiple dates in the same model. This is very lightly documented, and there are other problems that need to be dealt with you want to parse strings in other date formats.
This is not explicitly in the docs. Well, it's there, but the circumstances where it's required are not mentioned. If you need your validator to convert a non-ISO str
format to date
Pydantic will discover an invalid date format before your validator has run.
In these circumstances it's not sufficient to just apply multiple validators, or apply one validator to multiple fields, the pre=True
argument also needs to be supplied, to pre-empt the default formatting from raising an exception, before your validator can convert it:
from datetime import datetime, date
from pydantic import BaseModel, validator
def validate_date(value: str) -> date:
return datetime.strptime(value, "%d/%m/%Y").date()
class CommonModel(BaseModel):
created: date
modified: date
confirmed: date
closed: date
_validate_dates = validator(
'created', 'modified', 'confirmed', 'closed', pre=True, allow_reuse=True,
)(validate_date)
data = {
'created': '25/12/2000',
'modified': '31/12/2000',
'confirmed': '01/01/2001',
'closed': '11/09/2001',
}
try:
model = CommonModel(**data)
except Exception as e:
print(e.json())
The docs on this are so light I had to discover this myself, and I haven't found anyone else pointing this out, so I thought a demonstration of this kind of code would be helpful here.