Skip to content

Commit bf71a35

Browse files
authored
Merge branch 'main' into pytest-timeout
2 parents 009a398 + 2e8842f commit bf71a35

4 files changed

Lines changed: 30 additions & 5 deletions

File tree

.github/workflows/codeql.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ jobs:
6161

6262
# Initializes the CodeQL tools for scanning.
6363
- name: Initialize CodeQL
64-
uses: github/codeql-action/init@89a39a4e59826350b863aa6b6252a07ad50cf83e # v4
64+
uses: github/codeql-action/init@c793b717bc78562f491db7b0e93a3a178b099162 # v4
6565
with:
6666
languages: ${{ matrix.language }}
6767
build-mode: ${{ matrix.build-mode }}
@@ -88,6 +88,6 @@ jobs:
8888
echo ' make release'
8989
exit 1
9090
- name: Perform CodeQL Analysis
91-
uses: github/codeql-action/analyze@89a39a4e59826350b863aa6b6252a07ad50cf83e # v4
91+
uses: github/codeql-action/analyze@c793b717bc78562f491db7b0e93a3a178b099162 # v4
9292
with:
9393
category: "/language:${{matrix.language}}"

src/blueapi/core/context.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -526,7 +526,7 @@ def _type_spec_for_function(
526526
)
527527
return new_args
528528

529-
def _convert_type(self, typ: type | Any, no_default: bool = True) -> type:
529+
def _convert_type(self, typ: Any, no_default: bool = True) -> type:
530530
"""
531531
Recursively convert a type to something that can be deserialised by
532532
pydantic. Bluesky protocols (and types that extend them) are replaced

src/blueapi/core/event.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import itertools
2+
import logging
23
from abc import ABC, abstractmethod
34
from collections.abc import Callable
45
from typing import Generic, TypeVar
@@ -9,6 +10,8 @@
910
#: Subscription token type
1011
S = TypeVar("S")
1112

13+
LOGGER = logging.getLogger(__name__)
14+
1215

1316
class EventStream(ABC, Generic[E, S]):
1417
"""
@@ -75,6 +78,11 @@ def publish(self, event: E, correlation_id: str | None = None) -> None:
7578
correlation_id: An optional ID that may be used to correlate this
7679
event with other events
7780
"""
78-
81+
errs = []
7982
for callback in list(self._subscriptions.values()):
80-
callback(event, correlation_id)
83+
try:
84+
callback(event, correlation_id)
85+
except Exception as e:
86+
errs.append(e)
87+
if errs:
88+
raise ExceptionGroup(f"Error(s) publishing event: {event}", errs)

tests/unit_tests/core/test_event.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
from concurrent.futures import Future
33
from dataclasses import dataclass
44
from queue import Queue
5+
from unittest import mock
56

67
import pytest
78

@@ -76,6 +77,22 @@ def test_correlation_id(publisher: EventPublisher[MyEvent]) -> None:
7677
assert f.result(timeout=_TIMEOUT) == correlation_id
7778

7879

80+
def test_callback_exceptions_are_contained(publisher: EventPublisher[MyEvent]):
81+
event = MyEvent("foo")
82+
c_id = "bar"
83+
84+
# First call should raise exception, next should be fine
85+
handler = mock.Mock(side_effect=[ValueError("Bad Event"), ()])
86+
publisher.subscribe(handler)
87+
publisher.subscribe(handler)
88+
89+
with pytest.RaisesGroup(ValueError):
90+
publisher.publish(event, c_id)
91+
92+
# Both handlers should be called but the exception should still be raised
93+
handler.assert_has_calls([mock.call(event, c_id), mock.call(event, c_id)])
94+
95+
7996
def _drain(queue: Queue) -> Iterable:
8097
while not queue.empty():
8198
yield queue.get_nowait()

0 commit comments

Comments
 (0)