Python try / except issue with SMTPLib

12,567

Following on from the support from @AndreySobolev I got the following simple code working fine:

import smtplib, socket

try:
   mylib = smtplib.SMTP("127.0.0.1", 25)
except socket.error as e:
   print "could not connect"

So I then returned to my smtplib_client.py, and block commented out most of the "try" section. This worked fine... so bit by bit, I reinstated more and more of the try section.... and each and every time it worked fine. The final version is below. Other than what I do in my except socket.error handler, I can't say that I am aware of anything I have changed - other than I also added a server = None so as to stop the finally section working. Oh, and I also had to add "socket" to my list of imports. Without this I could understand the except not handling correctly - but I don't understand why it wasn't firing at all, or even generating a "not defined" error.... Odd!

Working code:

def runClient(configName = 'default', testFile = './test.xml'):
  cliCfg = getClientConfig(configName)
  print cliCfg.find('logfile').text
  # startLogger(cliCfg.find('logfile').text)
  clientCert = cliCfg.find('cert').text
  clientKey = cliCfg.find('key').text
  serverHost = cliCfg.find('serverhost').text
  serverPort = int(cliCfg.find('serverport').text)

  myMail = MailMessageHandler()
  msgSrc = myMail.readMessageSource(testFile)
  allMsgs = myMail.processMessages(msgSrc)
  inx = 1
  for msg in allMsgs:
    validMsg = True
    requiredKeys = ('ehlo', 'sndr', 'rcpt', 'body')
    for msgItems in requiredKeys:
      if len(msg[msgItems]) == 0:
        validMsg = False
    if validMsg:
      try:
        server = None
        server = smtplib.SMTP(serverHost, serverPort)
        server.ehlo(msg['ehlo'])
        thisSender = msg['sndr']
        thisRecipient = msg['rcpt']
        thisMessage = MIMEText(msg['body'])
        thisMessage['To'] = email.utils.formataddr(('', thisRecipient))
        thisMessage['From'] = email.utils.formataddr(('', thisSender))
        thisMessage['Subject'] = msg['subject']
        thisMessage['Message-id'] = email.utils.make_msgid()
        now = datetime.now()
        day = now.strftime('%a')
        date = now.strftime('%d %b %Y %X')
        thisMessage['Date'] = day + ', ' + date + ' -0000'
        if msg['tls'].lower() == 'true':
          server.starttls('certs/client/client.key', 'certs/client/client.crt')
          logging.info ("Message: " + thisMessage['Message-id'] + " to be sent over TLS")
        server.sendmail(thisSender, thisRecipient.split(","), thisMessage.as_string())
        logging.info ("Message: " + thisMessage['Message-id'] + " sent successfully to " + serverHost + ":" + cliCfg.find('serverport').text)
        logging.info ("Message: " + thisMessage['Message-id'] + " had sender: " + thisMessage['From'])
        logging.info ("Message: " + thisMessage['Message-id'] + " had recipient(s): " + thisMessage['To'])
      except socket.error as e:
        logging.error ("Could not connect to " + serverHost + ":" + cliCfg.find('serverport').text + " - is it listening / up?")
      except:
        print "Unknown error:", sys.exc_info()[0]
      finally:
        if server != None:
          server.quit()
    else:
      print "Improperly formatted source mail - please check"

Baffled, yet relieved! Thanks Andrey!

Share:
12,567
Steve Hall
Author by

Steve Hall

Jack of many trades, master of not so many! QTP specialist, HP certified. Can turn my hand to most forms of scripting though, especially for automated testing - QTP, TestComplete, a bit of Selenium etc etc. Just recently started to learn a bit of Ruby, Rails, and all the fun that goes with that. It's a steep curve, but I've brought my mountaineering kit with me :).... Or I was, until that got put on hold, and now I'm onto Python, and Twisted. What will it be next week? Stay tuned... :)

Updated on June 22, 2022

