Skip to content

Commit d6cc210

Browse files
committed
Correct invalid serialization of date/datetime/time/timedelta by
pulling downcast checks up
1 parent 396499c commit d6cc210

File tree

4 files changed

+96
-22
lines changed

4 files changed

+96
-22
lines changed

src/serializers/type_serializers/datetime_etc.rs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -119,15 +119,15 @@ macro_rules! build_temporal_serializer {
119119
exclude: Option<&Bound<'_, PyAny>>,
120120
extra: &Extra,
121121
) -> PyResult<Py<PyAny>> {
122-
match extra.mode {
123-
SerMode::Json => match $downcast(value) {
124-
Ok(py_value) => Ok(self.temporal_mode.$to_json(value.py(), py_value)?),
125-
Err(_) => {
126-
extra.warnings.on_fallback_py(self.get_name(), value, extra)?;
127-
infer_to_python(value, include, exclude, extra)
128-
}
122+
match $downcast(value) {
123+
Ok(py_value) => match extra.mode {
124+
SerMode::Json => Ok(self.temporal_mode.$to_json(value.py(), py_value)?),
125+
_ => Ok(value.clone().unbind()),
129126
},
130-
_ => infer_to_python(value, include, exclude, extra),
127+
_ => {
128+
extra.warnings.on_fallback_py(self.get_name(), value, extra)?;
129+
infer_to_python(value, include, exclude, extra)
130+
}
131131
}
132132
}
133133

src/serializers/type_serializers/timedelta.rs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -50,15 +50,15 @@ impl TypeSerializer for TimeDeltaSerializer {
5050
exclude: Option<&Bound<'_, PyAny>>,
5151
extra: &Extra,
5252
) -> PyResult<Py<PyAny>> {
53-
match extra.mode {
54-
SerMode::Json => match EitherTimedelta::try_from(value) {
55-
Ok(either_timedelta) => Ok(self.temporal_mode.timedelta_to_json(value.py(), either_timedelta)?),
56-
Err(_) => {
57-
extra.warnings.on_fallback_py(self.get_name(), value, extra)?;
58-
infer_to_python(value, include, exclude, extra)
59-
}
53+
match EitherTimedelta::try_from(value) {
54+
Ok(either_timedelta) => match extra.mode {
55+
SerMode::Json => Ok(self.temporal_mode.timedelta_to_json(value.py(), either_timedelta)?),
56+
_ => Ok(value.clone().unbind()),
6057
},
61-
_ => infer_to_python(value, include, exclude, extra),
58+
_ => {
59+
extra.warnings.on_fallback_py(self.get_name(), value, extra)?;
60+
infer_to_python(value, include, exclude, extra)
61+
}
6262
}
6363
}
6464

tests/serializers/test_datetime.py

Lines changed: 54 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,14 @@ def test_config_datetime(
168168
assert s.to_python(dt, mode='json') == expected_to_python
169169
assert s.to_json(dt) == expected_to_json
170170

171-
assert s.to_python({dt: 'foo'}) == {dt: 'foo'}
171+
with pytest.warns(
172+
UserWarning,
173+
match=(
174+
r'Expected `datetime` - serialized value may not be as expected '
175+
r"\[input_value=\{datetime\.datetime\([^)]*\): 'foo'\}, input_type=dict\]"
176+
),
177+
):
178+
assert s.to_python({dt: 'foo'}) == {dt: 'foo'}
172179
with pytest.warns(
173180
UserWarning,
174181
match=(
@@ -224,7 +231,14 @@ def test_config_date(
224231
assert s.to_python(dt, mode='json') == expected_to_python
225232
assert s.to_json(dt) == expected_to_json
226233

227-
assert s.to_python({dt: 'foo'}) == {dt: 'foo'}
234+
with pytest.warns(
235+
UserWarning,
236+
match=(
237+
r'Expected `date` - serialized value may not be as expected '
238+
r"\[input_value=\{datetime\.date\([^)]*\): 'foo'\}, input_type=dict\]"
239+
),
240+
):
241+
assert s.to_python({dt: 'foo'}) == {dt: 'foo'}
228242
with pytest.warns(
229243
UserWarning,
230244
match=(
@@ -280,7 +294,14 @@ def test_config_time(
280294
assert s.to_python(t, mode='json') == expected_to_python
281295
assert s.to_json(t) == expected_to_json
282296

283-
assert s.to_python({t: 'foo'}) == {t: 'foo'}
297+
with pytest.warns(
298+
UserWarning,
299+
match=(
300+
r'Expected `time` - serialized value may not be as expected '
301+
r"\[input_value=\{datetime\.time\([^)]*\): 'foo'\}, input_type=dict\]"
302+
),
303+
):
304+
assert s.to_python({t: 'foo'}) == {t: 'foo'}
284305
with pytest.warns(
285306
UserWarning,
286307
match=(
@@ -297,3 +318,33 @@ def test_config_time(
297318
),
298319
):
299320
assert s.to_json({t: 'foo'}) == expected_to_json_dict
321+
322+
323+
def test_union_datetime_respects_instanceof_check():
324+
serialization_schema = core_schema.plain_serializer_function_ser_schema(lambda v: None)
325+
json_validation_schema = core_schema.no_info_plain_validator_function(
326+
function=lambda v: v, serialization=serialization_schema
327+
)
328+
329+
test_custom_ser_schema = core_schema.json_schema(
330+
schema=json_validation_schema,
331+
serialization=serialization_schema,
332+
)
333+
334+
s = SchemaSerializer(core_schema.union_schema(choices=[core_schema.datetime_schema(), test_custom_ser_schema]))
335+
assert s.to_python('foo') is None
336+
337+
338+
def test_union_date_respects_instanceof_check():
339+
serialization_schema = core_schema.plain_serializer_function_ser_schema(lambda v: None)
340+
json_validation_schema = core_schema.no_info_plain_validator_function(
341+
function=lambda v: v, serialization=serialization_schema
342+
)
343+
344+
test_custom_ser_schema = core_schema.json_schema(
345+
schema=json_validation_schema,
346+
serialization=serialization_schema,
347+
)
348+
349+
s = SchemaSerializer(core_schema.union_schema(choices=[core_schema.date_schema(), test_custom_ser_schema]))
350+
assert s.to_python('foo') is None

tests/serializers/test_timedelta.py

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -183,7 +183,9 @@ def test_config_timedelta(
183183
assert s.to_python(td) == td
184184
assert s.to_python(td, mode='json') == expected_to_python
185185
assert s.to_json(td) == expected_to_json
186-
assert s.to_python({td: 'foo'}) == {td: 'foo'}
186+
187+
with pytest.warns(UserWarning):
188+
assert s.to_python({td: 'foo'}) == {td: 'foo'}
187189
with pytest.warns(UserWarning):
188190
assert s.to_python({td: 'foo'}, mode='json') == expected_to_python_dict
189191
with pytest.warns(
@@ -330,8 +332,14 @@ def test_config_timedelta_timedelta_ser_flag_prioritised(
330332
)
331333
assert s.to_python(td) == td
332334
assert s.to_python(td, mode='json') == expected_to_python
333-
assert s.to_python({td: 'foo'}) == {td: 'foo'}
334-
335+
with pytest.warns(
336+
UserWarning,
337+
match=(
338+
r'Expected `timedelta` - serialized value may not be as expected '
339+
r"\[input_value=\{datetime\.timedelta\([^)]*\): 'foo'\}, input_type=dict\]"
340+
),
341+
):
342+
assert s.to_python({td: 'foo'}) == {td: 'foo'}
335343
with pytest.warns(
336344
UserWarning,
337345
match=(
@@ -348,3 +356,18 @@ def test_config_timedelta_timedelta_ser_flag_prioritised(
348356
),
349357
):
350358
assert s.to_json({td: 'foo'}) == expected_to_json_dict
359+
360+
361+
def test_union_timedelta_respects_instanceof_check():
362+
serialization_schema = core_schema.plain_serializer_function_ser_schema(lambda v: None)
363+
json_validation_schema = core_schema.no_info_plain_validator_function(
364+
function=lambda v: v, serialization=serialization_schema
365+
)
366+
367+
test_custom_ser_schema = core_schema.json_schema(
368+
schema=json_validation_schema,
369+
serialization=serialization_schema,
370+
)
371+
372+
s = SchemaSerializer(core_schema.union_schema(choices=[core_schema.timedelta_schema(), test_custom_ser_schema]))
373+
assert s.to_python('foo') is None

0 commit comments

Comments
 (0)