Mobilizon-Reshare-condividi.../mobilizon_reshare/publishers/abstract.py

176 lines
5.5 KiB
Python

import inspect
import logging
from abc import ABC, abstractmethod
from typing import Optional
from dynaconf.utils.boxing import DynaBox
from jinja2 import Environment, FileSystemLoader, Template
from mobilizon_reshare.config.config import get_settings
from .exceptions import InvalidAttribute
from ..dataclasses import _MobilizonEvent
JINJA_ENV = Environment(loader=FileSystemLoader("/"))
logger = logging.getLogger(__name__)
class LoggerMixin:
def _log_debug(self, msg, *args, **kwargs):
self.__log(logging.DEBUG, msg, *args, **kwargs)
def _log_info(self, msg, *args, **kwargs):
self.__log(logging.INFO, msg, *args, **kwargs)
def _log_warning(self, msg, *args, **kwargs):
self.__log(logging.WARNING, msg, *args, **kwargs)
def _log_error(self, msg, *args, **kwargs):
self.__log(logging.ERROR, msg, *args, **kwargs)
def _log_critical(self, msg, *args, **kwargs):
self.__log(logging.CRITICAL, msg, *args, **kwargs)
def __log(self, level, msg, raise_error: type = None, *args, **kwargs):
method = inspect.currentframe().f_back.f_back.f_code.co_name
logger.log(level, f"{self}.{method}(): {msg}", *args, **kwargs)
if raise_error is not None:
raise raise_error(msg)
class ConfLoaderMixin:
_conf = tuple()
@property
def conf(self) -> DynaBox:
"""
Retrieves class's settings.
"""
cls = type(self)
try:
t, n = cls._conf or tuple()
return get_settings()[t][n]
except (KeyError, ValueError):
raise InvalidAttribute(
f"Class {cls.__name__} has invalid ``_conf`` attribute"
f" (should be 2-tuple)"
)
class AbstractPlatform(ABC, LoggerMixin, ConfLoaderMixin):
"""
Generic notifier class.
Shall be inherited from specific subclasses that will manage validation
process for messages and credentials, text formatting, posting, etc.
Attributes:
- ``message``: a formatted ``str``
"""
# Non-abstract subclasses should define ``_conf`` as a 2-tuple, where the
# first element is the type of class (either 'notifier' or 'publisher') and
# the second the name of its service (ie: 'facebook', 'telegram')
def __repr__(self):
return self.name
@property
@abstractmethod
def name(self):
pass
@abstractmethod
def _send(self, message: str, event: Optional[_MobilizonEvent] = None):
raise NotImplementedError # pragma: no cover
def send(self, message: str, event: Optional[_MobilizonEvent] = None):
"""
Sends a message to the target channel
"""
response = self._send(message, event)
self._validate_response(response)
@abstractmethod
def _validate_response(self, response):
raise NotImplementedError # pragma: no cover
@abstractmethod
def validate_credentials(self) -> None:
"""
Validates credentials.
Should raise ``PublisherError`` (or one of its subclasses) if
credentials are not valid.
"""
raise NotImplementedError # pragma: no cover
class AbstractEventFormatter(LoggerMixin, ConfLoaderMixin):
@abstractmethod
def _validate_event(self, event: _MobilizonEvent) -> None:
"""
Validates publisher's event.
Should raise ``PublisherError`` (or one of its subclasses) if event
is not valid.
"""
raise NotImplementedError # pragma: no cover
@abstractmethod
def _validate_message(self, message: str) -> None:
"""
Validates notifier's message.
Should raise ``PublisherError`` (or one of its subclasses) if message
is not valid.
"""
raise NotImplementedError # pragma: no cover
def validate_event(self, event: _MobilizonEvent) -> None:
self._validate_event(event)
self._validate_message(self.get_message_from_event(event))
@abstractmethod
def _preprocess_event(self, event):
"""
Allows publishers to preprocess events before feeding them to the template
"""
return event
def get_message_from_event(self, event: _MobilizonEvent) -> str:
"""
Retrieves a message from the event itself.
"""
event = self._preprocess_event(event)
message = event.format(self.get_message_template())
message = self._preprocess_message(message)
return message
def get_message_template(self) -> Template:
"""
Retrieves publisher's message template.
"""
template_path = self.conf.msg_template_path or self.default_template_path
return JINJA_ENV.get_template(template_path)
def get_recap_header(self):
template_path = (
self.conf.recap_header_template_path
or self.default_recap_header_template_path
)
return JINJA_ENV.get_template(template_path).render()
def get_recap_fragment_template(self) -> Template:
template_path = (
self.conf.recap_template_path or self.default_recap_template_path
)
return JINJA_ENV.get_template(template_path)
def get_recap_fragment(self, event: _MobilizonEvent) -> str:
"""
Retrieves the fragment that describes a single event inside the event recap.
"""
event = self._preprocess_event(event)
return event.format(self.get_recap_fragment_template())
def _preprocess_message(self, message: str):
return message