Skip to content

Commit 208e2a4

Browse files
authoredJan 26, 2024··
Merge pull request #58 from TeskaLabs/Enhancement/Generate-Iris-Error-Messages
Generate ASABIris error
2 parents 81605cf + 6e05b6e commit 208e2a4

File tree

8 files changed

+206
-76
lines changed

8 files changed

+206
-76
lines changed
 

‎asabiris/errors.py

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import enum
2+
3+
4+
class ASABIrisError(Exception):
5+
6+
def __init__(self, error_code, error_dict=None, tech_message=None, error_i18n_key=None):
7+
super().__init__()
8+
self.ErrorCode = error_code
9+
self.ErrorDict = error_dict
10+
self.TechMessage = tech_message
11+
# Adds a prefix to an error key (internationalization)
12+
if error_i18n_key is not None:
13+
self.Errori18nKey = "IrisError|" + error_i18n_key
14+
else:
15+
self.Errori18nKey = "IrisError|"
16+
17+
18+
class ErrorCode(enum.Enum):
19+
20+
def get_i18n_name(self):
21+
"""
22+
Adds a prefix to an error name.
23+
"""
24+
return 'asab-iris|{}'.format(self.name)
25+
26+
INVALID_FORMAT = 1001
27+
INVALID_PATH = 1002
28+
TEMPLATE_VARIABLE_UNDEFINED = 1003
29+
TEMPLATE_NOT_FOUND = 1004
30+
TEMPLATE_SYNTAX_ERROR = 1005
31+
JINJA2_ERROR = 1006
32+
GENERAL_ERROR = 1007

‎asabiris/exceptions.py

+14-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@ def __init__(self, message=None, *args, use_case=None, invalid_path=None):
1414
if message is not None:
1515
super().__init__(message, *args)
1616
elif invalid_path is not None:
17-
message = "The entered path '{}' is not correct. Please move your files to '/Templates/{}/'.".format(invalid_path, use_case)
17+
message = "The entered path '{}' is not correct. Please move your files to '/Templates/{}/'.".format(
18+
invalid_path, use_case)
1819
super().__init__(message, *args)
1920
else:
2021
super().__init__(message, *args)
@@ -47,3 +48,15 @@ def __init__(self, message=None, *args, template_path=None, variable_name=None):
4748
else:
4849
message = "'{}' in Jinja2 template '{}'.".format(variable_name, template_path)
4950
super().__init__(message, *args)
51+
52+
53+
class Jinja2TemplateSyntaxError(Exception):
54+
def __init__(self, message=None, *args, template_path=None, syntax_error=None):
55+
self.TemplatePath = template_path
56+
self.SyntaxError = syntax_error
57+
58+
if message is not None:
59+
super().__init__(message, *args)
60+
else:
61+
message = "'{}' in Jinja2 template '{}'.".format(syntax_error, template_path)
62+
super().__init__(message, *args)

‎asabiris/formatter/attachments.py

+17-3
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
import asab
88

9-
from ..exceptions import PathError, FormatError
9+
from ..errors import ASABIrisError, ErrorCode
1010
from ..utils import normalize_body
1111

1212

@@ -103,7 +103,14 @@ async def render_attachment(self, attachments, prefered_format='html'):
103103
)
104104

105105
else:
106-
raise FormatError(format=fmt)
106+
raise ASABIrisError(
107+
ErrorCode.INVALID_FORMAT,
108+
tech_message="Unsupported attachment format '{}' for template '{}'".format(fmt, template),
109+
error_i18n_key="The format '{{invalid_format}}' is not supported for attachment",
110+
error_dict={
111+
"invalid_format": fmt,
112+
}
113+
)
107114

108115
else:
109116
raise RuntimeError("Unknown attachment in API call")
@@ -122,6 +129,13 @@ def _determine_file_name(self, attachment) -> str:
122129

123130
async def _render_template(self, template, params):
124131
if not template.startswith('/Templates/Attachment/'):
125-
raise PathError(use_case='Attachment', invalid_path=template)
132+
raise ASABIrisError(
133+
ErrorCode.INVALID_PATH,
134+
tech_message="Cannot render attachment: Invalid template path for '{}'. Move it to '/Templates/Attachment/'.".format(template),
135+
error_i18n_key="Incorrect template path '{{incorrect_path}}'. Please move your templates to '/Templates/Attachment/'.",
136+
error_dict={
137+
"incorrect_path": template,
138+
}
139+
)
126140

127141
return await self.JinjaService.format(template, params)

‎asabiris/formatter/jinja/service.py

+48-3
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
import json
77
import jinja2
88

9-
from ...exceptions import PathError, Jinja2TemplateUndefinedError
9+
from ...errors import ASABIrisError, ErrorCode
1010
from ...formater_abc import FormatterABC
1111

1212
#
@@ -72,7 +72,14 @@ async def format(self, template_path, template_params):
7272
# Load the template
7373
template_io = await self.App.LibraryService.read(template_path)
7474
if template_io is None:
75-
raise PathError("Template '{}' not found".format(template_path))
75+
raise ASABIrisError(
76+
ErrorCode.TEMPLATE_NOT_FOUND,
77+
tech_message="Failed to render. Reason: Template {} does not exist".format(template_path),
78+
error_i18n_key="Template '{{incorrect_path}}' does not exist",
79+
error_dict={
80+
"incorrect_path": template_path,
81+
}
82+
)
7683
try:
7784
template = self.Environment.from_string(template_io.read().decode('utf-8'))
7885

@@ -82,7 +89,45 @@ async def format(self, template_path, template_params):
8289
# Do the rendering
8390
return template.render(context)
8491
except jinja2.exceptions.UndefinedError as e:
85-
raise Jinja2TemplateUndefinedError(template_path=template_path, variable_name=str(e))
92+
raise ASABIrisError(
93+
ErrorCode.TEMPLATE_VARIABLE_UNDEFINED,
94+
tech_message="'{}' is undefined in Jinja2 template '{}'.".format(template_path, e),
95+
error_i18n_key="Undefined variable: '{{variable_name}}' found in template path: '{{template_path}}'.",
96+
error_dict={
97+
"variable_name": str(e),
98+
"template_path": template_path
99+
}
100+
)
101+
except jinja2.TemplateSyntaxError as e:
102+
raise ASABIrisError(
103+
ErrorCode.TEMPLATE_SYNTAX_ERROR,
104+
tech_message="Syntax error: '{}' in Jinja2 template '{}'.".format(str(e), template_path),
105+
error_i18n_key="Syntax Error: '{{syntax_error}}' found in template path: '{{template_path}}'.",
106+
error_dict={
107+
"syntax_error": str(e),
108+
"template_path": template_path
109+
}
110+
)
111+
except jinja2.exceptions.TemplateError as e:
112+
raise ASABIrisError(
113+
ErrorCode.JINJA2_ERROR,
114+
tech_message="Jinja2 error '{}' occurred in template '{}'.".format(str(e), template_path),
115+
error_i18n_key="We encountered a problem '{{jinja2_error}}' located at: '{{template_path}}'.",
116+
error_dict={
117+
"jinja2_error": str(e),
118+
"template_path": template_path
119+
}
120+
)
121+
except Exception as e:
122+
raise ASABIrisError(
123+
ErrorCode.GENERAL_ERROR,
124+
tech_message="Error rendering template '{}': {}".format(template_path, str(e)),
125+
error_i18n_key="Error rendering '{{template_path}}': '{{error_message}}'.",
126+
error_dict={
127+
"template_path": template_path,
128+
"error_message": str(e)
129+
}
130+
)
86131

87132

88133
def construct_context(context, *other_dicts):

‎asabiris/handlers/kafkahandler.py

+19-5
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import asyncio
2+
import configparser
23
import json
34
import logging
45

@@ -10,29 +11,43 @@
1011

1112
from asabiris.schemas.emailschema import email_schema
1213
from asabiris.schemas.slackschema import slack_schema
14+
1315
#
1416

1517
L = logging.getLogger(__name__)
1618

1719

1820
#
1921

22+
def check_config(config, section, parameter):
23+
try:
24+
value = config.get(section, parameter)
25+
return value
26+
except configparser.NoOptionError:
27+
L.error("Configuration parameter '{}' is missing in section '{}'.".format(parameter, section))
28+
exit()
2029

21-
class KafkaHandler(asab.Service):
2230

31+
class KafkaHandler(asab.Service):
2332
# validate slack and email messages
2433
ValidationSchemaMail = fastjsonschema.compile(email_schema)
2534
ValidationSchemaSlack = fastjsonschema.compile(slack_schema)
35+
2636
# TODO: ValidationSchemaMSTeams = fastjsonschema.compile(msteams_schema)
2737

2838
def __init__(self, app, service_name="KafkaHandler"):
2939
super().__init__(app, service_name)
3040

