Skip to content

Commit a0ad63e

Browse files
gh-93973: Add all_errors to asyncio.create_connection (#93974)
Co-authored-by: Oleg Iarygin <[email protected]>
1 parent ac18665 commit a0ad63e

File tree

4 files changed

+54
-2
lines changed

4 files changed

+54
-2
lines changed

Doc/library/asyncio-eventloop.rst

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -377,7 +377,8 @@ Opening network connections
377377
local_addr=None, server_hostname=None, \
378378
ssl_handshake_timeout=None, \
379379
ssl_shutdown_timeout=None, \
380-
happy_eyeballs_delay=None, interleave=None)
380+
happy_eyeballs_delay=None, interleave=None, \
381+
all_errors=False)
381382
382383
Open a streaming transport connection to a given
383384
address specified by *host* and *port*.
@@ -468,6 +469,14 @@ Opening network connections
468469
to complete before aborting the connection. ``30.0`` seconds if ``None``
469470
(default).
470471

472+
* *all_errors* determines what exceptions are raised when a connection cannot
473+
be created. By default, only a single ``Exception`` is raised: the first
474+
exception if there is only one or all errors have same message, or a single
475+
``OSError`` with the error messages combined. When ``all_errors`` is ``True``,
476+
an ``ExceptionGroup`` will be raised containing all exceptions (even if there
477+
is only one).
478+
479+
471480
.. versionchanged:: 3.5
472481

473482
Added support for SSL/TLS in :class:`ProactorEventLoop`.
@@ -500,6 +509,9 @@ Opening network connections
500509

501510
Added the *ssl_shutdown_timeout* parameter.
502511

512+
.. versionchanged:: 3.12
513+
*all_errors* was added.
514+
503515
.. seealso::
504516

505517
The :func:`open_connection` function is a high-level alternative

Lib/asyncio/base_events.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -980,7 +980,8 @@ async def create_connection(
980980
local_addr=None, server_hostname=None,
981981
ssl_handshake_timeout=None,
982982
ssl_shutdown_timeout=None,
983-
happy_eyeballs_delay=None, interleave=None):
983+
happy_eyeballs_delay=None, interleave=None,
984+
all_errors=False):
984985
"""Connect to a TCP server.
985986
986987
Create a streaming transport connection to a given internet host and
@@ -1069,6 +1070,8 @@ async def create_connection(
10691070

10701071
if sock is None:
10711072
exceptions = [exc for sub in exceptions for exc in sub]
1073+
if all_errors:
1074+
raise ExceptionGroup("create_connection failed", exceptions)
10721075
if len(exceptions) == 1:
10731076
raise exceptions[0]
10741077
else:

Lib/test/test_asyncio/test_base_events.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1109,6 +1109,15 @@ def _socket(*args, **kw):
11091109

11101110
self.assertEqual(str(cm.exception), 'Multiple exceptions: err1, err2')
11111111

1112+
idx = -1
1113+
coro = self.loop.create_connection(MyProto, 'example.com', 80, all_errors=True)
1114+
with self.assertRaises(ExceptionGroup) as cm:
1115+
self.loop.run_until_complete(coro)
1116+
1117+
self.assertIsInstance(cm.exception, ExceptionGroup)
1118+
for e in cm.exception.exceptions:
1119+
self.assertIsInstance(e, OSError)
1120+
11121121
@patch_socket
11131122
def test_create_connection_timeout(self, m_socket):
11141123
# Ensure that the socket is closed on timeout
@@ -1228,6 +1237,14 @@ def getaddrinfo_task(*args, **kwds):
12281237
self.assertRaises(
12291238
OSError, self.loop.run_until_complete, coro)
12301239

1240+
coro = self.loop.create_connection(MyProto, 'example.com', 80, all_errors=True)
1241+
with self.assertRaises(ExceptionGroup) as cm:
1242+
self.loop.run_until_complete(coro)
1243+
1244+
self.assertIsInstance(cm.exception, ExceptionGroup)
1245+
self.assertEqual(len(cm.exception.exceptions), 1)
1246+
self.assertIsInstance(cm.exception.exceptions[0], OSError)
1247+
12311248
def test_create_connection_multiple(self):
12321249
async def getaddrinfo(*args, **kw):
12331250
return [(2, 1, 6, '', ('0.0.0.1', 80)),
@@ -1245,6 +1262,15 @@ def getaddrinfo_task(*args, **kwds):
12451262
with self.assertRaises(OSError):
12461263
self.loop.run_until_complete(coro)
12471264

1265+
coro = self.loop.create_connection(
1266+
MyProto, 'example.com', 80, family=socket.AF_INET, all_errors=True)
1267+
with self.assertRaises(ExceptionGroup) as cm:
1268+
self.loop.run_until_complete(coro)
1269+
1270+
self.assertIsInstance(cm.exception, ExceptionGroup)
1271+
for e in cm.exception.exceptions:
1272+
self.assertIsInstance(e, OSError)
1273+
12481274
@patch_socket
12491275
def test_create_connection_multiple_errors_local_addr(self, m_socket):
12501276

@@ -1276,6 +1302,16 @@ def getaddrinfo_task(*args, **kwds):
12761302
self.assertTrue(str(cm.exception).startswith('Multiple exceptions: '))
12771303
self.assertTrue(m_socket.socket.return_value.close.called)
12781304

1305+
coro = self.loop.create_connection(
1306+
MyProto, 'example.com', 80, family=socket.AF_INET,
1307+
local_addr=(None, 8080), all_errors=True)
1308+
with self.assertRaises(ExceptionGroup) as cm:
1309+
self.loop.run_until_complete(coro)
1310+
1311+
self.assertIsInstance(cm.exception, ExceptionGroup)
1312+
for e in cm.exception.exceptions:
1313+
self.assertIsInstance(e, OSError)
1314+
12791315
def _test_create_connection_ip_addr(self, m_socket, allow_inet_pton):
12801316
# Test the fallback code, even if this system has inet_pton.
12811317
if not allow_inet_pton:
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Add keyword argument ``all_errors`` to ``asyncio.create_connection`` so that multiple connection errors can be raised as an ``ExceptionGroup``.

0 commit comments

Comments
 (0)