Send the contents from unmodified print statement by e-mail in python - python

I have a script that runs main() and at the end I want to send the contents it has by e-mail. I don't want to write new files nor anything. Just have the original script be unmodified and at the end just send the contents of what it printed. Ideal code:
main()
send_mail()
I tried this:
def main():
print('HELLOWORLD')
def send_email(subject='subject', message='', destination='me#gmail.com', password_path=None):
from socket import gethostname
from email.message import EmailMessage
import smtplib
import json
import sys
server = smtplib.SMTP('smtp.gmail.com', 587)
smtplib.stdout = sys.stdout # <<<<<-------- why doesn't it work?
server.starttls()
with open(password_path) as f:
config = json.load(f)
server.login('me#gmail.com', config['password'])
# craft message
msg = EmailMessage()
#msg.set_content(message)
msg['Subject'] = subject
msg['From'] = 'me#gmail.com'
msg['To'] = destination
# send msg
server.send_message(msg)
if __name__ == '__main__':
main()
send_mail()
but it doesn't work.
I don't want to write other files or change the original python print statements. How to do this?
I tried this:
def get_stdout():
import sys
print('a')
print('b')
print('c')
repr(sys.stdout)
contents = ""
#with open('some_file.txt') as f:
#with open(sys.stdout) as f:
for line in sys.stdout.readlines():
contents += line
print(contents)
but it does not let me read sys.stdout because it says its not readable. How can I open it in readable or change it to readable in the first place?
I checked all of the following links but none helped:
How to send output from a python script to an email address
https://www.quora.com/How-can-I-send-an-output-from-a-Python-script-to-an-email-address
https://bytes.com/topic/python/answers/165835-email-module-redirecting-stdout
Redirect stdout to a file in Python?
How to handle both `with open(...)` and `sys.stdout` nicely?
Capture stdout from a script?

To send e-mails I am using:
def send_email(subject, message, destination, password_path=None):
from socket import gethostname
from email.message import EmailMessage
import smtplib
import json
server = smtplib.SMTP('smtp.gmail.com', 587)
server.starttls()
with open(password_path) as f:
config = json.load(f)
server.login('me123#gmail.com', config['password'])
# craft message
msg = EmailMessage()
message = f'{message}\nSend from Hostname: {gethostname()}'
msg.set_content(message)
msg['Subject'] = subject
msg['From'] = 'me123#gmail.com'
msg['To'] = destination
# send msg
server.send_message(msg)
note I have my password in a json file using an app password as suggested by this answer https://stackoverflow.com/a/60996409/3167448.
using this to collect the contents from stdout by writing it to a custom stdout file using the builtin function print:
import sys
from pathlib import Path
def my_print(*args, filepath='~/my_stdout.txt'):
filepath = Path(filepath).expanduser()
# do normal print
__builtins__['print'](*args, file=sys.__stdout__) #prints to terminal
# open my stdout file in update mode
with open(filepath, "a+") as f:
# save the content we are trying to print
__builtins__['print'](*args, file=f) #saves in a file
def collect_content_from_file(filepath):
filepath = Path(filepath).expanduser()
contents = ''
with open(filepath,'r') as f:
for line in f.readlines():
contents = contents + line
return contents
Note the a+ to be able to create the file if it already does NOT exist.
Note that if you want to delete the old contents of your custom my_stdout.txt you need to delete the file and check if it exists:
# remove my stdout if it exists
os.remove(Path('~/my_stdout.txt').expanduser()) if os.path.isfile(Path('~/my_stdout.txt').expanduser()) else None
The credits for the print code are from the answer here: How does one make an already opened file readable (e.g. sys.stdout)?

Related

How to process an incoming email with email.message.EmailMessage class in Python?

