Send pandas dataframe data as html e-mail - python

I want to send a pandas dataframe data as an HTML e-mail. Based on this post I could create an html with the dataframe. Code
import pandas as pd
import numpy as np
HEADER = '''
<html>
<head>
</head>
<body>
'''
FOOTER = '''
</body>
</html>
'''
df = pd.DataFrame([[1.1, 1.1, 1.1, 2.6, 2.5, 3.4,2.6,2.6,3.4,3.4,2.6,1.1,1.1,3.3], list('AAABBBBABCBDDD')]).T
with open('test.html', 'w') as f:
f.write(HEADER)
f.write(df.to_html(classes='df'))
f.write(FOOTER)
Now I want to send this as a html e-mail. I tried this. Can not figure out how to attach the html file?

Pandas has a function for this.
This will give the the html code for the table, after which you can embed it into an email with:
df = DataFrame(data)
email = " some html {df} lah lah"
email = email.format(df=df.to_html())

Finally found. This is the way it should be done.
filename = "test.html"
f = file(filename)
attachment = MIMEText(f.read(),'html')
msg.attach(attachment)

Related

Sending a pandas Dataframe using smtplib

I've seen a lot of threads here about this topic, however, none regarding this specific question.
I am sending a email with a pandas dataframe (df) as an html using pandas built in df.to_html() method. The email sends successfully. However, the df is displayed in the email as html, not in the desired table format. Can anyone offer assistance on how to ensure the df is displayed as a table, not in html in the email? The code is below:
import requests
import pandas as pd
import smtplib
MY_LAT =
MY_LNG =
API_KEY = ""
parameters = {
"lat": MY_LAT,
'lon': MY_LNG,
'exclude': "",
"appid": API_KEY
}
df = pd.read_csv("OWM.csv")
response = requests.get("https://api.openweathermap.org/data/2.5/onecall", params=parameters)
response.raise_for_status()
data = response.json()
consolidated_weather_12hour = []
for i in range(0, 12):
consolidated_weather_12hour.append((data['hourly'][i]['weather'][0]['id']))
hour7_forecast = []
for hours in consolidated_weather_12hour:
weather_id = df[df.weather_id == hours]
weather_description = weather_id['description']
for desc in weather_description.iteritems():
hour7_forecast.append(desc[1])
times = ['7AM', '8AM', '9AM', '10AM', '11AM', '12PM', '1PM', '2PM', '3PM', '4PM', '5PM', '6PM']
col_header = ["Description of Expected Weather"]
weather_df = pd.DataFrame(data=hour7_forecast, index=times, columns=col_header)
my_email = ""
password = ""
html_df = weather_df.to_html()
with smtplib.SMTP("smtp.gmail.com", 587) as connection:
connection.starttls() # Makes connection secure
connection.login(user=my_email, password=password)
connection.sendmail(from_addr=my_email, to_addrs="",
msg=f"Subject: 12 Hour Forecast Sterp"
"""\
<html>
<head></head>"
<body>
{0}
<body>
</html>
""".format(html_df))
just use df.to_html() to convert it into an html table that you can include in your html email
then when you send the mail you must set the mimetype to html
smtp = smtplib.SMTP("...")
msg = MIMEMultipart('alternative')
msg['Subject'] = subject_line
msg['From'] = from_addr
msg['To'] = ','.join(to_addrs)
# Create the body of the message (a plain-text and an HTML version).
part1 = MIMEText(plaintext, 'plain')
part2 = MIMEText(html, 'html')
smtp.sendmail(from_addr, to_addrs, msg.as_string())
you can use the library html2text to convert your html to markdown for clients that do not support html content (not many these days) if you do not feel like writing the plaintext on your own
as an aside... using jinja when you are working with html tends to simplify things...

Sending two Pandas dataframes side-by-side using HTML in email

