Skip to content

Commit

Permalink
fix: Handle optional (None) values for custom serializers (#84)
Browse files Browse the repository at this point in the history
Fixes #82
  • Loading branch information
timmc-edx authored Jul 28, 2022
1 parent 4adec18 commit 3cfb09b
Show file tree
Hide file tree
Showing 5 changed files with 59 additions and 2 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,12 @@ Unreleased
Changed
~~~~~~~

[0.11.1] - 2022-07-28
---------------------
Fixed
~~~~~
* Handle optional (None) values for custom serializers

[0.11.0] - 2022-07-21
---------------------
Added
Expand Down
2 changes: 1 addition & 1 deletion openedx_events/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,4 @@
more information about the project.
"""

__version__ = "0.11.0"
__version__ = "0.11.1"
15 changes: 15 additions & 0 deletions openedx_events/event_bus/avro/serializer.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,21 @@ def _get_non_attrs_serializer(serializers=None):
all_serializers = {**DEFAULT_SERIALIZERS, **param_serializers}

def _serialize_non_attrs_values(inst, field, value): # pylint: disable=unused-argument
if value is None:
# All default=None fields are implicit union types of NoneType
# and something else. (See ADR 7.) Note that if there isn't a
# default at all, field.default will be attrs.NOTHING, not None.
if field.default is None:
return None
else:
# Bail out early with an informative message rather
# than ending up with an inscrutable error from inside
# a custom serializer.
#
# If we ever make a custom serializer that can handle
# None as an input, we can remove this check.
raise Exception("None cannot be handled by custom serializers (and default=None was not set)")

for extended_class, serializer in all_serializers.items():
if field:
if issubclass(field.type, extended_class):
Expand Down
22 changes: 21 additions & 1 deletion openedx_events/event_bus/avro/tests/test_serializer.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,14 @@

from datetime import datetime

import pytest
from django.test import TestCase
from opaque_keys.edx.keys import CourseKey

from openedx_events.event_bus.avro.serializer import AvroSignalSerializer
from openedx_events.event_bus.avro.tests.test_utilities import (
CustomAttrsWithDefaults,
CustomAttrsWithoutDefaults,
EventData,
NestedAttrsWithDefaults,
NestedNonAttrs,
Expand Down Expand Up @@ -91,7 +94,7 @@ def test_serialization_with_custom_serializer_on_nested_fields(self):
"field_0": "a.val:a.nother.val"
}})

def test_serialization_of_optional_fields(self):
def test_serialization_of_optional_simple_fields(self):
SIGNAL = create_simple_signal({
"data": SimpleAttrsWithDefaults
})
Expand All @@ -105,6 +108,23 @@ def test_serialization_of_optional_fields(self):
'string_field': None,
'attrs_field': None}})

def test_serialization_of_optional_custom_fields(self):
SIGNAL = create_simple_signal({"data": CustomAttrsWithDefaults})
serializer = AvroSignalSerializer(SIGNAL)
event_data = {"data": CustomAttrsWithDefaults(coursekey_field=None, datetime_field=None)}
data_dict = serializer.to_dict(event_data)
self.assertDictEqual(data_dict, {"data": {'coursekey_field': None,
'datetime_field': None}})

def test_serialization_of_none_mandatory_custom_fields(self):
"""Check that None isn't accepted if field not optional."""
SIGNAL = create_simple_signal({"data": CustomAttrsWithoutDefaults})
serializer = AvroSignalSerializer(SIGNAL)
event_data = {"data": CustomAttrsWithoutDefaults(coursekey_field=None, datetime_field=None)}
with pytest.raises(Exception) as excinfo:
serializer.to_dict(event_data)
assert excinfo.value.args[0] == "None cannot be handled by custom serializers (and default=None was not set)"

def test_serialization_of_nested_optional_fields(self):
SIGNAL = create_simple_signal({
"data": NestedAttrsWithDefaults
Expand Down
16 changes: 16 additions & 0 deletions openedx_events/event_bus/avro/tests/test_utilities.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@
"""
import io
import re
from datetime import datetime

import attr
import fastavro
from opaque_keys.edx.keys import CourseKey

from openedx_events.event_bus.avro.custom_serializers import BaseCustomTypeAvroSerializer
from openedx_events.event_bus.avro.deserializer import AvroSignalDeserializer
Expand Down Expand Up @@ -108,6 +110,20 @@ class SimpleAttrsWithDefaults:
attrs_field = attr.ib(type=SimpleAttrs, default=None)


@attr.s(frozen=True)
class CustomAttrsWithDefaults:
"""Test attrs with nullable values"""
coursekey_field = attr.ib(type=CourseKey, default=None)
datetime_field = attr.ib(type=datetime, default=None)


@attr.s(frozen=True)
class CustomAttrsWithoutDefaults:
"""Test attrs without nullable values"""
coursekey_field = attr.ib(type=CourseKey)
datetime_field = attr.ib(type=datetime)


@attr.s(frozen=True)
class NestedAttrsWithDefaults:
"""Test attrs with nullable values"""
Expand Down

0 comments on commit 3cfb09b

Please sign in to comment.