Skip to content

Commit

Permalink
refactor!: move serializer and deserializer utility methods out of te…
Browse files Browse the repository at this point in the history
…sts (#189)
  • Loading branch information
navinkarkera authored Mar 9, 2023
1 parent 58b632e commit 3170416
Show file tree
Hide file tree
Showing 8 changed files with 81 additions and 45 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,13 @@ Change Log
Unreleased
----------

[7.0.0] - 2023-03-07
---------------------
Changed
~~~~~~~
* **Breaking change**: Moved serialize_event_data_to_bytes from openedx_events.event_bus.avro.tests.test_utilities to openedx_events.event_bus.avro.serializer
* **Breaking change**: Moved deserialize_bytes_to_event_data from openedx_events.event_bus.avro.tests.test_utilities to openedx_events.event_bus.avro.deserializer

[6.0.0] - 2023-02-23
---------------------
Changed
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__ = "6.0.0"
__version__ = "7.0.0"
17 changes: 17 additions & 0 deletions openedx_events/event_bus/avro/deserializer.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
"""
Deserialize Avro record dictionaries to events that can be sent with OpenEdxPublicSignals.
"""
import io
import json
from typing import get_args, get_origin

import attr
import fastavro

from .custom_serializers import DEFAULT_CUSTOM_SERIALIZERS
from .schema import schema_from_signal
Expand Down Expand Up @@ -81,6 +83,21 @@ def _avro_record_dict_to_event_data(signal, avro_record_dict, deserializers=None
for data_key, data_type in signal.init_data.items()}


def deserialize_bytes_to_event_data(bytes_from_wire, signal):
"""
Deserialize event_bus and Avro-serialized data.
Arguments:
bytes_from_wire: data that was serialized by an Avro serializer
signal: An instance of OpenEdxPublicSignal
"""
deserializer = AvroSignalDeserializer(signal)
schema_dict = deserializer.schema
data_file = io.BytesIO(bytes_from_wire)
as_dict = fastavro.schemaless_reader(data_file, schema_dict)
return deserializer.from_dict(as_dict)


class AvroSignalDeserializer:
"""
Class to deserialize Avro records into events that can be sent by self.signal.
Expand Down
21 changes: 21 additions & 0 deletions openedx_events/event_bus/avro/serializer.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
"""
Serialize events to Avro records.
"""
import io
import json

import attr
import fastavro

from .custom_serializers import DEFAULT_CUSTOM_SERIALIZERS
from .schema import schema_from_signal
Expand Down Expand Up @@ -77,6 +79,25 @@ def value_to_dict(value):
)


def serialize_event_data_to_bytes(event_data, signal):
"""
Serialize event data to bytes.
Arguments:
event_data: Event data to be sent via an OpenEdxPublicSignal's send_event method
signal: An instance of OpenEdxPublicSignal
Returns:
bytes: Byte representation of the event_data, to be sent over the wire
"""
serializer = AvroSignalSerializer(signal)
schema_dict = serializer.schema
out = io.BytesIO()
data_dict = serializer.to_dict(event_data)
fastavro.schemaless_writer(out, schema_dict, data_dict)
out.seek(0)
return out.read()


class AvroSignalSerializer:
"""
Class to serialize event data dictionaries into Avro record dictionaries that can be sent by an event bus.
Expand Down
6 changes: 2 additions & 4 deletions openedx_events/event_bus/avro/tests/test_avro.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,15 @@

from opaque_keys.edx.keys import CourseKey, UsageKey

from openedx_events.event_bus.avro.deserializer import AvroSignalDeserializer
from openedx_events.event_bus.avro.serializer import AvroSignalSerializer
from openedx_events.event_bus.avro.deserializer import AvroSignalDeserializer, deserialize_bytes_to_event_data
from openedx_events.event_bus.avro.serializer import AvroSignalSerializer, serialize_event_data_to_bytes
from openedx_events.event_bus.avro.tests.test_utilities import (
EventData,
NestedAttrsWithDefaults,
SimpleAttrsWithDefaults,
SubTestData0,
SubTestData1,
create_simple_signal,
deserialize_bytes_to_event_data,
serialize_event_data_to_bytes,
)
from openedx_events.tests.utils import FreezeSignalCacheMixin
from openedx_events.tooling import OpenEdxPublicSignal, load_all_signals
Expand Down
18 changes: 17 additions & 1 deletion openedx_events/event_bus/avro/tests/test_deserializer.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

from opaque_keys.edx.keys import CourseKey, UsageKey

from openedx_events.event_bus.avro.deserializer import AvroSignalDeserializer
from openedx_events.event_bus.avro.deserializer import AvroSignalDeserializer, deserialize_bytes_to_event_data
from openedx_events.event_bus.avro.tests.test_utilities import (
EventData,
NestedAttrsWithDefaults,
Expand Down Expand Up @@ -233,3 +233,19 @@ def test_deserialization_of_nested_list_with_complex_types_fails(self):
deserializer.signal = SIGNAL
with self.assertRaises(TypeError):
deserializer.from_dict(initial_dict)

def test_deserialize_bytes_to_event_data(self):
"""
Test deserialize_bytes_to_event_data utility function.
"""
SIGNAL = create_simple_signal({"test_data": EventData})
bytes_data = b'\x06foo\x14bar.course\x14a.sub.name\x1ea.nother.course\x1eb.uber.sub.name*b.uber.another.course'
expected = {"test_data": EventData(
"foo",
"bar.course",
SubTestData0("a.sub.name", "a.nother.course"),
SubTestData1("b.uber.sub.name", "b.uber.another.course"),
)}
deserialized = deserialize_bytes_to_event_data(bytes_data, SIGNAL)
self.assertIsInstance(deserialized["test_data"], EventData)
self.assertEqual(deserialized, expected)
17 changes: 16 additions & 1 deletion openedx_events/event_bus/avro/tests/test_serializer.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from django.test import TestCase
from opaque_keys.edx.keys import CourseKey, UsageKey

from openedx_events.event_bus.avro.serializer import AvroSignalSerializer
from openedx_events.event_bus.avro.serializer import AvroSignalSerializer, serialize_event_data_to_bytes
from openedx_events.event_bus.avro.tests.test_utilities import (
CustomAttrsWithDefaults,
CustomAttrsWithoutDefaults,
Expand Down Expand Up @@ -186,3 +186,18 @@ def test_serialization_of_nested_optional_fields(self):
'string_field': None,
'attrs_field': None
}}})