I'm trying to send some summary on my shares portfolio creation via email. I'm using Python + Pandas for the calculations and email.mime module to send html via email.
I am using Pandas to_html method and email.mime module to include the html in the email:
import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
import numpy as np
import pandas as pd
import pickle
from utils import mail
def send_fancy_mail(subject, text_message, html_message):
msg = MIMEMultipart('alternative')
msg['Subject'] = subject
msg['From'] = 'mymail#domain.com'
msg['To'] = settings.MAIL_RECIPIENTS
# Record the MIME types of both parts - text/plain and text/html.
part1 = MIMEText(text_message, 'plain')
part2 = MIMEText(html_message, 'html')
msg.attach(part1)
msg.attach(part2)
# Send the message via our own SMTP server.
s = smtplib.SMTP_SSL(settings.SMTP_HOST)
s.login(settings.SMTP_USER, settings.SMTP_PASSWORD)
s.send_message(msg)
s.quit()
fake_or_load = 'fake'
if fake_or_load == 'fake':
n_signals = 20
symbols = np.round(1000*np.random.rand(n_signals)).astype(int)
direction = ["Buy" for i in range(n_signals//2)]
direction.extend(["Sell" for i in range(n_signals//2)])
quantity = np.round(10000*np.random.rand(n_signals)).astype(int)
portfolio = pd.DataFrame({'symbols': symbols, 'direction': direction, 'quantity': quantity})
elif fake_or_load == 'load':
with open('c:\\\\temp\\signals_list', 'rb') as fp:
signals = pickle.load(fp)
portfolio = pd.DataFrame(signals)
portfolio.rename(index=str, inplace=True, columns={0: "symbol", 1: "direction", 2: "quantity"})
shares_to_buy = portfolio[portfolio['direction'] == 'Buy'].copy()
shares_to_buy.sort_values(by='quantity', inplace=True, ascending=False)
shares_to_sell = portfolio[portfolio['direction'] == 'Sell'].copy()
shares_to_sell.sort_values(by='quantity', inplace=True, ascending=False)
# The basic way to convert portfolio to html:
html_to_buy = shares_to_buy.to_html(index=False, header=True, col_space=20, justify='center')
html_to_sell = shares_to_sell.to_html(index=False, header=True, col_space=20, justify='center')
mail_body = "Test Message"
css = """
.div {
flex-direction: row;
}
"""
html_body = """
<html>
<head>
<style>{}</style>
</head>
<body>
<div>{}</div>
<div>{}</div>
</body>
</html>
""".format(css, html_to_buy, html_to_sell)
mail.send_fancy_mail("Test Mail", mail_body, html_body)
I would like to get two columns side-by-side in the email body, preferably able to regulate the column width as well. Is it possible to make the tables responsive?
If you want to create two columns, replace the two divs with the below table. Div's dont have the same support as tables on all email clients.
<table style="width:100%">
<tr>
<td>column 1</td>
<td>column 2</td>
</tr>
</table>

Adding colors to individual columns of a dataframe when sending dataframe as table in an email?

Color individual columns while sending data frame over email
I have a data frame that i am emailing as a table with an attachment and the code works nice and dandy, but i now require to color some specific columns with a different color, i tried changing the html part of rendering table and also tried the Style functionality of pandas data frame but to no avail. Here is my code:
from tabulate import tabulate
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
import smtplib
import pandas as pd
from email.mime.base import MIMEBase
from email import encoders
me = 'myemail#gmail'
password = 'mypassword'
server = 'smtp.gmail.com:587'
you = 'myreceipient#gmail.com'
text = """
Hello, Friend.
Here is your data:
{table}
Regards,
Me"""
# here i tried to directly apply a color style into the table via <p> tag
html = """
<html>
<head>
<style>
table, th, td {{ border: 1px solid black; border-collapse: collapse; }}
th, td {{ padding: 5px; }}
</style>
</head>
<body><p>Hello, Friend This data is from a data frame.</p>
<p>Here is your data:</p>
<p style = "color: yellow;" >
{table}
</p>
<p>Regards,</p>
<p>Me</p>
</body></html>
"""
df = pd.read_csv("MyFile.csv")
col_list = list(df.columns.values)
# here again a tried to setstyle directly on the pandas dataframe.
df.style.set_properties(**{'background-color': 'white',
'color': 'yellow',
'border-color': 'black'})
data = df
text = text.format(table=tabulate(data, headers=col_list, tablefmt="grid"))
html = html.format(table=tabulate(data, headers=col_list, tablefmt="html"))
message = MIMEMultipart(
"alternative", None, [MIMEText(text), MIMEText(html,'html')])
message['Subject'] = "sending early morning "
message['From'] = me
message['To'] = you
import glob, os
os.chdir("D://FolderLogFileUploads//")
file_list = []
filename = "File1.csv"
for file in glob.glob("*.csv"):
file_list.append(file)
if filename in file_list:
target_file = filename
attachment = open(target_file, 'rb')
part = MIMEBase('application', 'octet-stream')
part.set_payload((attachment).read())
encoders.encode_base64(part)
part.add_header('Content-Disposition', "attachment; filename= "+filename)
message.attach(part)
server = smtplib.SMTP(server)
server.ehlo()
server.starttls()
server.login(me, password)
server.sendmail(me, you, message.as_string())
print("message with attachment sent successfully")
server.quit()
PS:my col list is ["name", "city", "e_id", "pay_band"] and i want my col: "e_id" and "city" to be of a specific color say yellow.

Include Excel table in the body of an email in Python

Using Python, I am trying to send an email with an Excel table inside the body of an email. I would like to maintain all the conditional formatting from the Excel file. I can send the Excel file as an attachment easy, but I would like to also put the table inside the body of the email. I will convert it to an HTML table if I need to, but I need to know how to include the HTML table into body of the email then. Below attaches the file as an email, but I haven't been able to figure out how to put the table inside of the email. How can I do this?
msg = MIMEMultipart()
msg['Subject'] = 'Subject goes here'
msg.attach(MIMEText('Text goes here'))
part = MIMEBase('application', "octet-stream")
f = 'file_name.xlsx'
part.set_payload(open(f, "rb").read())
encoders.encode_base64(part)
part.add_header('Content-Disposition', 'attachment; filename="%s"' % f)
msg.attach(part)
Thanks for your help!
The easiest way to do is use pandas. Something like:
import pandas as pd
import smtplib
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
def generate_html():
read_file = pd.read_csv("example.csv")
html_file = read_file.to_html()
sendEmail(html_file)
def sendEmail(html_file):
msg = MIMEMultipart('alternative')
msg['Subject'] = "Hello"
msg['From'] = EmailFrom
msg['To'] = EmailTo
part = MIMEText(html_file, 'html')
msg.attach(part)
s = smtplib.SMTP("smtp.gmail.com")
s.sendmail(EmailFrom, EmailTo, msg.as_string())
s.quit()
You might want to look into openpyxl https://openpyxl.readthedocs.io/en/default/
Something like this might solve your problem:
import openpyxl
from openpyxl import load_workbook
workbook = load_workbook(f)
worksheet = workbook.get_active_sheet()
html_data = """
<html>
<head>
<title>
XLSX to HTML demo
<title>
<head>
<body>
<h3>
XLSX to HTML demo
<h3>
<table>
"""
ws_range = worksheet.range('A1:H13')
for row in ws_range:
html_data += "<tr>
for cell in row:
if cell.value is None:
html_data += "<td> + ' ' + "<td>
else:
html_data += "<td> + str(cell.value) + "<td>
html_data += "<tr>
html_data += "</table></body></html>
msg.attach(MIMEText(html_data))
with open(f, "rb") as fil:
part = MIMEApplication(
fil.read(),
Name=basename(f)
)
part['Content-Disposition'] = 'attachment; filename="{0}"'.format(basename(f))
msg.attach(part)
Inspired by https://jugad2.blogspot.ch/2013/11/publish-microsoft-excel-xlsx-data-to.html?m=1

python3 + Pandas styles + Change alternate row color

Hi i am using Pandas and displaying a table.
I there a function to apply alternate row color to make it clear to read.
Using below code I am sending table in mail and it works.
my code:
count = 1000
df = pandas.DataFrame.from_dict(result)
df["Total"] = df.T.sum()
html = """<!DOCTYPE html>
<html>
<body>
<h3> %i</h3>
{table_content}
</body>
</html>
""" % count
# Create message container - the correct MIME type is
# multipart/alternative.
msg = MIMEMultipart('alternative')
msg['Subject'] = " Report"
msg['From'] = sender
msg['To'] = recipients
part2 = MIMEText(html.df(
table_content=df.to_html(na_rep="0")), 'html')
msg.attach(part2)
You can use CSS, namely the tr:nth-child in combination with df.to_html(classes)
Adopt to your case:
from IPython.display import display, HTML
from sklearn.datasets import load_iris
import pandas as pd
import numpy as np
iris = load_iris()
df = pd.DataFrame(data= np.c_[iris['data'], iris['target']],
columns= iris['feature_names'] + ['target'])
HTML('''
<style>
.df tbody tr:nth-child(even) { background-color: lightblue; }
</style>
''' + df.to_html(classes="df"))
Update: Expanding to a specific example
I slightly rearranged the code to allow adding css, as it was conflicting with {} used by .format. You can add your variables to html_variables dict and use %()s to embed them into html. If your html becomes too complicated I recommend looking at using some template engine to make it more robust. Otherwise the code below should be self-explanatory.
html_template = '''<!DOCTYPE html>
<html>
<head>
<style>.df tbody tr:nth-child(even) {background-color: lightblue;}</style>
</head>
<body>
<h3>%(other_var)s</h3>
%(table_content)s
</body>
</html>
'''
html_vars = {'other_var':'IRIS Dataset','table_content':df.to_html(classes="df")}
html = html_template % html_vars
# Create message container - the correct MIME type is
# multipart/alternative.
msg = MIMEMultipart('alternative')
msg['Subject'] = "Report"
msg['From'] = sender
msg['To'] = recipient
part2 = MIMEText(html, 'html')
msg.attach(part2)
Old question, but I found a solution within pandas framework which I put for future use:
def rower(data):
s = data.index % 2 != 0
s = pd.concat([pd.Series(s)] * data.shape[1], axis=1) #6 or the n of cols u have
z = pd.DataFrame(np.where(s, 'background-color:#f2f2f2', ''),
index=data.index, columns=data.columns)
return z
df.style.apply(rower, axis=None)
Essentially the same as #oleg's answer, but rather than the html blocks you can use df.style.set_table_styles like such:
df.style.set_table_styles([{"selector":"tbody tr:nth-child(even)","props":[("background-color","lightgrey")]}])
I find it useful to use the df.style and have my own dark-theme styler. Feel free to use if it is helpful:
def dfdark(styler):
#styler.background_gradient(cmap='coolwarm')
#styler.color('white')
styler.set_table_styles([
{
"selector":"thead",
"props":[("background-color","grey")]
},
{
"selector":"tbody tr:nth-child(even)",
"props":[("background-color","lightgrey")]
},
{
"selector":"th.row_heading",
"props":[("background-color","grey")]
},
{
"selector":"td",
"props":[("border","white")]
},
])
return styler
#styler.format(color='grey')
df=pd.DataFrame(index=np.arange(10),columns=[1,2],data=np.random.normal(size=[10,2]))
df.style.pipe(dfdark)
which outputs:

Categories

Resources