Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow PVI devices to be initialized before connection #241

Merged
Merged
Show file tree
Hide file tree
Changes from 30 commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
914324a
Make backend optional on Signal initialisation
noemifrisina Apr 18, 2024
276d3ce
Run pre-commits
noemifrisina Apr 18, 2024
7a3dc47
Add small test for connection failure
noemifrisina Apr 18, 2024
0169d85
Merge branch 'main' into 194_initialise-devices-pre-connection
noemifrisina Apr 18, 2024
aaf6a29
Merge branch 'main' into 194_initialise-devices-pre-connection
noemifrisina Apr 19, 2024
53090ce
Merge branch 'main' into 194_initialise-devices-pre-connection
noemifrisina May 8, 2024
34ea837
Merge branch 'main' into 194_initialise-devices-pre-connection
noemifrisina May 9, 2024
a4d4ccc
DeviceVector change in _sim_common_blocks
noemifrisina May 10, 2024
66ad14d
Merge branch 'main' into 194_initialise-devices-pre-connection
noemifrisina May 30, 2024
719bcac
Save stashed change
noemifrisina May 30, 2024
1c33413
Fix typo
noemifrisina May 30, 2024
e293ed2
Fix error created from merging
noemifrisina May 30, 2024
acb4729
Remove leftover else
noemifrisina May 30, 2024
29cb033
Save the rest of the stashed changes
noemifrisina May 30, 2024
f21901a
Update src/ophyd_async/core/signal.py
noemifrisina May 31, 2024
b33c9a2
Keep initial_backend as None and change _backend
noemifrisina May 31, 2024
c043c4d
Update src/ophyd_async/epics/pvi/pvi.py
noemifrisina May 31, 2024
8200c76
Fix tests
noemifrisina May 31, 2024
22a6c37
Merge branch 'main' into 194_initialise-devices-pre-connection
noemifrisina May 31, 2024
be3768a
Update src/ophyd_async/core/signal.py
noemifrisina Jun 5, 2024
699b8d8
Update tests/core/test_signal.py
noemifrisina Jun 5, 2024
b071d78
Fix signal tests
noemifrisina Jun 5, 2024
7c1c659
Attempt at pvi test
noemifrisina Jun 6, 2024
06079fc
Fix test
noemifrisina Jun 6, 2024
72611f4
Merge branch 'main' into 194_initialise-devices-pre-connection
noemifrisina Jun 6, 2024
96695fa
Update src/ophyd_async/epics/pvi/pvi.py
noemifrisina Jun 7, 2024
47b998c
Update src/ophyd_async/epics/pvi/pvi.py
noemifrisina Jun 7, 2024
dbfad01
Update src/ophyd_async/epics/pvi/pvi.py
noemifrisina Jun 7, 2024
fd30e8e
_mock_common_blocks change
noemifrisina Jun 7, 2024
0ccf203
Remove double test case
noemifrisina Jun 7, 2024
bb5f4b1
Update tests/epics/test_pvi.py
noemifrisina Jun 10, 2024
dcc432b
Add optional block and remove device collector
noemifrisina Jun 10, 2024
6431e33
Test that even if optional signal still isn't initialised
noemifrisina Jun 10, 2024
ccae84b
Test that even if optional signal still isn't initialised
noemifrisina Jun 10, 2024
5bdf629
Update comment
noemifrisina Jun 10, 2024
bb9a9e5
Merge branch 'main' into 194_initialise-devices-pre-connection
evalott100 Jun 10, 2024
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
21 changes: 16 additions & 5 deletions src/ophyd_async/core/signal.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ class Signal(Device, Generic[T]):

