From 6e2acb16f9e1afdfef9dffa08fc0f9d888c99d72 Mon Sep 17 00:00:00 2001 From: Cristhian Garcia Date: Fri, 2 Feb 2024 15:07:27 -0500 Subject: [PATCH] chore: inject supported logs in EVENT_BUS_TRACKING_LOGS fix: only process signal if it is emitted from the event bus chore: change tracking log handler logging to debug test: update test with from_event_bus chore: install openedx-events v9.5.1 feat: migrate handler to eventtracking --- event_routing_backends/apps.py | 1 - event_routing_backends/handlers.py | 57 ------ event_routing_backends/settings/common.py | 190 +++++++++--------- event_routing_backends/tests/test_handlers.py | 106 ---------- requirements/base.in | 1 - requirements/base.txt | 15 +- requirements/dev.txt | 21 +- requirements/doc.txt | 17 +- requirements/pip.txt | 4 +- requirements/quality.txt | 15 +- requirements/test.txt | 11 +- 11 files changed, 140 insertions(+), 298 deletions(-) delete mode 100644 event_routing_backends/handlers.py delete mode 100644 event_routing_backends/tests/test_handlers.py diff --git a/event_routing_backends/apps.py b/event_routing_backends/apps.py index bedd2ba2..f8cd99d4 100644 --- a/event_routing_backends/apps.py +++ b/event_routing_backends/apps.py @@ -35,6 +35,5 @@ def ready(self): """ super().ready() # pylint: disable=import-outside-toplevel, unused-import - from event_routing_backends import handlers # noqa: F401 from event_routing_backends.processors.caliper import event_transformers as caliper_event_transformers from event_routing_backends.processors.xapi import event_transformers as xapi_event_transformers diff --git a/event_routing_backends/handlers.py b/event_routing_backends/handlers.py deleted file mode 100644 index a19e5a77..00000000 --- a/event_routing_backends/handlers.py +++ /dev/null @@ -1,57 +0,0 @@ -""" -This module contains the handlers for signals emitted by the analytics app. -""" -import json -import logging - -from django.dispatch import receiver -from eventtracking.backends.event_bus import EventBusRoutingBackend -from eventtracking.processors.exceptions import EventEmissionExit -from eventtracking.tracker import get_tracker -from openedx_events.analytics.signals import TRACKING_EVENT_EMITTED - -logger = logging.getLogger(__name__) - - -@receiver(TRACKING_EVENT_EMITTED) -def send_tracking_log_to_backends( - sender, signal, **kwargs -): # pylint: disable=unused-argument - """ - Listen for the TRACKING_EVENT_EMITTED signal and send the event to the enabled backends. - - The process is the following: - - 1. Unserialize the tracking log from the signal. - 2. Get the tracker instance to get the enabled backends (mongo, event_bus, logger, etc). - 3. Get the event bus backends that are the interested in the signals (multiple can be configured). - 4. Transform the event to xAPI or Caliper format. - 5. Send the transformed event to the different event bus backends. Any event bus backend can be configured - with multiples backends (xAPI or Caliper). - - This allows us to only send the tracking log to the event bus once and the event bus will send - the transformed event to the different configured backends. - """ - tracking_log = kwargs.get("tracking_log") - - event = { - "name": tracking_log.name, - "timestamp": tracking_log.timestamp, - "data": json.loads(tracking_log.data), - "context": json.loads(tracking_log.context), - } - - tracker = get_tracker() - - engines = { - name: engine - for name, engine in tracker.backends.items() - if isinstance(engine, EventBusRoutingBackend) - } - for name, engine in engines.items(): - try: - processed_event = engine.process_event(event) - logger.info('Successfully processed event "{}"'.format(event["name"])) - engine.send_to_backends(processed_event.copy()) - except EventEmissionExit: - logger.info("[EventEmissionExit] skipping event {}".format(event["name"])) diff --git a/event_routing_backends/settings/common.py b/event_routing_backends/settings/common.py index 153525f1..d859e887 100644 --- a/event_routing_backends/settings/common.py +++ b/event_routing_backends/settings/common.py @@ -58,6 +58,101 @@ def plugin_settings(settings): 'edx.course.enrollment.deactivated', 'edx.course.grade.passed.first_time' ] + allowed_xapi_events = [ + 'edx.course.enrollment.activated', + 'edx.course.enrollment.deactivated', + 'edx.course.enrollment.mode_changed', + 'edx.grades.subsection.grade_calculated', + 'edx.grades.course.grade_calculated', + 'edx.special_exam.timed.attempt.created', + 'edx.special_exam.timed.attempt.submitted', + 'edx.special_exam.practice.attempt.created', + 'edx.special_exam.practice.attempt.submitted', + 'edx.special_exam.proctored.attempt.created', + 'edx.special_exam.proctored.attempt.submitted', + 'edx.completion.block_completion.changed', + 'edx.forum.thread.created', + 'edx.forum.thread.deleted', + 'edx.forum.thread.edited', + 'edx.forum.thread.viewed', + 'edx.forum.thread.reported', + 'edx.forum.thread.unreported', + 'edx.forum.thread.voted', + 'edx.forum.response.created', + 'edx.forum.response.deleted', + 'edx.forum.response.edited', + 'edx.forum.response.reported', + 'edx.forum.response.unreported', + 'edx.forum.response.voted', + 'edx.forum.comment.created', + 'edx.forum.comment.deleted', + 'edx.forum.comment.edited', + 'edx.forum.comment.reported', + 'edx.forum.comment.unreported', + 'edx.ui.lms.link_clicked', + 'edx.ui.lms.sequence.outline.selected', + 'edx.ui.lms.outline.selected', + 'edx.ui.lms.sequence.next_selected', + 'edx.ui.lms.sequence.previous_selected', + 'edx.ui.lms.sequence.tab_selected', + 'showanswer', + 'edx.problem.hint.demandhint_displayed', + 'problem_check', + 'load_video', + 'edx.video.loaded', + 'play_video', + 'edx.video.played', + 'complete_video', + 'edx.video.completed', + 'stop_video', + 'edx.video.stopped', + 'pause_video', + 'edx.video.paused', + 'seek_video', + 'edx.video.position.changed', + 'hide_transcript', + 'edx.video.transcript.hidden', + 'show_transcript', + 'edx.video.transcript.shown', + 'speed_change_video', + 'video_hide_cc_menu', + 'edx.video.closed_captions.shown', + 'edx.video.closed_captions.hidden', + 'edx.video.language_menu.hidden', + 'video_show_cc_menu', + 'edx.video.language_menu.shown', + 'edx.course.grade.passed.first_time' + ] + allowed_caliper_events = [ + 'edx.course.enrollment.activated', + 'edx.course.enrollment.deactivated', + 'edx.ui.lms.link_clicked', + 'edx.ui.lms.sequence.outline.selected', + 'edx.ui.lms.outline.selected', + 'edx.ui.lms.sequence.next_selected', + 'edx.ui.lms.sequence.previous_selected', + 'edx.ui.lms.sequence.tab_selected', + 'showanswer', + 'edx.problem.hint.demandhint_displayed', + 'problem_check', + 'load_video', + 'edx.video.loaded', + 'play_video', + 'edx.video.played', + 'complete_video', + 'edx.video.completed', + 'stop_video', + 'edx.video.stopped', + 'pause_video', + 'edx.video.paused', + 'seek_video', + 'edx.video.position.changed', + 'edx.course.grade.passed.first_time', + 'edx.course.grade.now_passed', + 'edx.course.grade.now_failed' + ] + + settings.EVENT_BUS_TRACKING_LOGS = set(allowed_xapi_events + allowed_caliper_events) settings.EVENT_TRACKING_BACKENDS.update({ 'xapi': { @@ -68,71 +163,7 @@ def plugin_settings(settings): { 'ENGINE': 'eventtracking.processors.whitelist.NameWhitelistProcessor', 'OPTIONS': { - 'whitelist': [ - 'edx.course.enrollment.activated', - 'edx.course.enrollment.deactivated', - 'edx.course.enrollment.mode_changed', - 'edx.grades.subsection.grade_calculated', - 'edx.grades.course.grade_calculated', - 'edx.special_exam.timed.attempt.created', - 'edx.special_exam.timed.attempt.submitted', - 'edx.special_exam.practice.attempt.created', - 'edx.special_exam.practice.attempt.submitted', - 'edx.special_exam.proctored.attempt.created', - 'edx.special_exam.proctored.attempt.submitted', - 'edx.completion.block_completion.changed', - 'edx.forum.thread.created', - 'edx.forum.thread.deleted', - 'edx.forum.thread.edited', - 'edx.forum.thread.viewed', - 'edx.forum.thread.reported', - 'edx.forum.thread.unreported', - 'edx.forum.thread.voted', - 'edx.forum.response.created', - 'edx.forum.response.deleted', - 'edx.forum.response.edited', - 'edx.forum.response.reported', - 'edx.forum.response.unreported', - 'edx.forum.response.voted', - 'edx.forum.comment.created', - 'edx.forum.comment.deleted', - 'edx.forum.comment.edited', - 'edx.forum.comment.reported', - 'edx.forum.comment.unreported', - 'edx.ui.lms.link_clicked', - 'edx.ui.lms.sequence.outline.selected', - 'edx.ui.lms.outline.selected', - 'edx.ui.lms.sequence.next_selected', - 'edx.ui.lms.sequence.previous_selected', - 'edx.ui.lms.sequence.tab_selected', - 'showanswer', - 'edx.problem.hint.demandhint_displayed', - 'problem_check', - 'load_video', - 'edx.video.loaded', - 'play_video', - 'edx.video.played', - 'complete_video', - 'edx.video.completed', - 'stop_video', - 'edx.video.stopped', - 'pause_video', - 'edx.video.paused', - 'seek_video', - 'edx.video.position.changed', - 'hide_transcript', - 'edx.video.transcript.hidden', - 'show_transcript', - 'edx.video.transcript.shown', - 'speed_change_video', - 'video_hide_cc_menu', - 'edx.video.closed_captions.shown', - 'edx.video.closed_captions.hidden', - 'edx.video.language_menu.hidden', - 'video_show_cc_menu', - 'edx.video.language_menu.shown', - 'edx.course.grade.passed.first_time' - ] + 'whitelist': allowed_xapi_events } }, ], @@ -161,34 +192,7 @@ def plugin_settings(settings): { "ENGINE": "eventtracking.processors.whitelist.NameWhitelistProcessor", "OPTIONS": { - "whitelist": [ - 'edx.course.enrollment.activated', - 'edx.course.enrollment.deactivated', - 'edx.ui.lms.link_clicked', - 'edx.ui.lms.sequence.outline.selected', - 'edx.ui.lms.outline.selected', - 'edx.ui.lms.sequence.next_selected', - 'edx.ui.lms.sequence.previous_selected', - 'edx.ui.lms.sequence.tab_selected', - 'showanswer', - 'edx.problem.hint.demandhint_displayed', - 'problem_check', - 'load_video', - 'edx.video.loaded', - 'play_video', - 'edx.video.played', - 'complete_video', - 'edx.video.completed', - 'stop_video', - 'edx.video.stopped', - 'pause_video', - 'edx.video.paused', - 'seek_video', - 'edx.video.position.changed', - 'edx.course.grade.passed.first_time', - 'edx.course.grade.now_passed', - 'edx.course.grade.now_failed' - ] + "whitelist": allowed_caliper_events } } ], diff --git a/event_routing_backends/tests/test_handlers.py b/event_routing_backends/tests/test_handlers.py deleted file mode 100644 index 0a27a78d..00000000 --- a/event_routing_backends/tests/test_handlers.py +++ /dev/null @@ -1,106 +0,0 @@ -""" -Test handlers for signals emitted by the analytics app -""" - -from unittest.mock import Mock, patch - -from django.test import TestCase -from django.test.utils import override_settings -from eventtracking.django.django_tracker import DjangoTracker -from openedx_events.analytics.data import TrackingLogData - -from event_routing_backends.handlers import send_tracking_log_to_backends - - -class TestHandlers(TestCase): - """ - Tests handlers for signals emitted by the analytics app - """ - - @override_settings( - EVENT_TRACKING_BACKENDS={ - "event_bus": { - "ENGINE": "eventtracking.backends.event_bus.EventBusRoutingBackend", - "OPTIONS": {}, - }, - } - ) - @patch("event_routing_backends.handlers.get_tracker") - def test_send_tracking_log_to_backends( - self, mock_get_tracker - ): - """ - Test for send_tracking_log_to_backends - """ - tracker = DjangoTracker() - mock_get_tracker.return_value = tracker - mock_backend = Mock() - tracker.backends["event_bus"].send_to_backends = mock_backend - - send_tracking_log_to_backends( - sender=None, - signal=None, - tracking_log=TrackingLogData( - name="test_name", - timestamp="test_timestamp", - data="{}", - context="{}", - ), - ) - - mock_backend.assert_called_once_with( - { - "name": "test_name", - "timestamp": "test_timestamp", - "data": {}, - "context": {}, - } - ) - - @override_settings( - EVENT_TRACKING_BACKENDS={ - "event_bus": { - "ENGINE": "eventtracking.backends.event_bus.EventBusRoutingBackend", - "OPTIONS": { - "processors": [ - { - "ENGINE": "eventtracking.processors.whitelist.NameWhitelistProcessor", - "OPTIONS": { - "whitelist": ["no_test_name"] - } - } - ], - }, - }, - } - ) - @patch("event_routing_backends.handlers.get_tracker") - @patch("event_routing_backends.handlers.isinstance") - @patch("event_routing_backends.handlers.logger") - def test_send_tracking_log_to_backends_error( - self, mock_logger, mock_is_instance, mock_get_tracker - ): - """ - Test for send_tracking_log_to_backends - """ - tracker = DjangoTracker() - mock_get_tracker.return_value = tracker - - mock_is_instance.return_value = True - - x = send_tracking_log_to_backends( - sender=None, - signal=None, - tracking_log=TrackingLogData( - name="test_name", - timestamp="test_timestamp", - data="{}", - context="{}", - ), - ) - - assert x is None - - mock_logger.info.assert_called_once_with( - "[EventEmissionExit] skipping event {}".format("test_name") - ) diff --git a/requirements/base.in b/requirements/base.in index 4976998c..fbf0b237 100644 --- a/requirements/base.in +++ b/requirements/base.in @@ -16,4 +16,3 @@ edx-celeryutils apache-libcloud # For bulk event log loading fasteners # Locking tools, required by apache-libcloud, but somehow not installed with it openedx-filters -openedx-events diff --git a/requirements/base.txt b/requirements/base.txt index 1b2797bd..b83dd4a2 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -50,7 +50,7 @@ code-annotations==1.6.0 # via edx-toggles cryptography==42.0.2 # via django-fernet-fields-v2 -django==3.2.23 +django==3.2.24 # via # -c https://raw.githubusercontent.com/edx/edx-lint/master/edx_lint/files/common_constraints.txt # -r requirements/base.in @@ -75,7 +75,7 @@ django-crum==0.7.9 # edx-toggles django-fernet-fields-v2==0.9 # via -r requirements/base.in -django-model-utils==4.3.1 +django-model-utils==4.4.0 # via edx-celeryutils django-waffle==4.1.0 # via @@ -90,6 +90,7 @@ edx-django-utils==5.10.1 # django-config-models # edx-toggles # event-tracking + # openedx-events edx-opaque-keys[django]==2.5.1 # via openedx-events edx-toggles==5.1.1 @@ -118,10 +119,8 @@ markupsafe==2.1.5 # via jinja2 newrelic==9.6.0 # via edx-django-utils -openedx-events==9.4.0 - # via - # -r requirements/base.in - # event-tracking +openedx-events==9.5.1 + # via event-tracking openedx-filters==1.6.0 # via -r requirements/base.in pbr==6.0.0 @@ -142,7 +141,7 @@ python-dateutil==2.8.2 # via # -r requirements/base.in # celery -python-slugify==8.0.3 +python-slugify==8.0.4 # via code-annotations pytz==2024.1 # via @@ -178,7 +177,7 @@ typing-extensions==4.9.0 # asgiref # edx-opaque-keys # kombu -tzdata==2023.4 +tzdata==2024.1 # via # backports-zoneinfo # celery diff --git a/requirements/dev.txt b/requirements/dev.txt index 654b3218..7d397e40 100644 --- a/requirements/dev.txt +++ b/requirements/dev.txt @@ -20,7 +20,7 @@ asgiref==3.7.2 # via # -r requirements/quality.txt # django -astroid==3.0.2 +astroid==3.0.3 # via # -r requirements/quality.txt # pylint @@ -130,7 +130,7 @@ distlib==0.3.8 # via # -r requirements/ci.txt # virtualenv -django==3.2.23 +django==3.2.24 # via # -c https://raw.githubusercontent.com/edx/edx-lint/master/edx_lint/files/common_constraints.txt # -r requirements/quality.txt @@ -157,7 +157,7 @@ django-crum==0.7.9 # edx-toggles django-fernet-fields-v2==0.9 # via -r requirements/quality.txt -django-model-utils==4.3.1 +django-model-utils==4.4.0 # via # -r requirements/quality.txt # edx-celeryutils @@ -178,6 +178,7 @@ edx-django-utils==5.10.1 # django-config-models # edx-toggles # event-tracking + # openedx-events edx-i18n-tools==1.3.0 # via -r requirements/dev.in edx-lint==5.3.6 @@ -199,7 +200,7 @@ exceptiongroup==1.2.0 # pytest factory-boy==3.3.0 # via -r requirements/quality.txt -faker==22.6.0 +faker==23.1.0 # via # -r requirements/quality.txt # factory-boy @@ -266,7 +267,7 @@ newrelic==9.6.0 # via # -r requirements/quality.txt # edx-django-utils -openedx-events==9.4.0 +openedx-events==9.5.1 # via # -r requirements/quality.txt # event-tracking @@ -281,7 +282,7 @@ packaging==23.2 # pyproject-api # pytest # tox -path==16.9.0 +path==16.10.0 # via edx-i18n-tools pbr==6.0.0 # via @@ -319,9 +320,9 @@ pycparser==2.21 # via # -r requirements/quality.txt # cffi -pydantic==2.6.0 +pydantic==2.6.1 # via inflect -pydantic-core==2.16.1 +pydantic-core==2.16.2 # via pydantic pydocstyle==6.3.0 # via -r requirements/quality.txt @@ -378,7 +379,7 @@ python-dateutil==2.8.2 # -r requirements/quality.txt # celery # faker -python-slugify==8.0.3 +python-slugify==8.0.4 # via # -r requirements/quality.txt # code-annotations @@ -457,7 +458,7 @@ typing-extensions==4.9.0 # pydantic # pydantic-core # pylint -tzdata==2023.4 +tzdata==2024.1 # via # -r requirements/quality.txt # backports-zoneinfo diff --git a/requirements/doc.txt b/requirements/doc.txt index d1c36766..78af0986 100644 --- a/requirements/doc.txt +++ b/requirements/doc.txt @@ -99,7 +99,7 @@ cryptography==42.0.2 # secretstorage ddt==1.7.1 # via -r requirements/test.txt -django==3.2.23 +django==3.2.24 # via # -c https://raw.githubusercontent.com/edx/edx-lint/master/edx_lint/files/common_constraints.txt # -r requirements/test.txt @@ -125,7 +125,7 @@ django-crum==0.7.9 # edx-toggles django-fernet-fields-v2==0.9 # via -r requirements/test.txt -django-model-utils==4.3.1 +django-model-utils==4.4.0 # via # -r requirements/test.txt # edx-celeryutils @@ -158,6 +158,7 @@ edx-django-utils==5.10.1 # django-config-models # edx-toggles # event-tracking + # openedx-events edx-opaque-keys[django]==2.5.1 # via # -r requirements/test.txt @@ -175,7 +176,7 @@ exceptiongroup==1.2.0 # pytest factory-boy==3.3.0 # via -r requirements/test.txt -faker==22.6.0 +faker==23.1.0 # via # -r requirements/test.txt # factory-boy @@ -204,7 +205,7 @@ iniconfig==2.0.0 # pytest isodate==0.6.1 # via -r requirements/test.txt -jaraco-classes==3.3.0 +jaraco-classes==3.3.1 # via keyring jeepney==0.8.0 # via @@ -243,7 +244,7 @@ newrelic==9.6.0 # edx-django-utils nh3==0.2.15 # via readme-renderer -openedx-events==9.4.0 +openedx-events==9.5.1 # via # -r requirements/test.txt # event-tracking @@ -313,7 +314,7 @@ python-dateutil==2.8.2 # -r requirements/test.txt # celery # faker -python-slugify==8.0.3 +python-slugify==8.0.4 # via # -r requirements/test.txt # code-annotations @@ -402,7 +403,7 @@ tomli==2.0.1 # coverage # pyproject-hooks # pytest -twine==4.0.2 +twine==5.0.0 # via -r requirements/doc.in typing-extensions==4.9.0 # via @@ -413,7 +414,7 @@ typing-extensions==4.9.0 # kombu # pydata-sphinx-theme # rich -tzdata==2023.4 +tzdata==2024.1 # via # -r requirements/test.txt # backports-zoneinfo diff --git a/requirements/pip.txt b/requirements/pip.txt index a4cf5307..71954cc6 100644 --- a/requirements/pip.txt +++ b/requirements/pip.txt @@ -8,7 +8,7 @@ wheel==0.42.0 # via -r requirements/pip.in # The following packages are considered to be unsafe in a requirements file: -pip==23.3.2 +pip==24.0 # via -r requirements/pip.in -setuptools==69.0.3 +setuptools==69.1.0 # via -r requirements/pip.in diff --git a/requirements/quality.txt b/requirements/quality.txt index fcdb1bb5..de74df6c 100644 --- a/requirements/quality.txt +++ b/requirements/quality.txt @@ -18,7 +18,7 @@ asgiref==3.7.2 # via # -r requirements/test.txt # django -astroid==3.0.2 +astroid==3.0.3 # via # pylint # pylint-celery @@ -97,7 +97,7 @@ ddt==1.7.1 # via -r requirements/test.txt dill==0.3.8 # via pylint -django==3.2.23 +django==3.2.24 # via # -c https://raw.githubusercontent.com/edx/edx-lint/master/edx_lint/files/common_constraints.txt # -r requirements/test.txt @@ -123,7 +123,7 @@ django-crum==0.7.9 # edx-toggles django-fernet-fields-v2==0.9 # via -r requirements/test.txt -django-model-utils==4.3.1 +django-model-utils==4.4.0 # via # -r requirements/test.txt # edx-celeryutils @@ -144,6 +144,7 @@ edx-django-utils==5.10.1 # django-config-models # edx-toggles # event-tracking + # openedx-events edx-lint==5.3.6 # via -r requirements/quality.in edx-opaque-keys[django]==2.5.1 @@ -163,7 +164,7 @@ exceptiongroup==1.2.0 # pytest factory-boy==3.3.0 # via -r requirements/test.txt -faker==22.6.0 +faker==23.1.0 # via # -r requirements/test.txt # factory-boy @@ -211,7 +212,7 @@ newrelic==9.6.0 # via # -r requirements/test.txt # edx-django-utils -openedx-events==9.4.0 +openedx-events==9.5.1 # via # -r requirements/test.txt # event-tracking @@ -284,7 +285,7 @@ python-dateutil==2.8.2 # -r requirements/test.txt # celery # faker -python-slugify==8.0.3 +python-slugify==8.0.4 # via # -r requirements/test.txt # code-annotations @@ -345,7 +346,7 @@ typing-extensions==4.9.0 # faker # kombu # pylint -tzdata==2023.4 +tzdata==2024.1 # via # -r requirements/test.txt # backports-zoneinfo diff --git a/requirements/test.txt b/requirements/test.txt index 3ebfc395..7679c15a 100644 --- a/requirements/test.txt +++ b/requirements/test.txt @@ -111,7 +111,7 @@ django-crum==0.7.9 # edx-toggles django-fernet-fields-v2==0.9 # via -r requirements/base.txt -django-model-utils==4.3.1 +django-model-utils==4.4.0 # via # -r requirements/base.txt # edx-celeryutils @@ -132,6 +132,7 @@ edx-django-utils==5.10.1 # django-config-models # edx-toggles # event-tracking + # openedx-events edx-opaque-keys[django]==2.5.1 # via # -r requirements/base.txt @@ -147,7 +148,7 @@ exceptiongroup==1.2.0 # via pytest factory-boy==3.3.0 # via -r requirements/test.in -faker==22.6.0 +faker==23.1.0 # via factory-boy fastavro==1.9.3 # via @@ -185,7 +186,7 @@ newrelic==9.6.0 # via # -r requirements/base.txt # edx-django-utils -openedx-events==9.4.0 +openedx-events==9.5.1 # via # -r requirements/base.txt # event-tracking @@ -233,7 +234,7 @@ python-dateutil==2.8.2 # -r requirements/base.txt # celery # faker -python-slugify==8.0.3 +python-slugify==8.0.4 # via # -r requirements/base.txt # code-annotations @@ -285,7 +286,7 @@ typing-extensions==4.9.0 # edx-opaque-keys # faker # kombu -tzdata==2023.4 +tzdata==2024.1 # via # -r requirements/base.txt # backports-zoneinfo