Skip to content

Commit 65a5ce2

Browse files
corona10vstinner
authored andcommitted
bpo-39329: Add timeout parameter for smtplib.LMTP constructor (GH-17998)
1 parent 7d63780 commit 65a5ce2

File tree

5 files changed

+61
-33
lines changed

5 files changed

+61
-33
lines changed

Doc/library/smtplib.rst

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,8 @@ Protocol) and :rfc:`1869` (SMTP Service Extensions).
115115
If the *timeout* parameter is set to be zero, it will raise a
116116
:class:`ValueError` to prevent the creation of a non-blocking socket
117117

118-
.. class:: LMTP(host='', port=LMTP_PORT, local_hostname=None, source_address=None)
118+
.. class:: LMTP(host='', port=LMTP_PORT, local_hostname=None,
119+
source_address=None[, timeout])
119120

120121
The LMTP protocol, which is very similar to ESMTP, is heavily based on the
121122
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).
128129
Unix socket, LMTP generally don't support or require any authentication, but
129130
your mileage might vary.
130131

132+
.. versionchanged:: 3.9
133+
The optional *timeout* parameter was added.
134+
131135

132136
A nice selection of exceptions is defined as well:
133137

Doc/whatsnew/3.9.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -270,6 +270,9 @@ smtplib
270270
if the given timeout for their constructor is zero to prevent the creation of
271271
a non-blocking socket. (Contributed by Dong-hee Na in :issue:`39259`.)
272272

273+
:class:`~smtplib.LMTP` constructor now has an optional *timeout* parameter.
274+
(Contributed by Dong-hee Na in :issue:`39329`.)
275+
273276
signal
274277
------
275278

Lib/smtplib.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1066,19 +1066,23 @@ class LMTP(SMTP):
10661066
ehlo_msg = "lhlo"
10671067

10681068
def __init__(self, host='', port=LMTP_PORT, local_hostname=None,
1069-
source_address=None):
1069+
source_address=None, timeout=socket._GLOBAL_DEFAULT_TIMEOUT):
10701070
"""Initialize a new instance."""
10711071
super().__init__(host, port, local_hostname=local_hostname,
1072-
source_address=source_address)
1072+
source_address=source_address, timeout=timeout)
10731073

10741074
def connect(self, host='localhost', port=0, source_address=None):
10751075
"""Connect to the LMTP daemon, on either a Unix or a TCP socket."""
10761076
if host[0] != '/':
10771077
return super().connect(host, port, source_address=source_address)
10781078

1079+
if self.timeout is not None and not self.timeout:
1080+
raise ValueError('Non-blocking socket (timeout=0) is not supported')
1081+
10791082
# Handle Unix-domain sockets.
10801083
try:
10811084
self.sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
1085+
self.sock.settimeout(self.timeout)
10821086
self.file = None
10831087
self.sock.connect(host)
10841088
except OSError:

Lib/test/test_smtplib.py

Lines changed: 45 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ def server(evt, buf, serv):
5656
serv.close()
5757
evt.set()
5858

59-
class GeneralTests(unittest.TestCase):
59+
class GeneralTests:
6060

6161
def setUp(self):
6262
smtplib.socket = mock_socket
@@ -75,86 +75,101 @@ def testQuoteData(self):
7575
def testBasic1(self):
7676
mock_socket.reply_with(b"220 Hola mundo")
7777
# connects
78-
smtp = smtplib.SMTP(HOST, self.port)
79-
smtp.close()
78+
client = self.client(HOST, self.port)
79+
client.close()
8080

8181
def testSourceAddress(self):
8282
mock_socket.reply_with(b"220 Hola mundo")
8383
# connects
84-
smtp = smtplib.SMTP(HOST, self.port,
85-
source_address=('127.0.0.1',19876))
86-
self.assertEqual(smtp.source_address, ('127.0.0.1', 19876))
87-
smtp.close()
84+
client = self.client(HOST, self.port,
85+
source_address=('127.0.0.1',19876))
86+
self.assertEqual(client.source_address, ('127.0.0.1', 19876))
87+
client.close()
8888

8989
def testBasic2(self):
9090
mock_socket.reply_with(b"220 Hola mundo")
9191
# connects, include port in host name
92-
smtp = smtplib.SMTP("%s:%s" % (HOST, self.port))
93-
smtp.close()
92+
client = self.client("%s:%s" % (HOST, self.port))
93+
client.close()
9494