def __init__(
self,
backend: SignalBackend[T],
backend: Optional[SignalBackend[T]] = None,
timeout: Optional[float] = DEFAULT_TIMEOUT,
name: str = "",
) -> None:
Expand All @@ -66,13 +66,24 @@ def __init__(
super().__init__(name)

async def connect(
self, mock=False, timeout=DEFAULT_TIMEOUT, force_reconnect: bool = False
self,
mock=False,
timeout=DEFAULT_TIMEOUT,
force_reconnect: bool = False,
backend: Optional[SignalBackend[T]] = None,
):
if backend:
if self._initial_backend and backend is not self._initial_backend:
raise ValueError(
"Backend at connection different from initialised one."
)
self._backend = backend
if mock and not isinstance(self._backend, MockSignalBackend):
# Using a soft backend, look to the initial value
self._backend = MockSignalBackend(
initial_backend=self._initial_backend,
)
self._backend = MockSignalBackend(initial_backend=self._backend)

if self._backend is None:
raise RuntimeError("`connect` called on signal without backend")
self.log.debug(f"Connecting to {self.source}")
await self._backend.connect(timeout=timeout)

Expand Down
32 changes: 26 additions & 6 deletions src/ophyd_async/epics/pvi/pvi.py
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,15 @@ def _mock_common_blocks(device: Device, stripped_type: Optional[Type] = None):
sub_device_2 = device_cls(SoftSignalBackend(signal_dtype))
sub_device = DeviceVector({1: sub_device_1, 2: sub_device_2})
else:
sub_device = DeviceVector({1: device_cls(), 2: device_cls()})
if hasattr(device, device_name):
sub_device = getattr(device, device_name)
else:
sub_device = DeviceVector(
{
1: device_cls(),
2: device_cls(),
}
)

for sub_device_in_vector in sub_device.values():
_mock_common_blocks(sub_device_in_vector, stripped_type=device_cls)
Expand Down Expand Up @@ -296,7 +304,9 @@ async def fill_pvi_entries(


def create_children_from_annotations(
evalott100 marked this conversation as resolved.
Show resolved Hide resolved
device: Device, included_optional_fields: Tuple[str, ...] = ()
device: Device,
included_optional_fields: Tuple[str, ...] = (),
device_vectors: Optional[Dict[str, int]] = None,
):
"""For intializing blocks at __init__ of ``device``."""
for name, device_type in get_type_hints(type(device)).items():
Expand All @@ -307,12 +317,22 @@ def create_children_from_annotations(
continue
is_device_vector, device_type = _strip_device_vector(device_type)
if (
is_device_vector
(is_device_vector and (not device_vectors or name not in device_vectors))
or ((origin := get_origin(device_type)) and issubclass(origin, Signal))
or (isclass(device_type) and issubclass(device_type, Signal))
):
continue

sub_device = device_type()
setattr(device, name, sub_device)
create_children_from_annotations(sub_device)
if is_device_vector:
n_device_vector = DeviceVector(
{i: device_type() for i in range(1, device_vectors[name] + 1)}
)
setattr(device, name, n_device_vector)
for sub_device in n_device_vector.values():
create_children_from_annotations(
sub_device, device_vectors=device_vectors
)
else:
sub_device = device_type()
setattr(device, name, sub_device)
create_children_from_annotations(sub_device, device_vectors=device_vectors)
31 changes: 31 additions & 0 deletions tests/core/test_signal.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
ConfigSignal,
DeviceCollector,
HintedSignal,
MockSignalBackend,
Signal,
SignalR,
SignalRW,
SoftSignalBackend,
Expand Down Expand Up @@ -50,6 +52,35 @@ async def test_signals_equality_raises():
s1 > 4

evalott100 marked this conversation as resolved.
Show resolved Hide resolved

async def test_signal_can_be_given_backend_on_connect():
sim_signal = SignalR()
backend = MockSignalBackend(int)
assert sim_signal._backend is None
await sim_signal.connect(mock=False, backend=backend)
assert await sim_signal.get_value() == 0


async def test_signal_connect_fails_with_different_backend_on_connection():
sim_signal = Signal(MockSignalBackend(str))

with pytest.raises(ValueError):
await sim_signal.connect(mock=True, backend=MockSignalBackend(int))

with pytest.raises(ValueError):
await sim_signal.connect(mock=True, backend=SoftSignalBackend(str))


async def test_signal_connect_fails_if_different_backend_but_same_by_value():
initial_backend = MockSignalBackend(str)
sim_signal = Signal(initial_backend)

with pytest.raises(ValueError) as exc:
await sim_signal.connect(mock=False, backend=MockSignalBackend(str))
assert str(exc.value) == "Backend at connection different from initialised one."

await sim_signal.connect(mock=False, backend=initial_backend)


async def time_taken_by(coro) -> float:
start = time.monotonic()
await coro
Expand Down
48 changes: 48 additions & 0 deletions tests/epics/test_pvi.py
Original file line number Diff line number Diff line change
Expand Up @@ -144,3 +144,51 @@ async def test_device_create_children_from_annotations(
assert device.device is block_2_device
assert device.device.device is block_1_device
assert device.signal_device is top_block_1_device


@pytest.fixture
def pvi_test_device_with_device_vectors_t():
"""A fixture since pytest discourages init in test case classes"""

class TestDevice(Block2):
def __init__(self, prefix: str, name: str = ""):
self._prefix = prefix
create_children_from_annotations(self, device_vectors={"device_vector": 2})
evalott100 marked this conversation as resolved.
Show resolved Hide resolved
super().__init__(name)

async def connect(
self, mock: bool = False, timeout: float = DEFAULT_TIMEOUT
) -> None:
await fill_pvi_entries(
self, self._prefix + "PVI", timeout=timeout, mock=mock
)

await super().connect(mock=mock)

yield TestDevice


async def test_device_create_children_from_annotations_with_device_vectors(
pvi_test_device_with_device_vectors_t,
):
async with DeviceCollector(mock=True):
device = pvi_test_device_with_device_vectors_t("PREFIX:", name="test_device")

evalott100 marked this conversation as resolved.
Show resolved Hide resolved
assert device.device_vector[1].name == "test_device-device_vector-1"
assert device.device_vector[2].name == "test_device-device_vector-2"
block_1_device = device.device
block_2_device_vector = device.device_vector

# create_children_from_annotiations should have made blocks and DeviceVectors
# but not signals
assert hasattr(device, "device_vector")
assert isinstance(block_2_device_vector, DeviceVector)
assert isinstance(block_2_device_vector[1], Block1)
assert len(device.device_vector) == 2
assert isinstance(block_1_device, Block1)

await device.connect(mock=True)

# The memory addresses have not changed
assert device.device is block_1_device
assert device.device_vector is block_2_device_vector