Skip to content

Commit

Permalink
feat: new enterprise transaction data/signals
Browse files Browse the repository at this point in the history
  • Loading branch information
iloveagent57 committed May 14, 2024
1 parent f6a09f0 commit 540f372
Show file tree
Hide file tree
Showing 5 changed files with 169 additions and 2 deletions.
64 changes: 64 additions & 0 deletions openedx_events/enterprise/data.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,11 @@
These attributes follow the form of attr objects specified in OEP-49 data
pattern.
"""
from datetime import datetime
from uuid import UUID

import attr
from opaque_keys.edx.keys import CourseKey


@attr.s(frozen=True)
Expand All @@ -22,3 +25,64 @@ class SubsidyRedemption:
subsidy_identifier = attr.ib(type=str)
content_key = attr.ib(type=str)
lms_user_id = attr.ib(type=int)


@attr.s(frozen=True)
class LedgerTransactionReversal:
"""
Attributes of an ``openedx_ledger.Reversal`` record.
Arguments:
uuid (str): Primary identifier of the record.
created (datetime): When the record was created.
modified (datetime): When the record was last modified.
idempotency_key (str): Client-generated unique value to achieve idempotency of operations.
quantity (int): How many units of value this reversal represents (e.g. USD cents).
state (str): Current lifecyle state of the record, one of (created, pending, committed, failed).
metadata (dict): Additional/custom metadata related to this record.
"""
uuid = attr.ib(type=UUID)
created = attr.ib(type=datetime)
modified = attr.ib(type=datetime)
idempotency_key = attr.ib(type=str)
quantity = attr.ib(type=int)
state = attr.ib(type=str)
metadata = attr.ib(type=dict)


@attr.s(frozen=True)
class LedgerTransaction:
"""
Attributes of an ``openedx_ledger.Transaction`` record.
Arguments:
uuid (UUID): Primary identifier of the Transaction.
created (datetime): When the record was created.
modified (datetime): When the record was last modified.
idempotency_key (str): Client-generated unique value to achieve idempotency of operations.
quantity (int): How many units of value this transaction represents (e.g. USD cents).
state (str): Current lifecyle state of the record, one of (created, pending, committed, failed).
metadata (dict): Additional/custom metadata related to this record.
ledger_uuid (UUID): The primary identifier of this Transaction's ledger object.
subsidy_access_policy_uuid (UUID): The primary identifier of the subsidy access policy for this transaction.
lms_user_id (int): The LMS user id of the user associated with this transaction.
content_key (CourseKey): The course (run) key associated with this transaction.
parent_content_key (str): The parent (just course, not run) key for the course key.
fulfillment_identifier (str): The identifier of the subsidized enrollment record for a learner,
generated durning enrollment.
reversal (LedgerTransactionReversal): Any reversal associated with this transaction.
"""
uuid = attr.ib(type=UUID)
created = attr.ib(type=datetime)
modified = attr.ib(type=datetime)
idempotency_key = attr.ib(type=str)
quantity = attr.ib(type=int)
state = attr.ib(type=str)
metadata = attr.ib(type=dict)
ledger_uuid = attr.ib(type=UUID)
subsidy_access_policy_uuid = attr.ib(type=UUID)
lms_user_id = attr.ib(type=int)
content_key = attr.ib(type=CourseKey)
parent_content_key = attr.ib(type=str)
fulfillment_identifier = attr.ib(type=str)
reversal = attr.ib(type=LedgerTransactionReversal)
54 changes: 53 additions & 1 deletion openedx_events/enterprise/signals.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,11 @@
docs/decisions/0003-events-payload.rst
"""

from openedx_events.enterprise.data import SubsidyRedemption
from openedx_events.enterprise.data import (
LedgerTransaction,
LedgerTransactionReversal,
SubsidyRedemption,
)
from openedx_events.tooling import OpenEdxPublicSignal

# .. event_type: org.openedx.enterprise.subsidy.redeemed.v1
Expand All @@ -32,3 +36,51 @@
"redemption": SubsidyRedemption,
}
)


# .. event_type: org.openedx.enterprise_subsidies.ledger_transaction.created.v1
# .. event_name: LEDGER_TRANSACTION_CREATED
# .. event_description: emitted when an enterprise ledger transaction is created.
# .. event_data: LedgerTransaction
LEDGER_TRANSACTION_CREATED = OpenEdxPublicSignal(
event_type="org.openedx.enterprise_subsidies.ledger_transaction.created.v1",
data={
"ledger_transaction": LedgerTransaction,
}
)