I made an incoming email-handler, but it's so huge, I think that it can be simplified. I have read module email docs and find class EmailMessage, but i don't understand how to process an incoming email. How I can do that?
contents = sys.stdin.read()
msg = email.message_from_string(contents)
A great modern example of using the email library for parsing e-mail message is given in the official documentation:
import os
import sys
import tempfile
import mimetypes
import webbrowser
# Import the email modules we'll need
from email import policy
from email.parser import BytesParser
# An imaginary module that would make this work and be safe.
from imaginary import magic_html_parser
# In a real program you'd get the filename from the arguments.
with open('outgoing.msg', 'rb') as fp:
msg = BytesParser(policy=policy.default).parse(fp)
# Now the header items can be accessed as a dictionary, and any non-ASCII will
# be converted to unicode:
print('To:', msg['to'])
print('From:', msg['from'])
print('Subject:', msg['subject'])
# If we want to print a preview of the message content, we can extract whatever
# the least formatted payload is and print the first three lines. Of course,
# if the message has no plain text part printing the first three lines of html
# is probably useless, but this is just a conceptual example.
simplest = msg.get_body(preferencelist=('plain', 'html'))
print()
print(''.join(simplest.get_content().splitlines(keepends=True)[:3]))
ans = input("View full message?")
if ans.lower()[0] == 'n':
sys.exit()
# We can extract the richest alternative in order to display it:
richest = msg.get_body()
partfiles = {}
if richest['content-type'].maintype == 'text':
if richest['content-type'].subtype == 'plain':
for line in richest.get_content().splitlines():
print(line)
sys.exit()
elif richest['content-type'].subtype == 'html':
body = richest
else:
print("Don't know how to display {}".format(richest.get_content_type()))
sys.exit()
elif richest['content-type'].content_type == 'multipart/related':
body = richest.get_body(preferencelist=('html'))
for part in richest.iter_attachments():
fn = part.get_filename()
if fn:
extension = os.path.splitext(part.get_filename())[1]
else:
extension = mimetypes.guess_extension(part.get_content_type())
with tempfile.NamedTemporaryFile(suffix=extension, delete=False) as f:
f.write(part.get_content())
# again strip the <> to go from email form of cid to html form.
partfiles[part['content-id'][1:-1]] = f.name
else:
print("Don't know how to display {}".format(richest.get_content_type()))
sys.exit()
with tempfile.NamedTemporaryFile(mode='w', delete=False) as f:
# The magic_html_parser has to rewrite the href="cid:...." attributes to
# point to the filenames in partfiles. It also has to do a safety-sanitize
# of the html. It could be written using html.parser.
f.write(magic_html_parser(body.get_content(), partfiles))
webbrowser.open(f.name)
os.remove(f.name)
for fn in partfiles.values():
os.remove(fn)
# Of course, there are lots of email messages that could break this simple
# minded program, but it will handle the most common ones.

Select a random file from a directory and send it (Python, MIME)

