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

Remove assert in production code #728

Merged
merged 102 commits into from
Jan 15, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
102 commits
Select commit Hold shift + click to select a range
e773135
add S101 (assert) to ruff and exclude all test code directories
kivel Jan 7, 2025
8f5437f
assert removed from plan_stubs
kivel Jan 7, 2025
a1c47c9
test raise for setup_ndstats
kivel Jan 7, 2025
7b7557a
assert removed from fastcs panda control
kivel Jan 7, 2025
694fd4a
typo
kivel Jan 7, 2025
43db498
assert removed from fastcs table
kivel Jan 7, 2025
9dd323d
assert removed from epics sim Mover
kivel Jan 7, 2025
0d153db
assert removed from epics motor
kivel Jan 7, 2025
6aff2d6
assert removed from epics core p4p and aicoa
kivel Jan 7, 2025
0f98651
assert removed from epics adsimdetector
kivel Jan 7, 2025
9b25250
assert removed from epics adcore hdf_writer
kivel Jan 7, 2025
057598b
assert removed from TangoMover
kivel Jan 7, 2025
8314f84
allow assert in tango transport for now
kivel Jan 7, 2025
8c2f0ec
assert removed from PatternDetectorController
kivel Jan 8, 2025
dcbc2d4
assert removed from PatternDetector write_data_to_dataset
kivel Jan 8, 2025
ebf2646
assert removed from PatternDetector open_file
kivel Jan 8, 2025
62d785d
assert removed from PatternDetector collect_stream_docs
kivel Jan 8, 2025
c084c5a
assert removed from soft_signal_backend
kivel Jan 8, 2025
d4b8bbe
assert removed from SignalR._backend_or_chache
kivel Jan 8, 2025
6609d56
assert removed from _SignalCache.get_reading
kivel Jan 8, 2025
e88aee3
assert removed from _SignalCache._notify
kivel Jan 8, 2025
161cd58
assert removed from Table
kivel Jan 8, 2025
23a23dc
fix imports
kivel Jan 8, 2025
8d42e5d
assert removed from device_filler
kivel Jan 9, 2025
a57abfd
assert removed from StandardReadable.hints
kivel Jan 9, 2025
2969ba5
assert removed from StandardReadable.add_readables
kivel Jan 9, 2025
c0669f0
assert removed from Device.connect connector check
kivel Jan 9, 2025
43cd8db
assert removed from Device.connect task check
kivel Jan 9, 2025
f9c57c5
assert removed from DeviceVector.__setitem__
kivel Jan 9, 2025
b94d7e6
assert removed from DeviceProcessor._caller_locals
kivel Jan 9, 2025
baf1d57
add tests for StandardDetector.prepare
kivel Jan 9, 2025
38cdb54
assert removed from StandardDetector.prepare
kivel Jan 9, 2025
4bafe0d
add tests for StandardDetector.prepare
kivel Jan 9, 2025
e540f29
assert removed from StandardDetector.trigger
kivel Jan 9, 2025
1168c3e
assert removed from StandardDetector.complete
kivel Jan 9, 2025
49d1f71
cleaned up unused imports
kivel Jan 9, 2025
c30424b
more freedom in examples
kivel Jan 9, 2025
27b3c13
add S101 (assert) to ruff and exclude all test code directories
kivel Jan 7, 2025
0902353
assert removed from plan_stubs
kivel Jan 7, 2025
521b6a6
test raise for setup_ndstats
kivel Jan 7, 2025
3e8de46
assert removed from fastcs panda control
kivel Jan 7, 2025
8947946
typo
kivel Jan 7, 2025
01388fa
assert removed from fastcs table
kivel Jan 7, 2025
2fa2c22
assert removed from epics sim Mover
kivel Jan 7, 2025
a94a0ca
assert removed from epics motor
kivel Jan 7, 2025
3a495f3
assert removed from epics core p4p and aicoa
kivel Jan 7, 2025
d3680a7
assert removed from epics adsimdetector
kivel Jan 7, 2025
7cfc5c4
assert removed from TangoMover
kivel Jan 7, 2025
5c9e21f
allow assert in tango transport for now
kivel Jan 7, 2025
ab23cca
assert removed from PatternDetectorController
kivel Jan 8, 2025
6d3767f
assert removed from PatternDetector write_data_to_dataset
kivel Jan 8, 2025
32beff0
assert removed from PatternDetector open_file
kivel Jan 8, 2025
a44cf26
assert removed from PatternDetector collect_stream_docs
kivel Jan 8, 2025
bcf40c4
assert removed from soft_signal_backend
kivel Jan 8, 2025
b5d55f4
assert removed from SignalR._backend_or_chache
kivel Jan 8, 2025
301eb3c
assert removed from _SignalCache.get_reading
kivel Jan 8, 2025
d4b2340
assert removed from _SignalCache._notify
kivel Jan 8, 2025
8f7069a
assert removed from Table
kivel Jan 8, 2025
d5d09a7
fix imports
kivel Jan 8, 2025
b612654
assert removed from device_filler
kivel Jan 9, 2025
0489694
assert removed from StandardReadable.hints
kivel Jan 9, 2025
578cf25
assert removed from StandardReadable.add_readables
kivel Jan 9, 2025
76adfc3
assert removed from Device.connect connector check
kivel Jan 9, 2025
60905b1
assert removed from Device.connect task check
kivel Jan 9, 2025
0a1e394
assert removed from DeviceVector.__setitem__
kivel Jan 9, 2025
661c167
assert removed from DeviceProcessor._caller_locals
kivel Jan 9, 2025
4e91791
add tests for StandardDetector.prepare
kivel Jan 9, 2025
916d028
assert removed from StandardDetector.prepare
kivel Jan 9, 2025
b3cb2e6
add tests for StandardDetector.prepare
kivel Jan 9, 2025
da0476f
assert removed from StandardDetector.trigger
kivel Jan 9, 2025
cf375bf
assert removed from StandardDetector.complete
kivel Jan 9, 2025
0eaeb2e
cleaned up unused imports
kivel Jan 9, 2025
4ddf6f3
more freedom in examples
kivel Jan 9, 2025
17f23b8
line length reduced
kivel Jan 9, 2025
4995cc0
test case fixed for StandardDetector
kivel Jan 9, 2025
cde9d88
test removed due to upstream changes
kivel Jan 9, 2025
354b5a4
test ficed due to migration from hdf to fileio in upstream method
kivel Jan 9, 2025
f761030
test fixed to account for upstream changes
kivel Jan 9, 2025
c9721e7
assert removed from internal trigger in ADBaseController.prepare
kivel Jan 10, 2025
adbc2b2
assert removed from ADWriter
kivel Jan 10, 2025
90a6839
assert allowed in TestingIOC as it's only for static type checking
kivel Jan 10, 2025
457cda5
typo in src/ophyd_async/plan_stubs/_nd_attributes.py
kivel Jan 13, 2025
9dfbb92
re-order to have the happy path with priority
kivel Jan 13, 2025
d9998b5
fix type in test
kivel Jan 13, 2025
ab110f6
TypeGuard simplified
kivel Jan 13, 2025
0c0bda7
explicit check for not None in src/ophyd_async/core/_signal.py
kivel Jan 13, 2025
7e2a4dc
todo removed
kivel Jan 13, 2025
101b64e
fixed broken timeout calc when timeout is provided
kivel Jan 13, 2025
9d88281
fixed broken timeout in sim mover
kivel Jan 13, 2025
8af924f
make function private
kivel Jan 13, 2025
cc79c3a
check and return DeviceAnnotation
kivel Jan 13, 2025
de964ad
use returned DeviceAnnotation after check
kivel Jan 13, 2025
5cc06cf
move to free function
kivel Jan 13, 2025
6860060
moved triggerInfo check to free function
kivel Jan 13, 2025
5098783
added _ensure_reading to SignalCache
kivel Jan 14, 2025
a769e57
added missing type hints to SignalCache
kivel Jan 14, 2025
a3fa08d
removed commented code
kivel Jan 14, 2025
34e0b70
Merge branch 'main' into 710_remove_assert
kivel Jan 14, 2025
ffd3930
Merge branch 'main' into 710_remove_assert
kivel Jan 14, 2025
fa0a768
format fixes
kivel Jan 14, 2025
7f0e8fa
upgrade ruff version and reformat
kivel Jan 15, 2025
8592cbd
use same logic Tagno as for aioca and p4p callbacks
kivel Jan 15, 2025
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
6 changes: 5 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@ lint.select = [
"SLF", # self - https://docs.astral.sh/ruff/settings/#lintflake8-self
"PLC2701", # private import - https://docs.astral.sh/ruff/rules/import-private-name/
"LOG015", # root logger call - https://docs.astral.sh/ruff/rules/root-logger-call/
"S101", # assert = https://docs.astral.sh/ruff/rules/assert/
]
lint.ignore = [
"B901", # Return in a generator is needed for plans
Expand All @@ -166,7 +167,10 @@ lint.preview = true # so that preview mode PLC2701 is enabled
# By default, private member access is allowed in tests
# See https://github.com/DiamondLightSource/python-copier-template/issues/154
# Remove this line to forbid private member access in tests
"tests/**/*" = ["SLF001"]
"tests/**/*" = ["SLF001", "S101"]
"src/ophyd_async/testing/**/*" = ["SLF001", "S101"]
"system_tests/**/*" = ["SLF001", "S101"]
"docs/examples/**/*" = ["SLF001", "S101"]


[tool.importlinter]
Expand Down
36 changes: 24 additions & 12 deletions src/ophyd_async/core/_detector.py
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,13 @@ def hints(self) -> Hints:
DetectorWriterT = TypeVar("DetectorWriterT", bound=DetectorWriter)


def _ensure_trigger_info_exists(trigger_info: TriggerInfo | None) -> TriggerInfo:
# make absolute sure we realy have a valid TriggerInfo ... mostly for pylance
if trigger_info is None:
raise RuntimeError("Trigger info must be set before calling this method.")
return trigger_info


class StandardDetector(
Device,
Stageable,
Expand Down Expand Up @@ -279,8 +286,12 @@ async def trigger(self) -> None:
frame_timeout=None,
)
)
assert self._trigger_info
assert self._trigger_info.trigger is DetectorTrigger.INTERNAL

