Skip to content

Commit

Permalink
serialize config and backend kwargs correctly
Browse files Browse the repository at this point in the history
  • Loading branch information
hugobessa committed Dec 5, 2024
1 parent 38573f2 commit 7be7308
Show file tree
Hide file tree
Showing 3 changed files with 57 additions and 34 deletions.
2 changes: 1 addition & 1 deletion poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -1,25 +1,13 @@
import datetime
import logging
from typing import Generic, TypeVar
from typing import cast, Generic, TypeVar

from celery import Celery # type: ignore

from vintasend.services.dataclasses import Notification, NotificationContextDict
from vintasend.services.notification_adapters.async_base import AsyncBaseNotificationAdapter
from vintasend.services.notification_adapters.async_base import AsyncBaseNotificationAdapter, NotificationDict
from vintasend.services.notification_backends.base import BaseNotificationBackend
from vintasend.services.notification_template_renderers.base import BaseNotificationTemplateRenderer
from vintasend_celery.tasks.background_tasks import send_notification_task_factory
from vintasend.exceptions import (
NotificationContextGenerationError,
NotificationMarkFailedError,
NotificationMarkSentError,
NotificationSendError,
NotificationUserNotFoundError,
NotificationBodyTemplateRenderingError,
)


logger = logging.getLogger(__name__)


B = TypeVar("B", bound=BaseNotificationBackend)
Expand All @@ -31,19 +19,9 @@ class CeleryNotificationAdapter(Generic[B, T], AsyncBaseNotificationAdapter[B, T
def delayed_send(self, notification_dict: dict, context_dict: dict) -> None:
notification = self.notification_from_dict(notification_dict)
context = NotificationContextDict(**context_dict)
try:
super().send(notification, context) # type: ignore
except (
NotificationUserNotFoundError,
NotificationContextGenerationError,
NotificationMarkFailedError,
NotificationMarkSentError,
NotificationSendError,
NotificationBodyTemplateRenderingError,
) as e:
logger.exception(f"Error sending notification {notification.id}: {e}")
super().send(notification, context) # type: ignore

def notification_to_dict(self, notification: "Notification") -> dict:
def notification_to_dict(self, notification: "Notification") -> NotificationDict:
non_serializable_fields = ["send_after"]
serialized_notification = {}
for field in notification.__dataclass_fields__.keys():
Expand All @@ -55,7 +33,7 @@ def notification_to_dict(self, notification: "Notification") -> dict:
notification.send_after.isoformat() if notification.send_after else None
)

return serialized_notification
return cast(NotificationDict, serialized_notification)

def notification_from_dict(self, notification_dict: dict) -> "Notification":
notification_dict["send_after"] = (
Expand All @@ -78,4 +56,5 @@ def send(self, notification: "Notification", context: "NotificationContextDict")
)
],
backend_kwargs=self.backend.backend_kwargs,
config=self.serialize_config(),
)
56 changes: 50 additions & 6 deletions vintasend_celery/tests/test_services/test_adapters.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
from datetime import datetime
from decimal import Decimal
import uuid
from unittest import TestCase
from unittest.mock import patch

from celery import Celery # type: ignore
from vintasend.constants import NotificationStatus, NotificationTypes
from vintasend.services.dataclasses import Notification
from vintasend.services.notification_backends.stubs.fake_backend import FakeFileBackend
from vintasend.services.notification_backends.stubs.fake_backend import (
FakeFileBackend, FakeFileBackendWithNonSerializableKWArgs, Config
)
from vintasend.services.notification_template_renderers.stubs.fake_templated_email_renderer import (
FakeTemplateRenderer, FakeTemplateRendererWithException
)
Expand All @@ -26,6 +30,26 @@ class AsyncCeleryFakeEmailAdapter(
):
celery_app = celery_app

class AsyncCeleryFakeEmailAdapterWithBackendWithNonSerializableKWArgs(
CeleryNotificationAdapter[FakeFileBackendWithNonSerializableKWArgs, FakeTemplateRenderer],
FakeEmailAdapter[FakeFileBackendWithNonSerializableKWArgs, FakeTemplateRenderer],
):
celery_app = celery_app
config: Config

def serialize_config(self) -> dict:
return {
"config_a": str(self.config.config_a),
"config_b": self.config.config_b.isoformat(),
}

def restore_config(self, config: dict) -> Config:
self.config = Config(
config_a=Decimal(config["config_a"]),
config_b=datetime.fromisoformat(config["config_b"]),
)
return self.config


class AsyncCeleryFakeEmailAdapterTestCase(TestCase):
def setUp(self):
Expand Down Expand Up @@ -85,10 +109,7 @@ def test_send_notification_with_render_error(self):
renderer = FakeTemplateRendererWithException()
async_adapter = AsyncCeleryFakeEmailAdapter(template_renderer=renderer, backend=self.backend)

notification_service = NotificationService[
AsyncCeleryFakeEmailAdapter,
FakeFileBackend
](
notification_service = NotificationService(
[async_adapter],
self.backend,
)
Expand All @@ -97,10 +118,33 @@ def test_send_notification_with_render_error(self):
self.backend._store_notifications()

with patch(
"vintasend_celery.services.notification_adapters.celery_adapter_factory.logger.exception"
"vintasend.tasks.background_tasks.logger.exception"
) as mock_log_exception:
notification_service.send(notification)

mock_log_exception.assert_called_once()

assert len(self.async_adapter.sent_emails) == 0

def test_backend_with_non_serializable_kwargs(self):
notification = self.create_notification()
config = Config()
backend = FakeFileBackendWithNonSerializableKWArgs(
database_file_name="celery-adapter-tests-notifications.json",
config=config,
)
async_adapter = AsyncCeleryFakeEmailAdapterWithBackendWithNonSerializableKWArgs(
template_renderer=self.renderer, backend=backend, config=config
)

notification_service = NotificationService(
[async_adapter],
backend,
)

backend.notifications.append(notification)
backend._store_notifications()

notification_service.send(notification)

assert len(backend.notifications) == 1

0 comments on commit 7be7308

Please sign in to comment.