diff --git a/src/server/BreCal/services/email_handling.py b/src/server/BreCal/services/email_handling.py index e06021d..58ac710 100644 --- a/src/server/BreCal/services/email_handling.py +++ b/src/server/BreCal/services/email_handling.py @@ -15,13 +15,13 @@ from email.mime.application import MIMEApplication class EmailHandler(): """ - Creates an EmailHandler, which is capable of connecting to a mail server at a respective port, - as well as logging into a specific user's mail address. - Upon creating messages, these can be sent via this handler. + Creates an EmailHandler, which is capable of connecting to a mail server at a respective port, + as well as logging into a specific user's mail address. + Upon creating messages, these can be sent via this handler. Options: mail_server: address of the server, such as 'smtp.gmail.com' or 'w01d5503.kasserver.com - mail_port: + mail_port: 25 - SMTP Port, to send emails 110 - POP3 Port, to receive emails 143 - IMAP Port, to receive from IMAP @@ -38,6 +38,9 @@ class EmailHandler(): self.server = smtplib.SMTP_SSL(self.mail_server, self.mail_port) # alternatively, SMTP + # set the following to 0 to avoid log spamming + self.server.set_debuglevel(1) # 0: no debug, 1: debug + def check_state(self): """check, whether the server login took place and is open.""" try: @@ -45,7 +48,7 @@ class EmailHandler(): return status_code==250 # 250: b'2.0.0 Ok' except smtplib.SMTPServerDisconnected: return False - + def check_connection(self): """check, whether the server object is connected to the server. If not, connect it. """ try: @@ -53,7 +56,7 @@ class EmailHandler(): except smtplib.SMTPServerDisconnected: self.server.connect(self.mail_server, self.mail_port) return - + def check_login(self)->bool: """check, whether the server object is logged in as a user""" user = self.server.__dict__.get("user",None) @@ -61,8 +64,8 @@ class EmailHandler(): def login(self, interactive:bool=True): """ - login on the determined mail server's mail address. By default, this function opens an interactive window to - type the password without echoing (printing '*******' instead of readable characters). + login on the determined mail server's mail address. By default, this function opens an interactive window to + type the password without echoing (printing '*******' instead of readable characters). returns (status_code, status_msg) """ @@ -77,7 +80,7 @@ class EmailHandler(): def create_email(self, subject:str, message_body:str)->EmailMessage: """ Create an EmailMessage object, which contains the Email's header ("Subject"), content ("Message Body") and the sender's address ("From"). - The EmailMessage object does not contain the recipients yet, as these will be defined upon sending the Email. + The EmailMessage object does not contain the recipients yet, as these will be defined upon sending the Email. """ msg = EmailMessage() msg["Subject"] = subject @@ -85,16 +88,16 @@ class EmailHandler(): #msg["To"] = email_tgts # will be defined in self.send_email msg.set_content(message_body) return msg - + def build_recipients(self, email_tgts:list[str]): """ - email formatting does not support lists. Instead, items are joined into a comma-space-separated string. - Example: - [mail1@mail.com, mail2@mail.com] becomes + email formatting does not support lists. Instead, items are joined into a comma-space-separated string. + Example: + [mail1@mail.com, mail2@mail.com] becomes 'mail1@mail.com, mail2@mail.com' """ return ', '.join(email_tgts) - + def open_mime_application(self, path:str)->MIMEApplication: """open a local file, read the bytes into a MIMEApplication object, which is built with the proper subtype (based on the file extension)""" with open(path, 'rb') as file: @@ -102,24 +105,24 @@ class EmailHandler(): attachment.add_header('Content-Disposition','attachment',filename=str(os.path.basename(path))) return attachment - + def attach_file(self, path:str, msg:email.mime.multipart.MIMEMultipart)->None: """ - attach a file to the message. This function opens the file, reads its bytes, defines the mime type by the - path extension. The filename is appended as the header. + attach a file to the message. This function opens the file, reads its bytes, defines the mime type by the + path extension. The filename is appended as the header. mimetypes: # https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Common_types """ attachment = self.open_mime_application(path) msg.attach(attachment) return - + def send_email(self, msg:EmailMessage, email_tgts:list[str], cc_tgts:typing.Optional[list[str]]=None, bcc_tgts:typing.Optional[list[str]]=None, debug:bool=False)->typing.Union[dict,EmailMessage]: """ - send a prepared email message to recipients (email_tgts), copy (cc_tgts) and blind copy (bcc_tgts). - Returns a dictionary of feedback, which is commonly empty and the EmailMessage. - - When failing, this function returns an SMTP error instead of returning the default outputs. + send a prepared email message to recipients (email_tgts), copy (cc_tgts) and blind copy (bcc_tgts). + Returns a dictionary of feedback, which is commonly empty and the EmailMessage. + + When failing, this function returns an SMTP error instead of returning the default outputs. """ # Set the Recipients msg["To"] = self.build_recipients(email_tgts) @@ -130,15 +133,15 @@ class EmailHandler(): if bcc_tgts is not None: msg["Bcc"] = self.build_recipients(bcc_tgts) - # when debugging, do not send the Email, but return the EmailMessage. + # when debugging, do not send the Email, but return the EmailMessage. if debug: return {}, msg - + assert self.check_login(), f"currently not logged in. Cannot send an Email. Make sure to properly use self.login first. " # send the prepared EmailMessage via the server. feedback = self.server.send_message(msg) return feedback, msg - + def translate_mail_to_multipart(self, msg:EmailMessage): """EmailMessage does not support HTML and attachments. Hence, one can convert an EmailMessage object.""" if msg.is_multipart(): @@ -159,11 +162,11 @@ class EmailHandler(): # attach the remainder of the msg, such as the body, to the MIMEMultipart msg_new.attach(msg) return msg_new - + def print_email_attachments(self, msg:MIMEMultipart)->list[str]: """return a list of lines of an Email, which contain 'filename=' as a list. """ return [line_ for line_ in msg.as_string().split("\n") if "filename=" in line_] - + def close(self): self.server.__dict__.pop("user",None) self.server.__dict__.pop("password",None) diff --git a/src/server/BreCal/validators/validation_error.py b/src/server/BreCal/validators/validation_error.py index 30d093a..5040d28 100644 --- a/src/server/BreCal/validators/validation_error.py +++ b/src/server/BreCal/validators/validation_error.py @@ -59,7 +59,7 @@ def create_validation_error_response(ex:ValidationError, status_code:int=400, cr if create_log: logging.warning(ex) if ex is not None else logging.warning(message) - print(ex) if ex is not None else print(message) + # print(ex) if ex is not None else print(message) return (serialized_response, status_code) def create_werkzeug_error_response(ex:Forbidden, status_code:int=403, create_log:bool=True)->typing.Tuple[str,int]: @@ -71,7 +71,7 @@ def create_werkzeug_error_response(ex:Forbidden, status_code:int=403, create_log if create_log: logging.warning(ex) if ex is not None else logging.warning(message) - print(ex) if ex is not None else print(message) + # print(ex) if ex is not None else print(message) return serialized_response, status_code def create_dynamic_exception_response(ex, status_code:int=400, message:typing.Optional[str]=None, create_log:bool=True): @@ -83,5 +83,5 @@ def create_dynamic_exception_response(ex, status_code:int=400, message:typing.Op if create_log: logging.warning(ex) if ex is not None else logging.warning(message) - print(ex) if ex is not None else print(message) + # print(ex) if ex is not None else print(message) return (serialized_response, status_code) diff --git a/src/server/BreCal/validators/validation_rules.py b/src/server/BreCal/validators/validation_rules.py index 3cc5729..605ed4d 100644 --- a/src/server/BreCal/validators/validation_rules.py +++ b/src/server/BreCal/validators/validation_rules.py @@ -52,7 +52,8 @@ class ValidationRules(ValidationRuleFunctions): # 'translate' all error codes into readable, human-understandable format. evaluation_results = [(state, self.describe_error_message(msg)) for (state, msg) in evaluation_results] - logging.info(f"Validation results for shipcall {shipcall.id}: {evaluation_results}") + if evaluation_results: + logging.info(f"Validation results for shipcall {shipcall.id}: {evaluation_results}") # check, what the maximum state flag is and return it evaluation_state = np.max(np.array([result[0].value for result in evaluation_results])) if len(evaluation_results)>0 else StatusFlags.GREEN.value @@ -92,7 +93,7 @@ class ValidationRules(ValidationRuleFunctions): if evaluation_states_old[0] != evaluation_states_new[0]: pooledConnection = getPoolConnection() commands = pydapper.using(pooledConnection) - notification_type = 3 # RED (mapped to time_conflict) + notification_type = 3 # RED (mapped to time_conflict) if evaluation_states_new[0] == 2: match evaluation_states_old[0]: case 0: