-
Notifications
You must be signed in to change notification settings - Fork 5.7k
/
report.py
137 lines (122 loc) · 5.15 KB
/
report.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
# SPDX-License-Identifier: Apache-2.0
"""
Shows how to get a list of work items from a storage object, render it in both
HTML and text formats, and use Amazon Simple Email Service (Amazon SES) to send it as
an email report.
When the list of items is longer than a specified threshold, it is included as a CSV
attachment to the email instead of in the body of the email itself.
"""
import logging
from datetime import datetime
from email.mime.application import MIMEApplication
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from botocore.exceptions import ClientError
from flask import jsonify, render_template
from flask.views import MethodView
from storage import StorageError
from webargs import fields
from webargs.flaskparser import use_kwargs
logger = logging.getLogger(__name__)
class Report(MethodView):
"""
Encapsulates a report resource that gets work items from an
Amazon DynamoDB table and uses Amazon SES to send emails about them.
"""
def __init__(self, storage, email_sender, ses_client):
"""
:param storage: An object that manages moving data in and out of the underlying
table.
:param email_sender: The email address from which the email report is sent.
:param ses_client: A Boto3 Amazon SES client.
"""
self.storage = storage
self.email_sender = email_sender
self.ses_client = ses_client
def _format_mime_message(self, recipient, text, html, attachment, charset="utf-8"):
"""
Formats the report as a MIME message. When the the email contains an attachment,
it must be sent in MIME format.
"""
msg = MIMEMultipart("mixed")
msg["Subject"] = "Work items"
msg["From"] = self.email_sender
msg["To"] = recipient
msg_body = MIMEMultipart("alternative")
textpart = MIMEText(text.encode(charset), "plain", charset)
htmlpart = MIMEText(html.encode(charset), "html", charset)
msg_body.attach(textpart)
msg_body.attach(htmlpart)
att = MIMEApplication(attachment.encode(charset))
att.add_header("Content-Disposition", "attachment", filename="work_items.csv")
msg.attach(msg_body)
msg.attach(att)
return msg
@use_kwargs({"email": fields.Str(required=True)})
def post(self, email):
"""
Gets a list of work items from storage, makes a report of them, and
sends an email. The email is sent in both HTML and text format.
When 10 or fewer items are in the report, the items are included in the body
of the email. Otherwise, the items are included as an attachment in CSV format.
When your Amazon SES account is in the sandbox, both the sender and recipient
email addresses must be registered with Amazon SES.
:param email: The recipient's email address.
:return: An error message and an HTTP result code.
"""
response = None
result = 200
try:
work_items = self.storage.get_work_items(archived=False)
snap_time = datetime.now()
print(f"Sending report of {len(work_items)} items to {email}.")
html_report = render_template(
"report.html",
work_items=work_items,
item_count=len(work_items),
snap_time=snap_time,
)
text_report = render_template(
"report.txt",
work_items=work_items,
item_count=len(work_items),
snap_time=snap_time,
)
if len(work_items) > 10:
item_csv = render_template("work_items.csv", work_items=work_items)
mime_msg = self._format_mime_message(
email, text_report, html_report, item_csv
)
response = self.ses_client.send_raw_email(
Source=self.email_sender,
Destinations=[email],
RawMessage={"Data": mime_msg.as_string()},
)
else:
self.ses_client.send_email(
Source=self.email_sender,
Destination={"ToAddresses": [email]},
Message={
"Subject": {"Data": "Work items"},
"Body": {
"Html": {"Data": html_report},
"Text": {"Data": text_report},
},
},
)
except StorageError as err:
logger.exception(
"Couldn't get work items from storage. Here's why: %s", err
)
response = "A storage error occurred."
result = 500
except ClientError as err:
logger.exception(
"Couldn't send email. Here's why: %s: %s",
err.response["Error"]["Code"],
err.response["Error"]["Message"],
)
response = "An email error occurred."
result = 500
return jsonify(response), result