9595
def testLocalHostName(self):
9696
mock_socket.reply_with(b"220 Hola mundo")
9797
# check that supplied local_hostname is used
98-
smtp = smtplib.SMTP(HOST, self.port, local_hostname="testhost")
99-
self.assertEqual(smtp.local_hostname, "testhost")
100-
smtp.close()
98+
client = self.client(HOST, self.port, local_hostname="testhost")
99+
self.assertEqual(client.local_hostname, "testhost")
100+
client.close()
101101

102102
def testTimeoutDefault(self):
103103
mock_socket.reply_with(b"220 Hola mundo")
104104
self.assertIsNone(mock_socket.getdefaulttimeout())
105105
mock_socket.setdefaulttimeout(30)
106106
self.assertEqual(mock_socket.getdefaulttimeout(), 30)
107107
try:
108-
smtp = smtplib.SMTP(HOST, self.port)
108+
client = self.client(HOST, self.port)
109109
finally:
110110
mock_socket.setdefaulttimeout(None)
111-
self.assertEqual(smtp.sock.gettimeout(), 30)
112-
smtp.close()
111+
self.assertEqual(client.sock.gettimeout(), 30)
112+
client.close()
113113

114114
def testTimeoutNone(self):
115115
mock_socket.reply_with(b"220 Hola mundo")
116116
self.assertIsNone(socket.getdefaulttimeout())
117117
socket.setdefaulttimeout(30)
118118
try:
119-
smtp = smtplib.SMTP(HOST, self.port, timeout=None)
119+
client = self.client(HOST, self.port, timeout=None)
120120
finally:
121121
socket.setdefaulttimeout(None)
122-
self.assertIsNone(smtp.sock.gettimeout())
123-
smtp.close()
122+
self.assertIsNone(client.sock.gettimeout())
123+
client.close()
124124

125125
def testTimeoutZero(self):
126126
mock_socket.reply_with(b"220 Hola mundo")
127127
with self.assertRaises(ValueError):
128-
smtplib.SMTP(HOST, self.port, timeout=0)
128+
self.client(HOST, self.port, timeout=0)
129129

130130
def testTimeoutValue(self):
131131
mock_socket.reply_with(b"220 Hola mundo")
132-
smtp = smtplib.SMTP(HOST, self.port, timeout=30)
133-
self.assertEqual(smtp.sock.gettimeout(), 30)
134-
smtp.close()
132+
client = self.client(HOST, self.port, timeout=30)
133+
self.assertEqual(client.sock.gettimeout(), 30)
134+
client.close()
135135

136136
def test_debuglevel(self):
137137
mock_socket.reply_with(b"220 Hello world")
138-
smtp = smtplib.SMTP()
139-
smtp.set_debuglevel(1)
138+
client = self.client()
139+
client.set_debuglevel(1)
140140
with support.captured_stderr() as stderr:
141-
smtp.connect(HOST, self.port)
142-
smtp.close()
141+
client.connect(HOST, self.port)
142+
client.close()
143143
expected = re.compile(r"^connect:", re.MULTILINE)
144144
self.assertRegex(stderr.getvalue(), expected)
145145

146146
def test_debuglevel_2(self):
147147
mock_socket.reply_with(b"220 Hello world")
148-
smtp = smtplib.SMTP()
149-
smtp.set_debuglevel(2)
148+
client = self.client()
149+
client.set_debuglevel(2)
150150
with support.captured_stderr() as stderr:
151-
smtp.connect(HOST, self.port)
152-
smtp.close()
151+
client.connect(HOST, self.port)
152+
client.close()
153153
expected = re.compile(r"^\d{2}:\d{2}:\d{2}\.\d{6} connect: ",
154154
re.MULTILINE)
155155
self.assertRegex(stderr.getvalue(), expected)
156156

157157

158+
class SMTPGeneralTests(GeneralTests, unittest.TestCase):
159+
160+
client = smtplib.SMTP
161+
162+
163+
class LMTPGeneralTests(GeneralTests, unittest.TestCase):
164+
165+
client = smtplib.LMTP
166+
167+
def testTimeoutZero(self):
168+
super().testTimeoutZero()
169+
local_host = '/some/local/lmtp/delivery/program'
170+
with self.assertRaises(ValueError):
171+
self.client(local_host, timeout=0)
172+
158173
# Test server thread using the specified SMTP server class
159174
def debugging_server(serv, serv_evt, client_evt):
160175
serv_evt.set()
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
:class:`~smtplib.LMTP` constructor now has an optional *timeout* parameter.
2+
Patch by Dong-hee Na.

0 commit comments

Comments
 (0)