I am working on a python program that randomly selects a file from a directory and then sends it to you using the email.mimemodule. I am having a problem where I can choose the random file but I can't sent it due to this error:
File "C:\Users\Mihkel\Desktop\dnak.py", line 37, in sendmemeone
attachment =open(filename, 'rb')
TypeError: expected str, bytes or os.PathLike object, not list
Here is the code:
import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from email.mime.base import MIMEBase
from email import encoders
import os
import random
path ='C:/Users/Mihkel/Desktop/memes'
files = os.listdir(path)
index = random.randrange(0, len(files))
print(files[index])
def send():
email_user = 'yeetbotmemes#gmail.com'
email_send = 'miku.rebane#gmail.com'
subject = 'Test'
msg = MIMEMultipart()
msg['From'] = email_user
msg['To'] = email_send
msg['Subject'] = subject
body = 'Here is your very own dank meme of the day:'
msg.attach(MIMEText (body, 'plain'))
filename=files
attachment =open(filename, 'rb')
part = MIMEBase('application','octet-stream')
part.set_payload((attachment).read())
encoders.encode_base64(part)
part.add_header('Content-Disposition',"attachment;
filename= "+filename)
msg.attach(part)
text = msg.as_string()
server = smtplib.SMTP('smtp.gmail.com',587)
server.starttls()
server.login(email_user,"MY PASSWORD")
server.sendmail(email_user,email_send,text)
server.quit()
I believe it is just getting the filename as the selected random choice, how could I get it to select the file itself?
EDIT: After making the changes recommended I am now getting this error:
File "C:\Users\Mihkel\Desktop\e8re.py", line 29, in send
part.add_header('Content-Disposition',"attachment; filename= "+filename)
TypeError: can only concatenate str (not "list") to str
Seems like this part is still taking in the list, how would I fix that?
You select a random file and then throw it away (well, you print it, then throw it away):
files = os.listdir(path)
index = random.randrange(0, len(files))
print(files[index])
(which BTW you can do with random.choice(files))
and when calling open you pass it the entire files list:
filename = files
attachment = open(filename, 'rb')
Instead, pass open the file you selected:
attachment = open(random.choice(files), 'rb')
But, this still wouldn't work since listdir only returns the filenames and not the full path, so you will need to get it back, preferably with os.path.join:
attachment = open(os.path.join(path, random.choice(files)), 'rb')

Sending multiple HTML Tables in email

I have 3 HTML Tables from three .html files that I need to email using Python one below the other.
Currently only 1 table is getting attached. How to attach all 3?
#!/usr/bin/python
import time
import os,sys
from os import path
import re
import sys, ast
import subprocess
# Import smtplib for the actual sending function
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from subprocess import Popen, PIPE
msg = MIMEMultipart('alternative')
# Open a plain text file for reading. For this example, assume that
# the text file contains only ASCII characters.
html = open('/root/madhu_test/bpstest/results/outnss.html')
htmla = open('/root/madhu_test/bpstest/results/outs2c.html')
htmlb = open('/root/madhu_test/bpstest/results/outrecommended.html')
html = html.read()
htmla = htmla.read()
htmlb = htmlb.read()
part2 = MIMEText(html, 'html')
part3 = MIMEText(htmla, 'html')
part4 = MIMEText(htmlb, 'html')
msg.attach(part2)
msg.attach(part3)
msg.attach(part4)
msg["From"] = "sauravb#juniper.net"
msg["To"] = "sauravb#juniper.net"
msg["Subject"] = "Sanity performance report"
p = Popen(["/usr/sbin/sendmail", "-t", "-oi"], stdin=PIPE)
p.communicate(msg.as_string())
I had the same requirement. I used a single variable called "report" that contains all the html code in it. So I keep adding any number of tables to report varaible
report = ""
report += table1data
report += "<br><br>"
report += table2data
.
.
Finally attach this variable as the email text.
This worked for me.
A better solution might be to change MIMEMultipart('alternative') to MIMEMultipart('mixed')
This way it appends each attachment as opposed to choosing the best match for the email client.

Python Watchdog error when files are downloaded from FTP

I'm using Watchdog to watch a directory for new .xml files being downloaded via ftplib on a time interval. When Watchdog see the file, on_created() triggers a function to process/parse the xml, but it seems that the file download hasn't completed yet causing an missing data error in the subsequent function.
I've added a time.sleep(1) before function is called which has alleviated the error, but adding a delay seems like an unreliable method in the real world. I'm wondering if there's a method similar to a promise function I can use vs. a delay. Or maybe I've completely misdiagnosed the issue and there's a simple answer? Open to any suggestion.
FYI... the files sizes can vary from roughly 100K to 4-5mg.
FTP Function
def download(f):
ftpt = ftplib.FTP(server)
ftpt.login(username, password)
ftpt.cwd(ftp_dir)
print 'Connected to FTP directory'
if f.startswith('TLC-EMAILUPDATE'):
if os.path.exists(dl_dir + f) == 0:
fhandle = open(os.path.join(dl_dir, f), 'wb')
print 'Getting ' + f
ftpt.retrbinary('RETR ' + f, fhandle.write)
fhandle.close()
elif os.path.exists(dl_dir + f) == 1:
print 'File', f, 'Already Exists, Skipping Download'
ftp = ftplib.FTP(server)
ftp.login(username, password)
ftp.cwd(ftp_dir)
infiles = ftp.nlst()
pool = Pool(4)
pool.map(download, in files)
Watchdog
def on_created(self, event):
self.processfile(event)
base = os.path.basename(event.src_path)
if base.startswith('TLC-EMAILUPDATE'):
print 'File for load report has been flagged'
xmldoc = event.src_path
if os.path.isfile(xmldoc) == 1:
print 'File download complete'
send_email(xmldoc)
Send Mail (with sleep)
The exception is thrown at the content variable where the parsing fails to read any data from the downloaded file.
def send_email(xmldoc):
time.sleep(2)
content = str(parse_xml.create_template(xmldoc))
msg = MIMEText(content, TEXT_SUBTYPE)
msg['Subject'] = EMAIL_SUBJECT
msg['From'] = EMAIL_SENDER
msg['To'] = listToStr(EMAIL_RECEIVERS)
try:
smtpObj = SMTP(GMAIL_SMTP, GMAIL_SMTP_PORT)
smtpObj.ehlo()
smtpObj.starttls()
smtpObj.ehlo()
smtpObj.login(user=EMAIL_SENDER, password=EMAIL_PASS)
smtpObj.sendmail(EMAIL_SENDER, EMAIL_RECEIVERS, msg.as_string())
smtpObj.quit()
print 'Email has been sent to %s' % EMAIL_RECEIVERS
except SMTPException as error:
print 'Error: unable to send email : {err}'.format(err=error)
Simple answer: switch to monitoring the CLOSE_WRITE event. Alas Watchdog doesn't support it directly. Either:
1) switch to pyinotify and use the following code -- Linux only, not OSX
2) use Watchdog with on_any_event()
pyinotify example source
import os, sys
import pyinotify
class VideoComplete(pyinotify.ProcessEvent):
def process_IN_CLOSE_WRITE(self, event):
sys.stdout.write(
'video complete: {}\n'.format(event.pathname)
)
sys.stdout.flush()
def main():
wm = pyinotify.WatchManager()
notifier = pyinotify.Notifier(
wm, default_proc_fun=VideoComplete(),
)
mask = pyinotify.ALL_EVENTS
path = os.path.expanduser('~/Downloads/incoming')
wm.add_watch(path, mask, rec=True, auto_add=True)
notifier.loop()
if __name__=='__main__':
main()
download a file
echo beer > ~/Downloads/incoming/beer.txt
output
video complete: /home/johnm/Downloads/incoming/beer.txt