def test_serialize_event_data_to_bytes(self):
"""
Test serialize_event_data_to_bytes utility function.
"""
SIGNAL = create_simple_signal({"test_data": EventData})
event_data = {"test_data": EventData(
"foo",
"bar.course",
SubTestData0("a.sub.name", "a.nother.course"),
SubTestData1("b.uber.sub.name", "b.uber.another.course"),
)}
serialized = serialize_event_data_to_bytes(event_data, SIGNAL)
expected = b'\x06foo\x14bar.course\x14a.sub.name\x1ea.nother.course\x1eb.uber.sub.name*b.uber.another.course'
self.assertEqual(serialized, expected)
38 changes: 0 additions & 38 deletions openedx_events/event_bus/avro/tests/test_utilities.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
"""
Utility methods and classes for testing various modules in event_bus.avro.
"""
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
Expand All @@ -29,42 +27,6 @@ def create_simple_signal(data_dict):
)


def serialize_event_data_to_bytes(event_data, signal):
"""
Utility method to make sure an Avro serializer can actually serialize given a schema and data
to serialize
Arguments:
event_data: Event data to be sent via an OpenEdxPublicSignal's send_event method
signal: An instance of OpenEdxPublicSignal
Returns:
bytes: Byte representation of the event_data, to be sent over the wire
"""
serializer = AvroSignalSerializer(signal)
schema_dict = serializer.schema
out = io.BytesIO()
data_dict = serializer.to_dict(event_data)
fastavro.schemaless_writer(out, schema_dict, data_dict)
out.seek(0)
return out.read()


def deserialize_bytes_to_event_data(bytes_from_wire, signal):
"""
Utility method to make sure an Avro deserializer can actually deserialize given a event_bus and Avro-serialized
data
Arguments:
bytes_from_wire: data that was serialized by an Avro serializer
signal: An instance of OpenEdxPublicSignal
"""
deserializer = AvroSignalDeserializer(signal)
schema_dict = deserializer.schema
data_file = io.BytesIO(bytes_from_wire)
as_dict = fastavro.schemaless_reader(data_file, schema_dict)
return deserializer.from_dict(as_dict)


# Useful simple attr classes
@attr.s(auto_attribs=True)
class SimpleAttrs:
Expand Down

0 comments on commit 3170416

Please sign in to comment.