Skip to content

Commit 767faba

Browse files
authored
feat: Generate correct OpenAPI type and format for pydantic.AwareDate… (#4218)
1 parent 2324c65 commit 767faba

File tree

2 files changed

+66
-0
lines changed

2 files changed

+66
-0
lines changed

litestar/plugins/pydantic/plugins/schema.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,36 @@
194194
type=OpenAPIType.STRING, format=OpenAPIFormat.EMAIL, description="Name and email"
195195
),
196196
pydantic_v2.AnyUrl: Schema(type=OpenAPIType.STRING, format=OpenAPIFormat.URL),
197+
pydantic_v2.PastDate: Schema(
198+
type=OpenAPIType.STRING,
199+
format=OpenAPIFormat.DATE,
200+
description="date with the constraint that the value must be in the past",
201+
),
202+
pydantic_v2.FutureDate: Schema(
203+
type=OpenAPIType.STRING,
204+
format=OpenAPIFormat.DATE,
205+
description="date with the constraint that the value must be in the future",
206+
),
207+
pydantic_v2.PastDatetime: Schema(
208+
type=OpenAPIType.STRING,
209+
format=OpenAPIFormat.DATE_TIME,
210+
description="datetime with the constraint that the value must be in the past",
211+
),
212+
pydantic_v2.FutureDatetime: Schema(
213+
type=OpenAPIType.STRING,
214+
format=OpenAPIFormat.DATE_TIME,
215+
description="datetime with the constraint that the value must be in the future",
216+
),
217+
pydantic_v2.AwareDatetime: Schema(
218+
type=OpenAPIType.STRING,
219+
format=OpenAPIFormat.DATE_TIME,
220+
description="datetime with the constraint that the value must have timezone info",
221+
),
222+
pydantic_v2.NaiveDatetime: Schema(
223+
type=OpenAPIType.STRING,
224+
format=OpenAPIFormat.DATE_TIME,
225+
description="datetime with the constraint that the value must lack timezone info",
226+
),
197227
}
198228
)
199229
if int(pydantic_v2.version.version_short().split(".")[1]) >= 10:

tests/unit/test_plugins/test_pydantic/test_plugin_serialization.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,12 @@
2727
from . import PydanticVersion
2828

2929
TODAY = datetime.date.today()
30+
YESTERDAY_DATE = TODAY - datetime.timedelta(days=1)
31+
FUTURE_DATE = TODAY + datetime.timedelta(days=1)
32+
YESTERDAY_DATETIME = datetime.datetime(2023, 6, 14, 12, 0, 0)
33+
FUTURE_DATETIME = datetime.datetime(2030, 6, 16, 12, 0, 0)
34+
AWARE_DATETIME = datetime.datetime(2023, 6, 15, 12, 0, 0, tzinfo=datetime.timezone.utc)
35+
NAIVE_DATETIME = datetime.datetime(2023, 6, 15, 12, 0, 0).replace(tzinfo=None)
3036

3137

3238
class CustomStr(str):
@@ -122,6 +128,13 @@ class ModelV2(pydantic_v2.BaseModel):
122128
url: pydantic_v2.AnyUrl
123129
http_url: pydantic_v2.HttpUrl
124130

131+
paste_date: pydantic_v2.PastDate
132+
future_date: pydantic_v2.FutureDate
133+
paste_datetime: pydantic_v2.PastDatetime
134+
future_datetime: pydantic_v2.FutureDatetime
135+
aware_datetime: pydantic_v2.AwareDatetime
136+
naive_datetime: pydantic_v2.NaiveDatetime
137+
125138

126139
serializer = partial(default_serializer, type_encoders=PydanticInitPlugin.encoders())
127140

@@ -175,6 +188,12 @@ def model(pydantic_version: PydanticVersion) -> ModelV1 | ModelV2:
175188
conlist=[1],
176189
url="some://example.org/", # type: ignore[arg-type]
177190
http_url="http://example.org/", # type: ignore[arg-type]
191+
paste_date=YESTERDAY_DATE,
192+
future_date=FUTURE_DATE,
193+
paste_datetime=YESTERDAY_DATETIME,
194+
future_datetime=FUTURE_DATETIME,
195+
aware_datetime=AWARE_DATETIME,
196+
naive_datetime=NAIVE_DATETIME,
178197
)
179198

180199

@@ -198,9 +217,26 @@ def model(pydantic_version: PydanticVersion) -> ModelV1 | ModelV2:
198217
("conint", 1),
199218
("url", "some://example.org/"),
200219
("http_url", "http://example.org/"),
220+
("paste_date", YESTERDAY_DATE.isoformat()),
221+
("future_date", FUTURE_DATE.isoformat()),
222+
("paste_datetime", YESTERDAY_DATETIME.isoformat()),
223+
("future_datetime", FUTURE_DATETIME.isoformat()),
224+
("aware_datetime", AWARE_DATETIME.isoformat()),
225+
("naive_datetime", NAIVE_DATETIME.isoformat()),
201226
],
202227
)
203228
def test_default_serializer(model: ModelV1 | ModelV2, attribute_name: str, expected: Any) -> None:
229+
# Skip Pydantic v2-specific date fields when testing with ModelV1
230+
v2_only_fields = {
231+
"paste_date",
232+
"future_date",
233+
"paste_datetime",
234+
"future_datetime",
235+
"aware_datetime",
236+
"naive_datetime",
237+
}
238+
if isinstance(model, ModelV1) and attribute_name in v2_only_fields:
239+
pytest.skip(f"Field '{attribute_name}' only exists in Pydantic v2")
204240
assert serializer(getattr(model, attribute_name)) == expected
205241

206242

0 commit comments

Comments
 (0)