@@ -282,6 +282,17 @@ def set_configuration_state(
282282 ) -> None :
283283 """Sets the configuration state for this ``Step``.
284284
285+ The so-called 'configuration state' for a given ``Step`` is backed up by
286+ a :class:`ConfigurationState` class and is assigned to its :attr:`_configuration_state`
287+ attribute. There are two possible ``ConfigurationStates``:
288+ :class:`LeafConfigurationState` and :class:`NonLeafConfigurationState`.
289+
290+ This method sets the configuration state of this ``Step`` based on whether
291+ or not a :attr:`config_key` is set *and exists is the ``Step's`` configuration*
292+ (i.e. its portion of the user-suppled pipeline specification
293+ file); any required deviation from this behavior requires special
294+ handling.
295+
285296 Parameters
286297 ----------
287298 parent_config
@@ -378,8 +389,9 @@ def _validate_nonleaf(
378389 ) -> dict [str , list [str ]]:
379390 """Validates a non-leaf ``Step``."""
380391 errors = {}
381- for node in self .step_graph .nodes :
382- step = self .step_graph .nodes [node ]["step" ]
392+ nodes = self .step_graph .nodes
393+ for node in nodes :
394+ step = nodes [node ]["step" ]
383395 if isinstance (step , IOStep ):
384396 continue
385397 if step .name not in step_config :
@@ -390,7 +402,7 @@ def _validate_nonleaf(
390402 )
391403 if step_errors :
392404 errors .update (step_errors )
393- extra_steps = set (step_config .keys ()) - set (self . step_graph . nodes )
405+ extra_steps = set (step_config .keys ()) - set (nodes )
394406 for extra_step in extra_steps :
395407 errors [f"step { extra_step } " ] = [f"{ extra_step } is not a valid step." ]
396408 return errors
@@ -807,12 +819,43 @@ def set_configuration_state(
807819 The configuration for any implementations to be combined.
808820 input_data_config
809821 The input data configuration for the entire pipeline.
822+
823+ Notes
824+ -----
825+ A ``TemplatedStep`` is always assigned a :class:`NonLeafConfigurationState`
826+ even if it has no multiplicity since (despite having no copies to make) we
827+ still need to traverse the sub-``Steps`` to get to the one with a single
828+ :class:`~easylink.implementation.Implementation`, i.e. the one with a
829+ :class:`LeafConfigurationState`.
810830 """
811- num_repeats = len (self ._get_config (parent_config [self .name ]))
812- self .step_graph = self ._update_step_graph (num_repeats )
813- self .slot_mappings = self ._update_slot_mappings (num_repeats )
814- super ().set_configuration_state (
815- parent_config , combined_implementations , input_data_config
831+ step_config = parent_config [self .name ]
832+ if self .config_key not in step_config :
833+ # Special handle the step_graph update
834+ self .step_graph = StepGraph ()
835+ self .template_step .name = self .name
836+ self .step_graph .add_node_from_step (self .template_step )
837+ # Special handle the slot_mappings update
838+ input_mappings = [
839+ InputSlotMapping (slot , self .name , slot ) for slot in self .input_slots
840+ ]
841+ output_mappings = [
842+ OutputSlotMapping (slot , self .name , slot ) for slot in self .output_slots
843+ ]
844+ self .slot_mappings = {"input" : input_mappings , "output" : output_mappings }
845+ # Add the key back to the expanded config
846+ expanded_config = LayeredConfigTree ({self .name : step_config })
847+ else :
848+ expanded_config = self ._get_config (step_config )
849+ num_repeats = len (expanded_config )
850+ self .step_graph = self ._update_step_graph (num_repeats )
851+ self .slot_mappings = self ._update_slot_mappings (num_repeats )
852+ # Manually set the configuration state to non-leaf instead of relying
853+ # on super().get_configuration_state() because that method will erroneously
854+ # set to leaf state when we have no multiplicity (because in that case the
855+ # user didn't actually include the config_key in the pipeline specification
856+ # file, hence num_repeats == 1)
857+ self ._configuration_state = NonLeafConfigurationState (
858+ self , expanded_config , combined_implementations , input_data_config
816859 )
817860
818861 def _duplicate_template_step (self ) -> Step :
@@ -1105,9 +1148,10 @@ def validate_step(
11051148 initial ones are handled.
11061149
11071150 We update the :class:`easylink.graph_components.StepGraph` and ``SlotMappings``
1108- here as opposed to in :meth:`set_configuration_state` (as is done in :class:`TemplatedStep`)
1109- because ``ChoiceStep`` validation happens prior to setting the configuration
1110- state and actually requires the ``StepGraph`` and ``SlotMappings``.
1151+ in :meth:`validate_step` (as opposed to in :meth:`set_configuration_state`
1152+ as is done in :class:`TemplatedStep`) because :meth:`validate_step` is called
1153+ prior to :meth:`set_configuration_state`, but the validations itself actually
1154+ requires the updated ``StepGraph`` and ``SlotMappings``.
11111155
11121156 We do not attempt to validate the subgraph here if the 'type' key is unable
11131157 to be validated.
@@ -1136,7 +1180,7 @@ def validate_step(
11361180 ]
11371181 }
11381182
1139- # Handle the actual chosen step_config
1183+ # HACK: Update the step graph and mappings here because we need them for validation
11401184 self .step_graph = self ._update_step_graph (subgraph )
11411185 self .slot_mappings = self ._update_slot_mappings (subgraph )
11421186 # NOTE: A ChoiceStep is by definition non-leaf step
@@ -1163,11 +1207,11 @@ def set_configuration_state(
11631207
11641208 Notes
11651209 -----
1166- We update the :class:`~ easylink.graph_components.StepGraph` and
1167- :class:`SlotMappings<easylink.graph_components.SlotMapping>` in
1168- : meth:`validate_step` as opposed to here (as is done with
1169- :class:`TemplatedSteps<TemplatedStep>`) because ``ChoiceStep`` validation
1170- happens prior to this but requires the ``StepGraph`` and ``SlotMappings``.
1210+ We update the :class:`easylink.graph_components.StepGraph` and ``SlotMappings``
1211+ in :meth:`validate_step` (as opposed to in :meth:`set_configuration_state`
1212+ as is done in :class:`TemplatedStep`) because : meth:`validate_step` is called
1213+ prior to :meth:`set_configuration_state`, but the validations itself actually
1214+ requires the updated ``StepGraph`` and ``SlotMappings``.
11711215 """
11721216
11731217 chosen_parent_config = LayeredConfigTree (
@@ -1364,7 +1408,6 @@ def get_implementation_edges(self, edge: EdgeParams) -> list[EdgeParams]:
13641408 for mapping in mappings :
13651409 imp_edge = mapping .remap_edge (edge )
13661410 implementation_edges .append (imp_edge )
1367-
13681411 elif edge .target_node == self ._step .name :
13691412 mappings = [
13701413 mapping
@@ -1520,7 +1563,6 @@ def get_implementation_edges(self, edge: EdgeParams) -> list[EdgeParams]:
15201563 new_step = self ._step .step_graph .nodes [mapping .child_node ]["step" ]
15211564 imp_edges = new_step .get_implementation_edges (new_edge )
15221565 implementation_edges .extend (imp_edges )
1523-
15241566 elif edge .target_node == self ._step .name :
15251567 mappings = [
15261568 mapping
@@ -1544,8 +1586,9 @@ def _configure_subgraph_steps(self) -> None:
15441586 This method recursively traverses the ``StepGraph`` and sets the configuration
15451587 state for each ``Step`` until reaching all leaf nodes.
15461588 """
1547- for node in self ._step .step_graph .nodes :
1548- step = self ._step .step_graph .nodes [node ]["step" ]
1589+ nodes = self ._step .step_graph .nodes
1590+ for node in nodes :
1591+ step = nodes [node ]["step" ]
15491592 step .set_configuration_state (
15501593 self .pipeline_config , self .combined_implementations , self .input_data_config
15511594 )
0 commit comments