Comments

  • Steve Hall
    Steve Hall almost 2 years

    I've written a simple SMTP client using Python's SMTPLib. Just trying to add some error handling - specifically in this instance, when the target server to connect to is unavailable (eg, wrong IP specified!)

    Currently, the traceback looks like this:

    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
      File "smtplib_client.py", line 91, in runClient
        try:
      File "/usr/local/lib/python2.7/smtplib.py", line 251, in __init__
        (code, msg) = self.connect(host, port)
      File "/usr/local/lib/python2.7/smtplib.py", line 311, in connect
        self.sock = self._get_socket(host, port, self.timeout)
      File "/usr/local/lib/python2.7/smtplib.py", line 286, in _get_socket
        return socket.create_connection((host, port), timeout)
      File "/usr/local/lib/python2.7/socket.py", line 571, in create_connection
        raise err
    socket.error: [Errno 111] Connection refused
    

    So clearly, it's "create_connection" in socket.py going bang. This has it's own try / except block:

    for res in getaddrinfo(host, port, 0, SOCK_STREAM):
        af, socktype, proto, canonname, sa = res
        sock = None
        try:
          sock = socket(af, socktype, proto)
          if timeout is not _GLOBAL_DEFAULT_TIMEOUT:
              sock.settimeout(timeout)
          if source_address:
              sock.bind(source_address)
          sock.connect(sa)
          return sock
    
        except error as _:
          err = _
          if sock is not None:
              sock.close()
    
    if err is not None:
      raise err
    else:
      raise error("getaddrinfo returns an empty list")
    

    My runClient() function looks like:

    def runClient(configName = 'default', testFile = './test.xml'):
      cliCfg = getClientConfig(configName)
      print cliCfg.find('logfile').text
      # startLogger(cliCfg.find('logfile').text)
      clientCert = cliCfg.find('cert').text
      clientKey = cliCfg.find('key').text
      serverHost = cliCfg.find('serverhost').text
      serverPort = int(cliCfg.find('serverport').text)
    
      myMail = MailMessageHandler()
      msgSrc = myMail.readMessageSource(testFile)
      allMsgs = myMail.processMessages(msgSrc)
      inx = 1
      for msg in allMsgs:
        validMsg = True
        requiredKeys = ('ehlo', 'sndr', 'rcpt', 'body')
        for msgItems in requiredKeys:
          if len(msg[msgItems]) == 0:
            validMsg = False
        if validMsg:
          try:
            server = smtplib.SMTP(serverHost, serverPort)
            server.ehlo(msg['ehlo'])
            thisSender = msg['sndr']
            thisRecipient = msg['rcpt']
            thisMessage = MIMEText(msg['body'])
            thisMessage['To'] = email.utils.formataddr(('', thisRecipient))
            thisMessage['From'] = email.utils.formataddr(('', thisSender))
            thisMessage['Subject'] = msg['subject']
            thisMessage['Message-id'] = email.utils.make_msgid()
            now = datetime.now()
            day = now.strftime('%a')
            date = now.strftime('%d %b %Y %X')
            thisMessage['Date'] = day + ', ' + date + ' -0000'
            if msg['tls'].lower() == 'true':
              server.starttls('certs/client/client.key', 'certs/client/client.crt')
              logging.info ("Message: " + thisMessage['Message-id'] + " to be sent over TLS")
            server.sendmail(thisSender, thisRecipient.split(","), thisMessage.as_string())
            logging.info ("Message: " + thisMessage['Message-id'] + " sent successfully to " + serverHost + ":" + cliCfg.find('serverport').text)
            logging.info ("Message: " + thisMessage['Message-id'] + " had sender: " + thisMessage['From'])
            logging.info ("Message: " + thisMessage['Message-id'] + " had recipient(s): " + thisMessage['To'])
          except socket.error as e:
            print "Could not connect to server - is it down? ({0}): {1}".format(e.strerrror)
          except:
            print "Unknown error:", sys.exc_info()[0]
          finally:
              server.quit()
        else:
          print "Improperly formatted source mail - please check"
    

    What I don't get is - the traceback shows the call to raise err. So clearly err is not None, and so it must be set as part of except error as _:.... So the error is initially handled, but as part of the handler, a copy is created (err) - which is subsequently raised outside of the try/except block - so is unhandled. This unhandled error should then get "passed back up" the call stack (get_socket has no try/except block, nor do connect or __init__ - so outside of the try/except block for the original error in create_connection, the copy of the error, err should surely then "cascade" back to the try/except block in my runClient function?