edx-ace 1.11.4__py2.py3-none-any.whl → 1.12.1__py2.py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
edx_ace/__init__.py CHANGED
@@ -13,7 +13,7 @@ from .policy import Policy, PolicyResult
13
13
  from .recipient import Recipient
14
14
  from .recipient_resolver import RecipientResolver
15
15
 
16
- __version__ = '1.11.4'
16
+ __version__ = '1.12.1'
17
17
 
18
18
 
19
19
  __all__ = [
@@ -28,6 +28,7 @@ class ChannelType(Enum):
28
28
 
29
29
  EMAIL = 'email'
30
30
  PUSH = 'push'
31
+ BRAZE_PUSH = 'braze_push'
31
32
 
32
33
  def __str__(self):
33
34
  return str(self.value)
@@ -184,6 +185,8 @@ def get_channel_for_message(channel_type, message):
184
185
  channel_names = [settings.ACE_CHANNEL_DEFAULT_EMAIL]
185
186
  elif channel_type == ChannelType.PUSH and getattr(settings, "ACE_CHANNEL_DEFAULT_PUSH", None):
186
187
  channel_names = [settings.ACE_CHANNEL_DEFAULT_PUSH]
188
+ elif channel_type == ChannelType.BRAZE_PUSH and getattr(settings, "ACE_CHANNEL_BRAZE_PUSH", None):
189
+ channel_names = [settings.ACE_CHANNEL_BRAZE_PUSH]
187
190
 
188
191
  try:
189
192
  possible_channels = [
@@ -0,0 +1,64 @@
1
+ """
2
+ Channel for sending push notifications using braze.
3
+ """
4
+ import logging
5
+
6
+ from django.conf import settings
7
+
8
+ from edx_ace.channel import Channel, ChannelType
9
+ from edx_ace.message import Message
10
+ from edx_ace.renderers import RenderedPushNotification
11
+ from edx_ace.utils.braze import get_braze_client
12
+
13
+ LOG = logging.getLogger(__name__)
14
+
15
+
16
+ class BrazePushNotificationChannel(Channel):
17
+ """
18
+ A channel for sending push notifications using braze.
19
+ """
20
+ channel_type = ChannelType.BRAZE_PUSH
21
+ _CAMPAIGNS_SETTING = 'ACE_CHANNEL_BRAZE_PUSH_CAMPAIGNS'
22
+
23
+ @classmethod
24
+ def enabled(cls):
25
+ """
26
+ Returns: True iff braze client is available.
27
+ """
28
+ return bool(get_braze_client())
29
+
30
+ def deliver(self, message: Message, rendered_message: RenderedPushNotification) -> None:
31
+ """
32
+ Transmit a rendered message to a recipient.
33
+
34
+ Args:
35
+ message: The message to transmit.
36
+ rendered_message: The rendered content of the message that has been personalized
37
+ for this particular recipient.
38
+ """
39
+ braze_campaign = message.options['braze_campaign']
40
+ emails = message.options.get('emails') or [message.recipient.email_address]
41
+ campaign_id = self._campaign_id(braze_campaign)
42
+ if not campaign_id:
43
+ LOG.info('Could not find braze campaign for notification %s', braze_campaign)
44
+ return
45
+
46
+ try:
47
+ braze_client = get_braze_client()
48
+ braze_client.send_campaign_message(
49
+ campaign_id=campaign_id,
50
+ trigger_properties=message.context['post_data'],
51
+ emails=emails
52
+ )
53
+ LOG.info('Sent push notification for %s with Braze', braze_campaign)
54
+ except Exception as exc: # pylint: disable=broad-except
55
+ LOG.error(
56
+ 'Unable to send push notification for %s with Braze. Reason: %s',
57
+ braze_campaign,
58
+ str(exc)
59
+ )
60
+
61
+ @classmethod
62
+ def _campaign_id(cls, braze_campaign):
63
+ """Returns the campaign ID for a given ACE message name or None if no match is found"""
64
+ return getattr(settings, cls._CAMPAIGNS_SETTING, {}).get(braze_campaign)
edx_ace/presentation.py CHANGED
@@ -10,6 +10,7 @@ from edx_ace.channel import ChannelType
10
10
  RENDERERS = {
11
11
  ChannelType.EMAIL: renderers.EmailRenderer(),
12
12
  ChannelType.PUSH: renderers.PushNotificationRenderer(),
13
+ ChannelType.BRAZE_PUSH: renderers.BrazePushNotificationRenderer(),
13
14
  }
14
15
 
15
16
 
edx_ace/renderers.py CHANGED
@@ -99,3 +99,18 @@ class PushNotificationRenderer(AbstractRenderer):
99
99
  A renderer for :attr:`.ChannelType.PUSH` channels.
100
100
  """
101
101
  rendered_message_cls = RenderedPushNotification
102
+
103
+
104
+ @attr.s
105
+ class RenderedBrazePushNotification:
106
+ """
107
+ Encapsulates all values needed to send a :class:`.Message`
108
+ over an :attr:`.ChannelType.BRAZE_PUSH`.
109
+ """
110
+
111
+
112
+ class BrazePushNotificationRenderer(AbstractRenderer):
113
+ """
114
+ A renderer for :attr:`.ChannelType.PUSH` channels.
115
+ """
116
+ rendered_message_cls = RenderedBrazePushNotification
@@ -0,0 +1,105 @@
1
+ """
2
+ Tests for TestBrazePushNotificationChannel.
3
+ """
4
+ from unittest.mock import MagicMock, patch
5
+
6
+ import pytest
7
+
8
+ from django.contrib.auth import get_user_model
9
+ from django.test import TestCase, override_settings
10
+
11
+ from edx_ace.channel.braze_push_notification import BrazePushNotificationChannel
12
+ from edx_ace.message import Message
13
+ from edx_ace.recipient import Recipient
14
+ from edx_ace.renderers import RenderedBrazePushNotification
15
+
16
+ BRAZE_URL = "https://example.braze.com"
17
+ API_KEY = "test-api-key"
18
+ User = get_user_model()
19
+
20
+
21
+ @pytest.mark.django_db
22
+ @override_settings(
23
+ EDX_BRAZE_API_KEY=API_KEY,
24
+ EDX_BRAZE_API_SERVER=BRAZE_URL,
25
+ )
26
+ class TestBrazePushNotificationChannel(TestCase):
27
+
28
+ def setUp(self):
29
+ super().setUp()
30
+ self.user = User.objects.create(username='username', email='email@example.com')
31
+ self.lms_user_id = self.user.id
32
+ self.mocked_post_data = {
33
+ 'notification_type': 'new_response',
34
+ 'course_id': 'course-v1:edX+DemoX+Demo_Course',
35
+ 'content_url': 'http://localhost',
36
+ 'replier_name': 'verified',
37
+ 'post_title': 'New test response',
38
+ 'course_name': 'Demonstration Course',
39
+ 'thread_id': '67bedeb9ceb0b101343294c5',
40
+ 'topic_id': 'i4x-edx-eiorguegnru-course-foobarbaz',
41
+ 'response_id': '67ffa1f1ceb0b10134db3d8e',
42
+ 'comment_id': None,
43
+ 'strong': 'strong', 'p': 'p'
44
+ }
45
+
46
+ self.mocked_payload = {
47
+ 'campaign_id': '1234test',
48
+ 'trigger_properties': self.mocked_post_data,
49
+ 'emails': ['edx@example.com']
50
+ }
51
+
52
+ @patch('edx_ace.channel.braze_push_notification.get_braze_client', return_value=True)
53
+ def test_enabled(self, mock_braze_client):
54
+ """
55
+ Test that the channel is enabled when the settings are configured.
56
+ """
57
+ assert BrazePushNotificationChannel.enabled()
58
+
59
+ @patch('edx_ace.channel.braze_push_notification.get_braze_client', return_value=False)
60
+ def test_disabled(self, mock_braze_client):
61
+ """
62
+ Test that the channel is disabled when the settings are not configured.
63
+ """
64
+ assert not BrazePushNotificationChannel.enabled()
65
+
66
+ @override_settings(ACE_CHANNEL_BRAZE_PUSH_CAMPAIGNS={'new_response': "1234test"})
67
+ @patch('edx_ace.channel.braze_push_notification.get_braze_client')
68
+ def test_deliver_success(self, mock_braze_function):
69
+ mock_braze_client = MagicMock()
70
+ mock_braze_function.return_value = mock_braze_client
71
+ mock_braze_client.send_campaign_message = MagicMock(return_value=True)
72
+
73
+ rendered_message = RenderedBrazePushNotification()
74
+ message = Message(
75
+ app_label='testapp',
76
+ name="test_braze",
77
+ recipient=Recipient(lms_user_id="1", email_address="user@example.com"),
78
+ context={'post_data': self.mocked_post_data},
79
+ options={'emails': ['edx@example.com'], 'braze_campaign': 'new_response'}
80
+ )
81
+ channel = BrazePushNotificationChannel()
82
+ channel.deliver(message, rendered_message)
83
+ mock_braze_client.send_campaign_message.assert_called_once()
84
+ args, kwargs = mock_braze_client.send_campaign_message.call_args
85
+
86
+ # Verify the payload
87
+ self.assertEqual(kwargs, self.mocked_payload)
88
+
89
+ @patch('edx_ace.channel.braze_push_notification.get_braze_client')
90
+ def test_campaign_not_configured(self, mock_braze_function):
91
+ mock_braze_client = MagicMock()
92
+ mock_braze_function.return_value = mock_braze_client
93
+ mock_braze_client.send_campaign_message = MagicMock(return_value=True)
94
+
95
+ rendered_message = RenderedBrazePushNotification()
96
+ message = Message(
97
+ app_label='testapp',
98
+ name="test_braze",
99
+ recipient=Recipient(lms_user_id="1", email_address="user@example.com"),
100
+ context={'post_data': self.mocked_post_data},
101
+ options={'emails': ['edx@example.com'], 'braze_campaign': 'new_response'}
102
+ )
103
+ channel = BrazePushNotificationChannel()
104
+ channel.deliver(message, rendered_message)
105
+ mock_braze_client.send_campaign_message.assert_not_called()
@@ -24,12 +24,16 @@ class TestPolicy(TestCase):
24
24
  PolicyCase(deny_values=[set(ChannelType)], expected_channels=set()),
25
25
 
26
26
  # deny only email
27
- PolicyCase(deny_values=[{ChannelType.EMAIL}], expected_channels={ChannelType.PUSH}), # single policy
28
- PolicyCase(deny_values=[{ChannelType.EMAIL}, set()], expected_channels={ChannelType.PUSH}), # multiple policies
29
-
30
- # deny both email and push
31
- PolicyCase(deny_values=[{ChannelType.EMAIL, ChannelType.PUSH}], expected_channels=set()), # single policy
32
- PolicyCase(deny_values=[{ChannelType.EMAIL}, {ChannelType.PUSH}], expected_channels=set()), # multiple policies
27
+ PolicyCase(deny_values=[{ChannelType.EMAIL}],
28
+ expected_channels={ChannelType.PUSH, ChannelType.BRAZE_PUSH}), # single policy
29
+ PolicyCase(deny_values=[{ChannelType.EMAIL}, set()],
30
+ expected_channels={ChannelType.PUSH, ChannelType.BRAZE_PUSH}), # multiple policies
31
+
32
+ # deny email, push and braze_push
33
+ PolicyCase(deny_values=[{ChannelType.EMAIL, ChannelType.PUSH, ChannelType.BRAZE_PUSH}],
34
+ expected_channels=set()), # single policy
35
+ PolicyCase(deny_values=[{ChannelType.EMAIL}, {ChannelType.PUSH},
36
+ {ChannelType.BRAZE_PUSH}], expected_channels=set()), # multiple policies
33
37
 
34
38
  # deny all and email
35
39
  PolicyCase(deny_values=[{ChannelType.EMAIL}, set(ChannelType)], expected_channels=set()),
@@ -0,0 +1,59 @@
1
+ """
2
+ Test cases for utils.braze
3
+ """
4
+ from unittest.mock import patch
5
+
6
+ import pytest
7
+
8
+ from django.contrib.auth import get_user_model
9
+ from django.test import TestCase, override_settings
10
+
11
+ from edx_ace.utils.braze import get_braze_client
12
+
13
+ BRAZE_URL = "https://example.braze.com"
14
+ API_KEY = "test-api-key"
15
+ User = get_user_model()
16
+
17
+
18
+ @pytest.mark.django_db
19
+ class TestBrazeClient(TestCase):
20
+ """ Test cases for utils.braze """
21
+
22
+ @patch('edx_ace.utils.braze.BrazeClient')
23
+ def test_disabled(self, mock_braze_client):
24
+ """
25
+ Test that the channel is settings aren't configured.
26
+ """
27
+ result = get_braze_client()
28
+ self.assertEqual(result, None)
29
+ mock_braze_client.assert_not_called()
30
+
31
+ @override_settings(ACE_CHANNEL_BRAZE_API_KEY=API_KEY)
32
+ @patch('edx_ace.utils.braze.BrazeClient')
33
+ def test_braze_url_not_configured(self, mock_braze_client):
34
+ """
35
+ Test that the channel is settings aren't configured.
36
+ """
37
+ result = get_braze_client()
38
+ self.assertEqual(result, None)
39
+ mock_braze_client.assert_not_called()
40
+
41
+ @override_settings(ACE_CHANNEL_BRAZE_REST_ENDPOINT=API_KEY)
42
+ @patch('edx_ace.utils.braze.BrazeClient')
43
+ def test_braze_api_key_not_configured(self, mock_braze_client):
44
+ """
45
+ Test that the channel is settings aren't configured.
46
+ """
47
+ result = get_braze_client()
48
+ self.assertEqual(result, None)
49
+ mock_braze_client.assert_not_called()
50
+
51
+ @override_settings(ACE_CHANNEL_BRAZE_REST_ENDPOINT=API_KEY, ACE_CHANNEL_BRAZE_API_KEY=API_KEY)
52
+ @patch('edx_ace.utils.braze.BrazeClient', return_value=True)
53
+ def test_success(self, mock_braze_client):
54
+ """
55
+ Test that the channel is settings aren't configured.
56
+ """
57
+ result = get_braze_client()
58
+ self.assertEqual(result, True)
59
+ mock_braze_client.assert_called_once()
edx_ace/utils/braze.py ADDED
@@ -0,0 +1,27 @@
1
+ """
2
+ Helper Methods related to braze client
3
+ """
4
+
5
+ try:
6
+ from braze.client import BrazeClient
7
+ except ImportError:
8
+ BrazeClient = None
9
+ from django.conf import settings
10
+
11
+
12
+ def get_braze_client():
13
+ """ Returns a Braze client. """
14
+ if not BrazeClient:
15
+ return None
16
+
17
+ braze_api_key = getattr(settings, 'ACE_CHANNEL_BRAZE_API_KEY', None)
18
+ braze_api_url = getattr(settings, 'ACE_CHANNEL_BRAZE_REST_ENDPOINT', None)
19
+
20
+ if not braze_api_key or not braze_api_url:
21
+ return None
22
+
23
+ return BrazeClient(
24
+ api_key=braze_api_key,
25
+ api_url=f"https://{braze_api_url}",
26
+ app_id='',
27
+ )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: edx-ace
3
- Version: 1.11.4
3
+ Version: 1.12.1
4
4
  Summary: Framework for Messaging
5
5
  Home-page: https://github.com/openedx/edx-ace
6
6
  Author: edX
@@ -21,6 +21,7 @@ License-File: LICENSE.txt
21
21
  Requires-Dist: Django>=2.2
22
22
  Requires-Dist: attrs>=17.2.0
23
23
  Requires-Dist: django-push-notifications
24
+ Requires-Dist: edx-braze-client==1.0.2
24
25
  Requires-Dist: edx-django-utils>=5.14.2
25
26
  Requires-Dist: firebase-admin
26
27
  Requires-Dist: python-dateutil
@@ -1,4 +1,4 @@
1
- edx_ace/__init__.py,sha256=G-qIKxlqAqzy9u_msaKLkPFJqCvqVgw0ose1zPyN15s,636
1
+ edx_ace/__init__.py,sha256=8Te55LBdi7YPJOeNm5m6rlR0mkXoxjvd8w6JDfYwTMc,636
2
2
  edx_ace/ace.py,sha256=0nP8zvIWiME4qmpMT1PaV4nRDG6YR9Dxm7A6FGoBa8I,2501
3
3
  edx_ace/apps.py,sha256=xNedkdW6TNpm-W1uxnj3Vre2R1akkh2n_7DkSfKXmAk,216
4
4
  edx_ace/delivery.py,sha256=zo5lNHngycEn0tu-e3JM26njRVmaAttAH3D5ohz_468,2812
@@ -6,14 +6,15 @@ edx_ace/errors.py,sha256=y0MqT55qXLkpn_r5LVhHcYuU2-zHs56CQTAbNCqb72k,1065
6
6
  edx_ace/message.py,sha256=lhmPor9vnaLvC4NPyRgB-obpGjGv9Lni0obcOUTgyCg,8340
7
7
  edx_ace/monitoring.py,sha256=6nEcAJMCr9keCTJw9JjGecTg_J1ubcGJfuGiFq2B8G4,257
8
8
  edx_ace/policy.py,sha256=Zi0oupnmvgnW7jOkEpgsOd-gj6isR-3Mna2SxsdG4U8,2915
9
- edx_ace/presentation.py,sha256=XCQwPsl_2LZMcyF_aFX7b91d7rEzmQJiv8XgOSCyLQ0,838
9
+ edx_ace/presentation.py,sha256=o_BbUOzNxBqmcGHIq0PAhxouDo3cVaEnn7uJGCK9y4o,909
10
10
  edx_ace/recipient.py,sha256=ogZjjKorAc6yiqvcnXcAMsHz2uAfhYU5qLPUoifofXQ,576
11
11
  edx_ace/recipient_resolver.py,sha256=ChY0cgLSt_HioKSHyuCh7iHSJOsBWuivsOvAc6QyedE,959
12
- edx_ace/renderers.py,sha256=eJcTWyRhny-3PLHDV4Rozk1TJ0fjsqgOcFOWO_TTngk,3143
12
+ edx_ace/renderers.py,sha256=f1bPdmWpBvMa9OCPsq0NgnxsZi2Y4gwW5ZcYk9Qmpsw,3499
13
13
  edx_ace/serialization.py,sha256=EptnQqbI9j5kVqdUDAlm2pcm3dUsFwsP9tAji3y5uqc,3775
14
14
  edx_ace/signals.py,sha256=7kwpw7SgpPP6TRTtTQeGEOZ2cD_AcD4sSrpVxd_a19w,160
15
- edx_ace/channel/__init__.py,sha256=bQ8rpb_o0CvyWxZjOkmU9Au2S4kUCeKM8upvUb3yG7g,7030
15
+ edx_ace/channel/__init__.py,sha256=PzfSJhBbsW5a8r1NnMm5q1PhwSadSoeIehjhU-C88pM,7221
16
16
  edx_ace/channel/braze.py,sha256=k-R9jZpiBQTV0FQ3Z2gOipVgxNpBMPSLBL71muh9go4,10240
17
+ edx_ace/channel/braze_push_notification.py,sha256=XzFARmDotqthpARqvdvmcUy-ieEzusY-pH449nuciM4,2239
17
18
  edx_ace/channel/django_email.py,sha256=9TNdiFJ2U3QjKEriUl3_9twAFkH-E2EmrWujStUOWN0,2174
18
19
  edx_ace/channel/file.py,sha256=_Jx8o5Cw8IpEdaF7g-ljF7CEqswBHmuwyupm_tkI2wk,3028
19
20
  edx_ace/channel/mixins.py,sha256=kmSBFcBU2OhMqlzaDZPrZpT3lh6ccte0cQeCCvo8FNc,1313
@@ -26,9 +27,10 @@ edx_ace/tests/test_ace.py,sha256=H5ss4ah-v5XDJ47udl6Ob6iSVdcX0qkjPI2h0kgWp1k,327
26
27
  edx_ace/tests/test_date.py,sha256=RteoiCrc269BXnIerrtz8I0K1weldZ_Qg9Oc0Nd8ixs,639
27
28
  edx_ace/tests/test_delivery.py,sha256=6nTSm3y28U0J0ChfduVPxkRyMA-IVEZ9H8TkmSHktAI,5787
28
29
  edx_ace/tests/test_message.py,sha256=uAf3XltW605WieNGdi0u5p6bY_oSqLsmV5pHU3J_TiQ,4430
29
- edx_ace/tests/test_policy.py,sha256=T6Mm_q1Bi935WMBPS7sK5XbXM5izQ3y0KW0yiaQ5_GU,1597
30
+ edx_ace/tests/test_policy.py,sha256=bksNJntzD7YXaVx95LCR4n9uyxAfugXlen3HElUHsaU,1791
30
31
  edx_ace/tests/test_presentation.py,sha256=elgVz_sR04h3KKs73G0qQBzt0CazOPWKrDF2rVC_R4U,541
31
32
  edx_ace/tests/channel/test_braze.py,sha256=J7p9_xF5NTzZDC-9P8aFsV3cLc2RyDMBNVlyimwORuU,7296
33
+ edx_ace/tests/channel/test_braze_push_notification.py,sha256=1BhDIOjuCnZCjKWTy3jpf7978SyeLSv0xJqZ1Av-DXU,4241
32
34
  edx_ace/tests/channel/test_channel_helpers.py,sha256=QwzHhdpEGkdW_WV0bfLkj8nBzHy4zFoo_lsIVlN31QU,3196
33
35
  edx_ace/tests/channel/test_django_email.py,sha256=FCllUVAAtGEtDsosU-auf7zOApk-12oM_xWYs7CqVn8,4114
34
36
  edx_ace/tests/channel/test_file_email.py,sha256=0kWb5xhdHJMgMNrnlEM2QV4NFLZERWgRU-3Lx0MQxLM,1795
@@ -36,15 +38,17 @@ edx_ace/tests/channel/test_push_notification.py,sha256=u6BBplfPwQLMRL02VFlWlNDEw
36
38
  edx_ace/tests/channel/test_sailthru.py,sha256=Hfs-v2y9N9H2u0LlA_cJkhcuU0iC02wzwTt4pHsRS7o,3197
37
39
  edx_ace/tests/test_templates/testapp/edx_ace/testmessage/email/body.html,sha256=gDeXQeQFnC2EFzdJ-OwWL7yDdkiR42sUYIxyeJJUD9s,646
38
40
  edx_ace/tests/test_templates/testapp/edx_ace/testmessage/email/head.html,sha256=XvxX2Go_eKUtG0Oo1qPPMOtdS2IoGi7PSY98Di_Ev3s,19
41
+ edx_ace/tests/utils/test_braze_utils.py,sha256=aywPWmpnhUqSToWPiKMpduAyVojZ_s1ZeKzR3oSLu-o,1899
39
42
  edx_ace/tests/utils/test_signals.py,sha256=k0F76oxcnECd95MeGw666YD3d2z-mIx4AS_MTYjwY5Q,1513
40
43
  edx_ace/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
44
+ edx_ace/utils/braze.py,sha256=I0CcJELYkbfMTI3EWGkb8-unxO3Wkxu6C1GHA93H_0Y,622
41
45
  edx_ace/utils/date.py,sha256=Rmz3RAUCdd30hu1qcKH7FmAypaw1aSoZg-yfZig1c8A,1483
42
46
  edx_ace/utils/once.py,sha256=sY3szBh3gvvAjrKbKq4S2mCejompjh5YcYD7XOhzjGU,2024
43
47
  edx_ace/utils/plugins.py,sha256=U-l-eU2uWUiiwYV-H-2DfmwjoksqskAsYwS7QnThy2Q,2090
44
48
  edx_ace/utils/signals.py,sha256=efmnxI78juMLo6PJkPyfJ6pYTMY3kHnGy-_IR8FsDm8,1637
45
- edx_ace-1.11.4.dist-info/LICENSE.txt,sha256=VrSJ4gO4NCpskzfNHbaTB4VcN9Q213YdcHbpOZSwcOA,35138
46
- edx_ace-1.11.4.dist-info/METADATA,sha256=l7cVBcI9N2pVQnjasjuU0P--O3ZIIClIbBeurI1NqvM,10039
47
- edx_ace-1.11.4.dist-info/WHEEL,sha256=Kh9pAotZVRFj97E15yTA4iADqXdQfIVTHcNaZTjxeGM,110
48
- edx_ace-1.11.4.dist-info/entry_points.txt,sha256=hvoHnxCJGiV3KDdVi9YKPCCsub5O__3Q4dbVpTGPJoY,331
49
- edx_ace-1.11.4.dist-info/top_level.txt,sha256=5eg_80KI88VkeiCVqZUqcYcc_PfPOg8o1GA4HxsiRU8,8
50
- edx_ace-1.11.4.dist-info/RECORD,,
49
+ edx_ace-1.12.1.dist-info/LICENSE.txt,sha256=VrSJ4gO4NCpskzfNHbaTB4VcN9Q213YdcHbpOZSwcOA,35138
50
+ edx_ace-1.12.1.dist-info/METADATA,sha256=yc-FIlMAsN3VbiKOjkSCaG62Q6fVla9s31OZEpm1ZzA,10078
51
+ edx_ace-1.12.1.dist-info/WHEEL,sha256=Kh9pAotZVRFj97E15yTA4iADqXdQfIVTHcNaZTjxeGM,110
52
+ edx_ace-1.12.1.dist-info/entry_points.txt,sha256=9jbgYKntqyu4kP1pI_Js1BSWqKBG7OacftVn1BAKsLI,413
53
+ edx_ace-1.12.1.dist-info/top_level.txt,sha256=5eg_80KI88VkeiCVqZUqcYcc_PfPOg8o1GA4HxsiRU8,8
54
+ edx_ace-1.12.1.dist-info/RECORD,,
@@ -1,5 +1,6 @@
1
1
  [openedx.ace.channel]
2
2
  braze_email = edx_ace.channel.braze:BrazeEmailChannel
3
+ braze_push = edx_ace.channel.braze_push_notification:BrazePushNotificationChannel
3
4
  django_email = edx_ace.channel.django_email:DjangoEmailChannel
4
5
  file_email = edx_ace.channel.file:FileEmailChannel
5
6
  push_notification = edx_ace.channel.push_notification:PushNotificationChannel