2021-04-23 17:04:27 +02:00
|
|
|
import inspect
|
|
|
|
import logging
|
|
|
|
from abc import ABC, abstractmethod
|
2021-07-12 22:17:49 +02:00
|
|
|
|
2021-07-05 21:44:11 +02:00
|
|
|
from dynaconf.utils.boxing import DynaBox
|
|
|
|
from jinja2 import Environment, FileSystemLoader, Template
|
2021-08-27 23:45:24 +02:00
|
|
|
from requests import Response
|
2021-07-05 21:44:11 +02:00
|
|
|
|
2021-08-16 10:49:52 +02:00
|
|
|
from mobilizon_reshare.config.config import get_settings
|
|
|
|
from mobilizon_reshare.event.event import MobilizonEvent
|
2021-07-05 21:44:11 +02:00
|
|
|
from .exceptions import PublisherError, InvalidAttribute
|
|
|
|
|
|
|
|
JINJA_ENV = Environment(loader=FileSystemLoader("/"))
|
2021-04-23 17:04:27 +02:00
|
|
|
|
2021-05-02 10:24:46 +02:00
|
|
|
logger = logging.getLogger(__name__)
|
2021-04-23 17:04:27 +02:00
|
|
|
|
|
|
|
|
2021-07-05 21:44:11 +02:00
|
|
|
class AbstractNotifier(ABC):
|
2021-04-23 17:04:27 +02:00
|
|
|
"""
|
2021-07-05 21:44:11 +02:00
|
|
|
Generic notifier class.
|
2021-04-23 17:04:27 +02:00
|
|
|
Shall be inherited from specific subclasses that will manage validation
|
2021-07-05 21:44:11 +02:00
|
|
|
process for messages and credentials, text formatting, posting, etc.
|
2021-04-23 17:04:27 +02:00
|
|
|
|
2021-07-05 21:44:11 +02:00
|
|
|
Attributes:
|
|
|
|
- ``message``: a formatted ``str``
|
2021-04-23 17:04:27 +02:00
|
|
|
"""
|
|
|
|
|
2021-07-05 21:44:11 +02:00
|
|
|
# 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')
|
|
|
|
_conf = tuple()
|
|
|
|
|
2021-04-23 17:04:27 +02:00
|
|
|
def __repr__(self):
|
|
|
|
return type(self).__name__
|
|
|
|
|
|
|
|
__str__ = __repr__
|
|
|
|
|
2021-07-05 21:44:11 +02:00
|
|
|
@property
|
|
|
|
def conf(self) -> DynaBox:
|
|
|
|
"""
|
|
|
|
Retrieves class's settings.
|
|
|
|
"""
|
|
|
|
cls = type(self)
|
|
|
|
if cls in (AbstractPublisher, AbstractNotifier):
|
|
|
|
raise InvalidAttribute(
|
|
|
|
"Abstract classes cannot access notifiers/publishers' settings"
|
|
|
|
)
|
|
|
|
try:
|
|
|
|
t, n = cls._conf or tuple() # Avoid unpacking ``None``
|
2021-07-12 22:17:49 +02:00
|
|
|
return get_settings()[t][n]
|
2021-07-05 21:44:11 +02:00
|
|
|
except (KeyError, ValueError):
|
|
|
|
raise InvalidAttribute(
|
|
|
|
f"Class {cls.__name__} has invalid ``_conf`` attribute"
|
|
|
|
f" (should be 2-tuple)"
|
|
|
|
)
|
|
|
|
|
2021-08-27 23:45:24 +02:00
|
|
|
@abstractmethod
|
|
|
|
def send(self, message):
|
|
|
|
"""
|
|
|
|
Sends a message to the target channel
|
|
|
|
"""
|
|
|
|
raise NotImplementedError
|
|
|
|
|
2021-07-05 21:44:11 +02:00
|
|
|
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: PublisherError = 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)
|
|
|
|
|
|
|
|
def are_credentials_valid(self) -> bool:
|
|
|
|
try:
|
|
|
|
self.validate_credentials()
|
|
|
|
except PublisherError:
|
|
|
|
return False
|
|
|
|
return True
|
|
|
|
|
2021-04-23 17:04:27 +02:00
|
|
|
@abstractmethod
|
2021-07-05 21:44:11 +02:00
|
|
|
def validate_credentials(self) -> None:
|
2021-04-23 17:04:27 +02:00
|
|
|
"""
|
2021-07-05 21:44:11 +02:00
|
|
|
Validates credentials.
|
|
|
|
Should raise ``PublisherError`` (or one of its subclasses) if
|
|
|
|
credentials are not valid.
|
2021-04-23 17:04:27 +02:00
|
|
|
"""
|
|
|
|
raise NotImplementedError
|
|
|
|
|
|
|
|
@abstractmethod
|
2021-08-27 23:45:24 +02:00
|
|
|
def publish(self) -> None:
|
2021-04-23 17:04:27 +02:00
|
|
|
"""
|
2021-07-05 21:44:11 +02:00
|
|
|
Publishes the actual post on social media.
|
|
|
|
Should raise ``PublisherError`` (or one of its subclasses) if
|
|
|
|
anything goes wrong.
|
2021-04-23 17:04:27 +02:00
|
|
|
"""
|
|
|
|
raise NotImplementedError
|
|
|
|
|
2021-07-05 21:44:11 +02:00
|
|
|
def is_message_valid(self) -> bool:
|
2021-05-02 10:46:58 +02:00
|
|
|
try:
|
2021-07-05 21:44:11 +02:00
|
|
|
self.validate_message()
|
|
|
|
except PublisherError:
|
2021-05-02 10:46:58 +02:00
|
|
|
return False
|
|
|
|
return True
|
|
|
|
|
2021-07-05 21:44:11 +02:00
|
|
|
@abstractmethod
|
|
|
|
def validate_message(self) -> None:
|
|
|
|
"""
|
|
|
|
Validates notifier's message.
|
|
|
|
Should raise ``PublisherError`` (or one of its subclasses) if message
|
|
|
|
is not valid.
|
|
|
|
"""
|
|
|
|
raise NotImplementedError
|
|
|
|
|
|
|
|
|
|
|
|
class AbstractPublisher(AbstractNotifier):
|
|
|
|
"""
|
|
|
|
Generic publisher class.
|
|
|
|
Shall be inherited from specific subclasses that will manage validation
|
|
|
|
process for events and credentials, text formatting, posting, etc.
|
|
|
|
|
|
|
|
Attributes:
|
|
|
|
- ``event``: a ``MobilizonEvent`` containing every useful info from
|
|
|
|
the event
|
|
|
|
- ``message``: a formatted ``str``
|
|
|
|
"""
|
|
|
|
|
|
|
|
_conf = tuple()
|
|
|
|
|
|
|
|
def __init__(self, event: MobilizonEvent):
|
|
|
|
self.event = event
|
2021-08-27 23:45:24 +02:00
|
|
|
super().__init__()
|
2021-07-05 21:44:11 +02:00
|
|
|
|
2021-05-02 11:23:31 +02:00
|
|
|
def is_event_valid(self) -> bool:
|
2021-05-02 10:46:58 +02:00
|
|
|
try:
|
|
|
|
self.validate_event()
|
2021-07-05 21:44:11 +02:00
|
|
|
except PublisherError:
|
2021-05-02 10:46:58 +02:00
|
|
|
return False
|
|
|
|
return True
|
|
|
|
|
2021-04-23 17:04:27 +02:00
|
|
|
@abstractmethod
|
2021-07-05 21:44:11 +02:00
|
|
|
def validate_event(self) -> None:
|
2021-04-23 17:04:27 +02:00
|
|
|
"""
|
|
|
|
Validates publisher's event.
|
2021-07-05 21:44:11 +02:00
|
|
|
Should raise ``PublisherError`` (or one of its subclasses) if event
|
|
|
|
is not valid.
|
2021-04-23 17:04:27 +02:00
|
|
|
"""
|
|
|
|
raise NotImplementedError
|
|
|
|
|
2021-07-21 09:13:32 +02:00
|
|
|
def _preprocess_event(self):
|
|
|
|
"""
|
|
|
|
Allows publishers to preprocess events before feeding them to the template
|
|
|
|
"""
|
|
|
|
pass
|
|
|
|
|
2021-07-05 21:44:11 +02:00
|
|
|
def get_message_from_event(self) -> str:
|
|
|
|
"""
|
|
|
|
Retrieves a message from the event itself.
|
|
|
|
"""
|
2021-07-21 09:13:32 +02:00
|
|
|
self._preprocess_event()
|
2021-07-05 21:44:11 +02:00
|
|
|
return self.event.format(self.get_message_template())
|
2021-05-02 11:27:07 +02:00
|
|
|
|
2021-07-05 21:44:11 +02:00
|
|
|
def get_message_template(self) -> Template:
|
|
|
|
"""
|
|
|
|
Retrieves publisher's message template.
|
|
|
|
"""
|
2021-07-18 18:23:30 +02:00
|
|
|
template_path = self.conf.msg_template_path or self.default_template_path
|
2021-07-12 22:17:49 +02:00
|
|
|
return JINJA_ENV.get_template(template_path)
|
2021-08-27 23:45:24 +02:00
|
|
|
|
|
|
|
@abstractmethod
|
|
|
|
def _send(self, message) -> Response:
|
|
|
|
pass
|
|
|
|
|
|
|
|
@abstractmethod
|
|
|
|
def _validate_response(self, response: Response) -> None:
|
|
|
|
pass
|
|
|
|
|
|
|
|
def send(self, message):
|
|
|
|
res = self._send(message)
|
|
|
|
self._validate_response(res)
|
|
|
|
|
|
|
|
def publish(self) -> None:
|
|
|
|
self.send(message=self.get_message_from_event())
|