openedx-events 11.1.1__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.
- openedx_events/__init__.py +8 -0
- openedx_events/analytics/__init__.py +6 -0
- openedx_events/analytics/data.py +30 -0
- openedx_events/analytics/signals.py +25 -0
- openedx_events/apps.py +124 -0
- openedx_events/content_authoring/__init__.py +7 -0
- openedx_events/content_authoring/data.py +250 -0
- openedx_events/content_authoring/signals.py +372 -0
- openedx_events/data.py +142 -0
- openedx_events/enterprise/__init__.py +6 -0
- openedx_events/enterprise/data.py +241 -0
- openedx_events/enterprise/signals.py +132 -0
- openedx_events/event_bus/__init__.py +228 -0
- openedx_events/event_bus/avro/__init__.py +1 -0
- openedx_events/event_bus/avro/custom_serializers.py +227 -0
- openedx_events/event_bus/avro/deserializer.py +167 -0
- openedx_events/event_bus/avro/schema.py +244 -0
- openedx_events/event_bus/avro/serializer.py +141 -0
- openedx_events/event_bus/avro/types.py +14 -0
- openedx_events/exceptions.py +92 -0
- openedx_events/learning/__init__.py +6 -0
- openedx_events/learning/data.py +695 -0
- openedx_events/learning/signals.py +519 -0
- openedx_events/management/__init__.py +3 -0
- openedx_events/management/commands/__init__.py +5 -0
- openedx_events/management/commands/consume_events.py +74 -0
- openedx_events/management/commands/generate_avro_schemas.py +91 -0
- openedx_events/testing.py +140 -0
- openedx_events/tooling.py +340 -0
- openedx_events/utils.py +91 -0
- openedx_events-11.1.1.dist-info/METADATA +898 -0
- openedx_events-11.1.1.dist-info/RECORD +35 -0
- openedx_events-11.1.1.dist-info/WHEEL +5 -0
- openedx_events-11.1.1.dist-info/licenses/LICENSE.txt +202 -0
- openedx_events-11.1.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Data attributes for events within the architecture subdomain ``analytics``.
|
|
3
|
+
|
|
4
|
+
These attributes follow the form of attr objects specified in OEP-49 data
|
|
5
|
+
pattern.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from datetime import datetime
|
|
9
|
+
|
|
10
|
+
import attr
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@attr.s(frozen=True)
|
|
14
|
+
class TrackingLogData:
|
|
15
|
+
"""
|
|
16
|
+
Data related to tracking events.
|
|
17
|
+
|
|
18
|
+
Attributes:
|
|
19
|
+
name (str): event name
|
|
20
|
+
timestamp (datetime): timestamp of the event
|
|
21
|
+
data (str): json string representation of a dictionary with extra data (optional), e.g.,
|
|
22
|
+
>>> {"course_id": "course-v1:edX+DemoX+Demo_Course"}
|
|
23
|
+
context (dict): json string representation of a dictionary of context data
|
|
24
|
+
defined in https://docs.openedx.org/en/latest/developers/references/internal_data_formats/index.html
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
name = attr.ib(type=str)
|
|
28
|
+
timestamp = attr.ib(type=datetime)
|
|
29
|
+
data = attr.ib(type=str)
|
|
30
|
+
context = attr.ib(type=str)
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Standardized signals definitions for events within the architecture subdomain ``analytics``.
|
|
3
|
+
|
|
4
|
+
All signals defined in this module must follow the name and versioning
|
|
5
|
+
conventions specified in OEP-41.
|
|
6
|
+
|
|
7
|
+
They also must comply with the payload definition specified in
|
|
8
|
+
docs/decisions/0003-events-payload.rst
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from openedx_events.analytics.data import TrackingLogData
|
|
12
|
+
from openedx_events.tooling import OpenEdxPublicSignal
|
|
13
|
+
|
|
14
|
+
# .. event_type: org.openedx.analytics.tracking.event.emitted.v1
|
|
15
|
+
# .. event_name: TRACKING_EVENT_EMITTED
|
|
16
|
+
# .. event_key_field: tracking_log.name
|
|
17
|
+
# .. event_description: Emitted when a tracking log event is emitted.
|
|
18
|
+
# .. event_data: TrackingLogData
|
|
19
|
+
# .. event_trigger_repository: openedx/event-tracking
|
|
20
|
+
TRACKING_EVENT_EMITTED = OpenEdxPublicSignal(
|
|
21
|
+
event_type="org.openedx.analytics.tracking.event.emitted.v1",
|
|
22
|
+
data={
|
|
23
|
+
"tracking_log": TrackingLogData,
|
|
24
|
+
}
|
|
25
|
+
)
|
openedx_events/apps.py
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
"""
|
|
2
|
+
openedx_events Django application initialization.
|
|
3
|
+
"""
|
|
4
|
+
import logging
|
|
5
|
+
|
|
6
|
+
from django.apps import AppConfig
|
|
7
|
+
from django.conf import settings
|
|
8
|
+
|
|
9
|
+
from openedx_events.event_bus import get_producer
|
|
10
|
+
from openedx_events.exceptions import ProducerConfigurationError
|
|
11
|
+
from openedx_events.tooling import SIGNAL_PROCESSED_FROM_EVENT_BUS, OpenEdxPublicSignal, load_all_signals
|
|
12
|
+
|
|
13
|
+
logger = logging.getLogger(__name__)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def general_signal_handler(sender, signal, **kwargs): # pylint: disable=unused-argument
|
|
17
|
+
"""
|
|
18
|
+
Signal handler for producing events to configured event bus.
|
|
19
|
+
"""
|
|
20
|
+
event_type_producer_configs = getattr(settings, "EVENT_BUS_PRODUCER_CONFIG", {}).get(signal.event_type, {})
|
|
21
|
+
# event_type_producer_configs should look something like
|
|
22
|
+
# {
|
|
23
|
+
# "topic_a": { "event_key_field": "my.key.field", "enabled": True },
|
|
24
|
+
# "topic_b": { "event_key_field": "my.key.field", "enabled": False }
|
|
25
|
+
# }"
|
|
26
|
+
if kwargs.get(SIGNAL_PROCESSED_FROM_EVENT_BUS) is True:
|
|
27
|
+
logger.debug(
|
|
28
|
+
"Declining to send signal to the Event Bus since that's "
|
|
29
|
+
f"where it was sent from: {signal.event_type} (preventing recursion)"
|
|
30
|
+
)
|
|
31
|
+
return
|
|
32
|
+
|
|
33
|
+
event_data = {key: kwargs.get(key) for key in signal.init_data}
|
|
34
|
+
|
|
35
|
+
for topic in event_type_producer_configs.keys():
|
|
36
|
+
if event_type_producer_configs[topic]["enabled"] is True:
|
|
37
|
+
get_producer().send(
|
|
38
|
+
signal=signal,
|
|
39
|
+
topic=topic,
|
|
40
|
+
event_key_field=event_type_producer_configs[topic]["event_key_field"],
|
|
41
|
+
event_data=event_data,
|
|
42
|
+
event_metadata=kwargs["metadata"],
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class OpenedxEventsConfig(AppConfig):
|
|
47
|
+
"""
|
|
48
|
+
Configuration for the openedx_events Django application.
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
name = "openedx_events"
|
|
52
|
+
|
|
53
|
+
def _get_validated_signal_config(self, event_type, configuration):
|
|
54
|
+
"""
|
|
55
|
+
Validate signal configuration format.
|
|
56
|
+
|
|
57
|
+
Example expected signal configuration:
|
|
58
|
+
{
|
|
59
|
+
"topic_a": { "event_key_field": "my.key.field", "enabled": True },
|
|
60
|
+
"topic_b": { "event_key_field": "my.key.field", "enabled": False }
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
Raises:
|
|
64
|
+
ProducerConfigurationError: If configuration is not valid.
|
|
65
|
+
"""
|
|
66
|
+
if not isinstance(configuration, dict):
|
|
67
|
+
raise ProducerConfigurationError(
|
|
68
|
+
event_type=event_type,
|
|
69
|
+
message="Configuration for event_types should be a dict"
|
|
70
|
+
)
|
|
71
|
+
try:
|
|
72
|
+
signal = OpenEdxPublicSignal.get_signal_by_type(event_type)
|
|
73
|
+
except KeyError as exc:
|
|
74
|
+
raise ProducerConfigurationError(message=f"No OpenEdxPublicSignal of type: '{event_type}'.") from exc
|
|
75
|
+
for _, topic_configuration in configuration.items():
|
|
76
|
+
if not isinstance(topic_configuration, dict):
|
|
77
|
+
raise ProducerConfigurationError(
|
|
78
|
+
event_type=event_type,
|
|
79
|
+
message="One of the configuration objects is not a dictionary"
|
|
80
|
+
)
|
|
81
|
+
expected_keys = {"event_key_field": str, "enabled": bool}
|
|
82
|
+
for expected_key, expected_type in expected_keys.items():
|
|
83
|
+
if expected_key not in topic_configuration.keys():
|
|
84
|
+
raise ProducerConfigurationError(
|
|
85
|
+
event_type=event_type,
|
|
86
|
+
message=f"One of the configuration object is missing '{expected_key}' key."
|
|
87
|
+
)
|
|
88
|
+
if not isinstance(topic_configuration[expected_key], expected_type):
|
|
89
|
+
raise ProducerConfigurationError(
|
|
90
|
+
event_type=event_type,
|
|
91
|
+
message=(f"Expected type: {expected_type} for '{expected_key}', "
|
|
92
|
+
f"found: {type(topic_configuration[expected_key])}")
|
|
93
|
+
)
|
|
94
|
+
return signal
|
|
95
|
+
|
|
96
|
+
def ready(self):
|
|
97
|
+
"""
|
|
98
|
+
Read `EVENT_BUS_PRODUCER_CONFIG` setting and connects appropriate handlers to the events based on it.
|
|
99
|
+
|
|
100
|
+
Example expected configuration:
|
|
101
|
+
{
|
|
102
|
+
"org.openedx.content_authoring.xblock.deleted.v1" : {
|
|
103
|
+
"topic_a": { "event_key_field": "xblock_info.usage_key", "enabled": True },
|
|
104
|
+
"topic_b": { "event_key_field": "xblock_info.usage_key", "enabled": False }
|
|
105
|
+
},
|
|
106
|
+
"org.openedx.content_authoring.course.catalog_info.changed.v1" : {
|
|
107
|
+
"topic_c": {"event_key_field": "course_info.course_key", "enabled": True }
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
Raises:
|
|
112
|
+
ProducerConfigurationError: If `EVENT_BUS_PRODUCER_CONFIG` is not valid.
|
|
113
|
+
"""
|
|
114
|
+
load_all_signals()
|
|
115
|
+
signals_config = getattr(settings, "EVENT_BUS_PRODUCER_CONFIG", {})
|
|
116
|
+
if not isinstance(signals_config, dict):
|
|
117
|
+
raise ProducerConfigurationError(
|
|
118
|
+
message=("Setting 'EVENT_BUS_PRODUCER_CONFIG' should be a dictionary with event_type as"
|
|
119
|
+
" key and list or tuple of config dictionaries as values")
|
|
120
|
+
)
|
|
121
|
+
for event_type, configurations in signals_config.items():
|
|
122
|
+
signal = self._get_validated_signal_config(event_type, configurations)
|
|
123
|
+
signal.connect(general_signal_handler)
|
|
124
|
+
return super().ready()
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Package where events related to the ``content_authoring`` subdomain are implemented.
|
|
3
|
+
"""
|
|
4
|
+
# Comment broken up to deal with line-length issues with the long URL
|
|
5
|
+
|
|
6
|
+
# The ``content_authoring`` subdomain corresponds to {Architecture Subdomain} defined in OEP-41.
|
|
7
|
+
# https://open-edx-proposals.readthedocs.io/en/latest/architectural-decisions/oep-0041-arch-async-server-event-messaging.html
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Data attributes for events within the architecture subdomain ``content_authoring``.
|
|
3
|
+
|
|
4
|
+
These attributes follow the form of attr objects specified in OEP-49 data
|
|
5
|
+
pattern.
|
|
6
|
+
|
|
7
|
+
The attributes for the events come from the CourseDetailView in the LMS, with some unused fields removed
|
|
8
|
+
(see deprecation proposal at https://github.com/openedx/public-engineering/issues/160)
|
|
9
|
+
"""
|
|
10
|
+
from datetime import datetime
|
|
11
|
+
from typing import BinaryIO, List
|
|
12
|
+
|
|
13
|
+
import attr
|
|
14
|
+
from opaque_keys.edx.keys import CourseKey, UsageKey
|
|
15
|
+
from opaque_keys.edx.locator import (
|
|
16
|
+
LibraryCollectionLocator,
|
|
17
|
+
LibraryContainerLocator,
|
|
18
|
+
LibraryLocatorV2,
|
|
19
|
+
LibraryUsageLocatorV2,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@attr.s(frozen=True)
|
|
24
|
+
class CourseData:
|
|
25
|
+
"""
|
|
26
|
+
Data related to a course object.
|
|
27
|
+
|
|
28
|
+
Attributes:
|
|
29
|
+
course_key (CourseKey): identifier of the Course object.
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
course_key = attr.ib(type=CourseKey)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@attr.s(frozen=True)
|
|
36
|
+
class CourseScheduleData:
|
|
37
|
+
"""
|
|
38
|
+
Data related to a course schedule.
|
|
39
|
+
|
|
40
|
+
Attributes:
|
|
41
|
+
start (datetime): course start date.
|
|
42
|
+
pacing (str): 'instructor' or 'self'.
|
|
43
|
+
end (datetime): course end date (optional).
|
|
44
|
+
enrollment_start (datetime): start of course enrollment (optional).
|
|
45
|
+
enrollment_end (datetime): end of course enrollment (optional).
|
|
46
|
+
"""
|
|
47
|
+
|
|
48
|
+
start = attr.ib(type=datetime)
|
|
49
|
+
pacing = attr.ib(type=str)
|
|
50
|
+
end = attr.ib(type=datetime, default=None)
|
|
51
|
+
enrollment_start = attr.ib(type=datetime, default=None)
|
|
52
|
+
enrollment_end = attr.ib(type=datetime, default=None)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
@attr.s(frozen=True)
|
|
56
|
+
class CourseCatalogData:
|
|
57
|
+
"""
|
|
58
|
+
Data related to a course catalog entry.
|
|
59
|
+
|
|
60
|
+
Attributes:
|
|
61
|
+
course_key (CourseKey): identifier of the Course object.
|
|
62
|
+
name (str): course name.
|
|
63
|
+
schedule_data (CourseScheduleData): scheduling information for the course.
|
|
64
|
+
hidden (bool): whether the course is hidden from search (optional).
|
|
65
|
+
invitation_only (bool): whether the course requires an invitation to enroll.
|
|
66
|
+
"""
|
|
67
|
+
|
|
68
|
+
# basic identifiers
|
|
69
|
+
course_key = attr.ib(type=CourseKey)
|
|
70
|
+
name = attr.ib(type=str)
|
|
71
|
+
|
|
72
|
+
# additional marketing information
|
|
73
|
+
schedule_data = attr.ib(type=CourseScheduleData)
|
|
74
|
+
hidden = attr.ib(type=bool, default=False)
|
|
75
|
+
invitation_only = attr.ib(type=bool, default=False)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
@attr.s(frozen=True)
|
|
79
|
+
class XBlockData:
|
|
80
|
+
"""
|
|
81
|
+
Data related to an XBlock object.
|
|
82
|
+
|
|
83
|
+
Attributes:
|
|
84
|
+
usage_key (UsageKey): identifier of the XBlock object.
|
|
85
|
+
block_type (str): type of block.
|
|
86
|
+
version (UsageKey): identifier of the XBlock object with branch and version data (optional). This
|
|
87
|
+
could be used to get the exact version of the XBlock object.
|
|
88
|
+
"""
|
|
89
|
+
|
|
90
|
+
usage_key = attr.ib(type=UsageKey)
|
|
91
|
+
block_type = attr.ib(type=str)
|
|
92
|
+
version = attr.ib(type=UsageKey, default=None, kw_only=True)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
@attr.s(frozen=True)
|
|
96
|
+
class DuplicatedXBlockData(XBlockData):
|
|
97
|
+
"""
|
|
98
|
+
Data related to an XBlock object that has been duplicated.
|
|
99
|
+
|
|
100
|
+
This class extends XBlockData to include source_usage_key.
|
|
101
|
+
|
|
102
|
+
Attributes:
|
|
103
|
+
source_usage_key (UsageKey): identifier of the source XBlock object.
|
|
104
|
+
"""
|
|
105
|
+
|
|
106
|
+
source_usage_key = attr.ib(type=UsageKey)
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
@attr.s(frozen=True)
|
|
110
|
+
class CertificateSignatoryData:
|
|
111
|
+
"""
|
|
112
|
+
Data related to a certificate signatory. Subset of CertificateSignatory object from the LMS.
|
|
113
|
+
|
|
114
|
+
Attributes:
|
|
115
|
+
image (BinaryIO): certificate signature image. Take care that the image field is BinaryIO.
|
|
116
|
+
name (str): name of signatory.
|
|
117
|
+
organization (str): organization that signatory belongs to.
|
|
118
|
+
title (str): signatory title.
|
|
119
|
+
"""
|
|
120
|
+
|
|
121
|
+
# Note: Please take care that the image field is BinaryIO, which means
|
|
122
|
+
# that a file can be passed as an array of bytes. Watch the size of this file.
|
|
123
|
+
# It can potentially be large, making it difficult to pass this data structure through the Event Bus
|
|
124
|
+
# (CloudEvent messages, which should be 64K or less) and store it on disk space.
|
|
125
|
+
# We suggest referring to MAX_ASSET_UPLOAD_FILE_SIZE_IN_MB, i.e. restriction in the Studio for such cases.
|
|
126
|
+
image = attr.ib(type=BinaryIO)
|
|
127
|
+
# end Note
|
|
128
|
+
name = attr.ib(type=str)
|
|
129
|
+
organization = attr.ib(type=str)
|
|
130
|
+
title = attr.ib(type=str)
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
@attr.s(frozen=True)
|
|
134
|
+
class CertificateConfigData:
|
|
135
|
+
"""
|
|
136
|
+
Data related to a certificate configuration. Subset of CertificateConfig object from the LMS.
|
|
137
|
+
|
|
138
|
+
Attributes:
|
|
139
|
+
certificate_type (str): certificate type. Possible types are certificate relevant course modes:
|
|
140
|
+
- credit,
|
|
141
|
+
- verified,
|
|
142
|
+
- professional,
|
|
143
|
+
- no-id-professional,
|
|
144
|
+
- executive-education,
|
|
145
|
+
- paid-executive-education,
|
|
146
|
+
- paid-bootcamp,
|
|
147
|
+
- masters.
|
|
148
|
+
course_key (CourseKey): identifier of the Course object.
|
|
149
|
+
title (str): certificate title.
|
|
150
|
+
signatories (List[CertificateSignatoryData]): contains a collection of signatures
|
|
151
|
+
that belong to the certificate configuration.
|
|
152
|
+
is_active (bool): indicates whether the certifivate configuration is active.
|
|
153
|
+
"""
|
|
154
|
+
|
|
155
|
+
certificate_type = attr.ib(type=str)
|
|
156
|
+
course_key = attr.ib(type=CourseKey)
|
|
157
|
+
title = attr.ib(type=str)
|
|
158
|
+
signatories = attr.ib(type=List[CertificateSignatoryData], factory=list)
|
|
159
|
+
is_active = attr.ib(type=bool, default=False)
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
@attr.s(frozen=True)
|
|
163
|
+
class ContentLibraryData:
|
|
164
|
+
"""
|
|
165
|
+
Data related to a content library that has changed.
|
|
166
|
+
|
|
167
|
+
Attributes:
|
|
168
|
+
library_key (LibraryLocatorV2): a key that represents a v2 content library.
|
|
169
|
+
update_blocks (bool): DEPRECATED flag to indicate whether the content library blocks indexes should be updated.
|
|
170
|
+
Now we send individual events for each updated item instead.
|
|
171
|
+
"""
|
|
172
|
+
|
|
173
|
+
library_key = attr.ib(type=LibraryLocatorV2)
|
|
174
|
+
update_blocks = attr.ib(type=bool, default=False)
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
@attr.s(frozen=True)
|
|
178
|
+
class LibraryBlockData:
|
|
179
|
+
"""
|
|
180
|
+
Data related to a library block that has changed.
|
|
181
|
+
|
|
182
|
+
Attributes:
|
|
183
|
+
library_key (LibraryLocatorV2): a key that represents a v2 content library.
|
|
184
|
+
usage_key (LibraryUsageLocatorV2): a key that represents a XBlock in a Blockstore-based content library.
|
|
185
|
+
"""
|
|
186
|
+
|
|
187
|
+
library_key = attr.ib(type=LibraryLocatorV2)
|
|
188
|
+
usage_key = attr.ib(type=LibraryUsageLocatorV2)
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
@attr.s(frozen=True)
|
|
192
|
+
class ContentObjectData:
|
|
193
|
+
"""
|
|
194
|
+
Data related to a content object.
|
|
195
|
+
|
|
196
|
+
Attributes:
|
|
197
|
+
object_id (str): identifier of the Content object. This represents the id of the course or library block
|
|
198
|
+
as a string. For example:
|
|
199
|
+
>>> block-v1:SampleTaxonomyOrg2+STC1+2023_1+type@vertical+block@f8de78f0897049ce997777a3a31b6ea0
|
|
200
|
+
"""
|
|
201
|
+
|
|
202
|
+
object_id = attr.ib(type=str)
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
@attr.s(frozen=True)
|
|
206
|
+
class ContentObjectChangedData(ContentObjectData):
|
|
207
|
+
"""
|
|
208
|
+
Data related to a content object that has changed.
|
|
209
|
+
|
|
210
|
+
Attributes:
|
|
211
|
+
object_id (str): identifier of the Content object. This represents the id of the course or library block
|
|
212
|
+
as a string. For example:
|
|
213
|
+
>>> block-v1:SampleTaxonomyOrg2+STC1+2023_1+type@vertical+block@f8de78f0897049ce997777a3a31b6ea0
|
|
214
|
+
changes: list of changes made to this ContentObject, e.g. "tags", "collections". If list is empty,
|
|
215
|
+
assume everything has changed.
|
|
216
|
+
"""
|
|
217
|
+
|
|
218
|
+
changes = attr.ib(type=List[str], factory=list)
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
@attr.s(frozen=True)
|
|
222
|
+
class LibraryCollectionData:
|
|
223
|
+
"""
|
|
224
|
+
Data related to a library collection that has changed.
|
|
225
|
+
|
|
226
|
+
Attributes:
|
|
227
|
+
collection_key (LibraryCollectionLocator): identifies the collection within the library's learning package
|
|
228
|
+
background (bool): **DEPRECATED** field. Indicated the sender doesn't want to wait for handler to finish
|
|
229
|
+
execution. Now instead we recommend that simple handlers are synchronous and the _sender_ of the event should
|
|
230
|
+
send the event(s) from an async celery task if it is expected to result in a lot of handlers being called.
|
|
231
|
+
"""
|
|
232
|
+
|
|
233
|
+
collection_key = attr.ib(type=LibraryCollectionLocator)
|
|
234
|
+
background = attr.ib(type=bool, default=False)
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
@attr.s(frozen=True)
|
|
238
|
+
class LibraryContainerData:
|
|
239
|
+
"""
|
|
240
|
+
Data related to a library container that has changed.
|
|
241
|
+
|
|
242
|
+
Attributes:
|
|
243
|
+
container_key (LibraryContainerLocator): identifies the container (e.g. unit, section)
|
|
244
|
+
background (bool): **DEPRECATED** field. Indicated the sender doesn't want to wait for handler to finish
|
|
245
|
+
execution. Now instead we recommend that simple handlers are synchronous and the _sender_ of the event should
|
|
246
|
+
send the event(s) from an async celery task if it is expected to result in a lot of handlers being called.
|
|
247
|
+
"""
|
|
248
|
+
|
|
249
|
+
container_key = attr.ib(type=LibraryContainerLocator)
|
|
250
|
+
background = attr.ib(type=bool, default=False)
|