3141
self.Task = None
3242
self.JinjaService = app.get_service("JinjaService")
33-
topic = asab.Config.get("kafka", "topic")
34-
group_id = asab.Config.get("kafka", "group_id")
35-
bootstrap_servers = list(asab.Config.get("kafka", "bootstrap_servers").split(","))
43+
try:
44+
topic = check_config(asab.Config, "kafka", "topic")
45+
group_id = check_config(asab.Config, "kafka", "group_id")
46+
bootstrap_servers = check_config(asab.Config, "kafka", "bootstrap_servers").split(",")
47+
except configparser.NoOptionError:
48+
L.error("Configuration missing. Required parameters: Kafka/group_id/bootstrap_servers")
49+
exit()
50+
3651
self.Consumer = AIOKafkaConsumer(
3752
topic,
3853
group_id=group_id,
@@ -103,7 +118,6 @@ async def dispatch(self, msg):
103118
else:
104119
L.warning("Message type '{}' not implemented.".format(msg_type))
105120

106-
107121
async def send_email(self, json_data):
108122
await self.App.SendEmailOrchestrator.send_email(
109123
email_to=json_data["to"],

‎asabiris/handlers/webhandler.py

+28-14
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
from ..schemas.teamsschema import teams_schema
1313

1414
from ..exceptions import SMTPDeliverError, PathError, FormatError, Jinja2TemplateUndefinedError
15+
from ..errors import ASABIrisError
16+
1517
import slack_sdk.errors
1618
#
1719

@@ -74,7 +76,7 @@ async def send_email(self, request, *, json_data):
7476
}
7577
7678
```
77-
Attached will be retrieved from request.content when rendering the email is not required.
79+
Attached will be retrieved from request. Content when rendering the email is not required.
7880
7981
Example of the email body template:
8082
```
@@ -105,20 +107,32 @@ async def send_email(self, request, *, json_data):
105107
body_params=json_data["body"].get("params", {}), # Optional
106108
attachments=json_data.get("attachments", []), # Optional
107109
)
108-
except KeyError as e:
109-
raise aiohttp.web.HTTPNotFound(text="{}".format(e))
110-
111-
except jinja2.exceptions.UndefinedError as e:
112-
raise aiohttp.web.HTTPBadRequest(text="Jinja2 error: {}".format(e))
113-
114-
except SMTPDeliverError:
115-
raise aiohttp.web.HTTPServiceUnavailable(text="SMTP error")
116-
117-
except PathError as e:
118-
raise aiohttp.web.HTTPNotFound(text="{}".format(e))
110+
except ASABIrisError as e:
111+
response = {
112+
"result": "ERROR",
113+
"error": e.Errori18nKey,
114+
"error_dict": e.ErrorDict,
115+
"tech_err": e.TechMessage
116+
}
117+
return aiohttp.web.json_response(response, status=400)
119118

120-
except FormatError as e:
121-
raise aiohttp.web.HTTPBadRequest(text="{}".format(e))
119+
except KeyError as e:
120+
response = {
121+
"result": "ERROR",
122+
"error": "{{message}}",
123+
"error_dict": {"message": str(e)},
124+
"tech_err": str(e)
125+
}
126+
return aiohttp.web.json_response(response, status=404)
127+
128+
except SMTPDeliverError as e:
129+
response = {
130+
"result": "ERROR",
131+
"error": "{{message}}",
132+
"error_dict": {"message": str(e)},
133+
"tech_err": str(e)
134+
}
135+
return aiohttp.web.json_response(response, status=503)
122136

123137
# More specific exception handling goes here so that the service provides nice output
124138
return asab.web.rest.json_response(request, {"result": "OK"})

‎asabiris/orchestration/sendemail.py

+43-45
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
import logging
1616
from typing import List, Tuple, Dict
1717

18-
from ..exceptions import PathError, FormatError
18+
from ..errors import ASABIrisError, ErrorCode
1919

2020
#
2121

