[docs]classStandardReadableFormat(Enum):"""Declare how a `Device` should contribute to the `StandardReadable` verbs."""CHILD="CHILD""""Detect which verbs the child supports and contribute to: - `read()`, `describe()` if it is [](#bluesky.protocols.Readable) - `read_configuration()`, `describe_configuration()` if it is [](#bluesky.protocols.Configurable) - `stage()`, `unstage()` if it is [](#bluesky.protocols.Stageable) - `hints` if it [](#bluesky.protocols.HasHints) """CONFIG_SIGNAL="CONFIG_SIGNAL""""Contribute the `Signal` value to `read_configuration()` and `describe_configuration()` """HINTED_SIGNAL="HINTED_SIGNAL""""Contribute the monitored `Signal` value to `read()` and `describe()` and put the signal name in `hints` """UNCACHED_SIGNAL="UNCACHED_SIGNAL""""Contribute the uncached `Signal` value to `read()` and `describe()` """HINTED_UNCACHED_SIGNAL="HINTED_UNCACHED_SIGNAL""""Contribute the uncached `Signal` value to `read()` and `describe()` and put the signal name in `hints` """def__call__(self,parent:Device,child:Device):ifnotisinstance(parent,StandardReadable):raiseTypeError(f"Expected parent to be StandardReadable, got {parent}")parent.add_readables([child],self)
# Back compatclass_WarningMatcher:def__init__(self,name:str,target:StandardReadableFormat):self._name=nameself._target=targetdef__eq__(self,value:object)->bool:warnings.warn(DeprecationWarning(f"Use `StandardReadableFormat.{self._target.name}` "f"instead of `{self._name}`"),stacklevel=2,)returnvalue==self._targetdef_compat_format(name:str,target:StandardReadableFormat)->StandardReadableFormat:returncast(StandardReadableFormat,_WarningMatcher(name,target))ConfigSignal=_compat_format("ConfigSignal",StandardReadableFormat.CONFIG_SIGNAL)HintedSignal:Any=_compat_format("HintedSignal",StandardReadableFormat.HINTED_SIGNAL)HintedSignal.uncached=_compat_format("HintedSignal.uncached",StandardReadableFormat.HINTED_UNCACHED_SIGNAL)
[docs]classStandardReadable(Device,AsyncReadable,AsyncConfigurable,AsyncStageable,HasHints):"""Device that provides selected child Device values in `read()`. Provides the ability for children to be registered to: - Participate in `stage()` and `unstage()` - Provide their value in `read()` and `describe() - Provide their value in `read_configuration()` and `describe_configuration() - Select a value to appear in `hints` The behavior is customized with a [](#StandardReadableFormat) """# These must be immutable types to avoid accidental sharing between# different instances of the class_describe_config_funcs:tuple[Callable[[],Awaitable[dict[str,DataKey]]],...]=()_read_config_funcs:tuple[Callable[[],Awaitable[dict[str,Reading]]],...]=()_describe_funcs:tuple[Callable[[],Awaitable[dict[str,DataKey]]],...]=()_read_funcs:tuple[Callable[[],Awaitable[dict[str,Reading]]],...]=()_stageables:tuple[AsyncStageable,...]=()_has_hints:tuple[HasHints,...]=()
@propertydefhints(self)->Hints:hints:Hints={}fornew_hintinself._has_hints:# Merge the existing and new hints, based on the type of the value.# This avoids default dict merge behavior that overrides the values;# we want to combine them when they are Sequences, and ensure they are# identical when string values.forkey,valueinnew_hint.hints.items():# fail early for unkwon typesifisinstance(value,str):ifkeyinhints:ifhints[key]!=value:msg=f"Hints key {key} value may not be overridden"raiseRuntimeError(msg)else:hints[key]=value# type: ignore[literal-required]elifisinstance(value,Sequence):ifkeyinhints:fornew_valinvalue:ifnew_valinhints[key]:msg=f"Hint {key}{new_val} overrides existing hint"raiseRuntimeError(msg)hints[key]=(# type: ignore[literal-required]hints[key]+value# type: ignore[literal-required])else:hints[key]=value# type: ignore[literal-required]else:msg=(f"{new_hint.name}: Unknown type for value '{value}'"f" for key '{key}'")raiseTypeError(msg)returnhints
[docs]@contextmanagerdefadd_children_as_readables(self,format:StandardReadableFormat=StandardReadableFormat.CHILD,)->Generator[None,None,None]:"""Context manager that calls [](#add_readables) on child Devices added within. Scans `self.children()` on entry and exit to context manager, and calls `add_readables()` on any that are added with the provided `StandardReadableFormat`. """dict_copy=dict(self.children())yield# Set symmetric difference operator gives all newly added keys.new_dict=dict(self.children())forkey,valueinnew_dict.items():# Check if key already exists in dict_copy and if the value has changed.ifkeyindict_copyandvalue!=dict_copy[key]:error_msg=(f"Duplicate readable device found: '{key}' in {value.parent}. ""Derived class must not redefine a readable. ""See: https://github.com/bluesky/ophyd-async/issues/848. ""If this functionality is required, please raise an issue: ""https://github.com/bluesky/ophyd-async")raiseKeyError(error_msg)new_keys=dict_copy.keys()^new_dict.keys()new_values=[new_dict[key]forkeyinnew_keys]flattened_values=[]forvalueinnew_values:ifisinstance(value,DeviceVector):flattened_values.extend(value.values())else:flattened_values.append(value)new_devices=list(filter(lambdax:isinstance(x,Device),flattened_values))self.add_readables(new_devices,format)
[docs]defadd_readables(self,devices:Sequence[Device],format:StandardReadableFormat=StandardReadableFormat.CHILD,)->None:"""Add devices to contribute to various bluesky verbs. Use output from the given devices to contribute to the verbs of the following interfaces: - [](#bluesky.protocols.Readable) - [](#bluesky.protocols.Configurable) - [](#bluesky.protocols.Stageable) - [](#bluesky.protocols.HasHints) :param devices: The devices to be added :param format: Determines which of the devices functions are added to which verb as per the [](#StandardReadableFormat) documentation """defassert_device_is_signalr(device:Device)->SignalR:ifnotisinstance(device,SignalR):raiseTypeError(f"{device} is not a SignalR")returndevicefordeviceindevices:matchformat:caseStandardReadableFormat.CHILD:ifisinstance(device,AsyncConfigurable):self._describe_config_funcs+=(device.describe_configuration,)self._read_config_funcs+=(device.read_configuration,)ifisinstance(device,AsyncReadable):self._describe_funcs+=(device.describe,)self._read_funcs+=(device.read,)ifisinstance(device,AsyncStageable):self._stageables+=(device,)ifisinstance(device,HasHints):self._has_hints+=(device,)caseStandardReadableFormat.CONFIG_SIGNAL:signalr_device=assert_device_is_signalr(device=device)self._describe_config_funcs+=(signalr_device.describe,)self._read_config_funcs+=(signalr_device.read,)caseStandardReadableFormat.HINTED_SIGNAL:signalr_device=assert_device_is_signalr(device=device)self._describe_funcs+=(signalr_device.describe,)self._read_funcs+=(signalr_device.read,)self._stageables+=(signalr_device,)self._has_hints+=(_HintsFromName(signalr_device),)caseStandardReadableFormat.UNCACHED_SIGNAL:signalr_device=assert_device_is_signalr(device=device)self._describe_funcs+=(signalr_device.describe,)self._read_funcs+=(_UncachedRead(signalr_device),)caseStandardReadableFormat.HINTED_UNCACHED_SIGNAL:signalr_device=assert_device_is_signalr(device=device)self._describe_funcs+=(signalr_device.describe,)self._read_funcs+=(_UncachedRead(signalr_device),)self._has_hints+=(_HintsFromName(signalr_device),)