Skip to content

Commit 63c8dc0

Browse files
authored
Improve select()-related exceptions (#283)
- **Don't inherit from `BaseException`** - **Remove `SelectErrorGroup`** Fixes #280.
2 parents e7b98d1 + 2bcebfd commit 63c8dc0

File tree

3 files changed

+13
-26
lines changed

3 files changed

+13
-26
lines changed

RELEASE_NOTES.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@
4848

4949
- The `map()` function now takes a positional-only argument, if you were using `receiver.map(call=fun)` you should replace it with `receiver.map(func)`.
5050

51+
* `SelectError` now inherits from `channels.Error` instead of `BaseException`, so you should be able to catch it with `except Exception:` or `except channels.Error:`.
52+
5153
* `Selected`
5254

5355
- The `value` property was renamed to `message`.
@@ -69,7 +71,6 @@
6971

7072
- `Selected`
7173
- `SelectError`
72-
- `SelectErrorGroup`
7374
- `UnhandledSelectedError`
7475
- `select`
7576
- `selected_from`
@@ -108,6 +109,8 @@
108109

109110
This was removed alongside `Peekable` (it was only raised when using a `Receiver` that was converted into a `Peekable`).
110111

112+
* `SelectErrorGroup` was removed, a Python built-in `BaseExceptionGroup` is raised instead in case of unexpected errors while finalizing a `select()` loop, which will be automatically converted to a simple `ExceptionGroup` when no exception in the groups is a `BaseException`.
113+
111114
- `Timer`:
112115

113116
- `periodic()` and `timeout()`: The names proved to be too confusing, please use `Timer()` and pass a missing ticks policy explicitly instead. In general you can update your code by doing:

src/frequenz/channels/__init__.py

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -58,9 +58,6 @@
5858
* [SelectError][frequenz.channels.SelectError]: Base class for all errors
5959
related to [select][frequenz.channels.select].
6060
61-
* [SelectErrorGroup][frequenz.channels.SelectErrorGroup]: A group of errors
62-
raised by [select][frequenz.channels.select].
63-
6461
* [UnhandledSelectedError][frequenz.channels.UnhandledSelectedError]: An error
6562
raised by [select][frequenz.channels.select] that was not handled by the
6663
user.
@@ -85,7 +82,6 @@
8582
from ._select import (
8683
Selected,
8784
SelectError,
88-
SelectErrorGroup,
8985
UnhandledSelectedError,
9086
select,
9187
selected_from,
@@ -103,7 +99,6 @@
10399
"ReceiverError",
104100
"ReceiverStoppedError",
105101
"SelectError",
106-
"SelectErrorGroup",
107102
"Selected",
108103
"Sender",
109104
"SenderError",

src/frequenz/channels/_select.py

Lines changed: 9 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -43,11 +43,6 @@
4343
If this happens, it will raise an
4444
[`UnhandledSelectedError`][frequenz.channels.UnhandledSelectedError] exception.
4545
46-
Not handling a receiver is considered a programming error. Because of this, the
47-
exception is a subclass of [`BaseException`][BaseException] instead of
48-
[`Exception`][Exception]. This means that it will not be caught by [`except
49-
Exception`][Exception] blocks.
50-
5146
If for some reason you want to ignore a received message, just add the receiver to
5247
the if-chain and do nothing with the message:
5348
@@ -140,6 +135,7 @@
140135
from collections.abc import AsyncIterator
141136
from typing import Any, Generic, TypeGuard, TypeVar
142137

138+
from ._exceptions import Error
143139
from ._receiver import Receiver, ReceiverStoppedError
144140

145141
_T = TypeVar("_T")
@@ -277,14 +273,13 @@ def selected_from(
277273
return handled
278274

279275

280-
class SelectError(BaseException):
276+
class SelectError(Error):
281277
"""An error that happened during a [`select()`][frequenz.channels.select] operation.
282278
283279
This exception is raised when a `select()` iteration fails. It is raised as
284280
a single exception when one receiver fails during normal operation (while calling
285281
`ready()` for example). It is raised as a group exception
286-
([`SelectErrorGroup`][frequenz.channels.SelectErrorGroup]) when a `select` loop
287-
is cleaning up after it's done.
282+
([`BaseExceptionGroup`][]) when a `select` loop is cleaning up after it's done.
288283
"""
289284

290285

@@ -308,14 +303,6 @@ def __init__(self, selected: Selected[_T]) -> None:
308303
"""The selected receiver that was not handled."""
309304

310305

311-
class SelectErrorGroup(BaseExceptionGroup[BaseException], SelectError):
312-
"""Some receivers stopped with errors while cleaning up a `select()` operation.
313-
314-
The group is composed of the errors raised by the receivers when they are stopped
315-
during the cleanup of a [`select()`][frequenz.channels.select] loop.
316-
"""
317-
318-
319306
# Typing for select() is tricky. We had the idea of using a declarative design for
320307
# select, something like:
321308
#
@@ -430,8 +417,8 @@ async def select(*receivers: Receiver[Any]) -> AsyncIterator[Selected[Any]]:
430417
431418
Raises:
432419
UnhandledSelectedError: If a selected receiver was not handled in the if-chain.
433-
SelectErrorGroup: If there is an error while finishing the select operation and
434-
receivers fail while cleaning up.
420+
BaseExceptionGroup: If there is an error while finishing the select operation
421+
and receivers fail while cleaning up.
435422
SelectError: If there is an error while selecting receivers during normal
436423
operation. For example if a receiver raises an exception in the `ready()`
437424
method. Normal errors while receiving messages are not raised, but reported
@@ -487,7 +474,7 @@ async def _stop_pending_tasks(pending: set[asyncio.Task[bool]]) -> None:
487474
pending: The pending tasks to stop.
488475
489476
Raises:
490-
SelectErrorGroup: If the receivers raise any exceptions.
477+
BaseExceptionGroup: If the receivers raise any exceptions.
491478
"""
492479
if pending:
493480
for task in pending:
@@ -506,4 +493,6 @@ async def _stop_pending_tasks(pending: set[asyncio.Task[bool]]) -> None:
506493
# will be collected by the asyncio loop. This shouldn't be too bad as
507494
# errors produced by receivers will be re-raised when trying to use them
508495
# again.
509-
raise SelectErrorGroup("Some receivers failed when select()ing", exceptions)
496+
raise BaseExceptionGroup(
497+
"Some receivers failed when select()ing", exceptions
498+
)

0 commit comments

Comments
 (0)