PyEmailerAJM 1.8__tar.gz → 1.8.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. {pyemailerajm-1.8 → pyemailerajm-1.8.1}/PKG-INFO +2 -2
  2. pyemailerajm-1.8.1/PyEmailerAJM/_version.py +1 -0
  3. pyemailerajm-1.8.1/PyEmailerAJM/backend/__init__.py +34 -0
  4. pyemailerajm-1.8.1/PyEmailerAJM/backend/enums.py +36 -0
  5. pyemailerajm-1.8.1/PyEmailerAJM/backend/errs.py +31 -0
  6. pyemailerajm-1.8.1/PyEmailerAJM/backend/logger.py +94 -0
  7. pyemailerajm-1.8.1/PyEmailerAJM/backend/the_sandman.py +91 -0
  8. pyemailerajm-1.8.1/PyEmailerAJM/continuous_monitor/__init__.py +4 -0
  9. pyemailerajm-1.8.1/PyEmailerAJM/continuous_monitor/continuous_monitor.py +88 -0
  10. pyemailerajm-1.8.1/PyEmailerAJM/continuous_monitor/continuous_monitor_alert_send.py +120 -0
  11. pyemailerajm-1.8.1/PyEmailerAJM/msg/__init__.py +5 -0
  12. pyemailerajm-1.8.1/PyEmailerAJM/msg/alert_messages.py +252 -0
  13. pyemailerajm-1.8.1/PyEmailerAJM/msg/factory.py +75 -0
  14. pyemailerajm-1.8.1/PyEmailerAJM/msg/msg.py +253 -0
  15. pyemailerajm-1.8.1/PyEmailerAJM/searchers/__init__.py +3 -0
  16. pyemailerajm-1.8.1/PyEmailerAJM/searchers/searchers.py +137 -0
  17. {pyemailerajm-1.8 → pyemailerajm-1.8.1}/PyEmailerAJM.egg-info/PKG-INFO +2 -2
  18. pyemailerajm-1.8.1/PyEmailerAJM.egg-info/SOURCES.txt +29 -0
  19. {pyemailerajm-1.8 → pyemailerajm-1.8.1}/setup.py +4 -2
  20. pyemailerajm-1.8/PyEmailerAJM/_version.py +0 -1
  21. pyemailerajm-1.8/PyEmailerAJM.egg-info/SOURCES.txt +0 -15
  22. {pyemailerajm-1.8 → pyemailerajm-1.8.1}/LICENSE.txt +0 -0
  23. {pyemailerajm-1.8 → pyemailerajm-1.8.1}/PyEmailerAJM/__init__.py +0 -0
  24. {pyemailerajm-1.8 → pyemailerajm-1.8.1}/PyEmailerAJM/py_emailer_ajm.py +0 -0
  25. {pyemailerajm-1.8 → pyemailerajm-1.8.1}/PyEmailerAJM.egg-info/dependency_links.txt +0 -0
  26. {pyemailerajm-1.8 → pyemailerajm-1.8.1}/PyEmailerAJM.egg-info/requires.txt +0 -0
  27. {pyemailerajm-1.8 → pyemailerajm-1.8.1}/PyEmailerAJM.egg-info/top_level.txt +0 -0
  28. {pyemailerajm-1.8 → pyemailerajm-1.8.1}/README.md +0 -0
  29. {pyemailerajm-1.8 → pyemailerajm-1.8.1}/setup.cfg +0 -0
  30. {pyemailerajm-1.8 → pyemailerajm-1.8.1}/tests/test_PyEmailerAJM.py +0 -0
  31. {pyemailerajm-1.8 → pyemailerajm-1.8.1}/tests/test_logger.py +0 -0
  32. {pyemailerajm-1.8 → pyemailerajm-1.8.1}/tests/test_snooze_tracking.py +0 -0
@@ -1,9 +1,9 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: PyEmailerAJM
3
- Version: 1.8
3
+ Version: 1.8.1
4
4
  Summary: Allows for automating sending Email with the Outlook Desktop client. Future releases will add more client support
