@@ -38,6 +38,7 @@ def __init__(
3838 initial_values : _t .Optional [dict [str , _t .Iterable ]] = None ,
3939 input_events : _t .Optional [list [_t .Type [Event ]]] = None ,
4040 output_events : _t .Optional [list [_t .Type [Event ]]] = None ,
41+ event_field_coverage : _t .Optional [dict [str , list [str ]]] = None ,
4142 namespace : str = IO_NS_UNSET ,
4243 component : _t .Optional [Component ] = None ,
4344 ) -> None :
@@ -47,10 +48,27 @@ def __init__(
4748 self .initial_values = initial_values or {}
4849 self .input_events = input_events or []
4950 self .output_events = output_events or []
51+ self .event_field_coverage = event_field_coverage or {}
5052 if set (self .initial_values .keys ()) - set (self .inputs ):
5153 raise ValueError ("Initial values must be for input fields only." )
54+
5255 self ._component = component
56+ self ._initial_values = {k : deque (v ) for k , v in self .initial_values .items ()}
57+ self ._input_event_types = {Event .safe_type (evt .type ) for evt in self .input_events }
58+ self ._output_event_types = {Event .safe_type (evt .type ) for evt in self .output_events }
59+
60+ self ._logger = DI .logger .resolve_sync ().bind (
61+ cls = self .__class__ .__name__ , namespace = self .namespace
62+ )
63+ self ._logger .info ("IOController created" )
64+
65+ # Initialise channel stores
66+ self ._input_channels : dict [tuple [str , str ], Channel ] = {}
67+ self ._output_channels : dict [tuple [str , str ], Channel ] = {}
68+ self ._input_event_channels : dict [str , Channel ] = {}
69+ self ._output_event_channels : dict [str , Channel ] = {}
5370
71+ # Initialise buffers
5472 self .buf_fields : dict [str , IOBuffer ] = {
5573 _io_key_in : IOFieldBuffer (),
5674 _io_key_out : IOFieldBuffer (),
@@ -60,21 +78,9 @@ def __init__(
6078 _io_key_out : IOEventBuffer (),
6179 }
6280
63- self ._input_channels : dict [tuple [str , str ], Channel ] = {}
64- self ._output_channels : dict [tuple [str , str ], Channel ] = {}
65- self ._input_event_channels : dict [str , Channel ] = {}
66- self ._output_event_channels : dict [str , Channel ] = {}
67- self ._input_event_types = {Event .safe_type (evt .type ) for evt in self .input_events }
68- self ._output_event_types = {Event .safe_type (evt .type ) for evt in self .output_events }
69- self ._initial_values = {k : deque (v ) for k , v in self .initial_values .items ()}
70- self ._read_tasks : dict [str | _t_field_key , asyncio .Task ] = {}
81+ # Initialise orchestration state
7182 self ._is_closed = False
72-
73- self ._logger = DI .logger .resolve_sync ().bind (
74- cls = self .__class__ .__name__ , namespace = self .namespace
75- )
76- self ._logger .info ("IOController created" )
77-
83+ self ._read_tasks : dict [str | _t_field_key , asyncio .Task ] = {}
7884 self ._received_fields : dict [str , _t .Any ] = {}
7985 self ._received_fields_lock = asyncio .Lock ()
8086 self ._received_events : deque [Event ] = deque ()
@@ -86,8 +92,9 @@ def is_closed(self) -> bool:
8692 """Returns `True` if the `IOController` is closed, `False` otherwise."""
8793 return self ._is_closed
8894
89- @cached_property
90- def _has_field_inputs (self ) -> bool :
95+ @property
96+ def has_connected_field_inputs (self ) -> bool :
97+ """Returns whether any field inputs are connected via channels."""
9198 return len (self ._input_channels ) > 0
9299
93100 @cached_property
@@ -96,7 +103,7 @@ def _has_event_inputs(self) -> bool:
96103
97104 @cached_property
98105 def _has_inputs (self ) -> bool :
99- return self ._has_field_inputs or self ._has_event_inputs
106+ return self .has_connected_field_inputs or self ._has_event_inputs
100107
101108 async def read (self , timeout : float | None = None ) -> None :
102109 """Reads data and/or events from input channels.
@@ -139,7 +146,7 @@ async def read(self, timeout: float | None = None) -> None:
139146
140147 def _set_read_tasks (self ) -> list [asyncio .Task ]:
141148 read_tasks : list [asyncio .Task ] = []
142- if self ._has_field_inputs :
149+ if self .has_connected_field_inputs :
143150 if _fields_read_task not in self ._read_tasks :
144151 read_fields_task = asyncio .create_task (self ._read_fields (), name = _fields_read_task )
145152 self ._read_tasks [_fields_read_task ] = read_fields_task
@@ -374,7 +381,7 @@ def _add_channel_for_event(
374381
375382 def _create_input_field_group_tasks (self ) -> None :
376383 """Groups input field channels by field name and launches read tasks for group inputs."""
377- if not self ._has_field_inputs :
384+ if not self .has_connected_field_inputs :
378385 return
379386 field_channels : dict [str , list [tuple [_t_field_key , Channel ]]] = defaultdict (list )
380387 for key , chan in self ._input_channels .items ():
@@ -410,6 +417,7 @@ def dict(self) -> dict[str, _t.Any]: # noqa: D102
410417 "input_events" : [e .safe_type () for e in self .input_events ],
411418 "output_events" : [e .safe_type () for e in self .output_events ],
412419 "initial_values" : {k : list (v ) for k , v in self ._initial_values .items ()},
420+ "event_field_coverage" : {k : list (v ) for k , v in self .event_field_coverage .items ()},
413421 }
414422
415423
0 commit comments