self._trigger_info = _ensure_trigger_info_exists(self._trigger_info)
if self._trigger_info.trigger is not DetectorTrigger.INTERNAL:
msg = "The trigger method can only be called with INTERNAL triggering"
raise ValueError(msg)

# Arm the detector and wait for it to finish.
indices_written = await self._writer.get_indices_written()
await self._controller.arm()
Expand Down Expand Up @@ -311,16 +322,17 @@ async def prepare(self, value: TriggerInfo) -> None:
Args:
value: TriggerInfo describing how to trigger the detector
"""
if value.trigger != DetectorTrigger.INTERNAL:
assert value.deadtime, (
"Deadtime must be supplied when in externally triggered mode"
)
if value.deadtime:
required = self._controller.get_deadtime(value.livetime)
assert required <= value.deadtime, (
f"Detector {self._controller} needs at least {required}s deadtime, "
f"but trigger logic provides only {value.deadtime}s"
if value.trigger != DetectorTrigger.INTERNAL and not value.deadtime:
msg = "Deadtime must be supplied when in externally triggered mode"
raise ValueError(msg)
required_deadtime = self._controller.get_deadtime(value.livetime)
if value.deadtime and required_deadtime > value.deadtime:
msg = (
f"Detector {self._controller} needs at least {required_deadtime}s "
f"deadtime, but trigger logic provides only {value.deadtime}s"
)
raise ValueError(msg)

self._trigger_info = value
self._number_of_triggers_iter = iter(
self._trigger_info.number_of_triggers
Expand Down Expand Up @@ -350,7 +362,7 @@ async def kickoff(self):

@WatchableAsyncStatus.wrap
async def complete(self):
assert self._trigger_info
self._trigger_info = _ensure_trigger_info_exists(self._trigger_info)
indices_written = self._writer.observe_indices_written(
self._trigger_info.frame_timeout
or (
Expand Down
38 changes: 26 additions & 12 deletions src/ophyd_async/core/_device.py
Original file line number Diff line number Diff line change
Expand Up @@ -158,10 +158,12 @@ async def connect(
timeout:
Time to wait before failing with a TimeoutError.
"""
assert hasattr(self, "_connector"), (
f"{self}: doesn't have attribute `_connector`,"
" did you call `super().__init__` in your `__init__` method?"
)
if not hasattr(self, "_connector"):
msg = (
f"{self}: doesn't have attribute `_connector`,"
" did you call `super().__init__` in your `__init__` method?"
)
raise RuntimeError(msg)
if mock:
# Always connect in mock mode serially
if isinstance(mock, LazyMock):
Expand All @@ -182,7 +184,9 @@ async def connect(
self._mock = None
coro = self._connector.connect_real(self, timeout, force_reconnect)
self._connect_task = asyncio.create_task(coro)
assert self._connect_task, "Connect task not created, this shouldn't happen"
if not self._connect_task:
msg = "Connect task not created, this shouldn't happen"
raise RuntimeError(msg)
# Wait for it to complete
await self._connect_task

Expand Down Expand Up @@ -232,8 +236,12 @@ def __getitem__(self, key: int) -> DeviceT:
def __setitem__(self, key: int, value: DeviceT) -> None:
# Check the types on entry to dict to make sure we can't accidentally
# make a non-integer named child
assert isinstance(key, int), f"Expected int, got {key}"
assert isinstance(value, Device), f"Expected Device, got {value}"
if not isinstance(key, int):
msg = f"Expected int, got {key}"
raise TypeError(msg)
if not isinstance(value, Device):
msg = f"Expected Device, got {value}"
raise TypeError(msg)
self._children[key] = value
value.parent = self

Expand Down Expand Up @@ -271,14 +279,20 @@ def _caller_locals(self) -> dict[str, Any]:
raise ValueError
except ValueError:
_, _, tb = sys.exc_info()
assert tb, "Can't get traceback, this shouldn't happen"
if not tb:
msg = "Can't get traceback, this shouldn't happen"
raise RuntimeError(msg) # noqa: B904
caller_frame = tb.tb_frame
while caller_frame.f_locals.get("self", None) is self:
caller_frame = caller_frame.f_back
assert caller_frame, (
"No previous frame to the one with self in it, this shouldn't "
"happen"
)
if not caller_frame:
msg = (
"No previous frame to the one with self in it, "
"this shouldn't happen"
)
raise RuntimeError( # noqa: B904
msg
)
return caller_frame.f_locals.copy()

def __enter__(self) -> DeviceProcessor:
Expand Down
25 changes: 17 additions & 8 deletions src/ophyd_async/core/_device_filler.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,13 @@ def _logical(name: UniqueName) -> LogicalName:
return LogicalName(name.rstrip("_"))


def _check_device_annotation(annotation: Any) -> DeviceAnnotation:
if not isinstance(annotation, DeviceAnnotation):
msg = f"Annotation {annotation} is not a DeviceAnnotation"
raise TypeError(msg)
return annotation


@runtime_checkable
class DeviceAnnotation(Protocol):
@abstractmethod
Expand Down Expand Up @@ -150,8 +157,8 @@ def create_signals_from_annotations(
yield backend, extras
signal = child_type(backend)
for anno in extras:
assert isinstance(anno, DeviceAnnotation), anno
anno(self._device, signal)
device_annotation = _check_device_annotation(annotation=anno)
device_annotation(self._device, signal)
setattr(self._device, name, signal)
dest = self._filled_backends if filled else self._unfilled_backends
dest[_logical(name)] = (backend, child_type)
Expand All @@ -167,15 +174,17 @@ def create_devices_from_annotations(
yield connector, extras
device = child_type(connector=connector)
for anno in extras:
assert isinstance(anno, DeviceAnnotation), anno
anno(self._device, device)
device_annotation = _check_device_annotation(annotation=anno)
device_annotation(self._device, device)
setattr(self._device, name, device)
dest = self._filled_connectors if filled else self._unfilled_connectors
dest[_logical(name)] = connector

def create_device_vector_entries_to_mock(self, num: int):
for name, cls in self._vector_device_type.items():
assert cls, "Shouldn't happen"
if not cls:
msg = "Malformed device vector"
raise TypeError(msg)
for i in range(1, num + 1):
if issubclass(cls, Signal):
self.fill_child_signal(name, cls, i)
Expand Down Expand Up @@ -254,9 +263,9 @@ def fill_child_device(
# We need to add a new entry to a DeviceVector
vector = self._ensure_device_vector(name)
vector_device_type = self._vector_device_type[name] or device_type
assert issubclass(vector_device_type, Device), (
f"{vector_device_type} is not a Device"
)
if not issubclass(vector_device_type, Device):
msg = f"{vector_device_type} is not a Device"
raise TypeError(msg)
connector = self._device_connector_factory()
vector[vector_index] = vector_device_type(connector=connector)
elif child := getattr(self._device, name, None):
Expand Down
53 changes: 30 additions & 23 deletions src/ophyd_async/core/_readable.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,29 +123,31 @@ def hints(self) -> Hints:
# we want to combine them when they are Sequences, and ensure they are
# identical when string values.
for key, value in new_hint.hints.items():
# fail early for unkwon types
if isinstance(value, str):
if key in hints:
assert (
hints[key] == value # type: ignore[literal-required]
), f"Hints key {key} value may not be overridden"
if hints[key] != value:
msg = f"Hints key {key} value may not be overridden"
raise RuntimeError(msg)
else:
hints[key] = value # type: ignore[literal-required]
elif isinstance(value, Sequence):
if key in hints:
for new_val in value:
assert (
new_val not in hints[key] # type: ignore[literal-required]
), f"Hint {key} {new_val} overrides existing hint"
if new_val in hints[key]:
msg = f"Hint {key} {new_val} overrides existing hint"
raise RuntimeError(msg)
hints[key] = ( # type: ignore[literal-required]
hints[key] + value # type: ignore[literal-required]
)
else:
hints[key] = value # type: ignore[literal-required]
else:
raise TypeError(
f"{new_hint.name}: Unknown type for value '{value}' "
msg = (
f"{new_hint.name}: Unknown type for value '{value}'"
f" for key '{key}'"
)
raise TypeError(msg)

return hints

Expand Down Expand Up @@ -204,6 +206,11 @@ def add_readables(
`StandardReadableFormat` documentation
"""

def assert_device_is_signalr(device: Device) -> SignalR:
if not isinstance(device, SignalR):
raise TypeError(f"{device} is not a SignalR")
return device

for device in devices:
match format:
case StandardReadableFormat.CHILD:
Expand All @@ -218,24 +225,24 @@ def add_readables(
if isinstance(device, HasHints):
self._has_hints += (device,)
case StandardReadableFormat.CONFIG_SIGNAL:
assert isinstance(device, SignalR), f"{device} is not a SignalR"
self._describe_config_funcs += (device.describe,)
self._read_config_funcs += (device.read,)
signalr_device = assert_device_is_signalr(device=device)
self._describe_config_funcs += (signalr_device.describe,)
self._read_config_funcs += (signalr_device.read,)
case StandardReadableFormat.HINTED_SIGNAL:
assert isinstance(device, SignalR), f"{device} is not a SignalR"
self._describe_funcs += (device.describe,)
self._read_funcs += (device.read,)
self._stageables += (device,)
self._has_hints += (_HintsFromName(device),)
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),)
case StandardReadableFormat.UNCACHED_SIGNAL:
assert isinstance(device, SignalR), f"{device} is not a SignalR"
self._describe_funcs += (device.describe,)
self._read_funcs += (_UncachedRead(device),)
signalr_device = assert_device_is_signalr(device=device)
self._describe_funcs += (signalr_device.describe,)
self._read_funcs += (_UncachedRead(signalr_device),)
case StandardReadableFormat.HINTED_UNCACHED_SIGNAL:
assert isinstance(device, SignalR), f"{device} is not a SignalR"
self._describe_funcs += (device.describe,)
self._read_funcs += (_UncachedRead(device),)
self._has_hints += (_HintsFromName(device),)
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),)


class _UncachedRead:
Expand Down
38 changes: 22 additions & 16 deletions src/ophyd_async/core/_signal.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,30 +97,35 @@ def source(self) -> str:


class _SignalCache(Generic[SignalDatatypeT]):
def __init__(self, backend: SignalBackend[SignalDatatypeT], signal: Signal):
self._signal = signal
def __init__(self, backend: SignalBackend[SignalDatatypeT], signal: Signal) -> None:
self._signal: Signal[Any] = signal
self._staged = False
self._listeners: dict[Callback, bool] = {}
self._valid = asyncio.Event()
self._reading: Reading[SignalDatatypeT] | None = None
self.backend = backend
self.backend: SignalBackend[SignalDatatypeT] = backend
signal.log.debug(f"Making subscription on source {signal.source}")
backend.set_callback(self._callback)

def close(self):
def close(self) -> None:
self.backend.set_callback(None)
self._signal.log.debug(f"Closing subscription on source {self._signal.source}")

def _ensure_reading(self) -> Reading[SignalDatatypeT]:
if not self._reading:
msg = "Monitor not working"
raise RuntimeError(msg)
return self._reading

async def get_reading(self) -> Reading[SignalDatatypeT]:
await self._valid.wait()
assert self._reading is not None, "Monitor not working"
return self._reading
return self._ensure_reading()

async def get_value(self) -> SignalDatatypeT:
reading = await self.get_reading()
reading: Reading[SignalDatatypeT] = await self.get_reading()
return reading["value"]

def _callback(self, reading: Reading[SignalDatatypeT]):
def _callback(self, reading: Reading[SignalDatatypeT]) -> None:
self._signal.log.debug(
f"Updated subscription: reading of source {self._signal.source} changed "
f"from {self._reading} to {reading}"
Expand All @@ -134,12 +139,10 @@ def _notify(
self,
function: Callback[dict[str, Reading[SignalDatatypeT]] | SignalDatatypeT],
want_value: bool,
):
assert self._reading, "Monitor not working"
if want_value:
function(self._reading["value"])
else:
function({self._signal.name: self._reading})
) -> None:
function(self._ensure_reading()["value"]) if want_value else function(
{self._signal.name: self._ensure_reading()}
)

def subscribe(self, function: Callback, want_value: bool) -> None:
self._listeners[function] = want_value
Expand All @@ -150,7 +153,7 @@ def unsubscribe(self, function: Callback) -> bool:
self._listeners.pop(function)
return self._staged or bool(self._listeners)

def set_staged(self, staged: bool):
def set_staged(self, staged: bool) -> bool:
self._staged = staged
return self._staged or bool(self._listeners)

Expand All @@ -167,7 +170,10 @@ def _backend_or_cache(
if cached is None:
cached = self._cache is not None
if cached:
assert self._cache, f"{self.source} not being monitored"
if not self._cache:
msg = f"{self.source} not being monitored"
raise RuntimeError(msg)
# assert self._cache, f"{self.source} not being monitored"
return self._cache
else:
return self._connector.backend
Expand Down
3 changes: 2 additions & 1 deletion src/ophyd_async/core/_soft_signal_backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,8 @@ async def get_setpoint(self) -> SignalDatatypeT:
return self.reading["value"]

def set_callback(self, callback: Callback[Reading[SignalDatatypeT]] | None) -> None:
if callback and self.callback:
raise RuntimeError("Cannot set a callback when one is already set")
if callback:
assert not self.callback, "Cannot set a callback when one is already set"
callback(self.reading)
self.callback = callback
Loading
Loading