@@ -62,52 +62,44 @@ async def send_email(
6262
Send an email using specified parameters.
6363
...
6464
"""
65-
try:
66-
body_params = body_params or {}
67-
attachments = attachments or []
68-
email_cc = email_cc or []
69-
email_bcc = email_bcc or []
70-
71-
# Rendering the template
72-
body_html, email_subject_body = await self._render_template(body_template, body_params)
73-
74-
# If email_subject is not provided or is empty use email_subject_body
75-
if email_subject is None or email_subject == '':
76-
email_subject = email_subject_body
77-
78-
# Processing attachments
79-
atts_gen = self.AttachmentRenderingService.render_attachment(attachments)
80-
81-
# Sending the email
82-
await self.SmtpService.send(
83-
email_from=email_from,
84-
email_to=email_to,
85-
email_cc=email_cc,
86-
email_bcc=email_bcc,
87-
email_subject=email_subject,
88-
body=body_html,
89-
attachments=atts_gen,
90-
)
91-
L.info("Email sent successfully to: {}".format(', '.join(email_to)))
92-
93-
# TODO: Capture common exceptions and print useful error messages
94-
except Exception as e:
95-
L.exception("Error occurred when preparing the email")
96-
97-
error_message, error_subject = self._generate_error_message(str(e))
98-
await self.SmtpService.send(
99-
email_from=email_from,
100-
email_to=email_to,
101-
email_cc=email_cc,
102-
email_bcc=email_bcc,
103-
email_subject=error_subject,
104-
body=error_message
105-
)
65+
body_params = body_params or {}
66+
attachments = attachments or []
67+
email_cc = email_cc or []
68+
email_bcc = email_bcc or []
69+
70+
# Rendering the template
71+
body_html, email_subject_body = await self._render_template(body_template, body_params)
72+
73+
# If email_subject is not provided or is empty use email_subject_body
74+
if email_subject is None or email_subject == '':
75+
email_subject = email_subject_body
76+
77+
# Processing attachments
78+
atts_gen = self.AttachmentRenderingService.render_attachment(attachments)
79+
80+
# Sending the email
81+
await self.SmtpService.send(
82+
email_from=email_from,
83+
email_to=email_to,
84+
email_cc=email_cc,
85+
email_bcc=email_bcc,
86+
email_subject=email_subject,
87+
body=body_html,
88+
attachments=atts_gen,
89+
)
90+
L.info("Email sent successfully to: {}".format(', '.join(email_to)))
10691

10792

10893
async def _render_template(self, template: str, params: Dict) -> Tuple[str, str]:
10994
if not template.startswith('/Templates/Email/'):
110-
raise PathError(use_case='Email', invalid_path=template)
95+
raise ASABIrisError(
96+
ErrorCode.INVALID_PATH,
97+
tech_message="Incorrect template path '{}'. Move templates to '/Templates/Email/".format(template),
98+
error_i18n_key="Incorrect template path '{{incorrect_path}}'. Please move your templates to '/Templates/Email/",
99+
error_dict={
100+
"incorrect_path": template,
101+
}
102+
)
111103

112104
jinja_output = await self.JinjaService.format(template, params)
113105

@@ -121,8 +113,14 @@ async def _render_template(self, template: str, params: Dict) -> Tuple[str, str]
121113
return body, subject
122114

123115
else:
124-
raise FormatError(format=ext)
125-
116+
raise ASABIrisError(
117+
ErrorCode.INVALID_FORMAT,
118+
tech_message="Unsupported conversion format '{}' for template '{}'".format(ext, template),
119+
error_i18n_key="The format '{{invalid_format}}' is not supported",
120+
error_dict={
121+
"invalid_format": ext,
122+
}
123+
)
126124

127125
def _generate_error_message(self, specific_error: str) -> Tuple[str, str]:
128126
timestamp = datetime.datetime.now(tz=datetime.timezone.utc).strftime("%Y-%m-%d %H:%M:%S")

‎asabiris/output/smtp/service.py

+5-5
Original file line numberDiff line numberDiff line change
@@ -143,23 +143,23 @@ async def send(
143143

144144
except aiosmtplib.errors.SMTPConnectError as e:
145145
L.error("Connection failed: {}".format(e), struct_data={"host": self.Host, "port": self.Port})
146-
raise SMTPDeliverError("SMTP delivery failed")
146+
raise SMTPDeliverError("SMTP delivery failed.Reason: {}".format(e))
147147

148148
except aiosmtplib.errors.SMTPAuthenticationError as e:
149149
L.exception("Generic error: {}".format(e), struct_data={"host": self.Host})
150-
raise SMTPDeliverError("SMTP delivery failed")
150+
raise SMTPDeliverError("SMTP delivery failed.Reason: {}".format(e))
151151

152152
except aiosmtplib.errors.SMTPResponseException as e:
153153
L.error("SMTP Error", struct_data={"message": e.message, "code": e.code, "host": self.Host})
154-
raise SMTPDeliverError("SMTP delivery failed")
154+
raise SMTPDeliverError("SMTP delivery failed.Reason: {}".format(e))
155155

156156
except aiosmtplib.errors.SMTPServerDisconnected as e:
157157
L.error("Server disconnected: {}; check the SMTP credentials".format(e), struct_data={"host": self.Host})
158-
raise SMTPDeliverError("SMTP delivery failed")
158+
raise SMTPDeliverError("SMTP delivery failed.Reason: {}".format(e))
159159

160160
except Exception as e:
161161
L.error("Generic error: {}; check credentials".format(e), struct_data={"host": self.Host})
162-
raise SMTPDeliverError("SMTP delivery failed")
162+
raise SMTPDeliverError("SMTP delivery failed.Reason: {}".format(e))
163163

164164
L.log(asab.LOG_NOTICE, "Email sent", struct_data={'result': result[1], "host": self.Host})
165165

0 commit comments

Comments
 (0)
Please sign in to comment.