5
5
  Home-page: https://github.com/amcsparron2793-Water/PyEmailer
6
- Download-URL: https://github.com/amcsparron2793-Water/PyEmailer/archive/refs/tags/1.8.tar.gz
6
+ Download-URL: https://github.com/amcsparron2793-Water/PyEmailer/archive/refs/tags/1.8.1.tar.gz
7
7
  Author: Amcsparron
8
8
  Author-email: amcsparron@albanyny.gov
9
9
  License: MIT License
@@ -0,0 +1 @@
1
+ __version__ = '1.8.1'
@@ -0,0 +1,34 @@
1
+ from PyEmailerAJM.backend.errs import *
2
+ from PyEmailerAJM.backend.enums import BasicEmailFolderChoices, AlertTypes
3
+ from PyEmailerAJM.backend.the_sandman import TheSandman
4
+ from PyEmailerAJM.backend.logger import PyEmailerLogger
5
+ import warnings
6
+ import functools
7
+
8
+
9
+ def deprecated(reason: str = ""):
10
+ """
11
+ Decorator that marks a function or method as deprecated.
12
+
13
+ :param reason: Optional message to explain what to use instead
14
+ or when the feature will be removed.
15
+ """
16
+
17
+ def decorator(func):
18
+ @functools.wraps(func)
19
+ def wrapper(*args, **kwargs):
20
+ message = f"Function '{func.__name__}' is deprecated."
21
+ if reason:
22
+ message += f" {reason}"
23
+ warnings.warn(message, category=DeprecationWarning, stacklevel=2)
24
+ return func(*args, **kwargs)
25
+
26
+ return wrapper
27
+
28
+ return decorator
29
+
30
+
31
+ __all__ = ['deprecated', 'EmailerNotSetupError', 'InvalidAlertLevel',
32
+ 'DisplayManualQuit', 'NoMessagesFetched',
33
+ 'UnrecognizedEmailError', 'BasicEmailFolderChoices',
34
+ 'AlertTypes', 'TheSandman', 'PyEmailerLogger']
@@ -0,0 +1,36 @@
1
+ from enum import Enum, IntEnum
2
+
3
+
4
+ class AlertTypes(Enum):
5
+ """
6
+ An enumeration to represent different alert types with associated integer values.
7
+
8
+ Attributes:
9
+ WARNING: An alert type indicating a warning with an associated value of 5.
10
+ CRITICAL_WARNING: An alert type indicating a critical warning with an associated value of 24.
11
+ OVERDUE: An alert type indicating an overdue alert with an associated value of 48.
12
+
13
+ Methods:
14
+ __str__: Returns the name of the alert type as a string.
15
+ """
16
+ WARNING = 5
17
+ CRITICAL_WARNING = 24
18
+ OVERDUE = 48
19
+
20
+ def __str__(self):
21
+ return self.name
22
+
23
+
24
+ class BasicEmailFolderChoices(IntEnum):
25
+ INBOX = 6
26
+ SENT_ITEMS = 5
27
+ DRAFTS = 16
28
+ DELETED_ITEMS = 3
29
+ OUTBOX = 4
30
+
31
+ def __str__(self):
32
+ """Return the enum name as a string."""
33
+ return self.name
34
+
35
+ def __repr__(self):
36
+ return f"<{self.__class__.__name__}.{self.name} ({self.value})>"
@@ -0,0 +1,31 @@
1
+ from typing import Optional
2
+
3
+ # noinspection PyUnresolvedReferences
4
+ from pywintypes import com_error
5
+
6
+
7
+ class EmailerNotSetupError(Exception):
8
+ ...
9
+
10
+
11
+ class DisplayManualQuit(Exception):
12
+ ...
13
+
14
+
15
+ class InvalidAlertLevel(Exception):
16
+ DEFAULT_ERR_MSG = 'Invalid alert level: {}'
17
+
18
+ def __init__(self, msg: '_AlertMessageBase', **kwargs):
19
+ self.msg = msg
20
+ self.err_msg_str = kwargs.get('err_msg_str', self.DEFAULT_ERR_MSG.format(self.msg.ALERT_LEVEL))
21
+ super().__init__(self.err_msg_str)
22
+
23
+
24
+ class NoMessagesFetched(Exception):
25
+ ...
26
+
27
+
28
+ class UnrecognizedEmailError(com_error):
29
+ def __init__(self, err_msg: Optional[str] = None, **kwargs):
30
+ self.err_msg = err_msg
31
+ super().__init__(self.err_msg, **kwargs)
@@ -0,0 +1,94 @@
1
+ from logging import Filter, DEBUG, ERROR, Handler, FileHandler, StreamHandler, Logger, WARNING
2
+ from pathlib import Path
3
+ from typing import Union
4
+
5
+ from EasyLoggerAJM import EasyLogger, OutlookEmailHandler, StreamHandlerIgnoreExecInfo
6
+ from PyEmailerAJM.msg import Msg
7
+ from PyEmailerAJM import __project_name__, __project_root__
8
+
9
+
10
+ class DupeDebugFilter(Filter):
11
+ PREFIXES_TO_IGNORE = ["FW:", "RE:"]
12
+
13
+ def __init__(self, name="DebugDedupeFilter"):
14
+ super().__init__(name)
15
+ self.logged_messages = set()
16
+
17
+ def _clean_str(self, in_str):
18
+ for x in self.__class__.PREFIXES_TO_IGNORE:
19
+ in_str = in_str.replace(x, '')
20
+ return in_str
21
+
22
+ def filter(self, record):
23
+ # We only log the message if it has not been logged before
24
+ if record.levelno != DEBUG:
25
+ return True
26
+ clean_msg = self._clean_str(record.msg)
27
+ if clean_msg not in list(self.logged_messages):
28
+ self.logged_messages.add(clean_msg)
29
+ return True
30
+ return False
31
+
32
+
33
+ class PyEmailerLogger(EasyLogger):
34
+ ROOT_LOG_LOCATION_DEFAULT = Path(__project_root__, 'logs').resolve()
35
+
36
+ def __call__(self):
37
+ return self.logger
38
+
39
+ @staticmethod
40
+ def _add_dupe_debug_to_handler(handler: Handler):
41
+ dupe_debug_filter = DupeDebugFilter()
42
+ handler.addFilter(dupe_debug_filter)
43
+
44
+ def initialize_logger(self, logger=None, **kwargs) -> Union[Logger, '_EasyLoggerCustomLogger']:
45
+ self.logger = super().initialize_logger(logger=logger, **kwargs)
46
+ self.logger.propagate = False
47
+ return self.logger
48
+
49
+ def setup_email_handler(self, **kwargs):
50
+ """
51
+ Sets up the email handler for the logger using the OutlookEmailHandler.
52
+
53
+ :param kwargs: Keyword arguments to configure the email handler.
54
+ - email_msg: Specifies the email message content (default: None).
55
+ - logger_admins: Specifies the list of admin emails (default: None).
56
+ :return: None
57
+ :rtype: None
58
+ """
59
+ # noinspection PyTypeChecker
60
+ OutlookEmailHandler.VALID_EMAIL_MSG_TYPES = [Msg]
61
+ try:
62
+ # noinspection PyTypeChecker
63
+ email_handler = OutlookEmailHandler(email_msg=kwargs.get('email_msg', None),
64
+ project_name=self.project_name,
65
+ logger_dir_path=self.log_location,
66
+ recipient=kwargs.get('logger_admins', None))
67
+ except ValueError as e:
68
+ self.logger.error(e.args[0], exc_info=True)
69
+ raise e from None
70
+
71
+ email_handler.setLevel(ERROR)
72
+ email_handler.setFormatter(self.formatter)
73
+ self.logger.addHandler(email_handler)
74
+
75
+ def _add_filter_to_file_handler(self, handler: FileHandler):
76
+ self._add_dupe_debug_to_handler(handler)
77
+
78
+ def _add_filter_to_stream_handler(self, handler: StreamHandler):
79
+ self._add_dupe_debug_to_handler(handler)
80
+
81
+ @property
82
+ def project_name(self):
83
+ return super().project_name
84
+
85
+ @project_name.setter
86
+ def project_name(self, value):
87
+ if value is None:
88
+ value = __project_name__
89
+ super().__setattr__('_project_name', value)
90
+
91
+ def create_stream_handler(self, log_level_to_stream=WARNING, **kwargs):
92
+ stream_handler = kwargs.get('stream_handler_instance', StreamHandlerIgnoreExecInfo())
93
+ super().create_stream_handler(log_level_to_stream=log_level_to_stream,
94
+ stream_handler_instance=stream_handler, **kwargs)
@@ -0,0 +1,91 @@
1
+ from datetime import datetime
2
+ from logging import getLogger
3
+ from time import sleep
4
+ from typing import Union, Optional
5
+
6
+
7
+ class TheSandman:
8
+ """
9
+ A utility class to facilitate and log time delays with custom sleep durations.
10
+
11
+ Attributes:
12
+ DEFAULT_SLEEP_TIME_SECONDS (int): Default sleep duration in seconds if not specified.
13
+ sleep_time (int): Actual sleep time (in seconds) to be used by the instance.
14
+ sleep_time_string (str): Human-readable string representing the current sleep duration.
15
+ logger (Logger): Logging instance for logging messages related to sleep operations.
16
+
17
+ Methods:
18
+ sleep_time_string:
19
+ Property getter and setter for updating the human-readable sleep time string.
20
+
21
+ sleep_round():
22
+ Splits the sleep duration into two equal parts. It logs and prints messages depicting the current sleep state, including the remaining time, and sleeps for the given durations.
23
+ """
24
+ # default 600 secs = 10 minutes
25
+ DEFAULT_SLEEP_TIME_SECONDS = 600
26
+ DEFAULT_SNOOZE_EXPIRATION_LIMIT_HOURS = 24
27
+ SECONDS_IN_HOUR = 3600
28
+
29
+ def __init__(self, sleep_time_seconds=None, **kwargs):
30
+ self.snooze_expiration_limit_hours = kwargs.get('snooze_expiration_limit_hours',
31
+ self.__class__.DEFAULT_SNOOZE_EXPIRATION_LIMIT_HOURS)
32
+ self.sleep_time: int = sleep_time_seconds or self.__class__.DEFAULT_SLEEP_TIME_SECONDS
33
+ self._is_time_remaining = False
34
+ self._sleep_time_string = None
35
+ self.logger: getLogger = kwargs.get('logger', getLogger(__name__))
36
+ self.sleep_time_string = self.sleep_time
37
+ self.logger.info(f'TheSandman initialized - sleep time set as {self.sleep_time_string}')
38
+
39
+ @property
40
+ def sleep_time_string(self):
41
+ return self._sleep_time_string
42
+
43
+ @sleep_time_string.setter
44
+ def sleep_time_string(self, value: int):
45
+ if self._is_time_remaining:
46
+ more = 'more'
47
+ else:
48
+ more = ''
49
+ if value >= 60:
50
+ self._sleep_time_string = f'sleeping for {value // 60} {more} minute(s)'
51
+ else:
52
+ self._sleep_time_string = f'sleeping for {value} {more} second(s)'
53
+
54
+ def sleep(self, sleep_time_seconds: int, **kwargs):
55
+ """
56
+ :param sleep_time_seconds: The number of seconds the function should pause execution.
57
+ :type sleep_time_seconds: int
58
+ :return: None
59
+ :rtype: None
60
+ """
61
+
62
+ self.sleep_time_string = self.sleep_time if not self._is_time_remaining else sleep_time_seconds
63
+ self.logger.info(self.sleep_time_string, **kwargs)
64
+ sleep(sleep_time_seconds)
65
+
66
+ def sleep_in_rounds(self, rounds=2, **kwargs):
67
+ """
68
+ :param rounds: Number of intervals in which the total sleep time is divided. Defaults to 2.
69
+ :type rounds: int
70
+ :return: None
71
+ :rtype: None
72
+
73
+ """
74
+ dev_mode = kwargs.pop('dev_mode', True)
75
+ print_msg = kwargs.pop('print_msg', True)
76
+ self._is_time_remaining = False
77
+ for sr in range(rounds):
78
+ if sr == rounds - 1:
79
+ self._is_time_remaining = True
80
+ self.sleep((self.sleep_time // rounds), print_msg=print_msg, **kwargs)
81
+
82
+ @classmethod
83
+ def is_snooze_expired(cls, snoozed_at: datetime, snooze_expiration_limit_hours: Optional[int] = None):
84
+ if not snooze_expiration_limit_hours:
85
+ snooze_expiration_limit_hours = cls.DEFAULT_SNOOZE_EXPIRATION_LIMIT_HOURS
86
+ snooze_expiration_limit_seconds = snooze_expiration_limit_hours * cls.SECONDS_IN_HOUR
87
+ time_since_snooze = (datetime.now() - snoozed_at)
88
+ if time_since_snooze.total_seconds() >= snooze_expiration_limit_seconds:
89
+ #print('msg_snoozed expired! Unsnoozing now!')
90
+ return True
91
+ return False
@@ -0,0 +1,4 @@
1
+ from PyEmailerAJM.continuous_monitor.continuous_monitor import ContinuousMonitor
2
+ from PyEmailerAJM.continuous_monitor.continuous_monitor_alert_send import ContinuousMonitorAlertSend
3
+
4
+ __all__ = ['ContinuousMonitor', 'ContinuousMonitorAlertSend']
@@ -0,0 +1,88 @@
1
+ from abc import abstractmethod
2
+ from typing import Callable
3
+
4
+ from PyEmailerAJM.backend import AlertTypes
5
+ from PyEmailerAJM.continuous_monitor.backend.continuous_monitor_base import ContinuousMonitorBase
6
+ from PyEmailerAJM.msg import MsgFactory
7
+
8
+
9
+ class ContinuousMonitor(ContinuousMonitorBase):
10
+ TITLE_STRING = " Watching for emails with alerts in {} folder ".center(100, '*')
11
+ MSG_FACTORY_CLASS: MsgFactory = MsgFactory
12
+
13
+ def GetMessages(self, folder_index=None):
14
+ """
15
+ :param folder_index: Index of the folder from which messages are retrieved. Defaults to None if not specified.
16
+ :type folder_index: int, optional
17
+ :return: A list of sorted and filtered message objects, each containing an alert.
18
+ :rtype: list
19
+ """
20
+ msgs = super().GetMessages(folder_index)
21
+ sorted_msgs = [self.__class__.MSG_FACTORY_CLASS.get_msg(x, logger=self.logger, snooze_checker=self.snooze_tracker) for x in msgs]
22
+ alert_messages = [x for x in sorted_msgs if x is not None and x.msg_alert]
23
+ return alert_messages
24
+
25
+ def _set_args_for_endless_watch(self):
26
+ """
27
+ Sets specific arguments for the endless_watch process.
28
+
29
+ :return: None
30
+ :rtype: None
31
+ """
32
+ self.send_emails = False
33
+ self.auto_send = False
34
+ self.display_window = False
35
+
36
+ @abstractmethod
37
+ def _postprocess_alert(self, alert_level=None, **kwargs):
38
+ ...
39
+
40
+ def check_for_alerts(self):
41
+ """
42
+ Checks for emails in the specified folder and identifies if there are any alerts. Alerts,
43
+ if present, are categorized as overdue, warning, or critical warning, and are processed accordingly.
44
+ Then logs the result of the check.
45
+
46
+ :return: None
47
+ :rtype: None
48
+
49
+ """
50
+ self.logger.info("\nChecking for emails with an alert...", print_msg=True)
51
+ self.refresh_messages()
52
+ if self.has_overdue:
53
+ self._print_and_postprocess(AlertTypes.OVERDUE)
54
+
55
+ elif self.has_warning:
56
+ self._print_and_postprocess(AlertTypes.WARNING)
57
+
58
+ elif self.has_critical_warning:
59
+ self._print_and_postprocess(AlertTypes.CRITICAL_WARNING)
60
+
61
+ else:
62
+ self.logger.info(f"No emails with an alert detected in {self.read_folder}", print_msg=True)
63
+
64
+ self.snooze_tracker.snooze_msgs(self.all_messages)
65
+
66
+ def endless_watch(self, stop_condition: Callable[[], bool] = None):
67
+ if not self.dev_mode:
68
+ self._set_args_for_endless_watch()
69
+
70
+ stop_condition = stop_condition or (lambda: False) # Default stop_condition
71
+ email_dir_name = self.read_folder.name if self.read_folder else None
72
+
73
+ self.logger.info(self.__class__.TITLE_STRING.format(email_dir_name), print_msg=True)
74
+
75
+ while not stop_condition():
76
+ try:
77
+ self.check_for_alerts()
78
+ self._was_refreshed = False
79
+ self.sleep_timer.sleep_in_rounds()
80
+ except KeyboardInterrupt:
81
+ self.logger.error("KeyboardInterrupt detected, exiting program.")
82
+ break
83
+
84
+
85
+ if __name__ == '__main__':
86
+ ContinuousMonitor.MSG_FACTORY_CLASS.ALERT_SUBJECT_KEYWORDS = ['training']
87
+ cm = ContinuousMonitor(False, False, dev_mode=False, show_warning_logs_in_console=True, )
88
+ cm.endless_watch()
@@ -0,0 +1,120 @@
1
+ from typing import Optional
2
+
3
+ from PyEmailerAJM.continuous_monitor import ContinuousMonitor
4
+
5
+ NO_COLORIZER = False
6
+
7
+
8
+ class ContinuousMonitorAlertSend(ContinuousMonitor):
9
+ ADMIN_EMAIL_LOGGER = []
10
+ ADMIN_EMAIL = []
11
+ DEFAULT_SUBJECT = "Email Alert"
12
+ DEFAULT_MSG_BODY = ("Dear {admin_email_names},\n\n"
13
+ "There is an Email in the inbox that has an alert ({msg_tuple}). \n\n"
14
+ "Thanks,\n"
15
+ "{email_sender}")
16
+ ATTRS_TO_CHECK = ['ADMIN_EMAIL', 'ADMIN_EMAIL_LOGGER']
17
+
18
+ def __init__(self, display_window: bool, send_emails: bool, **kwargs):
19
+
20
+ super().__init__(display_window, send_emails, **kwargs)
21
+ if not self.dev_mode:
22
+ if type(self) is ContinuousMonitorAlertSend:
23
+ self.__class__.check_for_class_attrs(self.__class__.ATTRS_TO_CHECK)
24
+ else:
25
+ self.logger.warning(f"IS DEV MODE - NOT checking for class attributes "
26
+ f"({', '.join(self.__class__.ATTRS_TO_CHECK)}) for ContinuousMonitorAlertSend")
27
+
28
+ def __init_subclass__(cls, **kwargs):
29
+ cls.check_for_class_attrs(cls.ATTRS_TO_CHECK)
30
+
31
+ def _set_args_for_endless_watch(self):
32
+ self.send_emails = True
33
+ self.auto_send = True
34
+ self.display_window = False
35
+ self.logger.debug("send_emails, auto_send, and display_window set to True for endless_watch()")
36
+
37
+ def SetupEmail(self, recipient: Optional[str] = None, subject: str = DEFAULT_SUBJECT,
38
+ text: str = None, attachments: list = None, **kwargs):
39
+ """
40
+ :param recipient: Email recipient(s). If not provided, defaults to ADMIN_EMAIL or a semicolon-separated string of recipients in case of a list.
41
+ :type recipient: Optional[str]
42
+ :param subject: Subject of the email. Defaults to DEFAULT_SUBJECT.
43
+ :type subject: str
44
+ :param text: Body text of the email. If not provided, defaults to the response_body attribute.
45
+ :type text: str
46
+ :param attachments: A list of attachments to include in the email.
47
+ :type attachments: list
48
+ :param kwargs: Additional keyword arguments passed to the parent SetupEmail method.
49
+ :type kwargs: dict
50
+ :return: The resulting email setup performed by the superclass's SetupEmail method.
51
+ :rtype: Any
52
+ """
53
+ if not recipient:
54
+ recipient = self.__class__.ADMIN_EMAIL
55
+ if isinstance(recipient, list):
56
+ recipient = ' ;'.join(recipient)
57
+ if not text:
58
+ text = self.response_body
59
+ return super().SetupEmail(recipient=recipient, subject=subject,
60
+ text=text, attachments=attachments, **kwargs)
61
+
62
+ def get_response_body_alert_level(self, msg: '_AlertMsgBase'):
63
+ """
64
+ :param msg: The message object which contains the alert level information.
65
+ :type msg: _AlertMsgBase
66
+ :return: The alert level string, optionally colorized if coloring is enabled.
67
+ :rtype: str
68
+ """
69
+ if NO_COLORIZER:
70
+ self.logger.debug("colorizer not available, using plain text for alert level")
71
+ rb_alert_string = msg.__class__.ALERT_LEVEL.name
72
+ else:
73
+ self.logger.debug("colorizer available, using colorized alert level")
74
+ color = self.colorizer.get_alert_color(msg.__class__.ALERT_LEVEL)
75
+ rb_alert_string = self.colorizer.colorize(msg.__class__.ALERT_LEVEL.name,
76
+ color=color,
77
+ html_mode=True)
78
+ return rb_alert_string
79
+
80
+ @property
81
+ def email_signature(self):
82
+ return ('<br>'.join(super().email_signature.split('\n'))
83
+ if super().email_signature is not None else None)
84
+
85
+ @property
86
+ def response_body(self):
87
+ """
88
+ Processes and formats the response body by compiling alert messages and their corresponding alert levels,
89
+ then generating a formatted string containing a summary of these messages.
90
+
91
+ :return: Processed and formatted response body string
92
+ :rtype: str
93
+ """
94
+ alert_msgs = [(x.subject, self.get_response_body_alert_level(x)) for x in self.GetMessages()]
95
+ msg_tuple = ', '.join([' - '.join(x) for x in alert_msgs])
96
+ formatted_admin_email_names = ', '.join([x.split('@')[0] for
97
+ x in self.__class__.ADMIN_EMAIL]
98
+ ).replace('\n', '<br>')
99
+ formatted_full_body = self.__class__.DEFAULT_MSG_BODY.format(email_sender=self.email_signature,
100
+ msg_tuple=msg_tuple,
101
+ admin_email_names=formatted_admin_email_names
102
+ ).replace('\n', '<br>')
103
+ return formatted_full_body
104
+
105
+ def _postprocess_alert(self, alert_level=None, **kwargs):
106
+ self.SendOrDisplay(**kwargs)
107
+
108
+ def refresh_messages(self):
109
+ self.SetupEmail()
110
+ super().refresh_messages()
111
+
112
+
113
+ if __name__ == '__main__':
114
+ ContinuousMonitorAlertSend.MSG_FACTORY_CLASS.ALERT_SUBJECT_KEYWORDS = ['training']
115
+ ContinuousMonitorAlertSend.ADMIN_EMAIL = ['amcsparron@albanyny.gov']
116
+ ContinuousMonitorAlertSend.ADMIN_EMAIL_LOGGER = ContinuousMonitorAlertSend.ADMIN_EMAIL
117
+ cm = ContinuousMonitorAlertSend(False, False,
118
+ dev_mode=False,
119
+ show_warning_logs_in_console=True) #, email_sig_filename='Andrew Full.txt')
120
+ cm.endless_watch()
@@ -0,0 +1,5 @@
1
+ # pylint: disable=cyclic-import, wrong-import-position
2
+ from PyEmailerAJM.msg.msg import Msg, FailedMsg
3
+ from PyEmailerAJM.msg.factory import MsgFactory
4
+
5
+ __all__ = ['Msg', 'FailedMsg', 'MsgFactory']