[Python-checkins] bpo-39329: Add timeout parameter for smtplib.LMTP constructor (GH-17998)

Victor Stinner webhook-mailer at python.org
Tue Jan 14 16:42:17 EST 2020


https://github.com/python/cpython/commit/65a5ce247f177c4c52cfd104d9df0c2f3b1c91f0
commit: 65a5ce247f177c4c52cfd104d9df0c2f3b1c91f0
branch: master
author: Dong-hee Na <donghee.na92 at gmail.com>
committer: Victor Stinner <vstinner at python.org>
date: 2020-01-14T22:42:09+01:00
summary:

bpo-39329: Add timeout parameter for smtplib.LMTP constructor (GH-17998)

files:
A Misc/NEWS.d/next/Library/2020-01-14-22-16-07.bpo-39329.6OKGBn.rst
M Doc/library/smtplib.rst
M Doc/whatsnew/3.9.rst
M Lib/smtplib.py
M Lib/test/test_smtplib.py

diff --git a/Doc/library/smtplib.rst b/Doc/library/smtplib.rst
index f6ac123823b69..a88e358eae5fd 100644
--- a/Doc/library/smtplib.rst
+++ b/Doc/library/smtplib.rst
@@ -115,7 +115,8 @@ Protocol) and :rfc:`1869` (SMTP Service Extensions).
       If the *timeout* parameter is set to be zero, it will raise a
       :class:`ValueError` to prevent the creation of a non-blocking socket
 
-.. class:: LMTP(host='', port=LMTP_PORT, local_hostname=None, source_address=None)
+.. class:: LMTP(host='', port=LMTP_PORT, local_hostname=None,
+                source_address=None[, timeout])
 
    The LMTP protocol, which is very similar to ESMTP, is heavily based on the
    standard SMTP client. It's common to use Unix sockets for LMTP, so our
@@ -128,6 +129,9 @@ Protocol) and :rfc:`1869` (SMTP Service Extensions).
    Unix socket, LMTP generally don't support or require any authentication, but
    your mileage might vary.
 
+   .. versionchanged:: 3.9
+      The optional *timeout* parameter was added.
+
 
 A nice selection of exceptions is defined as well:
 
diff --git a/Doc/whatsnew/3.9.rst b/Doc/whatsnew/3.9.rst
index c94999999208d..451902ab1dbd5 100644
--- a/Doc/whatsnew/3.9.rst
+++ b/Doc/whatsnew/3.9.rst
@@ -270,6 +270,9 @@ smtplib
 if the given timeout for their constructor is zero to prevent the creation of
 a non-blocking socket. (Contributed by Dong-hee Na in :issue:`39259`.)
 
+:class:`~smtplib.LMTP` constructor  now has an optional *timeout* parameter.
+(Contributed by Dong-hee Na in :issue:`39329`.)
+
 signal
 ------
 
diff --git a/Lib/smtplib.py b/Lib/smtplib.py
index 4d5cdb5ac0ad9..7808ba01cba88 100755
--- a/Lib/smtplib.py
+++ b/Lib/smtplib.py
@@ -1066,19 +1066,23 @@ class LMTP(SMTP):
     ehlo_msg = "lhlo"
 
     def __init__(self, host='', port=LMTP_PORT, local_hostname=None,
-                 source_address=None):
+                 source_address=None, timeout=socket._GLOBAL_DEFAULT_TIMEOUT):
         """Initialize a new instance."""
         super().__init__(host, port, local_hostname=local_hostname,
-                         source_address=source_address)
+                         source_address=source_address, timeout=timeout)
 
     def connect(self, host='localhost', port=0, source_address=None):
         """Connect to the LMTP daemon, on either a Unix or a TCP socket."""
         if host[0] != '/':
             return super().connect(host, port, source_address=source_address)
 
+        if self.timeout is not None and not self.timeout:
+            raise ValueError('Non-blocking socket (timeout=0) is not supported')
+
         # Handle Unix-domain sockets.
         try:
             self.sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
+            self.sock.settimeout(self.timeout)
             self.file = None
             self.sock.connect(host)
         except OSError:
diff --git a/Lib/test/test_smtplib.py b/Lib/test/test_smtplib.py
index cc5c4b1346488..067c01c10c1b3 100644
--- a/Lib/test/test_smtplib.py
+++ b/Lib/test/test_smtplib.py
@@ -56,7 +56,7 @@ def server(evt, buf, serv):
         serv.close()
         evt.set()
 
-class GeneralTests(unittest.TestCase):
+class GeneralTests:
 
     def setUp(self):
         smtplib.socket = mock_socket
@@ -75,29 +75,29 @@ def testQuoteData(self):
     def testBasic1(self):
         mock_socket.reply_with(b"220 Hola mundo")
         # connects
-        smtp = smtplib.SMTP(HOST, self.port)
-        smtp.close()
+        client = self.client(HOST, self.port)
+        client.close()
 
     def testSourceAddress(self):
         mock_socket.reply_with(b"220 Hola mundo")
         # connects
