From 302389af4a932f82398a1c2086d9ed57de86ffa5 Mon Sep 17 00:00:00 2001 From: Rebecca Graber Date: Tue, 26 Sep 2023 15:38:56 -0400 Subject: [PATCH] feat!: new event bus config format --- openedx_events/apps.py | 49 ++++++++++++++------ openedx_events/tests/test_producer_config.py | 19 ++++---- test_utils/test_settings.py | 29 ++++-------- 3 files changed, 53 insertions(+), 44 deletions(-) diff --git a/openedx_events/apps.py b/openedx_events/apps.py index 0219eb95..95b2bdd5 100644 --- a/openedx_events/apps.py +++ b/openedx_events/apps.py @@ -14,14 +14,15 @@ def general_signal_handler(sender, signal, **kwargs): # pylint: disable=unused- """ Signal handler for publishing events to configured event bus. """ - configurations = getattr(settings, "EVENT_BUS_PRODUCER_CONFIG", {}).get(signal.event_type, ()) + configurations = getattr(settings, "EVENT_BUS_PRODUCER_CONFIG", {}).get(signal.event_type, {}) event_data = {key: kwargs.get(key) for key in signal.init_data} - for configuration in configurations: - if configuration["enabled"]: + + for topic in configurations.keys(): + if configurations[topic]["enabled"]: get_producer().send( signal=signal, - topic=configuration["topic"], - event_key_field=configuration["event_key_field"], + topic=topic, + event_key_field=configurations[topic]["event_key_field"], event_data=event_data, event_metadata=kwargs["metadata"], ) @@ -34,40 +35,47 @@ class OpenedxEventsConfig(AppConfig): name = "openedx_events" - def _get_validated_signal_config(self, event_type, configurations): + def _get_validated_signal_config(self, event_type, configuration): """ Validate signal configuration format. + Example expected signal configuration: + { + "topic_a": { "event_key_field": "my.key.field", "enabled": True }, + "topic_b": { "event_key_field": "my.key.field", "enabled": False } + } + Raises: ProducerConfigurationError: If configuration is not valid. """ - if not isinstance(configurations, list) and not isinstance(configurations, tuple): + if not isinstance(configuration, dict) and not isinstance(configuration, dict): raise ProducerConfigurationError( event_type=event_type, - message="Configuration for event_types should be a list or a tuple of dictionaries" + message="Configuration for event_types should be a dict" ) try: signal = OpenEdxPublicSignal.get_signal_by_type(event_type) except KeyError as exc: raise ProducerConfigurationError(message=f"No OpenEdxPublicSignal of type: '{event_type}'.") from exc - for configuration in configurations: - if not isinstance(configuration, dict): + for topic, topic_configuration in configuration.items(): + print(f"{topic_configuration=}") + if not isinstance(topic_configuration, dict): raise ProducerConfigurationError( event_type=event_type, - message="One of the configuration object is not a dictionary" + message="One of the configuration objects is not a dictionary" ) - expected_keys = {"topic": str, "event_key_field": str, "enabled": bool} + expected_keys = {"event_key_field": str, "enabled": bool} for expected_key, expected_type in expected_keys.items(): - if expected_key not in configuration: + if expected_key not in topic_configuration.keys(): raise ProducerConfigurationError( event_type=event_type, message=f"One of the configuration object is missing '{expected_key}' key." ) - if not isinstance(configuration[expected_key], expected_type): + if not isinstance(topic_configuration[expected_key], expected_type): raise ProducerConfigurationError( event_type=event_type, message=(f"Expected type: {expected_type} for '{expected_key}', " - f"found: {type(configuration[expected_key])}") + f"found: {type(topic_configuration[expected_key])}") ) return signal @@ -75,6 +83,17 @@ def ready(self): """ Read `EVENT_BUS_PRODUCER_CONFIG` setting and connects appropriate handlers to the events based on it. + Example expected configuration: + { + "org.openedx.content_authoring.xblock.deleted.v1" : { + "topic_a": { "event_key_field": "xblock_info.usage_key", "enabled": True }, + "topic_b": { "event_key_field": "xblock_info.usage_key", "enabled": False } + }, + "org.openedx.content_authoring.course.catalog_info.changed.v1" : { + "topic_c": {"event_key_field": "course_info.course_key", "enabled": True } + } + } + Raises: ProducerConfigurationError: If `EVENT_BUS_PRODUCER_CONFIG` is not valid. """ diff --git a/openedx_events/tests/test_producer_config.py b/openedx_events/tests/test_producer_config.py index ee84bd79..b558c856 100644 --- a/openedx_events/tests/test_producer_config.py +++ b/openedx_events/tests/test_producer_config.py @@ -78,21 +78,23 @@ def test_configuration_is_validated(self): with pytest.raises(ProducerConfigurationError, match="should be a dictionary"): apps.get_app_config("openedx_events").ready() - with override_settings(EVENT_BUS_PRODUCER_CONFIG={"invalid.event.type": []}): + with override_settings(EVENT_BUS_PRODUCER_CONFIG={"invalid.event.type": {}}): with pytest.raises(ProducerConfigurationError, match="No OpenEdxPublicSignal of type"): apps.get_app_config("openedx_events").ready() with override_settings(EVENT_BUS_PRODUCER_CONFIG={"org.openedx.content_authoring.xblock.deleted.v1": ""}): - with pytest.raises(ProducerConfigurationError, match="should be a list or a tuple"): + with pytest.raises(ProducerConfigurationError, match="should be a dict"): apps.get_app_config("openedx_events").ready() - with override_settings(EVENT_BUS_PRODUCER_CONFIG={"org.openedx.content_authoring.xblock.deleted.v1": [""]}): - with pytest.raises(ProducerConfigurationError, match="object is not a dictionary"): + with override_settings(EVENT_BUS_PRODUCER_CONFIG={"org.openedx.content_authoring.xblock.deleted.v1": + {"topic": ""}}): + with pytest.raises(ProducerConfigurationError, match="One of the configuration objects is not a" + " dictionary"): apps.get_app_config("openedx_events").ready() with override_settings( EVENT_BUS_PRODUCER_CONFIG={ - "org.openedx.content_authoring.xblock.deleted.v1": [{"topic": "some", "enabled": True}] + "org.openedx.content_authoring.xblock.deleted.v1": {"some": {"enabled": True}} } ): with pytest.raises(ProducerConfigurationError, match="missing 'event_key_field' key."): @@ -100,9 +102,10 @@ def test_configuration_is_validated(self): with override_settings( EVENT_BUS_PRODUCER_CONFIG={ - "org.openedx.content_authoring.xblock.deleted.v1": [ - {"topic": "some", "enabled": 1, "event_key_field": "some"} - ] + "org.openedx.content_authoring.xblock.deleted.v1": + { + "some": {"enabled": 1, "event_key_field": "some"} + } } ): with pytest.raises( diff --git a/test_utils/test_settings.py b/test_utils/test_settings.py index a0fd9143..096aa155 100644 --- a/test_utils/test_settings.py +++ b/test_utils/test_settings.py @@ -30,28 +30,15 @@ SECRET_KEY = "not-so-secret-key" EVENT_BUS_PRODUCER_CONFIG = { - 'org.openedx.content_authoring.xblock.published.v1': ( + 'org.openedx.content_authoring.xblock.published.v1': { - 'topic': 'content-authoring-xblock-lifecycle', - 'event_key_field': 'xblock_info.usage_key', - 'enabled': True + 'content-authoring-xblock-lifecycle': {'event_key_field': 'xblock_info.usage_key', 'enabled': True}, + 'content-authoring-all-status': {'event_key_field': 'xblock_info.usage_key', 'enabled': True}, + 'content-authoring-xblock-published': {'event_key_field': 'xblock_info.usage_key', 'enabled': False} }, + 'org.openedx.content_authoring.xblock.deleted.v1': { - 'topic': 'content-authoring-all-status', - 'event_key_field': 'xblock_info.usage_key', - 'enabled': True - }, - { - 'topic': 'content-authoring-xblock-published', - 'event_key_field': 'xblock_info.usage_key', - 'enabled': False - }, - ), - 'org.openedx.content_authoring.xblock.deleted.v1': ( - { - 'topic': 'content-authoring-xblock-lifecycle', - 'event_key_field': 'xblock_info.usage_key', - 'enabled': True - }, - ), + 'content-authoring-xblock-lifecycle': {'event_key_field': 'xblock_info.usage_key', 'enabled': True}, + } } +