diff --git a/CHANGELOG.md b/CHANGELOG.md index 15b1cba3b..4269ee9f2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,7 +27,7 @@ All notable changes to this project will be documented in this file. * ... * ### Resolved Issues - * ... + * [656: Missing usage of slots in classes with DAQmx attributes](https://github.com/ni/nidaqmx-python/issues/656) * ### Major Changes * Added support for mioDAQ configurable digital voltage. diff --git a/generated/nidaqmx/system/_watchdog_modules/expiration_state.py b/generated/nidaqmx/system/_watchdog_modules/expiration_state.py index 2ffa70d05..df6c10b62 100644 --- a/generated/nidaqmx/system/_watchdog_modules/expiration_state.py +++ b/generated/nidaqmx/system/_watchdog_modules/expiration_state.py @@ -10,6 +10,8 @@ class ExpirationState: """ Represents a DAQmx Watchdog expiration state. """ + __slots__ = ('_handle', '_physical_channel', '_interpreter') + def __init__(self, task_handle, physical_channel, interpreter): self._handle = task_handle self._physical_channel = physical_channel diff --git a/generated/nidaqmx/system/system.py b/generated/nidaqmx/system/system.py index c2489899b..99ed519b8 100644 --- a/generated/nidaqmx/system/system.py +++ b/generated/nidaqmx/system/system.py @@ -33,6 +33,7 @@ class System: operations on DAQ hardware, and creates classes from which you can get information about the hardware. """ + __slots__ = ('_interpreter') def __init__(self, grpc_options=None): """ diff --git a/generated/nidaqmx/system/watchdog.py b/generated/nidaqmx/system/watchdog.py index 09e4b8b57..99caa5c54 100644 --- a/generated/nidaqmx/system/watchdog.py +++ b/generated/nidaqmx/system/watchdog.py @@ -21,6 +21,8 @@ class WatchdogTask: """ Represents the watchdog configurations for a DAQmx task. """ + __slots__ = ('_handle', '_close_on_exit', '_saved_name', '_interpreter', '_expiration_states', '__weakref__') + def __init__(self, device_name, task_name='', timeout=10, grpc_options=None): """ Creates and configures a task that controls the watchdog timer of a diff --git a/generated/nidaqmx/task/_export_signals.py b/generated/nidaqmx/task/_export_signals.py index dc1104d54..47750a9f2 100644 --- a/generated/nidaqmx/task/_export_signals.py +++ b/generated/nidaqmx/task/_export_signals.py @@ -9,6 +9,8 @@ class ExportSignals: """ Represents the exported signal configurations for a DAQmx task. """ + __slots__ = ('_handle', '_interpreter') + def __init__(self, task_handle, interpreter): self._handle = task_handle self._interpreter = interpreter diff --git a/generated/nidaqmx/task/_in_stream.py b/generated/nidaqmx/task/_in_stream.py index 29fa08017..7b87b310c 100644 --- a/generated/nidaqmx/task/_in_stream.py +++ b/generated/nidaqmx/task/_in_stream.py @@ -22,6 +22,8 @@ class InStream: used in conjunction with reader classes to read samples from an NI-DAQmx task. """ + __slots__ = ('_task', '_handle', '_interpreter', '_timeout') + def __init__(self, task, interpreter): self._task = task self._handle = task._handle diff --git a/generated/nidaqmx/task/_out_stream.py b/generated/nidaqmx/task/_out_stream.py index 7eea625dc..277f9541b 100644 --- a/generated/nidaqmx/task/_out_stream.py +++ b/generated/nidaqmx/task/_out_stream.py @@ -12,6 +12,8 @@ class OutStream: used in conjunction with writer classes to write samples to an NI-DAQmx task. """ + __slots__ = ('_task', '_handle', '_interpreter', '_auto_start', '_timeout') + def __init__(self, task, interpreter): self._task = task self._handle = task._handle diff --git a/generated/nidaqmx/task/_task.py b/generated/nidaqmx/task/_task.py index 498aa8059..af3fb1e1f 100644 --- a/generated/nidaqmx/task/_task.py +++ b/generated/nidaqmx/task/_task.py @@ -55,6 +55,10 @@ class Task: """ Represents a DAQmx Task. """ + __slots__ = ('_handle', '_close_on_exit', '_saved_name', '_grpc_options', '_event_handlers', '_interpreter', + '_ai_channels', '_ao_channels', '_ci_channels', '_co_channels', '_di_channels', '_do_channels', + '_export_signals', '_in_stream', '_timing', '_triggers', '_out_stream', '_event_handler_lock', + '__weakref__') def __init__(self, new_task_name='', *, grpc_options=None): """ diff --git a/generated/nidaqmx/task/_timing.py b/generated/nidaqmx/task/_timing.py index 23e3da0c5..6b443f1a2 100644 --- a/generated/nidaqmx/task/_timing.py +++ b/generated/nidaqmx/task/_timing.py @@ -12,6 +12,8 @@ class Timing: """ Represents the timing configurations for a DAQmx task. """ + __slots__ = ('_handle', '_interpreter') + def __init__(self, task_handle, interpreter): self._handle = task_handle self._interpreter = interpreter diff --git a/generated/nidaqmx/task/triggering/_arm_start_trigger.py b/generated/nidaqmx/task/triggering/_arm_start_trigger.py index f7d56203e..cb2dbf01e 100644 --- a/generated/nidaqmx/task/triggering/_arm_start_trigger.py +++ b/generated/nidaqmx/task/triggering/_arm_start_trigger.py @@ -8,6 +8,8 @@ class ArmStartTrigger: """ Represents the arm start trigger configurations for a DAQmx task. """ + __slots__ = ('_handle', '_interpreter') + def __init__(self, task_handle, interpreter): self._handle = task_handle self._interpreter = interpreter diff --git a/generated/nidaqmx/task/triggering/_handshake_trigger.py b/generated/nidaqmx/task/triggering/_handshake_trigger.py index cdd3572f8..0701af7bc 100644 --- a/generated/nidaqmx/task/triggering/_handshake_trigger.py +++ b/generated/nidaqmx/task/triggering/_handshake_trigger.py @@ -8,6 +8,8 @@ class HandshakeTrigger: """ Represents the handshake trigger configurations for a DAQmx task. """ + __slots__ = ('_handle', '_interpreter') + def __init__(self, task_handle, interpreter): self._handle = task_handle self._interpreter = interpreter diff --git a/generated/nidaqmx/task/triggering/_pause_trigger.py b/generated/nidaqmx/task/triggering/_pause_trigger.py index 270c69bf7..51633871f 100644 --- a/generated/nidaqmx/task/triggering/_pause_trigger.py +++ b/generated/nidaqmx/task/triggering/_pause_trigger.py @@ -10,6 +10,8 @@ class PauseTrigger: """ Represents the pause trigger configurations for a DAQmx task. """ + __slots__ = ('_handle', '_interpreter') + def __init__(self, task_handle, interpreter): self._handle = task_handle self._interpreter = interpreter diff --git a/generated/nidaqmx/task/triggering/_reference_trigger.py b/generated/nidaqmx/task/triggering/_reference_trigger.py index 5bcfc3136..e253d5d96 100644 --- a/generated/nidaqmx/task/triggering/_reference_trigger.py +++ b/generated/nidaqmx/task/triggering/_reference_trigger.py @@ -12,6 +12,8 @@ class ReferenceTrigger: """ Represents the reference trigger configurations for a DAQmx task. """ + __slots__ = ('_handle', '_interpreter') + def __init__(self, task_handle, interpreter): self._handle = task_handle self._interpreter = interpreter diff --git a/generated/nidaqmx/task/triggering/_start_trigger.py b/generated/nidaqmx/task/triggering/_start_trigger.py index 416991273..e4288b897 100644 --- a/generated/nidaqmx/task/triggering/_start_trigger.py +++ b/generated/nidaqmx/task/triggering/_start_trigger.py @@ -13,6 +13,8 @@ class StartTrigger: """ Represents the start trigger configurations for a DAQmx task. """ + __slots__ = ('_handle', '_interpreter') + def __init__(self, task_handle, interpreter): self._handle = task_handle self._interpreter = interpreter diff --git a/generated/nidaqmx/task/triggering/_triggers.py b/generated/nidaqmx/task/triggering/_triggers.py index 374b3980c..914851f6f 100644 --- a/generated/nidaqmx/task/triggering/_triggers.py +++ b/generated/nidaqmx/task/triggering/_triggers.py @@ -13,6 +13,8 @@ class Triggers: """ Represents the trigger configurations for a DAQmx task. """ + __slots__ = ('_handle', '_interpreter', '_arm_start_trigger', '_handshake_trigger', '_pause_trigger', '_reference_trigger', '_start_trigger') + def __init__(self, task_handle, interpreter): self._handle = task_handle self._interpreter = interpreter diff --git a/src/codegen/templates/system/_watchdog_modules/expiration_state.py.mako b/src/codegen/templates/system/_watchdog_modules/expiration_state.py.mako index 238c70e78..6710d17d4 100644 --- a/src/codegen/templates/system/_watchdog_modules/expiration_state.py.mako +++ b/src/codegen/templates/system/_watchdog_modules/expiration_state.py.mako @@ -16,6 +16,8 @@ class ExpirationState: """ Represents a DAQmx Watchdog expiration state. """ + __slots__ = ('_handle', '_physical_channel', '_interpreter') + def __init__(self, task_handle, physical_channel, interpreter): self._handle = task_handle self._physical_channel = physical_channel diff --git a/src/codegen/templates/system/system.py.mako b/src/codegen/templates/system/system.py.mako index fe0e2487d..bfad15cf0 100644 --- a/src/codegen/templates/system/system.py.mako +++ b/src/codegen/templates/system/system.py.mako @@ -40,6 +40,7 @@ class System: operations on DAQ hardware, and creates classes from which you can get information about the hardware. """ + __slots__ = ('_interpreter') def __init__(self, grpc_options=None): """ diff --git a/src/codegen/templates/system/watchdog.py.mako b/src/codegen/templates/system/watchdog.py.mako index 8bd6edeeb..5c8c71b91 100644 --- a/src/codegen/templates/system/watchdog.py.mako +++ b/src/codegen/templates/system/watchdog.py.mako @@ -27,6 +27,8 @@ class WatchdogTask: """ Represents the watchdog configurations for a DAQmx task. """ + __slots__ = ('_handle', '_close_on_exit', '_saved_name', '_interpreter', '_expiration_states', '__weakref__') + def __init__(self, device_name, task_name='', timeout=10, grpc_options=None): """ Creates and configures a task that controls the watchdog timer of a diff --git a/src/codegen/templates/task/_export_signals.py.mako b/src/codegen/templates/task/_export_signals.py.mako index 1d44de16d..f71bf5ca0 100644 --- a/src/codegen/templates/task/_export_signals.py.mako +++ b/src/codegen/templates/task/_export_signals.py.mako @@ -21,6 +21,8 @@ class ExportSignals: """ Represents the exported signal configurations for a DAQmx task. """ + __slots__ = ('_handle', '_interpreter') + def __init__(self, task_handle, interpreter): self._handle = task_handle self._interpreter = interpreter diff --git a/src/codegen/templates/task/_in_stream.py.mako b/src/codegen/templates/task/_in_stream.py.mako index bd353a773..87e85f6c9 100644 --- a/src/codegen/templates/task/_in_stream.py.mako +++ b/src/codegen/templates/task/_in_stream.py.mako @@ -27,6 +27,8 @@ class InStream: used in conjunction with reader classes to read samples from an NI-DAQmx task. """ + __slots__ = ('_task', '_handle', '_interpreter', '_timeout') + def __init__(self, task, interpreter): self._task = task self._handle = task._handle diff --git a/src/codegen/templates/task/_out_stream.py.mako b/src/codegen/templates/task/_out_stream.py.mako index d3b1ed539..2fb5e8a22 100644 --- a/src/codegen/templates/task/_out_stream.py.mako +++ b/src/codegen/templates/task/_out_stream.py.mako @@ -18,6 +18,8 @@ class OutStream: used in conjunction with writer classes to write samples to an NI-DAQmx task. """ + __slots__ = ('_task', '_handle', '_interpreter', '_auto_start', '_timeout') + def __init__(self, task, interpreter): self._task = task self._handle = task._handle diff --git a/src/codegen/templates/task/_timing.py.mako b/src/codegen/templates/task/_timing.py.mako index b61c320a9..c0e817091 100644 --- a/src/codegen/templates/task/_timing.py.mako +++ b/src/codegen/templates/task/_timing.py.mako @@ -24,6 +24,8 @@ class Timing: """ Represents the timing configurations for a DAQmx task. """ + __slots__ = ('_handle', '_interpreter') + def __init__(self, task_handle, interpreter): self._handle = task_handle self._interpreter = interpreter diff --git a/src/codegen/templates/task/triggering/_arm_start_trigger.py.mako b/src/codegen/templates/task/triggering/_arm_start_trigger.py.mako index e612846d7..d9a03d15e 100644 --- a/src/codegen/templates/task/triggering/_arm_start_trigger.py.mako +++ b/src/codegen/templates/task/triggering/_arm_start_trigger.py.mako @@ -22,6 +22,8 @@ class ArmStartTrigger: """ Represents the arm start trigger configurations for a DAQmx task. """ + __slots__ = ('_handle', '_interpreter') + def __init__(self, task_handle, interpreter): self._handle = task_handle self._interpreter = interpreter diff --git a/src/codegen/templates/task/triggering/_handshake_trigger.py.mako b/src/codegen/templates/task/triggering/_handshake_trigger.py.mako index 77aed622f..21e9be9c8 100644 --- a/src/codegen/templates/task/triggering/_handshake_trigger.py.mako +++ b/src/codegen/templates/task/triggering/_handshake_trigger.py.mako @@ -21,6 +21,8 @@ class HandshakeTrigger: """ Represents the handshake trigger configurations for a DAQmx task. """ + __slots__ = ('_handle', '_interpreter') + def __init__(self, task_handle, interpreter): self._handle = task_handle self._interpreter = interpreter diff --git a/src/codegen/templates/task/triggering/_pause_trigger.py.mako b/src/codegen/templates/task/triggering/_pause_trigger.py.mako index 60d743a1d..b58093423 100644 --- a/src/codegen/templates/task/triggering/_pause_trigger.py.mako +++ b/src/codegen/templates/task/triggering/_pause_trigger.py.mako @@ -22,6 +22,8 @@ class PauseTrigger: """ Represents the pause trigger configurations for a DAQmx task. """ + __slots__ = ('_handle', '_interpreter') + def __init__(self, task_handle, interpreter): self._handle = task_handle self._interpreter = interpreter diff --git a/src/codegen/templates/task/triggering/_reference_trigger.py.mako b/src/codegen/templates/task/triggering/_reference_trigger.py.mako index 8c309d4b0..12e39916c 100644 --- a/src/codegen/templates/task/triggering/_reference_trigger.py.mako +++ b/src/codegen/templates/task/triggering/_reference_trigger.py.mako @@ -24,6 +24,8 @@ class ReferenceTrigger: """ Represents the reference trigger configurations for a DAQmx task. """ + __slots__ = ('_handle', '_interpreter') + def __init__(self, task_handle, interpreter): self._handle = task_handle self._interpreter = interpreter diff --git a/src/codegen/templates/task/triggering/_start_trigger.py.mako b/src/codegen/templates/task/triggering/_start_trigger.py.mako index d073c45e4..27de6128f 100644 --- a/src/codegen/templates/task/triggering/_start_trigger.py.mako +++ b/src/codegen/templates/task/triggering/_start_trigger.py.mako @@ -25,6 +25,8 @@ class StartTrigger: """ Represents the start trigger configurations for a DAQmx task. """ + __slots__ = ('_handle', '_interpreter') + def __init__(self, task_handle, interpreter): self._handle = task_handle self._interpreter = interpreter diff --git a/src/codegen/templates/task/triggering/_triggers.py.mako b/src/codegen/templates/task/triggering/_triggers.py.mako index b47f395ff..c40f77289 100644 --- a/src/codegen/templates/task/triggering/_triggers.py.mako +++ b/src/codegen/templates/task/triggering/_triggers.py.mako @@ -28,6 +28,8 @@ class Triggers: """ Represents the trigger configurations for a DAQmx task. """ + __slots__ = ('_handle', '_interpreter', '_arm_start_trigger', '_handshake_trigger', '_pause_trigger', '_reference_trigger', '_start_trigger') + def __init__(self, task_handle, interpreter): self._handle = task_handle self._interpreter = interpreter diff --git a/src/handwritten/task/_task.py b/src/handwritten/task/_task.py index 498aa8059..af3fb1e1f 100644 --- a/src/handwritten/task/_task.py +++ b/src/handwritten/task/_task.py @@ -55,6 +55,10 @@ class Task: """ Represents a DAQmx Task. """ + __slots__ = ('_handle', '_close_on_exit', '_saved_name', '_grpc_options', '_event_handlers', '_interpreter', + '_ai_channels', '_ao_channels', '_ci_channels', '_co_channels', '_di_channels', '_do_channels', + '_export_signals', '_in_stream', '_timing', '_triggers', '_out_stream', '_event_handler_lock', + '__weakref__') def __init__(self, new_task_name='', *, grpc_options=None): """ diff --git a/tests/component/system/test_system.py b/tests/component/system/test_system.py index 0640dbf55..7c9b8b799 100644 --- a/tests/component/system/test_system.py +++ b/tests/component/system/test_system.py @@ -5,6 +5,7 @@ import nidaqmx from nidaqmx.constants import PowerUpChannelType from nidaqmx.error_codes import DAQmxErrors +from nidaqmx.system import System from nidaqmx.types import AOPowerUpState @@ -121,3 +122,8 @@ def test_invalid_power_up_states___set_analog_power_up_states___throws_invalid_a system.set_analog_power_up_states(device_name, power_up_states) assert exc_info.value.error_code == DAQmxErrors.INVALID_ATTRIBUTE_VALUE + + +def test___system___set_nonexistent_property___raises_exception(system: System): + with pytest.raises(AttributeError): + system.nonexistent_property = "foo" # type: ignore[attr-defined] diff --git a/tests/component/task/test_in_stream.py b/tests/component/task/test_in_stream.py index 277dba970..2d045eee8 100644 --- a/tests/component/task/test_in_stream.py +++ b/tests/component/task/test_in_stream.py @@ -219,3 +219,8 @@ def test___valid_path___start_new_file___returns_assigned_value(ai_task: nidaqmx ai_task.in_stream.start_new_file(expected_file_path) assert ai_task.in_stream.logging_file_path == pathlib.Path(expected_file_path) + + +def test___in_stream___set_nonexistent_property___raises_exception(task: nidaqmx.Task): + with pytest.raises(AttributeError): + task.in_stream.nonexistent_property = "foo" # type: ignore[attr-defined] diff --git a/tests/component/task/test_out_stream.py b/tests/component/task/test_out_stream.py index 07930455a..dffbc8101 100644 --- a/tests/component/task/test_out_stream.py +++ b/tests/component/task/test_out_stream.py @@ -47,3 +47,8 @@ def test___odd_sized_array___write___returns_whole_samples( samples_written = task.out_stream.write(data) assert samples_written == 9 + + +def test___out_stream___set_nonexistent_property___raises_exception(task: nidaqmx.Task): + with pytest.raises(AttributeError): + task.out_stream.nonexistent_property = "foo" # type: ignore[attr-defined] diff --git a/tests/component/task/test_timing.py b/tests/component/task/test_timing.py index b8e56bd42..92ce6b6ed 100644 --- a/tests/component/task/test_timing.py +++ b/tests/component/task/test_timing.py @@ -156,3 +156,8 @@ def test___timing___cfg_burst_handshaking_export_clock___sets_properties( sim_6535_di_single_line_task.export_signals.rdy_for_xfer_event_lvl_active_lvl == ready_event_active_level ) + + +def test___timing___set_nonexistent_property___raises_exception(task: Task): + with pytest.raises(AttributeError): + task.timing.nonexistent_property = "foo" # type: ignore[attr-defined] diff --git a/tests/component/task/test_triggers_properties.py b/tests/component/task/test_triggers_properties.py index f76182f23..38fa71179 100644 --- a/tests/component/task/test_triggers_properties.py +++ b/tests/component/task/test_triggers_properties.py @@ -160,3 +160,33 @@ def test___ai_voltage_time_aware_task___reset_timestamp_property___returns_defau assert when_value.hour == localized_default_value.hour assert when_value.minute == localized_default_value.minute assert when_value.second == localized_default_value.second + + +def test___trigger___set_nonexistent_property___raises_exception(task: Task): + with pytest.raises(AttributeError): + task.triggers.nonexistent_property = "foo" # type: ignore[attr-defined] + + +def test___arm_start_trigger___set_nonexistent_property___raises_exception(task: Task): + with pytest.raises(AttributeError): + task.triggers.arm_start_trigger.nonexistent_property = "foo" # type: ignore[attr-defined] + + +def test___handshake_trigger___set_nonexistent_property___raises_exception(task: Task): + with pytest.raises(AttributeError): + task.triggers.handshake_trigger.nonexistent_property = "foo" # type: ignore[attr-defined] + + +def test___pause_trigger___set_nonexistent_property___raises_exception(task: Task): + with pytest.raises(AttributeError): + task.triggers.pause_trigger.nonexistent_property = "foo" # type: ignore[attr-defined] + + +def test___reference_trigger___set_nonexistent_property___raises_exception(task: Task): + with pytest.raises(AttributeError): + task.triggers.reference_trigger.nonexistent_property = "foo" # type: ignore[attr-defined] + + +def test___start_trigger___set_nonexistent_property___raises_exception(task: Task): + with pytest.raises(AttributeError): + task.triggers.start_trigger.nonexistent_property = "foo" # type: ignore[attr-defined] diff --git a/tests/component/test_export_signals.py b/tests/component/test_export_signals.py new file mode 100644 index 000000000..1e64a5c81 --- /dev/null +++ b/tests/component/test_export_signals.py @@ -0,0 +1,8 @@ +import pytest + +import nidaqmx + + +def test___export_signals___set_nonexistent_property___raises_exception(task: nidaqmx.Task): + with pytest.raises(AttributeError): + task.export_signals.nonexistent_property = "foo" # type: ignore[attr-defined] diff --git a/tests/component/test_task.py b/tests/component/test_task.py index 78526319a..811eb16eb 100644 --- a/tests/component/test_task.py +++ b/tests/component/test_task.py @@ -1,3 +1,5 @@ +import weakref + import pytest import nidaqmx @@ -197,3 +199,9 @@ def test___task___add_global_channels___adds_to_channel_names(task: nidaqmx.Task task.add_global_channels([persisted_channel, persisted_channel2]) assert task.channel_names == [persisted_channel.name, persisted_channel2.name] + + +def test___task___create_weakref___succeeds(task: nidaqmx.Task): + ref = weakref.ref(task) + task2 = ref() + assert task is task2 diff --git a/tests/component/test_task_properties.py b/tests/component/test_task_properties.py index 674689027..e8e2564bd 100644 --- a/tests/component/test_task_properties.py +++ b/tests/component/test_task_properties.py @@ -35,3 +35,8 @@ def test___get_devices___shared_interpreter(ai_task: Task): devices = ai_task.devices assert all(dev._interpreter is ai_task._interpreter for dev in devices) + + +def test___task___set_nonexistent_property___raises_exception(task: Task): + with pytest.raises(AttributeError): + task.nonexistent_property = "foo" # type: ignore[attr-defined] diff --git a/tests/component/test_watchdog.py b/tests/component/test_watchdog.py index 4c92968e4..72efd0cfd 100644 --- a/tests/component/test_watchdog.py +++ b/tests/component/test_watchdog.py @@ -1,5 +1,8 @@ +import weakref from typing import Callable +import pytest + from nidaqmx.constants import WatchdogAOExpirState, WatchdogCOExpirState from nidaqmx.system import Device from nidaqmx.system.watchdog import AOExpirationState, COExpirationState, WatchdogTask @@ -88,3 +91,44 @@ def test___watchdog_task___clear_expiration___no_error( watchdog_task.start() watchdog_task.clear_expiration() + + +def test___watchdog_task___create_weakref___succeeds( + generate_watchdog_task: Callable[..., WatchdogTask], + sim_9189_device: Device, +): + watchdog_task = generate_watchdog_task(f"{sim_9189_device.name}", timeout=0.8) + ref = weakref.ref(watchdog_task) + watchdog_task2 = ref() + assert watchdog_task is watchdog_task2 + + +def test___watchdog_task___set_nonexistent_property___raises_exception( + generate_watchdog_task: Callable[..., WatchdogTask], + sim_9189_device: Device, +): + watchdog_task = generate_watchdog_task(f"{sim_9189_device.name}", timeout=0.8) + + with pytest.raises(AttributeError): + watchdog_task.nonexistent_property = "foo" # type: ignore[attr-defined] + + +def test___watchdog_expiration_states___set_nonexistent_property___raises_exception( + generate_watchdog_task: Callable[..., WatchdogTask], + sim_9189_device: Device, + sim_9263_device: Device, +): + watchdog_task = generate_watchdog_task(f"{sim_9189_device.name}", timeout=0.8) + expir_states = [ + AOExpirationState( + physical_channel=sim_9263_device.ao_physical_chans[0].name, + expiration_state=0.0, + output_type=WatchdogAOExpirState.VOLTAGE, + ) + ] + watchdog_task.cfg_watchdog_ao_expir_states(expir_states) + + with pytest.raises(AttributeError): + watchdog_task.expiration_states[ + sim_9263_device.ao_physical_chans[0].name + ].nonexistent_property = "foo" # type: ignore[attr-defined]