mirror of
https://github.com/python/cpython.git
synced 2024-11-27 03:45:08 +08:00
gh-74166: Add option to get all errors from socket.create_connection (GH-91586)
This commit is contained in:
parent
0860b26a4f
commit
39a54ba638
@ -660,7 +660,7 @@ The following functions all create :ref:`socket objects <socket-objects>`.
|
||||
Windows support added.
|
||||
|
||||
|
||||
.. function:: create_connection(address[, timeout[, source_address]])
|
||||
.. function:: create_connection(address[, timeout[, source_address[, all_errors]]])
|
||||
|
||||
Connect to a TCP service listening on the internet *address* (a 2-tuple
|
||||
``(host, port)``), and return the socket object. This is a higher-level
|
||||
@ -679,9 +679,18 @@ The following functions all create :ref:`socket objects <socket-objects>`.
|
||||
socket to bind to as its source address before connecting. If host or port
|
||||
are '' or 0 respectively the OS default behavior will be used.
|
||||
|
||||
When a connection cannot be created, an exception is raised. By default,
|
||||
it is the exception from the last address in the list. If *all_errors*
|
||||
is ``True``, it is an :exc:`ExceptionGroup` containing the errors of all
|
||||
attempts.
|
||||
|
||||
.. versionchanged:: 3.2
|
||||
*source_address* was added.
|
||||
|
||||
.. versionchanged:: 3.11
|
||||
*all_errors* was added.
|
||||
|
||||
|
||||
.. function:: create_server(address, *, family=AF_INET, backlog=None, reuse_port=False, dualstack_ipv6=False)
|
||||
|
||||
Convenience function which creates a TCP socket bound to *address* (a 2-tuple
|
||||
|
@ -357,6 +357,10 @@ socket
|
||||
* Add CAN Socket support for NetBSD.
|
||||
(Contributed by Thomas Klausner in :issue:`30512`.)
|
||||
|
||||
* :meth:`~socket.create_connection` has an option to raise, in case of
|
||||
failure to connect, an :exc:`ExceptionGroup` containing all errors
|
||||
instead of only raising the last error.
|
||||
(Contributed by Irit Katriel in :issue:`29980`).
|
||||
|
||||
sqlite3
|
||||
-------
|
||||
|
@ -806,7 +806,7 @@ def getfqdn(name=''):
|
||||
_GLOBAL_DEFAULT_TIMEOUT = object()
|
||||
|
||||
def create_connection(address, timeout=_GLOBAL_DEFAULT_TIMEOUT,
|
||||
source_address=None):
|
||||
source_address=None, all_errors=False):
|
||||
"""Connect to *address* and return the socket object.
|
||||
|
||||
Convenience function. Connect to *address* (a 2-tuple ``(host,
|
||||
@ -816,11 +816,13 @@ def create_connection(address, timeout=_GLOBAL_DEFAULT_TIMEOUT,
|
||||
global default timeout setting returned by :func:`getdefaulttimeout`
|
||||
is used. If *source_address* is set it must be a tuple of (host, port)
|
||||
for the socket to bind as a source address before making the connection.
|
||||
A host of '' or port 0 tells the OS to use the default.
|
||||
A host of '' or port 0 tells the OS to use the default. When a connection
|
||||
cannot be created, raises the last error if *all_errors* is False,
|
||||
and an ExceptionGroup of all errors if *all_errors* is True.
|
||||
"""
|
||||
|
||||
host, port = address
|
||||
err = None
|
||||
exceptions = []
|
||||
for res in getaddrinfo(host, port, 0, SOCK_STREAM):
|
||||
af, socktype, proto, canonname, sa = res
|
||||
sock = None
|
||||
@ -832,20 +834,24 @@ def create_connection(address, timeout=_GLOBAL_DEFAULT_TIMEOUT,
|
||||
sock.bind(source_address)
|
||||
sock.connect(sa)
|
||||
# Break explicitly a reference cycle
|
||||
err = None
|
||||
exceptions.clear()
|
||||
return sock
|
||||
|
||||
except error as _:
|
||||
err = _
|
||||
except error as exc:
|
||||
if not all_errors:
|
||||
exceptions.clear() # raise only the last error
|
||||
exceptions.append(exc)
|
||||
if sock is not None:
|
||||
sock.close()
|
||||
|
||||
if err is not None:
|
||||
if len(exceptions):
|
||||
try:
|
||||
raise err
|
||||
if not all_errors:
|
||||
raise exceptions[0]
|
||||
raise ExceptionGroup("create_connection failed", exceptions)
|
||||
finally:
|
||||
# Break explicitly a reference cycle
|
||||
err = None
|
||||
exceptions = None
|
||||
else:
|
||||
raise error("getaddrinfo returns an empty list")
|
||||
|
||||
|
@ -5177,6 +5177,24 @@ class NetworkConnectionNoServer(unittest.TestCase):
|
||||
expected_errnos = socket_helper.get_socket_conn_refused_errs()
|
||||
self.assertIn(cm.exception.errno, expected_errnos)
|
||||
|
||||
def test_create_connection_all_errors(self):
|
||||
port = socket_helper.find_unused_port()
|
||||
try:
|
||||
socket.create_connection((HOST, port), all_errors=True)
|
||||
except ExceptionGroup as e:
|
||||
eg = e
|
||||
else:
|
||||
self.fail('expected connection to fail')
|
||||
|
||||
self.assertIsInstance(eg, ExceptionGroup)
|
||||
for e in eg.exceptions:
|
||||
self.assertIsInstance(e, OSError)
|
||||
|
||||
addresses = socket.getaddrinfo(
|
||||
'localhost', port, 0, socket.SOCK_STREAM)
|
||||
# assert that we got an exception for each address
|
||||
self.assertEqual(len(addresses), len(eg.exceptions))
|
||||
|
||||
def test_create_connection_timeout(self):
|
||||
# Issue #9792: create_connection() should not recast timeout errors
|
||||
# as generic socket errors.
|
||||
|
@ -0,0 +1 @@
|
||||
Add option to raise all errors from :meth:`~socket.create_connection` in an :exc:`ExceptionGroup` when it fails to create a connection. The default remains to raise only the last error that had occurred when multiple addresses were tried.
|
Loading…
Reference in New Issue
Block a user