Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion Doc/library/asyncio-eventloop.rst
Original file line number Diff line number Diff line change
Expand Up @@ -377,7 +377,8 @@ Opening network connections
local_addr=None, server_hostname=None, \
ssl_handshake_timeout=None, \
ssl_shutdown_timeout=None, \
happy_eyeballs_delay=None, interleave=None)
happy_eyeballs_delay=None, interleave=None, \
all_errors=False)

Open a streaming transport connection to a given
address specified by *host* and *port*.
Expand Down Expand Up @@ -468,6 +469,14 @@ Opening network connections
to complete before aborting the connection. ``30.0`` seconds if ``None``
(default).

* *all_errors* determines what exceptions are raised when a connection cannot
be created. By default, only a single ``Exception`` is raised: the first
exception if there is only one or all errors have same message, or a single
``OSError`` with the error messages combined. When ``all_errors`` is ``True``,
an ``ExceptionGroup`` will be raised containing all exceptions (even if there
is only one).


.. versionchanged:: 3.5

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

Added the *ssl_shutdown_timeout* parameter.

.. versionchanged:: 3.12
*all_errors* was added.

.. seealso::

The :func:`open_connection` function is a high-level alternative
Expand Down
5 changes: 4 additions & 1 deletion Lib/asyncio/base_events.py
Original file line number Diff line number Diff line change
Expand Up @@ -980,7 +980,8 @@ async def create_connection(
local_addr=None, server_hostname=None,
ssl_handshake_timeout=None,
ssl_shutdown_timeout=None,
happy_eyeballs_delay=None, interleave=None):
happy_eyeballs_delay=None, interleave=None,
all_errors=False):
"""Connect to a TCP server.

Create a streaming transport connection to a given internet host and
Expand Down Expand Up @@ -1069,6 +1070,8 @@ async def create_connection(

if sock is None:
exceptions = [exc for sub in exceptions for exc in sub]
if all_errors:
raise ExceptionGroup("create_connection failed", exceptions)
if len(exceptions) == 1:
raise exceptions[0]
else:
Expand Down
36 changes: 36 additions & 0 deletions Lib/test/test_asyncio/test_base_events.py
Original file line number Diff line number Diff line change
Expand Up @@ -1110,6 +1110,15 @@ def _socket(*args, **kw):

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

idx = -1
coro = self.loop.create_connection(MyProto, 'example.com', 80, all_errors=True)
with self.assertRaises(ExceptionGroup) as cm:
self.loop.run_until_complete(coro)

self.assertIsInstance(cm.exception, ExceptionGroup)
for e in cm.exception.exceptions:
self.assertIsInstance(e, OSError)

@patch_socket
def test_create_connection_timeout(self, m_socket):
# Ensure that the socket is closed on timeout
Expand Down Expand Up @@ -1229,6 +1238,14 @@ def getaddrinfo_task(*args, **kwds):
self.assertRaises(
OSError, self.loop.run_until_complete, coro)

coro = self.loop.create_connection(MyProto, 'example.com', 80, all_errors=True)
with self.assertRaises(ExceptionGroup) as cm:
self.loop.run_until_complete(coro)

self.assertIsInstance(cm.exception, ExceptionGroup)
self.assertEqual(len(cm.exception.exceptions), 1)
self.assertIsInstance(cm.exception.exceptions[0], OSError)

def test_create_connection_multiple(self):
async def getaddrinfo(*args, **kw):
return [(2, 1, 6, '', ('0.0.0.1', 80)),
Expand All @@ -1246,6 +1263,15 @@ def getaddrinfo_task(*args, **kwds):
with self.assertRaises(OSError):
self.loop.run_until_complete(coro)

coro = self.loop.create_connection(
MyProto, 'example.com', 80, family=socket.AF_INET, all_errors=True)
with self.assertRaises(ExceptionGroup) as cm:
self.loop.run_until_complete(coro)

self.assertIsInstance(cm.exception, ExceptionGroup)
for e in cm.exception.exceptions:
self.assertIsInstance(e, OSError)

@patch_socket
def test_create_connection_multiple_errors_local_addr(self, m_socket):

Expand Down Expand Up @@ -1277,6 +1303,16 @@ def getaddrinfo_task(*args, **kwds):
self.assertTrue(str(cm.exception).startswith('Multiple exceptions: '))
self.assertTrue(m_socket.socket.return_value.close.called)

coro = self.loop.create_connection(
MyProto, 'example.com', 80, family=socket.AF_INET,
local_addr=(None, 8080), all_errors=True)
with self.assertRaises(ExceptionGroup) as cm:
self.loop.run_until_complete(coro)

self.assertIsInstance(cm.exception, ExceptionGroup)
for e in cm.exception.exceptions:
self.assertIsInstance(e, OSError)

def _test_create_connection_ip_addr(self, m_socket, allow_inet_pton):
# Test the fallback code, even if this system has inet_pton.
if not allow_inet_pton:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add keyword argument ``all_errors`` to ``asyncio.create_connection`` so that multiple connection errors can be raised as an ``ExceptionGroup``.