kootenpv / yagmail

Send email in Python conveniently for gmail using yagmail
MIT License
2.66k stars 265 forks source link

Displaying Styled Dataframes Inline #49

Closed nutcrackerhf closed 5 years ago

nutcrackerhf commented 8 years ago

Hi - thanks for building this module.

Pandas 0.18 includes a style attribute that makes dataframes pretty. When rendered in html for inclusion in an email using render, the dataframe is nicely displayed inline as html and can be saved to an html file on the local path.

yagmail is able to send an email form a notebook, but the resulting message body contains degraded versions of the html dataframe rendering, irrespective of the method with which it was added to the yagmail code.

Code below shows an example of a styled dataframe which looks nice in a notebook. It is then converted to html and attempts are made to embed the html in-line with fidelity to the original styling. All attempts fail.

import numpy as np
import pandas as pd
import yagmail

#Create Array
num = np.array([[ 0.17899619,  0.33093259,  0.2076353,   0.06130814,],
            [ 0.20392888,  0.42653105,  0.33325891,  0.10473969,],
            [ 0.17038247,  0.19081956,  0.10119709,  0.09032416,],
            [-0.10606583, -0.13680513, -0.13129103, -0.03684349,],
            [ 0.20319428,  0.28340985,  0.20994867,  0.11728491,],
            [ 0.04396872,  0.23703525,  0.09359683,  0.11486036,],
            [ 0.27801304, -0.05769304, -0.06202813,  0.04722761,],])

days = ['5 days', '10 days', '20 days', '60 days']

prices = ['AAPL', 'ADBE', 'AMD', 'AMZN', 'CRM', 'EXPE', 'FB']

#Convert Array to DataFrame
df = pd.DataFrame(num, index=prices, columns=days)

#Style the DataFrame

df_styled = df.style\
  .bar(color='#d65f5f')\
  .set_precision(2)\
  .set_caption('Set Title of the Table')

# Render the styled df in html
df_styled_html = df_styled.render()

#Save the styled/rendered html tables as a file
with open('styled_rendered.html', 'w') as f:
    f.write(df_styled_html)

#Send email

yag = yagmail.SMTP()

subject = 'Styled DataFrame in HTML Format'

contents = [df_styled_html,
        'styled_rendered.html', 
        yagmail.inline(df_styled),
        yagmail.inline(df_styled_html),
        yagmail.inline('styled_rendered.html')]

yag.send('user@domain.com', subject, contents)

When df_styled_html is passed, we get a CSS string in the body of the email. Perhaps the email code is not appplying CSS styules to the dataframe natively?

Thanks.

kootenpv commented 8 years ago

I know that to apply CSS in any email, it cannot be <style> tags, but has to be like <h1 style="color:red">text</h1>. Maybe that insight helps?

Also, I think you do not want inline... Try it without using yagmail.inline, and then using an html_string. This will then get parsed as a whole and become the email message. Other option is to pass a html filepath string.

E.g.: yag.send(contents=df_styled_html). or yag.send(contents='styled_rendered.html')

If you can apply CSS onto this HTML, that's what you probably need. You might want to consider stripping \n, since they are converted to </br>

nutcrackerhf commented 8 years ago

Thanks. Python has a built-in html escaper which can be applied to a styled/rendered html object like df_styled_rendered in the sample code above:

import html
df_styled_html_escaped = html.escape(df_styled_html)

You can then pass df_styled_html_escaped into the yagmail function using yag.send(contents=df_styled_html_escaped).

This function sends an email which begins with the appropriate <style type="text/css">. Unfortunately it is printed at the top of the email body, rather than interpreted and rendered as styled html.

kootenpv commented 8 years ago

Just in case it wasn't clear: it is email that does not allow <style> tags, not yagmail. So the CSS has to be on the nodes themselves rather than in a separate <style> tag. You could perhaps write a function that applies the styling onto the nodes in your html? Or am I the one misunderstanding what you've tried?

cyung-aa commented 6 years ago

Hi, I have such similar question. The same html string with css and <style> tags can be displayed using msg=MIMEMultipart() ... part=MIMEText(html_string,'html',_charset="utf-8") msg.attach(part) smtpObj.sendmail(_user,to_list,msg.as_string())

But yag.send(contents=html_string) doesn't work

lisrael1 commented 6 years ago

like kootenpv wrote - seems like the problem is that style tags are not inline i used 'from premailer import transform' to fix the render() output and it worked example: html=df.round(2).style.bar(subset=['a', 'c'], align='mid', color=['#d65f5f', '#5fba7d']).render() html_inline=transform(html)

kootenpv commented 5 years ago

Just throwing it out there, this is now also possible:

yag.send(contents="test\ntest", newline_to_break=False)

Closing, but feel free to open.