Skip to content

Commit

Permalink
Store notifications.
Browse files Browse the repository at this point in the history
  • Loading branch information
fishinthecalculator committed May 22, 2023
1 parent 3874acf commit a81845a
Show file tree
Hide file tree
Showing 20 changed files with 286 additions and 74 deletions.
6 changes: 5 additions & 1 deletion mobilizon_reshare/dataclasses/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,12 @@
from mobilizon_reshare.dataclasses.event_publication_status import (
_EventPublicationStatus,
)
from mobilizon_reshare.dataclasses.publication import _EventPublication
from mobilizon_reshare.dataclasses.publication import (
_EventPublication,
_PublicationNotification,
)

EventPublication = _EventPublication
MobilizonEvent = _MobilizonEvent
EventPublicationStatus = _EventPublicationStatus
PublicationNotification = _PublicationNotification
5 changes: 5 additions & 0 deletions mobilizon_reshare/dataclasses/publication.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,11 @@ class RecapPublication(BasePublication):
events: List[_MobilizonEvent]


@dataclass
class _PublicationNotification(BasePublication):
publication: _EventPublication


@atomic()
async def build_publications_for_event(
event: _MobilizonEvent, publishers: Iterator[str]
Expand Down
23 changes: 14 additions & 9 deletions mobilizon_reshare/main/publish.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,32 +13,37 @@
)
from mobilizon_reshare.event.event_selection_strategies import select_event_to_publish
from mobilizon_reshare.publishers import get_active_publishers
from mobilizon_reshare.publishers.coordinators.event_publishing.dry_run import (
DryRunPublisherCoordinator,
)
from mobilizon_reshare.publishers.coordinators.event_publishing.notify import (
PublicationFailureNotifiersCoordinator,
)
from mobilizon_reshare.publishers.coordinators.event_publishing.publish import (
PublisherCoordinatorReport,
PublisherCoordinator,
)
from mobilizon_reshare.storage.query.write import save_publication_report
from mobilizon_reshare.storage.query.write import (
save_publication_report,
save_notification_report,
)
from mobilizon_reshare.publishers.coordinators.event_publishing.dry_run import (
DryRunPublisherCoordinator,
)

logger = logging.getLogger(__name__)


async def publish_publications(
publications: list[_EventPublication],
) -> PublisherCoordinatorReport:
report = PublisherCoordinator(publications).run()
publishers_report = PublisherCoordinator(publications).run()
await save_publication_report(publishers_report)

await save_publication_report(report)
for publication_report in report.reports:
for publication_report in publishers_report.reports:
if not publication_report.successful:
PublicationFailureNotifiersCoordinator(publication_report,).notify_failure()
notifiers_report = PublicationFailureNotifiersCoordinator(publication_report,).notify_failure()
if notifiers_report:
await save_notification_report(notifiers_report)

return report
return publishers_report


def perform_dry_run(publications: list[_EventPublication]):
Expand Down
6 changes: 2 additions & 4 deletions mobilizon_reshare/models/notification.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,8 @@


class NotificationStatus(IntEnum):
WAITING = 1
FAILED = 2
PARTIAL = 3
COMPLETED = 4
FAILED = 0
COMPLETED = 1


class Notification(Model):
Expand Down
4 changes: 2 additions & 2 deletions mobilizon_reshare/publishers/coordinators/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ def successful(self):

def get_failure_message(self):
return (
f"Publication failed with status: {self.status}.\n" f"Reason: {self.reason}"
f"Publication failed with status: {self.status.name}.\n" f"Reason: {self.reason}"
)


Expand All @@ -26,7 +26,7 @@ class BaseCoordinatorReport:

@property
def successful(self):
return all(r.status == PublicationStatus.COMPLETED for r in self.reports)
return all(r.successful for r in self.reports)


logger = logging.getLogger(__name__)
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ def get_failure_message(self):
logger.error("Report of failure without reason.", exc_info=True)