# .. event_type: org.openedx.enterprise_subsidies.ledger_transaction.committed.v1
# .. event_name: LEDGER_TRANSACTION_COMMITTED
# .. event_description: emitted when an enterprise ledger transaction is committed.
# .. event_data: LedgerTransaction
LEDGER_TRANSACTION_COMMITTED = OpenEdxPublicSignal(
event_type="org.openedx.enterprise_subsidies.ledger_transaction.committed.v1",
data={
"ledger_transaction": LedgerTransaction,
}
)


# .. event_type: org.openedx.enterprise_subsidies.ledger_transaction.failed.v1
# .. event_name: LEDGER_TRANSACTION_FAILED
# .. event_description: emitted when an enterprise ledger transaction fails.
# .. event_data: LedgerTransaction
LEDGER_TRANSACTION_FAILED = OpenEdxPublicSignal(
event_type="org.openedx.enterprise_subsidies.ledger_transaction.failed.v1",
data={
"ledger_transaction": LedgerTransaction,
}
)


# .. event_type: org.openedx.enterprise_subsidies.ledger_transaction.reversed.v1
# .. event_name: LEDGER_TRANSACTION_REVERSED
# .. event_description: emitted when an enterprise ledger transaction is reversed.
# .. event_data: LedgerTransaction
LEDGER_TRANSACTION_REVERSED = OpenEdxPublicSignal(
event_type="org.openedx.enterprise_subsidies.ledger_transaction.reversed.v1",
data={
"ledger_transaction": LedgerTransaction,
}
)
22 changes: 22 additions & 0 deletions openedx_events/event_bus/avro/custom_serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"""
from abc import ABC, abstractmethod
from datetime import datetime
from uuid import UUID

from ccx_keys.locator import CCXLocator
from opaque_keys.edx.keys import CourseKey, UsageKey
Expand Down Expand Up @@ -149,11 +150,32 @@ def deserialize(data: str):
return LibraryUsageLocatorV2.from_string(data)


class UuidAvroSerializer(BaseCustomTypeAvroSerializer):
"""
CustomTypeAvroSerializer for the UUID class.
https://github.com/cloudevents/spec/blob/v1.0.2/cloudevents/formats/avro-format.md#21-type-system-mapping
"""

cls = UUID
field_type = PYTHON_TYPE_TO_AVRO_MAPPING[str]

@staticmethod
def serialize(obj) -> str:
"""Serialize obj into string."""
return str(obj)

@staticmethod
def deserialize(data: str):
"""Deserialize string into obj."""
return UUID(data)


DEFAULT_CUSTOM_SERIALIZERS = [
CourseKeyAvroSerializer,
CcxCourseLocatorAvroSerializer,
DatetimeAvroSerializer,
LibraryLocatorV2AvroSerializer,
LibraryUsageLocatorV2AvroSerializer,
UsageKeyAvroSerializer,
UuidAvroSerializer,
]
28 changes: 27 additions & 1 deletion openedx_events/event_bus/avro/tests/test_custom_serializers.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
"""Test custom servializers"""
from uuid import uuid4, UUID
from unittest import TestCase

from ccx_keys.locator import CCXLocator

from openedx_events.event_bus.avro.custom_serializers import CcxCourseLocatorAvroSerializer
from ..custom_serializers import UuidAvroSerializer


class TestCCXLocatorSerailizer(TestCase):
Expand All @@ -19,7 +21,7 @@ def test_serialize(self):
result1 = CcxCourseLocatorAvroSerializer.serialize(obj1)
self.assertEqual(result1, expected1)

def test_deseialize(self):
def test_deserialize(self):
"""
Test case for deserializing CCXLocator object.
"""
Expand All @@ -28,3 +30,27 @@ def test_deseialize(self):
expected1 = CCXLocator(org="edx", course="DemoX", run="Demo_course", ccx="1")
result1 = CcxCourseLocatorAvroSerializer.deserialize(data1)
self.assertEqual(result1, expected1)


class TestUuidAvroSerializer(TestCase):
"""
Tests case for Avro UUID de-/serialization.
"""
def test_serialize(self):
"""
Test UUID Avro serialization.
"""
some_uuid = uuid4()
expected_result = str(some_uuid)
actual_result = UuidAvroSerializer.serialize(some_uuid)
self.assertEqual(actual_result, expected_result)

def test_deserialize(self):
"""
Test UUID Avro de-serialization.
"""

uuid_str = str(uuid4())
expected_result = UUID(uuid_str)
actual_result = UuidAvroSerializer.deserialize(uuid_str)
self.assertEqual(actual_result, expected_result)
3 changes: 3 additions & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,6 @@ multi_line_output = 3

[wheel]
universal = 1

[flake8]
max-line-length = 120

0 comments on commit 540f372

Please sign in to comment.