-        smtp = smtplib.SMTP(HOST, self.port,
-                source_address=('127.0.0.1',19876))
-        self.assertEqual(smtp.source_address, ('127.0.0.1', 19876))
-        smtp.close()
+        client = self.client(HOST, self.port,
+                             source_address=('127.0.0.1',19876))
+        self.assertEqual(client.source_address, ('127.0.0.1', 19876))
+        client.close()
 
     def testBasic2(self):
         mock_socket.reply_with(b"220 Hola mundo")
         # connects, include port in host name
-        smtp = smtplib.SMTP("%s:%s" % (HOST, self.port))
-        smtp.close()
+        client = self.client("%s:%s" % (HOST, self.port))
+        client.close()
 
     def testLocalHostName(self):
         mock_socket.reply_with(b"220 Hola mundo")
         # check that supplied local_hostname is used
-        smtp = smtplib.SMTP(HOST, self.port, local_hostname="testhost")
-        self.assertEqual(smtp.local_hostname, "testhost")
-        smtp.close()
+        client = self.client(HOST, self.port, local_hostname="testhost")
+        self.assertEqual(client.local_hostname, "testhost")
+        client.close()
 
     def testTimeoutDefault(self):
         mock_socket.reply_with(b"220 Hola mundo")
@@ -105,56 +105,71 @@ def testTimeoutDefault(self):
         mock_socket.setdefaulttimeout(30)
         self.assertEqual(mock_socket.getdefaulttimeout(), 30)
         try:
-            smtp = smtplib.SMTP(HOST, self.port)
+            client = self.client(HOST, self.port)
         finally:
             mock_socket.setdefaulttimeout(None)
-        self.assertEqual(smtp.sock.gettimeout(), 30)
-        smtp.close()
+        self.assertEqual(client.sock.gettimeout(), 30)
+        client.close()
 
     def testTimeoutNone(self):
         mock_socket.reply_with(b"220 Hola mundo")
         self.assertIsNone(socket.getdefaulttimeout())
         socket.setdefaulttimeout(30)
         try:
-            smtp = smtplib.SMTP(HOST, self.port, timeout=None)
+            client = self.client(HOST, self.port, timeout=None)
         finally:
             socket.setdefaulttimeout(None)
-        self.assertIsNone(smtp.sock.gettimeout())
-        smtp.close()
+        self.assertIsNone(client.sock.gettimeout())
+        client.close()
 
     def testTimeoutZero(self):
         mock_socket.reply_with(b"220 Hola mundo")
         with self.assertRaises(ValueError):
-            smtplib.SMTP(HOST, self.port, timeout=0)
+            self.client(HOST, self.port, timeout=0)
 
     def testTimeoutValue(self):
         mock_socket.reply_with(b"220 Hola mundo")
-        smtp = smtplib.SMTP(HOST, self.port, timeout=30)
-        self.assertEqual(smtp.sock.gettimeout(), 30)
-        smtp.close()
+        client = self.client(HOST, self.port, timeout=30)
+        self.assertEqual(client.sock.gettimeout(), 30)
+        client.close()
 
     def test_debuglevel(self):
         mock_socket.reply_with(b"220 Hello world")
-        smtp = smtplib.SMTP()
-        smtp.set_debuglevel(1)
+        client = self.client()
+        client.set_debuglevel(1)
         with support.captured_stderr() as stderr:
-            smtp.connect(HOST, self.port)
-        smtp.close()
+            client.connect(HOST, self.port)
+        client.close()
         expected = re.compile(r"^connect:", re.MULTILINE)
         self.assertRegex(stderr.getvalue(), expected)
 
     def test_debuglevel_2(self):
         mock_socket.reply_with(b"220 Hello world")
-        smtp = smtplib.SMTP()
-        smtp.set_debuglevel(2)
+        client = self.client()
+        client.set_debuglevel(2)
         with support.captured_stderr() as stderr:
-            smtp.connect(HOST, self.port)
-        smtp.close()
+            client.connect(HOST, self.port)
+        client.close()
         expected = re.compile(r"^\d{2}:\d{2}:\d{2}\.\d{6} connect: ",
                               re.MULTILINE)
         self.assertRegex(stderr.getvalue(), expected)
 
 
+class SMTPGeneralTests(GeneralTests, unittest.TestCase):
+
+    client = smtplib.SMTP
+
+
+class LMTPGeneralTests(GeneralTests, unittest.TestCase):
+
+    client = smtplib.LMTP
+
+    def testTimeoutZero(self):
+        super().testTimeoutZero()
+        local_host = '/some/local/lmtp/delivery/program'
+        with self.assertRaises(ValueError):
+            self.client(local_host, timeout=0)
+
 # Test server thread using the specified SMTP server class
 def debugging_server(serv, serv_evt, client_evt):
     serv_evt.set()
diff --git a/Misc/NEWS.d/next/Library/2020-01-14-22-16-07.bpo-39329.6OKGBn.rst b/Misc/NEWS.d/next/Library/2020-01-14-22-16-07.bpo-39329.6OKGBn.rst
new file mode 100644
index 0000000000000..1e3da4618b41c
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2020-01-14-22-16-07.bpo-39329.6OKGBn.rst
@@ -0,0 +1,2 @@
+:class:`~smtplib.LMTP` constructor now has an optional *timeout* parameter.
+Patch by Dong-hee Na.



More information about the Python-checkins mailing list