return (
f"Publication {self.publication.id} failed with status: {self.status}.\n"
f"Publication {self.publication.id} failed with status: {self.status.name}.\n"
f"Reason: {self.reason}\n"
f"Publisher: {self.publication.publisher.name}\n"
f"Event: {self.publication.event.name}"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,33 +1,92 @@
from abc import ABC, abstractmethod
from typing import List
from dataclasses import dataclass, field
from typing import List, Optional, Sequence

from mobilizon_reshare.dataclasses import PublicationNotification, EventPublication
from mobilizon_reshare.models.notification import NotificationStatus
from mobilizon_reshare.models.publication import PublicationStatus
from mobilizon_reshare.publishers import get_active_notifiers
from mobilizon_reshare.publishers.abstract import AbstractPlatform
from mobilizon_reshare.publishers.coordinators import logger
from mobilizon_reshare.publishers.coordinators.event_publishing.publish import (
from mobilizon_reshare.publishers.abstract import (
AbstractPlatform,
)
from mobilizon_reshare.publishers.coordinators import (
logger,
BasePublicationReport,
BaseCoordinatorReport,
)
from mobilizon_reshare.publishers.coordinators.event_publishing import (
EventPublicationReport,
)
from mobilizon_reshare.publishers.platforms.platform_mapping import get_notifier_class
from mobilizon_reshare.publishers.platforms.platform_mapping import (
get_notifier_class,
get_formatter_class,
)


@dataclass
class PublicationNotificationReport(BasePublicationReport):
status: NotificationStatus
notification: PublicationNotification

@property
def successful(self):
return self.status == NotificationStatus.COMPLETED

def get_failure_message(self):
if not self.reason:
logger.error("Report of failure without reason.", exc_info=True)
return (
f"Failed with status: {self.status.name}.\n"
f"Reason: {self.reason}\n"
f"Publisher: {self.notification.publisher.name}\n"
f"Publication: {self.notification.publication.id}"
)


@dataclass
class NotifierCoordinatorReport(BaseCoordinatorReport):
reports: Sequence[PublicationNotificationReport]
notifications: Sequence[PublicationNotification] = field(default_factory=list)


class Sender:
def __init__(self, message: str, platforms: List[AbstractPlatform] = None):
def __init__(
self,
message: str,
publication: EventPublication,
platforms: List[AbstractPlatform] = None,
):
self.message = message
self.platforms = platforms
self.publication = publication

def send_to_all(self):
def send_to_all(self) -> NotifierCoordinatorReport:
reports = []
notifications = []
for platform in self.platforms:
notification = PublicationNotification(
platform, get_formatter_class(platform.name)(), self.publication
)
try:
platform.send(self.message)
report = PublicationNotificationReport(
NotificationStatus.COMPLETED, self.message, notification
)
except Exception as e:
logger.critical(f"Failed to send message:\n{self.message}")
msg = f"[{platform.name}] Failed to notify failure of message:\n{self.message}"
logger.critical(msg)
logger.exception(e)
report = PublicationNotificationReport(
NotificationStatus.FAILED, msg, notification
)
notifications.append(notification)
reports.append(report)
return NotifierCoordinatorReport(reports=reports, notifications=notifications)


class AbstractNotifiersCoordinator(ABC):
def __init__(
self, report: EventPublicationReport, notifiers: List[AbstractPlatform] = None
self, report: BasePublicationReport, notifiers: List[AbstractPlatform] = None
):
self.platforms = notifiers or [
get_notifier_class(notifier)() for notifier in get_active_notifiers()
Expand All @@ -44,10 +103,17 @@ class PublicationFailureNotifiersCoordinator(AbstractNotifiersCoordinator):
Sends a notification of a failure report to the active platforms
"""

def notify_failure(self):
report: EventPublicationReport
platforms: List[AbstractPlatform]

def notify_failure(self) -> Optional[NotifierCoordinatorReport]:
logger.info("Sending failure notifications")
if self.report.status == PublicationStatus.FAILED:
Sender(self.report.get_failure_message(), self.platforms).send_to_all()
return Sender(
self.report.get_failure_message(),
self.report.publication,
self.platforms,
).send_to_all()


class PublicationFailureLoggerCoordinator(PublicationFailureNotifiersCoordinator):
Expand Down
3 changes: 2 additions & 1 deletion mobilizon_reshare/storage/query/read.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ async def get_all_publishers() -> list[Publisher]:

async def prefetch_event_relations(queryset: QuerySet[Event]) -> list[Event]:
return (
await queryset.prefetch_related("publications__publisher")
await queryset.prefetch_related("publications__publisher", "publications__notifications")
.order_by("begin_datetime")
.distinct()
)
Expand All @@ -46,6 +46,7 @@ async def prefetch_publication_relations(
await queryset.prefetch_related(
"publisher",
"event",
"notifications",
"event__publications",
"event__publications__publisher",
)
Expand Down
22 changes: 22 additions & 0 deletions mobilizon_reshare/storage/query/write.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,15 @@
get_mobilizon_events_without_publications,
)
from mobilizon_reshare.models.event import Event
from mobilizon_reshare.models.notification import Notification
from mobilizon_reshare.models.publication import Publication
from mobilizon_reshare.models.publisher import Publisher
from mobilizon_reshare.publishers.coordinators.event_publishing import (
EventPublicationReport,
)
from mobilizon_reshare.publishers.coordinators.event_publishing.notify import (
NotifierCoordinatorReport,
)
from mobilizon_reshare.publishers.coordinators.event_publishing.publish import (
PublisherCoordinatorReport,
)
Expand Down Expand Up @@ -64,6 +68,24 @@ async def save_publication_report(
await upsert_publication(publication_report, event)


@atomic()
async def save_notification_report(
coordinator_report: NotifierCoordinatorReport,
) -> None:
"""
Store a notification process outcome
"""
for report in coordinator_report.reports:
publisher = await Publisher.filter(name=report.notification.publisher.name).first()

await Notification.create(
publication_id=report.notification.publication.id,
target_id=publisher.id,
status=report.status,
message=report.reason,
)


@atomic()
async def create_unpublished_events(
events_from_mobilizon: Iterable[MobilizonEvent],
Expand Down
16 changes: 13 additions & 3 deletions sample_settings/docker_web/settings.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@
debug = false
default = true
local_state_dir = "/var/lib/mobilizon-reshare"
#db_path = "@format {this.local_state_dir}/events.db"
#db_url = "@format sqlite://{this.local_state_dir}/events.db"
db_url = "@format postgres://mobilizon_reshare:mobilizon_reshare@db:5432/mobilizon_reshare"
locale = "en-uk"

[default.source.mobilizon]
url="https://some-mobilizon.com/api"
Expand All @@ -28,6 +29,15 @@ class = "logging.StreamHandler"
formatter = "standard"
stream = "ext://sys.stderr"

[default.logging.handlers.file]
level = "INFO"
class = "logging.handlers.RotatingFileHandler"
formatter = "standard"
filename = "@format {this.local_state_dir}/mobilizon_reshare.log"
maxBytes = 52428800
backupCount = 500
encoding = "utf8"

[default.logging.root]
level = "DEBUG"
handlers = ['console']
level = "INFO"
handlers = ['console', 'file']
31 changes: 28 additions & 3 deletions tests/commands/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
import mobilizon_reshare.storage.query.read
from mobilizon_reshare.models.publisher import Publisher
import mobilizon_reshare.main.recap
from mobilizon_reshare.publishers.coordinators.event_publishing import notify
from tests import today
from tests.conftest import event_1, event_0

Expand Down Expand Up @@ -138,15 +137,41 @@ def _mock_format_class(name):
return mock_formatter_class

monkeypatch.setattr(
notify, "get_notifier_class", _mock_notifier_class,
mobilizon_reshare.publishers.coordinators.event_publishing.notify,
"get_notifier_class",
_mock_notifier_class,
)
monkeypatch.setattr(
mobilizon_reshare.publishers.coordinators.event_publishing.notify,
"get_formatter_class",
_mock_format_class,
)
monkeypatch.setattr(
mobilizon_reshare.publishers.coordinators.event_publishing.notify,
"get_notifier_class",
_mock_notifier_class,
)
monkeypatch.setattr(
mobilizon_reshare.publishers.platforms.platform_mapping,
"get_formatter_class",
_mock_format_class,
)
monkeypatch.setattr(
mobilizon_reshare.publishers.coordinators.event_publishing.notify,
"get_formatter_class",
_mock_format_class,
)

monkeypatch.setattr(notify, "get_active_notifiers", _mock_active_notifier)
monkeypatch.setattr(
mobilizon_reshare.publishers.coordinators.event_publishing.notify,
"get_active_notifiers",
_mock_active_notifier,
)
monkeypatch.setattr(
mobilizon_reshare.config.notifiers,
"get_active_notifiers",
lambda s: [],
)


@pytest.fixture
Expand Down
Loading

0 comments on commit a81845a

Please sign in to comment.