Skip to content
Open
Show file tree
Hide file tree
Changes from 2 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
38 changes: 38 additions & 0 deletions src/ophyd_async/core/_device.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,39 @@ async def connect_real(self, device: Device, timeout: float, force_reconnect: bo
await wait_for_connection(**coros)


DEVICE_RESERVED_ATTRS = {
"add_callback",
"exception",
"done",
"success",
Comment thread
shihab-dls marked this conversation as resolved.
Outdated
"name",
"collect_asset_docs",
"get_index",
"read_configuration",
"describe_configuration",
"trigger",
"prepare",
"read",
"describe",
"describe_collect",
"collect",
"collect_pages",
"set",
"locate",
"kickoff",
"complete",
"stage",
"unstage",
"pause",
"resume",
"stop",
"subscribe",
"clear_sub",
"check_value",
"hints",
}


class Device(HasName):
"""Common base class for all Ophyd Async Devices.

Expand Down Expand Up @@ -152,6 +185,11 @@ def __setattr__(self, name: str, value: Any) -> None:
# ...hence not doing an isinstance check for attributes we
# know not to be Devices
elif name not in _not_device_attrs and isinstance(value, Device):
if name in DEVICE_RESERVED_ATTRS:
raise NameError(
f"`{name}` is used in one of the bluesky protocols. "
f"Please use `{name}_` instead."
)
Comment thread
shihab-dls marked this conversation as resolved.
Outdated
value.parent = self
self._child_devices[name] = value
# And if the name is set, then set the name of all children,
Expand Down
14 changes: 14 additions & 0 deletions tests/core/test_device.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
soft_signal_rw,
wait_for_connection,
)
from ophyd_async.core._device import DEVICE_RESERVED_ATTRS # noqa: PLC2701
from ophyd_async.epics import motor
from ophyd_async.plan_stubs import ensure_connected

Expand Down Expand Up @@ -67,6 +68,19 @@ def get_source(self) -> str:
return self.signal_ref().source


@pytest.mark.parametrize("attr_name", DEVICE_RESERVED_ATTRS)
def test_attr_in_bluesky_protocols(attr_name):
class DeviceWithProtocolName(Device):
def __init__(self, name: str = "") -> None:
super().__init__(name)
# use setattr so we can inject the name dynamically
setattr(self, attr_name, soft_signal_rw(int, name="foo"))

expected_msg = f"Please use `{attr_name}_` instead"
with pytest.raises(NameError, match=expected_msg):
DeviceWithProtocolName("bar")


async def test_device_connect_missing_connector() -> None:
# Create an instance of Device without calling __init__
device = object.__new__(Device)
Expand Down
Loading