How to send a zip file as an attachment in python?

I have looked through many tutorials, as well as other question here on stack overflow, and the documentation and explanation are at minimum, just unexplained code. I would like to send a file that I already have zipped, and send it as an attachment. I have tried copy and pasting the code provided, but its not working, hence I cannot fix the problem.
So what I am asking is if anyone knows who to explain how smtplib as well as email and MIME libraries work together to send a file, more specifically, how to do it with a zip file. Any help would be appreciated.
This is the code that everyone refers to:
import smtplib
import zipfile
import tempfile
from email import encoders
from email.message import Message
from email.mime.base import MIMEBase
from email.mime.multipart import MIMEMultipart
def send_file_zipped(the_file, recipients, sender='you#you.com'):
myzip = zipfile.ZipFile('file.zip', 'w')
# Create the message
themsg = MIMEMultipart()
themsg['Subject'] = 'File %s' % the_file
themsg['To'] = ', '.join(recipients)
themsg['From'] = sender
themsg.preamble = 'I am not using a MIME-aware mail reader.\n'
msg = MIMEBase('application', 'zip')
msg.set_payload(zf.read())
encoders.encode_base64(msg)
msg.add_header('Content-Disposition', 'attachment',
filename=the_file + '.zip')
themsg.attach(msg)
themsg = themsg.as_string()
# send the message
smtp = smtplib.SMTP()
smtp.connect()
smtp.sendmail(sender, recipients, themsg)
smtp.close()
I suspect the issue is this code zips a file as well. I don't want to zip anything as I already have a zipped file I would like to send. In either case, this code is poorly documented as well as the python libraries themselves as they provide no insight on anything past img file and text files.
UPDATE: Error I am getting now. I have also updated what is in my file with the code above
Traceback (most recent call last):
File "/Users/Zeroe/Documents/python_hw/cgi-bin/zip_it.py", line 100, in <module>
send_file_zipped('hw5.zip', 'avaldez#oswego.edu')
File "/Users/Zeroe/Documents/python_hw/cgi-bin/zip_it.py", line 32, in send_file_zipped
msg.set_payload(myzip.read())
TypeError: read() takes at least 2 arguments (1 given)
I don't really see the problem. Just omit the part which creates the zip file and, instead, just load the zip file you have.
Essentially, this part here
msg = MIMEBase('application', 'zip')
msg.set_payload(zf.read())
encoders.encode_base64(msg)
msg.add_header('Content-Disposition', 'attachment',
filename=the_file + '.zip')
themsg.attach(msg)
creates the attachment. The
msg.set_payload(zf.read())
sets, well, the payload of the attachment to what you read from the file zf (probably meaning zip file).
Just open your zip file beforehand and let this line read from it.
I agree the email package is not well documented yet. I investigated it before and wrote a wrapper module that simplifies these kinds of tasks. For example, the following works:
from pycopia import ezmail
# Get the data
data = open("/usr/lib64/python2.7/test/zipdir.zip").read()
# Make a proper mime message object.
zipattachement = ezmail.MIMEApplication.MIMEApplication(data, "zip",
filename="zipdir.zip")
# send it.
ezmail.ezmail(["Here is the zip file.", zipattachement],
To="me#mydomain.com", From="me#mydomain.com", subject="zip send test")
And that's all you need once you have everything installed and configured. :-)
My answer uses shutil to zip a directory containing the attachments and then adds the .zip to the email.
# Importing Dependencies
from email.mime.multipart import MIMEMultipart
from email.mime.application import MIMEApplication
from email.mime.text import MIMEText
from email.mime.base import MIMEBase
from email import encoders
import smtplib
import shutil
def Send_Email():
# Create a multipart message
msg = MIMEMultipart()
Body = MIMEText( {Enter Email Body as str here} )
# Add Headers
msg['Subject'] = ''
msg['From'] = ''
msg['To'] = ''
msg['CC'] = ''
msg['BCC'] = ''
# Add body to email
msg.attach(Body)
# Using Shutil to Zip a Directory
dir_name = {Add Path of the Directory to be Zipped}
output_filename = {Add Output Zip File Path}
shutil.make_archive(output_filename, 'zip', dir_name)
part = MIMEBase("application", "octet-stream")
part.set_payload(open(output_filename + ".zip", "rb").read())
encoders.encode_base64(part)
part.add_header("Content-Disposition", "attachment; filename=\"%s.zip\"" % (output_filename))
msg.attach(part)

Categories