bpo-39329: Add timeout parameter for smtplib.LMTP constructor (GH-17998)
This commit is contained in:
parent
7d6378051f
commit
65a5ce247f
|
@ -115,7 +115,8 @@ Protocol) and :rfc:`1869` (SMTP Service Extensions).
|
||||||
If the *timeout* parameter is set to be zero, it will raise a
|
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:`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
|
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
|
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
|
Unix socket, LMTP generally don't support or require any authentication, but
|
||||||
your mileage might vary.
|
your mileage might vary.
|
||||||
|
|
||||||
|
.. versionchanged:: 3.9
|
||||||
|
The optional *timeout* parameter was added.
|
||||||
|
|
||||||
|
|
||||||
A nice selection of exceptions is defined as well:
|
A nice selection of exceptions is defined as well:
|
||||||
|
|
||||||
|
|
|
@ -270,6 +270,9 @@ smtplib
|
||||||
if the given timeout for their constructor is zero to prevent the creation of
|
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`.)
|
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
|
signal
|
||||||
------
|
------
|
||||||
|
|
||||||
|
|
|
@ -1066,19 +1066,23 @@ class LMTP(SMTP):
|
||||||
ehlo_msg = "lhlo"
|
ehlo_msg = "lhlo"
|
||||||
|
|
||||||
def __init__(self, host='', port=LMTP_PORT, local_hostname=None,
|
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."""
|
"""Initialize a new instance."""
|
||||||
super().__init__(host, port, local_hostname=local_hostname,
|
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):
|
def connect(self, host='localhost', port=0, source_address=None):
|
||||||
"""Connect to the LMTP daemon, on either a Unix or a TCP socket."""
|
"""Connect to the LMTP daemon, on either a Unix or a TCP socket."""
|
||||||
if host[0] != '/':
|
if host[0] != '/':
|
||||||
return super().connect(host, port, source_address=source_address)
|
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.
|
# Handle Unix-domain sockets.
|
||||||
try:
|
try:
|
||||||
self.sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
self.sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
||||||
|
self.sock.settimeout(self.timeout)
|
||||||
self.file = None
|
self.file = None
|
||||||
self.sock.connect(host)
|
self.sock.connect(host)
|
||||||
except OSError:
|
except OSError:
|
||||||
|
|
|
@ -56,7 +56,7 @@ def server(evt, buf, serv):
|
||||||
serv.close()
|
serv.close()
|
||||||
evt.set()
|
evt.set()
|
||||||
|
|
||||||
class GeneralTests(unittest.TestCase):
|
class GeneralTests:
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
smtplib.socket = mock_socket
|
smtplib.socket = mock_socket
|
||||||
|
@ -75,29 +75,29 @@ class GeneralTests(unittest.TestCase):
|
||||||
def testBasic1(self):
|
def testBasic1(self):
|
||||||
mock_socket.reply_with(b"220 Hola mundo")
|
mock_socket.reply_with(b"220 Hola mundo")
|
||||||
# connects
|
# connects
|
||||||
smtp = smtplib.SMTP(HOST, self.port)
|
client = self.client(HOST, self.port)
|
||||||
smtp.close()
|
client.close()
|
||||||
|
|
||||||
def testSourceAddress(self):
|
def testSourceAddress(self):
|
||||||
mock_socket.reply_with(b"220 Hola mundo")
|
mock_socket.reply_with(b"220 Hola mundo")
|
||||||
# connects
|
# connects
|
||||||
smtp = smtplib.SMTP(HOST, self.port,
|
client = self.client(HOST, self.port,
|
||||||
source_address=('127.0.0.1',19876))
|
source_address=('127.0.0.1',19876))
|
||||||
self.assertEqual(smtp.source_address, ('127.0.0.1', 19876))
|
self.assertEqual(client.source_address, ('127.0.0.1', 19876))
|
||||||
smtp.close()
|
client.close()
|
||||||
|
|
||||||
def testBasic2(self):
|
def testBasic2(self):
|
||||||
mock_socket.reply_with(b"220 Hola mundo")
|
mock_socket.reply_with(b"220 Hola mundo")
|
||||||
# connects, include port in host name
|
# connects, include port in host name
|
||||||
smtp = smtplib.SMTP("%s:%s" % (HOST, self.port))
|
client = self.client("%s:%s" % (HOST, self.port))
|
||||||
smtp.close()
|
client.close()
|
||||||
|
|
||||||
def testLocalHostName(self):
|
def testLocalHostName(self):
|
||||||
mock_socket.reply_with(b"220 Hola mundo")
|
mock_socket.reply_with(b"220 Hola mundo")
|
||||||
# check that supplied local_hostname is used
|
# check that supplied local_hostname is used
|
||||||
smtp = smtplib.SMTP(HOST, self.port, local_hostname="testhost")
|
client = self.client(HOST, self.port, local_hostname="testhost")
|
||||||
self.assertEqual(smtp.local_hostname, "testhost")
|
self.assertEqual(client.local_hostname, "testhost")
|
||||||
smtp.close()
|
client.close()
|
||||||
|
|
||||||
def testTimeoutDefault(self):
|
def testTimeoutDefault(self):
|
||||||
mock_socket.reply_with(b"220 Hola mundo")
|
mock_socket.reply_with(b"220 Hola mundo")
|
||||||
|
@ -105,56 +105,71 @@ class GeneralTests(unittest.TestCase):
|
||||||
mock_socket.setdefaulttimeout(30)
|
mock_socket.setdefaulttimeout(30)
|
||||||
self.assertEqual(mock_socket.getdefaulttimeout(), 30)
|
self.assertEqual(mock_socket.getdefaulttimeout(), 30)
|
||||||
try:
|
try:
|
||||||
smtp = smtplib.SMTP(HOST, self.port)
|
client = self.client(HOST, self.port)
|
||||||
finally:
|
finally:
|
||||||
mock_socket.setdefaulttimeout(None)
|
mock_socket.setdefaulttimeout(None)
|
||||||
self.assertEqual(smtp.sock.gettimeout(), 30)
|
self.assertEqual(client.sock.gettimeout(), 30)
|
||||||
smtp.close()
|
client.close()
|
||||||
|
|
||||||
def testTimeoutNone(self):
|
def testTimeoutNone(self):
|
||||||
mock_socket.reply_with(b"220 Hola mundo")
|
mock_socket.reply_with(b"220 Hola mundo")
|
||||||
self.assertIsNone(socket.getdefaulttimeout())
|
self.assertIsNone(socket.getdefaulttimeout())
|
||||||
socket.setdefaulttimeout(30)
|
socket.setdefaulttimeout(30)
|
||||||
try:
|
try:
|
||||||
smtp = smtplib.SMTP(HOST, self.port, timeout=None)
|
client = self.client(HOST, self.port, timeout=None)
|
||||||
finally:
|
finally:
|
||||||
socket.setdefaulttimeout(None)
|
socket.setdefaulttimeout(None)
|
||||||
self.assertIsNone(smtp.sock.gettimeout())
|
self.assertIsNone(client.sock.gettimeout())
|
||||||
smtp.close()
|
client.close()
|
||||||
|
|
||||||
def testTimeoutZero(self):
|
def testTimeoutZero(self):
|
||||||
mock_socket.reply_with(b"220 Hola mundo")
|
mock_socket.reply_with(b"220 Hola mundo")
|
||||||
with self.assertRaises(ValueError):
|
with self.assertRaises(ValueError):
|
||||||
smtplib.SMTP(HOST, self.port, timeout=0)
|
self.client(HOST, self.port, timeout=0)
|
||||||
|
|
||||||
def testTimeoutValue(self):
|
def testTimeoutValue(self):
|
||||||
mock_socket.reply_with(b"220 Hola mundo")
|
mock_socket.reply_with(b"220 Hola mundo")
|
||||||
smtp = smtplib.SMTP(HOST, self.port, timeout=30)
|
client = self.client(HOST, self.port, timeout=30)
|
||||||
self.assertEqual(smtp.sock.gettimeout(), 30)
|
self.assertEqual(client.sock.gettimeout(), 30)
|
||||||
smtp.close()
|
client.close()
|
||||||
|
|
||||||
def test_debuglevel(self):
|
def test_debuglevel(self):
|
||||||
mock_socket.reply_with(b"220 Hello world")
|
mock_socket.reply_with(b"220 Hello world")
|
||||||
smtp = smtplib.SMTP()
|
client = self.client()
|
||||||
smtp.set_debuglevel(1)
|
client.set_debuglevel(1)
|
||||||
with support.captured_stderr() as stderr:
|
with support.captured_stderr() as stderr:
|
||||||
smtp.connect(HOST, self.port)
|
client.connect(HOST, self.port)
|
||||||
smtp.close()
|
client.close()
|
||||||
expected = re.compile(r"^connect:", re.MULTILINE)
|
expected = re.compile(r"^connect:", re.MULTILINE)
|
||||||
self.assertRegex(stderr.getvalue(), expected)
|
self.assertRegex(stderr.getvalue(), expected)
|
||||||
|
|
||||||
def test_debuglevel_2(self):
|
def test_debuglevel_2(self):
|
||||||
mock_socket.reply_with(b"220 Hello world")
|
mock_socket.reply_with(b"220 Hello world")
|
||||||
smtp = smtplib.SMTP()
|
client = self.client()
|
||||||
smtp.set_debuglevel(2)
|
client.set_debuglevel(2)
|
||||||
with support.captured_stderr() as stderr:
|
with support.captured_stderr() as stderr:
|
||||||
smtp.connect(HOST, self.port)
|
client.connect(HOST, self.port)
|
||||||
smtp.close()
|
client.close()
|
||||||
expected = re.compile(r"^\d{2}:\d{2}:\d{2}\.\d{6} connect: ",
|
expected = re.compile(r"^\d{2}:\d{2}:\d{2}\.\d{6} connect: ",
|
||||||
re.MULTILINE)
|
re.MULTILINE)
|
||||||
self.assertRegex(stderr.getvalue(), expected)
|
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
|
# Test server thread using the specified SMTP server class
|
||||||
def debugging_server(serv, serv_evt, client_evt):
|
def debugging_server(serv, serv_evt, client_evt):
|
||||||
serv_evt.set()
|
serv_evt.set()
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
:class:`~smtplib.LMTP` constructor now has an optional *timeout* parameter.
|
||||||
|
Patch by Dong-hee Na.
|
Loading…
Reference in New Issue