7

I've got a python script that calls a bunch of functions, each of which writes output to stdout. Sometimes when I run it, I'd like to send the output in an e-mail (along with a generated file). I'd like to know how I can capture the output in memory so I can use the email module to build the e-mail.

My ideas so far were:

  • use a memory-mapped file (but it seems like I have to reserve space on disk for this, and I don't know how long the output will be)
  • bypass all this and pipe the output to sendmail (but this may be difficult if I also want to attach the file)

4 Answers 4

14

I modified None's answer to make it a context manager:

import sys, StringIO, contextlib

class Data(object):
    pass

@contextlib.contextmanager
def capture_stdout():
    old = sys.stdout
    capturer = StringIO.StringIO()
    sys.stdout = capturer
    data = Data()
    yield data
    sys.stdout = old
    data.result = capturer.getvalue()

Usage:

with capture_stdout() as capture:
    print 'Hello'
    print 'Goodbye'
assert capture.result == 'Hello\nGoodbye\n'
Sign up to request clarification or add additional context in comments.

Comments

8

It's pretty simple to capture output.

import sys, StringIO
old_stdout = sys.stdout
capturer = StringIO.StringIO()
sys.stdout = capturer
#call functions
print "Hi"
#end functions
sys.stdout = old_stdout
output = capturer.getvalue()

Comments

4

You said that your script "calls a bunch of functions" so I'm assuming that they're python functions accessible from your program. I'm also assuming you're using print to generate the output in all these functions. If that's the case, you can just replace sys.stdout with a StringIO.StringIO which will intercept all the stuff you're writing. Then you can finally call the .getValue method on your StringIO to get everything that has been sent to the output channel. This will also work for external programs using the subprocess module which write to sys.stdout.

This is a cheap way. I'd recommend that you do your output using the logging module. You'll have much more control over how it does it's output and you can control it more easily as well.

4 Comments

Thanks. Cheap should be fine for now. If I were going to use logging, how would I build the e-mail (keeping in mind there is a file to be attached)?
+1 for logging module, prints are really limited in what you can do about them
You're welcome. Creating a simple email with a text body can be done using the smtpmail package. For an attachment, you'd have to mime-encode the attachment using the utils in the email package and then attach it to your email. Then finally send it using smptmail. There are some examples here docs.python.org/library/email-examples.html
As for getting the contents you've logged, you can simply create an EmailHandler and add it to your logger which will hold what you're logging so that you can get it later, format it and send it.
3

And I modified Gary Robinson's answer to make sure that stdout is always restored, even if there's an exception:

import sys, StringIO, contextlib

class Data(object):
    pass

@contextlib.contextmanager
def capture_stdout():
    old = sys.stdout
    capturer = StringIO.StringIO()
    data = Data()
    try:
        sys.stdout = capturer
        yield data
    finally:
        sys.stdout = old
        data.result = capturer.getvalue()

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.