diff options
-rwxr-xr-x | Doc/library/socket.rst | 11 | ||||
-rw-r--r-- | Doc/whatsnew/3.11.rst | 4 | ||||
-rwxr-xr-x | Lib/socket.py | 24 | ||||
-rwxr-xr-x | Lib/test/test_socket.py | 18 | ||||
-rw-r--r-- | Misc/NEWS.d/next/Library/2022-04-15-20-56-31.gh-issue-74166.70KlvL.rst | 1 |
5 files changed, 48 insertions, 10 deletions
diff --git a/Doc/library/socket.rst b/Doc/library/socket.rst index f8392ba..d7a4401 100755 --- a/Doc/library/socket.rst +++ b/Doc/library/socket.rst @@ -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 diff --git a/Doc/whatsnew/3.11.rst b/Doc/whatsnew/3.11.rst index 27a5a0f..baff687 100644 --- a/Doc/whatsnew/3.11.rst +++ b/Doc/whatsnew/3.11.rst @@ -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 ------- diff --git a/Lib/socket.py b/Lib/socket.py index ef82c49..97362d9 100755 --- a/Lib/socket.py +++ b/Lib/socket.py @@ -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") diff --git a/Lib/test/test_socket.py b/Lib/test/test_socket.py index 97cc626..6133637 100755 --- a/Lib/test/test_socket.py +++ b/Lib/test/test_socket.py @@ -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. diff --git a/Misc/NEWS.d/next/Library/2022-04-15-20-56-31.gh-issue-74166.70KlvL.rst b/Misc/NEWS.d/next/Library/2022-04-15-20-56-31.gh-issue-74166.70KlvL.rst new file mode 100644 index 0000000..ddd4eb7 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2022-04-15-20-56-31.gh-issue-74166.70KlvL.rst @@ -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. |