From 5a3251364b8d298195d1d5e324f4d6b3a86039bd Mon Sep 17 00:00:00 2001 From: sneakers-the-rat Date: Mon, 21 Oct 2024 17:31:00 -0700 Subject: [PATCH 1/7] committing failing test first... testing exclude_defaults with an incomparable type --- tests/serializers/test_typed_dict.py | 32 ++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/tests/serializers/test_typed_dict.py b/tests/serializers/test_typed_dict.py index df507a248..6d50a16e1 100644 --- a/tests/serializers/test_typed_dict.py +++ b/tests/serializers/test_typed_dict.py @@ -169,6 +169,38 @@ def test_exclude_default(): assert v.to_json({'foo': 1, 'bar': b'[default]'}) == b'{"foo":1,"bar":"[default]"}' assert v.to_json({'foo': 1, 'bar': b'[default]'}, exclude_defaults=True) == b'{"foo":1}' +def test_exclude_incomparable_default(): + """Values that can't be compared with eq are treated as not equal to the default""" + def ser_x(*args): + return [1,2,3] + + cls_schema = core_schema.any_schema( + serialization=core_schema.plain_serializer_function_ser_schema( + ser_x + ) + ) + + class Incomparable: + + __pydantic_serializer__ = SchemaSerializer(cls_schema) + + def __get_pydantic_core_schema__(*args): + return cls_schema + + def __eq__(self, other): + raise NotImplementedError("Can't be compared!") + + v = SchemaSerializer( + core_schema.typed_dict_schema( + { + 'foo': core_schema.typed_dict_field(core_schema.with_default_schema(core_schema.any_schema(), default=None)), + } + ) + ) + instance = Incomparable() + assert v.to_python({'foo': instance}, exclude_defaults=True)['foo'] == [1,2,3] + assert v.to_json({'foo': instance}, exclude_defaults=True) == b'{"foo":[1,2,3]}' + def test_function_plain_field_serializer_to_python(): class Model(TypedDict): From bd4689b948049475b9c77026fffbcca877971b8f Mon Sep 17 00:00:00 2001 From: sneakers-the-rat Date: Mon, 21 Oct 2024 17:42:56 -0700 Subject: [PATCH 2/7] commiting fix - handle errors in equality comparison in exclude defaults --- src/serializers/fields.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/serializers/fields.rs b/src/serializers/fields.rs index 32c16b18a..5e4484b6c 100644 --- a/src/serializers/fields.rs +++ b/src/serializers/fields.rs @@ -75,8 +75,10 @@ impl SerField { fn exclude_default(value: &Bound<'_, PyAny>, extra: &Extra, serializer: &CombinedSerializer) -> PyResult { if extra.exclude_defaults { if let Some(default) = serializer.get_default(value.py())? { - if value.eq(default)? { - return Ok(true); + return match value.eq(default) { + Ok(true) => Ok(true), + Ok(false) => Ok(false), + Err(_E) => Ok(false), } } } From 1cd380ea0cc2962d6747255b130d278e54517663 Mon Sep 17 00:00:00 2001 From: sneakers-the-rat Date: Mon, 21 Oct 2024 17:48:18 -0700 Subject: [PATCH 3/7] lint --- tests/serializers/test_typed_dict.py | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/tests/serializers/test_typed_dict.py b/tests/serializers/test_typed_dict.py index 6d50a16e1..96c590cee 100644 --- a/tests/serializers/test_typed_dict.py +++ b/tests/serializers/test_typed_dict.py @@ -169,19 +169,16 @@ def test_exclude_default(): assert v.to_json({'foo': 1, 'bar': b'[default]'}) == b'{"foo":1,"bar":"[default]"}' assert v.to_json({'foo': 1, 'bar': b'[default]'}, exclude_defaults=True) == b'{"foo":1}' + def test_exclude_incomparable_default(): """Values that can't be compared with eq are treated as not equal to the default""" + def ser_x(*args): - return [1,2,3] + return [1, 2, 3] - cls_schema = core_schema.any_schema( - serialization=core_schema.plain_serializer_function_ser_schema( - ser_x - ) - ) + cls_schema = core_schema.any_schema(serialization=core_schema.plain_serializer_function_ser_schema(ser_x)) class Incomparable: - __pydantic_serializer__ = SchemaSerializer(cls_schema) def __get_pydantic_core_schema__(*args): @@ -193,12 +190,14 @@ def __eq__(self, other): v = SchemaSerializer( core_schema.typed_dict_schema( { - 'foo': core_schema.typed_dict_field(core_schema.with_default_schema(core_schema.any_schema(), default=None)), + 'foo': core_schema.typed_dict_field( + core_schema.with_default_schema(core_schema.any_schema(), default=None) + ), } ) ) instance = Incomparable() - assert v.to_python({'foo': instance}, exclude_defaults=True)['foo'] == [1,2,3] + assert v.to_python({'foo': instance}, exclude_defaults=True)['foo'] == [1, 2, 3] assert v.to_json({'foo': instance}, exclude_defaults=True) == b'{"foo":[1,2,3]}' From c081959c1998ea0d6ca56e840d575825763db028 Mon Sep 17 00:00:00 2001 From: sneakers-the-rat Date: Mon, 21 Oct 2024 17:50:37 -0700 Subject: [PATCH 4/7] now rust linter --- src/serializers/fields.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/serializers/fields.rs b/src/serializers/fields.rs index 5e4484b6c..a9226c5b3 100644 --- a/src/serializers/fields.rs +++ b/src/serializers/fields.rs @@ -79,7 +79,7 @@ fn exclude_default(value: &Bound<'_, PyAny>, extra: &Extra, serializer: &Combine Ok(true) => Ok(true), Ok(false) => Ok(false), Err(_E) => Ok(false), - } + }; } } Ok(false) From cae0633b81c98c18a5520fcd2b10fa6fb24fbdfa Mon Sep 17 00:00:00 2001 From: sneakers-the-rat Date: Mon, 21 Oct 2024 17:54:06 -0700 Subject: [PATCH 5/7] continuing to lint... --- src/serializers/fields.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/serializers/fields.rs b/src/serializers/fields.rs index a9226c5b3..a79a26984 100644 --- a/src/serializers/fields.rs +++ b/src/serializers/fields.rs @@ -78,7 +78,7 @@ fn exclude_default(value: &Bound<'_, PyAny>, extra: &Extra, serializer: &Combine return match value.eq(default) { Ok(true) => Ok(true), Ok(false) => Ok(false), - Err(_E) => Ok(false), + Err(_e) => Ok(false), }; } } From 85cb502d719108753fdad5343bb33d312f22dd00 Mon Sep 17 00:00:00 2001 From: sneakers-the-rat Date: Mon, 21 Oct 2024 18:00:43 -0700 Subject: [PATCH 6/7] adding additional test cases for that sweet total coverage check --- tests/serializers/test_typed_dict.py | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/tests/serializers/test_typed_dict.py b/tests/serializers/test_typed_dict.py index 96c590cee..608f84a55 100644 --- a/tests/serializers/test_typed_dict.py +++ b/tests/serializers/test_typed_dict.py @@ -187,6 +187,14 @@ def __get_pydantic_core_schema__(*args): def __eq__(self, other): raise NotImplementedError("Can't be compared!") + class NeqComparable(Incomparable): + def __eq__(self, other): + return False + + class EqComparable(Incomparable): + def __eq__(self, other): + return True + v = SchemaSerializer( core_schema.typed_dict_schema( { @@ -196,9 +204,13 @@ def __eq__(self, other): } ) ) - instance = Incomparable() - assert v.to_python({'foo': instance}, exclude_defaults=True)['foo'] == [1, 2, 3] - assert v.to_json({'foo': instance}, exclude_defaults=True) == b'{"foo":[1,2,3]}' + + assert v.to_python({'foo': Incomparable()}, exclude_defaults=True)['foo'] == [1, 2, 3] + assert v.to_json({'foo': Incomparable()}, exclude_defaults=True) == b'{"foo":[1,2,3]}' + assert v.to_python({'foo': NeqComparable()}, exclude_defaults=True)['foo'] == [1, 2, 3] + assert v.to_json({'foo': NeqComparable()}, exclude_defaults=True) == b'{"foo":[1,2,3]}' + assert v.to_python({'foo': EqComparable()}, exclude_defaults=True) == {} + assert v.to_json({'foo': EqComparable()}, exclude_defaults=True) == b'{}' def test_function_plain_field_serializer_to_python(): From e80518abfa5184eca93a9abb1f5048feee24e68d Mon Sep 17 00:00:00 2001 From: Jonny Saunders Date: Tue, 22 Oct 2024 03:33:10 -0700 Subject: [PATCH 7/7] Update src/serializers/fields.rs Co-authored-by: David Hewitt --- src/serializers/fields.rs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/serializers/fields.rs b/src/serializers/fields.rs index a79a26984..fa0b37580 100644 --- a/src/serializers/fields.rs +++ b/src/serializers/fields.rs @@ -75,11 +75,7 @@ impl SerField { fn exclude_default(value: &Bound<'_, PyAny>, extra: &Extra, serializer: &CombinedSerializer) -> PyResult { if extra.exclude_defaults { if let Some(default) = serializer.get_default(value.py())? { - return match value.eq(default) { - Ok(true) => Ok(true), - Ok(false) => Ok(false), - Err(_e) => Ok(false), - }; + return Ok(value.eq(default).unwrap_or(false)); } } Ok(false)