Repository: kubeflow/pipelines

Issue:
[sdk] Cannot refer to the loop parameter within the grandchild task
### Environment

*  KFP version:
Vertex Pipelines

*  KFP SDK version: 
1.8.2

### Steps to reproduce
Run the following pipeline on Vertex Pipelines:
```
from kfp import components
from kfp import dsl
import kfp.v2.compiler as compiler
 
@components.create_component_from_func
def print_op(msg: str):
  print(msg)
 
@dsl.pipeline(
    name='pipeline-with-loops-and-conditions',
    pipeline_root=PIPELINE_ROOT,
)
def my_pipeline(text_parameter: str = '[{"p_a": -1, "p_b": ["hello", "world"]}, {"p_a": 2, "p_b": ["nihao", "shijie"]}]'):
  with dsl.ParallelFor(text_parameter) as item:
    with dsl.Condition(item.p_a != "-1"):
      with dsl.ParallelFor(item.p_b) as item_p_b:
        print_op(item_p_b)
          
 
compiler.Compiler().compile(pipeline_func=my_pipeline, 
                            package_path='loops-and-conditions.json')
```


### Expected result

Expect no error

### Actual results

Get HTTP 400 error with message:
`Component input parameter 'pipelineparam--text_parameter-loop-item' is not defined`

[sdk] Cannot refer to the loop parameter within the grandchild task
### Environment

*  KFP version:
Vertex Pipelines

*  KFP SDK version: 
1.8.2

### Steps to reproduce
Run the following pipeline on Vertex Pipelines:
```
from kfp import components
from kfp import dsl
import kfp.v2.compiler as compiler
 
@components.create_component_from_func
def print_op(msg: str):
  print(msg)
 
@dsl.pipeline(
    name='pipeline-with-loops-and-conditions',
    pipeline_root=PIPELINE_ROOT,
)
def my_pipeline(text_parameter: str = '[{"p_a": -1, "p_b": ["hello", "world"]}, {"p_a": 2, "p_b": ["nihao", "shijie"]}]'):
  with dsl.ParallelFor(text_parameter) as item:
    with dsl.Condition(item.p_a != "-1"):
      with dsl.ParallelFor(item.p_b) as item_p_b:
        print_op(item_p_b)
          
 
compiler.Compiler().compile(pipeline_func=my_pipeline, 
                            package_path='loops-and-conditions.json')
```


### Expected result

Expect no error

### Actual results

Get HTTP 400 error with message:
`Component input parameter 'pipelineparam--text_parameter-loop-item' is not defined`



Discussion:

Resolving patch:
diff --git a/sdk/python/kfp/dsl/_for_loop.py b/sdk/python/kfp/dsl/_for_loop.py
--- a/sdk/python/kfp/dsl/_for_loop.py
+++ b/sdk/python/kfp/dsl/_for_loop.py
@@ -1,9 +1,10 @@
 import re
-from typing import List, Union, Dict, Text, Any, Tuple, Optional
+from typing import Any, Dict, List, Optional, Tuple, Union
 
 from kfp import dsl
+from kfp.dsl import _pipeline_param
 
-ItemList = List[Union[int, float, str, Dict[Text, Any]]]
+ItemList = List[Union[int, float, str, Dict[str, Any]]]
 
 
 class LoopArguments(dsl.PipelineParam):
@@ -20,15 +21,15 @@ class LoopArguments(dsl.PipelineParam):
     LEGAL_SUBVAR_NAME_REGEX = re.compile(r'[a-zA-Z_][0-9a-zA-Z_]*')
 
     @classmethod
-    def _subvar_name_is_legal(cls, proposed_variable_name: Text):
+    def _subvar_name_is_legal(cls, proposed_variable_name: str):
         return re.match(cls.LEGAL_SUBVAR_NAME_REGEX,
                         proposed_variable_name) is not None
 
     def __init__(self,
                  items: Union[ItemList, dsl.PipelineParam],
-                 code: Text,
-                 name_override: Optional[Text] = None,
-                 op_name: Optional[Text] = None,
+                 code: str,
+                 name_override: Optional[str] = None,
+                 op_name: Optional[str] = None,
                  *args,
                  **kwargs):
         """LoopArguments represent the set of items to loop over in a
@@ -43,7 +44,7 @@ def __init__(self,
             variable name.
           code: A unique code used to identify these loop arguments.  Should
             match the code for the ParallelFor ops_group which created these
-            _LoopArguments.  This prevents parameter name collisions.
+            LoopArguments.  This prevents parameter name collisions.
         """
         if name_override is None:
             super().__init__(name=self._make_name(code), *args, **kwargs)
@@ -76,7 +77,11 @@ def __init__(self,
                 setattr(
                     self, subvar_name,
                     LoopArgumentVariable(
-                        self.name, subvar_name, loop_args_op_name=self.op_name))
+                        loop_args_name=self.name,
+                        this_variable_name=subvar_name,
+                        loop_args_op_name=self.op_name,
+                        loop_args=self,
+                    ))
 
         self.items_or_pipeline_param = items
         self.referenced_subvar_names = []
@@ -97,7 +102,11 @@ def __getattr__(self, item):
         # of time
         self.referenced_subvar_names.append(item)
         return LoopArgumentVariable(
-            self.name, item, loop_args_op_name=self.op_name)
+            loop_args_name=self.name,
+            this_variable_name=item,
+            loop_args_op_name=self.op_name,
+            loop_args=self,
+        )
 
     def to_list_for_task_yaml(self):
         if isinstance(self.items_or_pipeline_param, (list, tuple)):
@@ -108,7 +117,7 @@ def to_list_for_task_yaml(self):
                 'not pipeline param items.')
 
     @classmethod
-    def _make_name(cls, code: Text):
+    def _make_name(cls, code: str):
         """Make a name for this parameter.
 
         Code is a
@@ -116,7 +125,7 @@ def _make_name(cls, code: Text):
         return '{}-{}'.format(cls.LOOP_ITEM_PARAM_NAME_BASE, code)
 
     @classmethod
-    def name_is_loop_argument(cls, param_name: Text) -> bool:
+    def name_is_loop_argument(cls, param_name: str) -> bool:
         """Return True if the given parameter name looks like a loop argument.
 
         Either it came from a withItems loop item or withParams loop
@@ -126,17 +135,25 @@ def name_is_loop_argument(cls, param_name: Text) -> bool:
           or cls.name_is_withparams_loop_argument(param_name)
 
     @classmethod
-    def name_is_withitems_loop_argument(cls, param_name: Text) -> bool:
+    def name_is_withitems_loop_argument(cls, param_name: str) -> bool:
         """Return True if the given parameter name looks like it came from a
         loop arguments parameter."""
         return (cls.LOOP_ITEM_PARAM_NAME_BASE + '-') in param_name
 
     @classmethod
-    def name_is_withparams_loop_argument(cls, param_name: Text) -> bool:
+    def name_is_withparams_loop_argument(cls, param_name: str) -> bool:
         """Return True if the given parameter name looks like it came from a
         withParams loop item."""
         return ('-' + cls.LOOP_ITEM_NAME_BASE) in param_name
 
+    @classmethod
+    def remove_loop_item_base_name(cls, param_name: str) -> str:
+        """Removes the last LOOP_ITEM_NAME_BASE from the end of param name."""
+        if ('-' + cls.LOOP_ITEM_NAME_BASE) in param_name:
+            # Split from the right, so that it handles multi-level nested args.
+            return param_name.rsplit('-' + cls.LOOP_ITEM_NAME_BASE, 1)[0]
+        return param_name
+
 
 class LoopArgumentVariable(dsl.PipelineParam):
     """Represents a subvariable for loop arguments.
@@ -148,21 +165,26 @@ class LoopArgumentVariable(dsl.PipelineParam):
 
     def __init__(
         self,
-        loop_args_name: Text,
-        this_variable_name: Text,
-        loop_args_op_name: Text,
+        loop_args_name: str,
+        this_variable_name: str,
+        loop_args_op_name: Optional[str],
+        # For backward compatible, add loop_args as an optional argument.
+        # Ideally, this should replace loop_args_name and loop_args_op_name.
+        loop_args: Optional[LoopArguments] = None,
     ):
         """
     If the user ran:
         with dsl.ParallelFor([{'a': 1, 'b': 2}, {'a': 3, 'b': 4}]) as item:
             ...
-    Then there's be one _LoopArgumentsVariable for 'a' and another for 'b'.
+    Then there's be one LoopArgumentsVariable for 'a' and another for 'b'.
 
     Args:
-      loop_args_name:  the name of the _LoopArguments object that this is
+      loop_args_name:  The name of the LoopArguments object that this is
         a subvariable to.
-      this_variable_name: the name of this subvariable, which is the name
+      this_variable_name: The name of this subvariable, which is the name
         of the dict key that spawned this subvariable.
+      loop_args_op_name: The name of the op that produced the loop arguments.
+      loop_args: Optional; The LoopArguments object this subvariable is based on.
     """
         super().__init__(
             name=self.get_name(
@@ -170,9 +192,15 @@ def __init__(
                 this_variable_name=this_variable_name),
             op_name=loop_args_op_name,
         )
+        self.loop_args = loop_args
+
+    @property
+    def items_or_pipeline_param(
+            self) -> Union[ItemList, _pipeline_param.PipelineParam]:
+        return self.loop_args.items_or_pipeline_param
 
     @classmethod
-    def get_name(cls, loop_args_name: Text, this_variable_name: Text) -> Text:
+    def get_name(cls, loop_args_name: str, this_variable_name: str) -> str:
         """Get the name.
 
         Args:
@@ -187,15 +215,14 @@ def get_name(cls, loop_args_name: Text, this_variable_name: Text) -> Text:
                                this_variable_name)
 
     @classmethod
-    def name_is_loop_arguments_variable(cls, param_name: Text) -> bool:
+    def name_is_loop_arguments_variable(cls, param_name: str) -> bool:
         """Return True if the given parameter name looks like it came from a
         LoopArgumentsVariable."""
         return re.match('.+%s.+' % cls.SUBVAR_NAME_DELIMITER,
                         param_name) is not None
 
     @classmethod
-    def parse_loop_args_name_and_this_var_name(cls,
-                                               t: Text) -> Tuple[Text, Text]:
+    def parse_loop_args_name_and_this_var_name(cls, t: str) -> Tuple[str, str]:
         """Get the loop arguments param name and this subvariable name from the
         given parameter name."""
         m = re.match(
@@ -208,7 +235,7 @@ def parse_loop_args_name_and_this_var_name(cls,
             )['this_var_name']
 
     @classmethod
-    def get_subvar_name(cls, t: Text) -> Text:
+    def get_subvar_name(cls, t: str) -> str:
         """Get the subvariable name from a given LoopArgumentsVariable
         parameter name."""
         out = cls.parse_loop_args_name_and_this_var_name(t)
diff --git a/sdk/python/kfp/dsl/component_spec.py b/sdk/python/kfp/dsl/component_spec.py
--- a/sdk/python/kfp/dsl/component_spec.py
+++ b/sdk/python/kfp/dsl/component_spec.py
@@ -16,9 +16,7 @@
 from typing import List, Optional, Tuple, Union
 
 from kfp.components import _structures as structures
-from kfp.dsl import _for_loop
-from kfp.dsl import _pipeline_param
-from kfp.dsl import dsl_utils
+from kfp.dsl import _for_loop, _pipeline_param, dsl_utils
 from kfp.pipeline_spec import pipeline_spec_pb2
 from kfp.v2.components.types import type_utils
 
@@ -191,13 +189,16 @@ def build_task_inputs_spec(
     """
     for param in pipeline_params or []:
 
-        param_name, subvar_name = _exclude_loop_arguments_variables(param)
+        param_full_name, subvar_name = _exclude_loop_arguments_variables(param)
         input_name = additional_input_name_for_pipelineparam(param.full_name)
 
+        param_name = param.name
         if subvar_name:
             task_spec.inputs.parameters[
                 input_name].parameter_expression_selector = (
                     'parseJson(string_value)["{}"]'.format(subvar_name))
+            param_name = _for_loop.LoopArguments.remove_loop_item_base_name(
+                _exclude_loop_arguments_variables(param_name)[0])
 
         if type_utils.is_parameter_type(param.param_type):
             if param.op_name and dsl_utils.sanitize_task_name(
@@ -207,12 +208,13 @@ def build_task_inputs_spec(
                         dsl_utils.sanitize_task_name(param.op_name))
                 task_spec.inputs.parameters[
                     input_name].task_output_parameter.output_parameter_key = (
-                        param.name)
+                        param_name)
             else:
                 task_spec.inputs.parameters[
                     input_name].component_input_parameter = (
-                        param_name if is_parent_component_root else
-                        additional_input_name_for_pipelineparam(param_name))
+                        param_full_name if is_parent_component_root else
+                        additional_input_name_for_pipelineparam(param_full_name)
+                    )
         else:
             if param.op_name and dsl_utils.sanitize_task_name(
                     param.op_name) in tasks_in_current_dag:
@@ -221,11 +223,12 @@ def build_task_inputs_spec(
                         dsl_utils.sanitize_task_name(param.op_name))
                 task_spec.inputs.artifacts[
                     input_name].task_output_artifact.output_artifact_key = (
-                        param.name)
+                        param_name)
             else:
                 task_spec.inputs.artifacts[
                     input_name].component_input_artifact = (
-                        param_name if is_parent_component_root else input_name)
+                        param_full_name
+                        if is_parent_component_root else input_name)
 
 
 def update_task_inputs_spec(
@@ -345,7 +348,6 @@ def update_task_inputs_spec(
                 component_input_parameter = (
                     additional_input_name_for_pipelineparam(
                         component_input_parameter))
-
             assert component_input_parameter in parent_component_inputs.parameters, \
               'component_input_parameter: {} not found. All inputs: {}'.format(
                   component_input_parameter, parent_component_inputs)
diff --git a/sdk/python/kfp/v2/compiler/compiler.py b/sdk/python/kfp/v2/compiler/compiler.py
--- a/sdk/python/kfp/v2/compiler/compiler.py
+++ b/sdk/python/kfp/v2/compiler/compiler.py
@@ -260,6 +260,8 @@ def _get_all_subgroups_and_ops(self, group: dsl.OpsGroup):
             subgroups.extend(group.groups)
         return subgroups
 
+    # TODO: revisit this when we re-implement compiler for TaskSpec.
+    # I think we only need inputs/outputs for opsgroups but not for ops.
     def _get_inputs_outputs(
         self,
         pipeline: dsl.Pipeline,
@@ -274,28 +276,29 @@ def _get_inputs_outputs(
         """Get inputs and outputs of each group and op.
 
         Args:
-          pipeline: The instantiated pipeline object.
-          args: The list of pipeline function arguments as PipelineParam.
-          root_group: The root OpsGroup.
-          op_groups: The dict of op name to parent groups.
-          opsgroup_groups: The dict of opsgroup name to parent groups.
-          condition_params: The dict of group name to pipeline params referenced in
-            the conditions in that group.
-          op_name_to_for_loop_op: The dict of op name to loop ops.
+            pipeline: The instantiated pipeline object.
+            args: The list of pipeline function arguments as PipelineParam.
+            root_group: The root OpsGroup.
+            op_groups: The dict of op name to parent groups.
+            opsgroup_groups: The dict of opsgroup name to parent groups.
+            condition_params: The dict of group name to pipeline params
+              referenced in the conditions in that group.
+            op_name_to_for_loop_op: The dict of op name to loop ops.
 
         Returns:
-          A tuple (inputs, outputs).
-          inputs and outputs are dicts with key being the group/op names and values
-          being list of tuples (param, producing_op_name). producing_op_name is the
-          name of the op that produces the param. If the param is a pipeline param
-          (no producer op), then producing_op_name is None.
+            A tuple (inputs, outputs).
+            inputs and outputs are dicts with key being the group/op names and
+            values being list of tuples (param, producing_op_name).
+            producing_op_name is the name of the op that produces the param.
+            If the param is a pipeline param (no producer op), then
+            producing_op_name is None.
         """
         inputs = collections.defaultdict(set)
         outputs = collections.defaultdict(set)
 
-        # Fix possible missing type -- PipelineParam parsed from command line args
-        # doesn't contain the type information, as the `.param_type` is not included
-        # during PipelineParam serialization.
+        # Fix possible missing type -- PipelineParam parsed from command line
+        # args doesn't contain the type information, as the `.param_type` is not
+        # included during PipelineParam serialization.
         all_params = {param.pattern: param for param in args}
         for op in pipeline.ops.values():
             for param in op.inputs + list(op.outputs.values()) + list(
@@ -312,99 +315,151 @@ def _get_inputs_outputs(
             # considered.
             for param in op.inputs + list(condition_params[op.name]):
 
-                # if the value is already provided (immediate value), then no need to
-                # expose it as input for its parent groups.
+                # if the value is already provided (immediate value), then no
+                # need to expose it as input for its parent groups.
                 if param.value:
                     continue
+
+                # params_to_add could be a list of PipelineParams when loop args
+                # are involved. Given a nested loops example as follows:
+                #
+                #  def my_pipeline(loop_parameter: list):
+                #       with dsl.ParallelFor(loop_parameter) as item:
+                #           with dsl.ParallelFor(item.p_a) as item_p_a:
+                #               print_op(item_p_a.q_a)
+                #
+                # The print_op takes an input of
+                # {{pipelineparam:op=;name=loop_parameter-loop-item-subvar-p_a-loop-item-subvar-q_a}}.
+                # Given this, we calculate the list of PipelineParams potentially
+                # needed by acros DAG levels as follows:
+                #
+                # [{{pipelineparam:op=;name=loop_parameter-loop-item-subvar-p_a-loop-item-subvar-q_a}},
+                #  {{pipelineparam:op=;name=loop_parameter-loop-item-subvar-p_a-loop-item}},
+                #  {{pipelineparam:op=;name=loop_parameter-loop-item-subvar-p_a}},
+                #  {{pipelineparam:op=;name=loop_parameter-loop-item}},
+                #  {{pipelineparam:op=;name=loop_parameter}}]
+                #
+                # For the above example, the first loop needs the input of
+                # {{pipelineparam:op=;name=loop_parameter}},
+                # the second loop needs the input of
+                # {{pipelineparam:op=;name=loop_parameter-loop-item}}
+                # and the print_op needs the input of
+                # {{pipelineparam:op=;name=loop_parameter-loop-item-subvar-p_a-loop-item}}
+                #
+                # When we traverse a DAG in a top-down direction, we add params
+                # from the end, and pop it out when it's no longer needed by the
+                # subdag.
+                # When we traverse a DAG in a bottom-up direction, we add params
+                # from the front, and pop it out when it's no longer needed by
+                # the parent dag.
+                params_to_add = collections.deque()
+                param_to_add = param
+
+                while isinstance(param_to_add, (
+                        _for_loop.LoopArguments,
+                        _for_loop.LoopArgumentVariable,
+                )):
+                    params_to_add.append(param_to_add)
+                    if isinstance(param_to_add, _for_loop.LoopArgumentVariable):
+                        param_to_add = param_to_add.loop_args
+                    elif param_to_add.items_is_pipeline_param:
+                        param_to_add = param_to_add.items_or_pipeline_param
+                if isinstance(param_to_add, dsl.PipelineParam):
+                    params_to_add.append(param_to_add)
+
                 if param.op_name:
+                    # The PipelineParam is produced by a component.
+
                     upstream_op = pipeline.ops[param.op_name]
                     upstream_groups, downstream_groups = (
-                        self._get_uncommon_ancestors(op_groups, opsgroup_groups,
-                                                     upstream_op, op))
+                        self._get_uncommon_ancestors(
+                            op_groups=op_groups,
+                            opsgroup_groups=opsgroup_groups,
+                            op1=upstream_op,
+                            op2=op,
+                        ))
+
                     for i, group_name in enumerate(downstream_groups):
                         if i == 0:
-                            # If it is the first uncommon downstream group, then the input
-                            # comes from the first uncommon upstream group.
-                            inputs[group_name].add((param, upstream_groups[0]))
+                            # If it is the first uncommon downstream group, then
+                            # the input comes from the first uncommon upstream
+                            # group.
+                            producer_op = upstream_groups[0]
                         else:
-                            # If not the first downstream group, then the input is passed down
-                            # from its ancestor groups so the upstream group is None.
-                            inputs[group_name].add((param, None))
+                            # If not the first downstream group, then the input
+                            # is passed down from its ancestor groups so the
+                            # upstream group is None.
+                            producer_op = None
+
+                        inputs[group_name].add((params_to_add[-1], producer_op))
+
+                        if group_name in op_name_to_for_loop_op:
+                            loop_group = op_name_to_for_loop_op[group_name]
+
+                            # Pop out the last elements from params_to_add if it
+                            # is found in the current (loop) DAG. Downstreams
+                            # would only need the more specific versions for it.
+                            if params_to_add[
+                                    -1].full_name in loop_group.loop_args.full_name:
+                                params_to_add.pop()
+                                if not params_to_add:
+                                    break
+
                     for i, group_name in enumerate(upstream_groups):
                         if i == len(upstream_groups) - 1:
-                            # If last upstream group, it is an operator and output comes from container.
+                            # If last upstream group, it is an operator and
+                            # output comes from container.
                             outputs[group_name].add((param, None))
                         else:
-                            # If not last upstream group, output value comes from one of its child.
+                            # If not last upstream group, output value comes from
+                            # one of its child.
                             outputs[group_name].add(
                                 (param, upstream_groups[i + 1]))
                 else:
-                    if not op.is_exit_handler:
+                    # The PipelineParam is not produced by an op. It's either a
+                    # top-level pipeline input, or a constant value to loop args.
+
+                    # TODO: revisit if this is correct.
+                    if op.is_exit_handler:
+                        continue
+
+                    # For PipelineParam as a result of constant value used as
+                    # loop arguments, we have to go from bottom-up because the
+                    # PipelineParam can be originated from the middle a DAG,
+                    # which is not needed and visible to its parent DAG.
+                    if isinstance(
+                            param, (_for_loop.LoopArguments,
+                                    _for_loop.LoopArgumentVariable)
+                    ) and _for_loop.LoopArguments.name_is_withitems_loop_argument(
+                            param.full_name):
                         for group_name in op_groups[op.name][::-1]:
-                            # if group is for loop group and param is that loop's param, then the param
-                            # is created by that for loop ops_group and it shouldn't be an input to
-                            # any of its parent groups.
-                            inputs[group_name].add((param, None))
+
+                            inputs[group_name].add((params_to_add[0], None))
                             if group_name in op_name_to_for_loop_op:
                                 # for example:
-                                #   loop_group.loop_args.name = 'loop-item-param-99ca152e'
-                                #   param.name =                'loop-item-param-99ca152e--a'
+                                #   loop_group.loop_args.name = 'loop-item-param-1'
+                                #   param.name =                'loop-item-param-1-subvar-a'
                                 loop_group = op_name_to_for_loop_op[group_name]
-                                if loop_group.loop_args.name in param.name:
-                                    break
 
-        # Generate the input/output for recursive opsgroups
-        # It propagates the recursive opsgroups IO to their ancester opsgroups
-        def _get_inputs_outputs_recursive_opsgroup(group: dsl.OpsGroup):
-            #TODO: refactor the following codes with the above
-            if group.recursive_ref:
-                params = [(param, False) for param in group.inputs]
-                params.extend([(param, True)
-                               for param in list(condition_params[group.name])])
-                for param, is_condition_param in params:
-                    if param.value:
-                        continue
+                                if params_to_add[
+                                        0].full_name in loop_group.loop_args.full_name:
+                                    params_to_add.popleft()
+                                    if not params_to_add:
+                                        break
+                    else:
+                        # For PipelineParam from pipeline input, go top-down just
+                        # like we do for PipelineParam produced by an op.
+                        for group_name in op_groups[op.name]:
+
+                            inputs[group_name].add((params_to_add[-1], None))
+                            if group_name in op_name_to_for_loop_op:
+                                loop_group = op_name_to_for_loop_op[group_name]
 
-                    if param.op_name:
-                        upstream_op = pipeline.ops[param.op_name]
-                        upstream_groups, downstream_groups = \
-                          self._get_uncommon_ancestors(op_groups, opsgroup_groups, upstream_op, group)
-                        for i, g in enumerate(downstream_groups):
-                            if i == 0:
-                                inputs[g].add((param, upstream_groups[0]))
-                            # There is no need to pass the condition param as argument to the downstream ops.
-                            #TODO: this might also apply to ops. add a TODO here and think about it.
-                            elif i == len(downstream_groups
-                                         ) - 1 and is_condition_param:
-                                continue
-                            else:
-                                inputs[g].add((param, None))
-                        for i, g in enumerate(upstream_groups):
-                            if i == len(upstream_groups) - 1:
-                                outputs[g].add((param, None))
-                            else:
-                                outputs[g].add((param, upstream_groups[i + 1]))
-                    elif not is_condition_param:
-                        for g in op_groups[group.name]:
-                            inputs[g].add((param, None))
-            for subgroup in group.groups:
-                _get_inputs_outputs_recursive_opsgroup(subgroup)
-
-        _get_inputs_outputs_recursive_opsgroup(root_group)
-
-        # Generate the input for SubGraph along with parallelfor
-        for subgraph in opsgroup_groups:
-            if subgraph in op_name_to_for_loop_op:
-                # The opsgroup list is sorted with the farthest group as the first and the opsgroup
-                # itself as the last. To get the latest opsgroup which is not the opsgroup itself -2 is used.
-                parent = opsgroup_groups[subgraph][-2]
-                if parent and parent.startswith('subgraph'):
-                    # propagate only op's pipeline param from subgraph to parallelfor
-                    loop_op = op_name_to_for_loop_op[subgraph]
-                    pipeline_param = loop_op.loop_args.items_or_pipeline_param
-                    if loop_op.items_is_pipeline_param and pipeline_param.op_name:
-                        inputs[parent].add(
-                            (pipeline_param, pipeline_param.op_name))
+                                if params_to_add[
+                                        -1].full_name in loop_group.loop_args.full_name:
+                                    params_to_add.pop()
+                                    if not params_to_add:
+                                        break
 
         return inputs, outputs
 
@@ -592,32 +647,36 @@ def _populate_metrics_in_dag_outputs(
     def _group_to_dag_spec(
         self,
         group: dsl.OpsGroup,
-        inputs: Dict[str, List[Tuple[dsl.PipelineParam, str]]],
-        outputs: Dict[str, List[Tuple[dsl.PipelineParam, str]]],
+        inputs: Mapping[str, List[Tuple[dsl.PipelineParam, str]]],
         dependencies: Dict[str, List[_GroupOrOp]],
         pipeline_spec: pipeline_spec_pb2.PipelineSpec,
         deployment_config: pipeline_spec_pb2.PipelineDeploymentConfig,
         rootgroup_name: str,
-        op_to_parent_groups: Dict[str, List[str]],
+        op_to_parent_groups: Mapping[str, List[str]],
+        opgroup_to_parent_groups: Mapping[str, List[str]],
+        op_name_to_for_loop_op: Mapping[str, dsl.ParallelFor],
     ) -> None:
         """Generate IR spec given an OpsGroup.
 
         Args:
-          group: The OpsGroup to generate spec for.
-          inputs: The inputs dictionary. The keys are group/op names and values are
-            lists of tuples (param, producing_op_name).
-          outputs: The outputs dictionary. The keys are group/op names and values
-            are lists of tuples (param, producing_op_name).
-          dependencies: The group dependencies dictionary. The keys are group/op
-            names, and the values are lists of dependent groups/ops.
-          pipeline_spec: The pipeline_spec to update in-place.
-          deployment_config: The deployment_config to hold all executors.
-          rootgroup_name: The name of the group root. Used to determine whether the
-            component spec for the current group should be the root dag.
-          op_to_parent_groups: The dict of op name to parent groups. Key is the op's
-            name. Value is a list of ancestor groups including the op itself. The
-            list of a given op is sorted in a way that the farthest group is the
-            first and the op itself is the last.
+            group: The OpsGroup to generate spec for.
+            inputs: The inputs dictionary. The keys are group/op names and values
+              are lists of tuples (param, producing_op_name).
+            dependencies: The group dependencies dictionary. The keys are group
+              or op names, and the values are lists of dependent groups/ops.
+            pipeline_spec: The pipeline_spec to update in-place.
+            deployment_config: The deployment_config to hold all executors.
+            rootgroup_name: The name of the group root. Used to determine whether
+              the component spec for the current group should be the root dag.
+            op_to_parent_groups: The dict of op name to parent groups. Key is the
+              op's name. Value is a list of ancestor groups including the op
+              itself. The list of a given op is sorted in a way that the farthest
+               group is the first and the op itself is the last.
+            opgroup_to_parent_groups: The dict of group name to parent groups.
+              Key is the group name. Value is a list of ancestor groups including
+              the group itself. The list of a given group is sorted in a way that
+              the farthest group is the first and the op itself is the last.
+            op_name_to_for_loop_op: The dict of op name to loop ops.
         """
         group_component_name = dsl_utils.sanitize_component_name(group.name)
 
@@ -708,13 +767,28 @@ def _group_to_dag_spec(
                         'Setting parallelism in ParallelFor is not supported yet.'
                         'The setting is ignored.')
 
-                # "Punch the hole", adding additional inputs (other than loop arguments
-                # which will be handled separately) needed by its subgroup or tasks.
+                # "Punch the hole", adding additional inputs (other than loop
+                # arguments which will be handled separately) needed by its
+                # subgroup or tasks.
                 loop_subgroup_params = []
+
                 for param in subgroup_params:
-                    if isinstance(param, (_for_loop.LoopArguments,
-                                          _for_loop.LoopArgumentVariable)):
-                        continue
+                    # Skip 'withItems' loop arguments if it's from an inner loop.
+                    if isinstance(
+                            param, (_for_loop.LoopArguments,
+                                    _for_loop.LoopArgumentVariable)
+                    ) and _for_loop.LoopArguments.name_is_withitems_loop_argument(
+                            param.name):
+                        withitems_loop_arg_found_in_self_or_upstream = False
+                        for group_name in opgroup_to_parent_groups[
+                                subgroup.name][::-1]:
+                            if group_name in op_name_to_for_loop_op:
+                                loop_group = op_name_to_for_loop_op[group_name]
+                                if param.name in loop_group.loop_args.name:
+                                    withitems_loop_arg_found_in_self_or_upstream = True
+                                    break
+                        if not withitems_loop_arg_found_in_self_or_upstream:
+                            continue
                     loop_subgroup_params.append(param)
 
                 if subgroup.items_is_pipeline_param:
@@ -723,6 +797,8 @@ def _group_to_dag_spec(
                     # another task or an input as global pipeline parameters.
                     loop_subgroup_params.append(
                         subgroup.loop_args.items_or_pipeline_param)
+                else:
+                    loop_subgroup_params.append(subgroup.loop_args)
 
                 dsl_component_spec.build_component_inputs_spec(
                     component_spec=subgroup_component_spec,
@@ -775,25 +851,34 @@ def _group_to_dag_spec(
                             subgroup.loop_args.full_name))
                     raw_values = subgroup.loop_args.to_list_for_task_yaml()
 
-                    subgroup_component_spec.input_definitions.parameters[
-                        input_parameter_name].type = pipeline_spec_pb2.PrimitiveType.STRING
+                    dsl_component_spec.pop_input_from_task_spec(
+                        task_spec=subgroup_task_spec,
+                        input_name=input_parameter_name)
+
                     subgroup_task_spec.parameter_iterator.items.raw = json.dumps(
                         raw_values, sort_keys=True)
                     subgroup_task_spec.parameter_iterator.item_input = (
                         input_parameter_name)
 
-            if isinstance(subgroup,
-                          dsl.OpsGroup) and subgroup.type == 'condition':
+            if isinstance(subgroup, dsl.Condition):
 
                 # "punch the hole", adding inputs needed by its subgroup or tasks.
+                condition_params = list(subgroup_params)
+                for param in [
+                        subgroup.condition.operand1,
+                        subgroup.condition.operand2,
+                ]:
+                    if isinstance(param, dsl.PipelineParam):
+                        condition_params.append(param)
+
                 dsl_component_spec.build_component_inputs_spec(
                     component_spec=subgroup_component_spec,
-                    pipeline_params=subgroup_params,
+                    pipeline_params=condition_params,
                     is_root_component=False,
                 )
                 dsl_component_spec.build_task_inputs_spec(
                     subgroup_task_spec,
-                    subgroup_params,
+                    condition_params,
                     tasks_in_current_dag,
                     is_parent_component_root,
                 )
@@ -917,34 +1002,37 @@ def _create_pipeline_spec(
 
         condition_params = self._get_condition_params_for_ops(root_group)
         op_name_to_for_loop_op = self._get_for_loop_ops(root_group)
-        inputs, outputs = self._get_inputs_outputs(
-            pipeline,
-            args,
-            root_group,
-            op_name_to_parent_groups,
-            opgroup_name_to_parent_groups,
-            condition_params,
-            op_name_to_for_loop_op,
+
+        inputs, _ = self._get_inputs_outputs(
+            pipeline=pipeline,
+            args=args,
+            root_group=root_group,
+            op_groups=op_name_to_parent_groups,
+            opsgroup_groups=opgroup_name_to_parent_groups,
+            condition_params=condition_params,
+            op_name_to_for_loop_op=op_name_to_for_loop_op,
         )
+
         dependencies = self._get_dependencies(
-            pipeline,
-            root_group,
-            op_name_to_parent_groups,
-            opgroup_name_to_parent_groups,
-            opsgroups,
-            condition_params,
+            pipeline=pipeline,
+            root_group=root_group,
+            op_groups=op_name_to_parent_groups,
+            opsgroups_groups=opgroup_name_to_parent_groups,
+            opsgroups=opsgroups,
+            condition_params=condition_params,
         )
 
         for opsgroup_name in opsgroups.keys():
             self._group_to_dag_spec(
-                opsgroups[opsgroup_name],
-                inputs,
-                outputs,
-                dependencies,
-                pipeline_spec,
-                deployment_config,
-                root_group.name,
-                op_name_to_parent_groups,
+                group=opsgroups[opsgroup_name],
+                inputs=inputs,
+                dependencies=dependencies,
+                pipeline_spec=pipeline_spec,
+                deployment_config=deployment_config,
+                rootgroup_name=root_group.name,
+                op_to_parent_groups=op_name_to_parent_groups,
+                opgroup_to_parent_groups=opgroup_name_to_parent_groups,
+                op_name_to_for_loop_op=op_name_to_for_loop_op,
             )
 
         # Exit Handler


Test patch:
diff --git a/sdk/python/kfp/v2/compiler_cli_tests/compiler_cli_tests.py b/sdk/python/kfp/v2/compiler_cli_tests/compiler_cli_tests.py
--- a/sdk/python/kfp/v2/compiler_cli_tests/compiler_cli_tests.py
+++ b/sdk/python/kfp/v2/compiler_cli_tests/compiler_cli_tests.py
@@ -114,14 +114,8 @@ def test_pipeline_with_nested_conditions(self):
     def test_pipeline_with_nested_conditions_yaml(self):
         self._test_compile_py_to_json('pipeline_with_nested_conditions_yaml')
 
-    def test_pipeline_with_loop_static(self):
-        self._test_compile_py_to_json('pipeline_with_loop_static')
-
-    def test_pipeline_with_loop_parameter(self):
-        self._test_compile_py_to_json('pipeline_with_loop_parameter')
-
-    def test_pipeline_with_loop_output(self):
-        self._test_compile_py_to_json('pipeline_with_loop_output')
+    def test_pipeline_with_loops(self):
+        self._test_compile_py_to_json('pipeline_with_loops')
 
     def test_pipeline_with_nested_loops(self):
         self._test_compile_py_to_json('pipeline_with_nested_loops')
diff --git a/sdk/python/kfp/v2/compiler_cli_tests/test_data/pipeline_with_loop_output.json b/sdk/python/kfp/v2/compiler_cli_tests/test_data/pipeline_with_loop_output.json
deleted file mode 100644
--- a/sdk/python/kfp/v2/compiler_cli_tests/test_data/pipeline_with_loop_output.json
+++ /dev/null
@@ -1,234 +0,0 @@
-{
-  "pipelineSpec": {
-    "components": {
-      "comp-args-generator-op": {
-        "executorLabel": "exec-args-generator-op",
-        "outputDefinitions": {
-          "parameters": {
-            "Output": {
-              "type": "STRING"
-            }
-          }
-        }
-      },
-      "comp-for-loop-1": {
-        "dag": {
-          "tasks": {
-            "print-op": {
-              "cachingOptions": {
-                "enableCache": true
-              },
-              "componentRef": {
-                "name": "comp-print-op"
-              },
-              "inputs": {
-                "parameters": {
-                  "msg": {
-                    "componentInputParameter": "pipelineparam--args-generator-op-Output-loop-item"
-                  }
-                }
-              },
-              "taskInfo": {
-                "name": "print-op"
-              }
-            },
-            "print-op-2": {
-              "cachingOptions": {
-                "enableCache": true
-              },
-              "componentRef": {
-                "name": "comp-print-op-2"
-              },
-              "inputs": {
-                "parameters": {
-                  "msg": {
-                    "componentInputParameter": "pipelineparam--args-generator-op-Output-loop-item",
-                    "parameterExpressionSelector": "parseJson(string_value)[\"A_a\"]"
-                  }
-                }
-              },
-              "taskInfo": {
-                "name": "print-op-2"
-              }
-            },
-            "print-op-3": {
-              "cachingOptions": {
-                "enableCache": true
-              },
-              "componentRef": {
-                "name": "comp-print-op-3"
-              },
-              "inputs": {
-                "parameters": {
-                  "msg": {
-                    "componentInputParameter": "pipelineparam--args-generator-op-Output-loop-item",
-                    "parameterExpressionSelector": "parseJson(string_value)[\"B_b\"]"
-                  }
-                }
-              },
-              "taskInfo": {
-                "name": "print-op-3"
-              }
-            }
-          }
-        },
-        "inputDefinitions": {
-          "parameters": {
-            "pipelineparam--args-generator-op-Output": {
-              "type": "STRING"
-            },
-            "pipelineparam--args-generator-op-Output-loop-item": {
-              "type": "STRING"
-            }
-          }
-        }
-      },
-      "comp-print-op": {
-        "executorLabel": "exec-print-op",
-        "inputDefinitions": {
-          "parameters": {
-            "msg": {
-              "type": "STRING"
-            }
-          }
-        }
-      },
-      "comp-print-op-2": {
-        "executorLabel": "exec-print-op-2",
-        "inputDefinitions": {
-          "parameters": {
-            "msg": {
-              "type": "STRING"
-            }
-          }
-        }
-      },
-      "comp-print-op-3": {
-        "executorLabel": "exec-print-op-3",
-        "inputDefinitions": {
-          "parameters": {
-            "msg": {
-              "type": "STRING"
-            }
-          }
-        }
-      }
-    },
-    "deploymentSpec": {
-      "executors": {
-        "exec-args-generator-op": {
-          "container": {
-            "args": [
-              "----output-paths",
-              "{{$.outputs.parameters['Output'].output_file}}"
-            ],
-            "command": [
-              "sh",
-              "-ec",
-              "program_path=$(mktemp)\nprintf \"%s\" \"$0\" > \"$program_path\"\npython3 -u \"$program_path\" \"$@\"\n",
-              "def args_generator_op():\n    import json\n    return json.dumps([{\n        'A_a': '1',\n        'B_b': '2'\n    }, {\n        'A_a': '10',\n        'B_b': '20'\n    }],\n                      sort_keys=True)\n\ndef _serialize_str(str_value: str) -> str:\n    if not isinstance(str_value, str):\n        raise TypeError('Value \"{}\" has type \"{}\" instead of str.'.format(\n            str(str_value), str(type(str_value))))\n    return str_value\n\nimport argparse\n_parser = argparse.ArgumentParser(prog='Args generator op', description='')\n_parser.add_argument(\"----output-paths\", dest=\"_output_paths\", type=str, nargs=1)\n_parsed_args = vars(_parser.parse_args())\n_output_files = _parsed_args.pop(\"_output_paths\", [])\n\n_outputs = args_generator_op(**_parsed_args)\n\n_outputs = [_outputs]\n\n_output_serializers = [\n    _serialize_str,\n\n]\n\nimport os\nfor idx, output_file in enumerate(_output_files):\n    try:\n        os.makedirs(os.path.dirname(output_file))\n    except OSError:\n        pass\n    with open(output_file, 'w') as f:\n        f.write(_output_serializers[idx](_outputs[idx]))\n"
-            ],
-            "image": "python:3.7"
-          }
-        },
-        "exec-print-op": {
-          "container": {
-            "args": [
-              "--msg",
-              "{{$.inputs.parameters['msg']}}"
-            ],
-            "command": [
-              "sh",
-              "-ec",
-              "program_path=$(mktemp)\nprintf \"%s\" \"$0\" > \"$program_path\"\npython3 -u \"$program_path\" \"$@\"\n",
-              "def print_op(msg):\n    print(msg)\n\nimport argparse\n_parser = argparse.ArgumentParser(prog='Print op', description='')\n_parser.add_argument(\"--msg\", dest=\"msg\", type=str, required=True, default=argparse.SUPPRESS)\n_parsed_args = vars(_parser.parse_args())\n\n_outputs = print_op(**_parsed_args)\n"
-            ],
-            "image": "python:3.7"
-          }
-        },
-        "exec-print-op-2": {
-          "container": {
-            "args": [
-              "--msg",
-              "{{$.inputs.parameters['msg']}}"
-            ],
-            "command": [
-              "sh",
-              "-ec",
-              "program_path=$(mktemp)\nprintf \"%s\" \"$0\" > \"$program_path\"\npython3 -u \"$program_path\" \"$@\"\n",
-              "def print_op(msg):\n    print(msg)\n\nimport argparse\n_parser = argparse.ArgumentParser(prog='Print op', description='')\n_parser.add_argument(\"--msg\", dest=\"msg\", type=str, required=True, default=argparse.SUPPRESS)\n_parsed_args = vars(_parser.parse_args())\n\n_outputs = print_op(**_parsed_args)\n"
-            ],
-            "image": "python:3.7"
-          }
-        },
-        "exec-print-op-3": {
-          "container": {
-            "args": [
-              "--msg",
-              "{{$.inputs.parameters['msg']}}"
-            ],
-            "command": [
-              "sh",
-              "-ec",
-              "program_path=$(mktemp)\nprintf \"%s\" \"$0\" > \"$program_path\"\npython3 -u \"$program_path\" \"$@\"\n",
-              "def print_op(msg):\n    print(msg)\n\nimport argparse\n_parser = argparse.ArgumentParser(prog='Print op', description='')\n_parser.add_argument(\"--msg\", dest=\"msg\", type=str, required=True, default=argparse.SUPPRESS)\n_parsed_args = vars(_parser.parse_args())\n\n_outputs = print_op(**_parsed_args)\n"
-            ],
-            "image": "python:3.7"
-          }
-        }
-      }
-    },
-    "pipelineInfo": {
-      "name": "pipeline-with-loop-output-args"
-    },
-    "root": {
-      "dag": {
-        "tasks": {
-          "args-generator-op": {
-            "cachingOptions": {
-              "enableCache": true
-            },
-            "componentRef": {
-              "name": "comp-args-generator-op"
-            },
-            "taskInfo": {
-              "name": "args-generator-op"
-            }
-          },
-          "for-loop-1": {
-            "componentRef": {
-              "name": "comp-for-loop-1"
-            },
-            "dependentTasks": [
-              "args-generator-op"
-            ],
-            "inputs": {
-              "parameters": {
-                "pipelineparam--args-generator-op-Output": {
-                  "taskOutputParameter": {
-                    "outputParameterKey": "Output",
-                    "producerTask": "args-generator-op"
-                  }
-                }
-              }
-            },
-            "parameterIterator": {
-              "itemInput": "pipelineparam--args-generator-op-Output-loop-item",
-              "items": {
-                "inputParameter": "pipelineparam--args-generator-op-Output"
-              }
-            },
-            "taskInfo": {
-              "name": "for-loop-1"
-            }
-          }
-        }
-      }
-    },
-    "schemaVersion": "2.0.0",
-    "sdkVersion": "kfp-1.7.2"
-  },
-  "runtimeConfig": {
-    "gcsOutputDirectory": "dummy_root"
-  }
-}
\ No newline at end of file
diff --git a/sdk/python/kfp/v2/compiler_cli_tests/test_data/pipeline_with_loop_parameter.json b/sdk/python/kfp/v2/compiler_cli_tests/test_data/pipeline_with_loop_parameter.json
deleted file mode 100644
--- a/sdk/python/kfp/v2/compiler_cli_tests/test_data/pipeline_with_loop_parameter.json
+++ /dev/null
@@ -1,204 +0,0 @@
-{
-  "pipelineSpec": {
-    "components": {
-      "comp-for-loop-1": {
-        "dag": {
-          "tasks": {
-            "print-op": {
-              "cachingOptions": {
-                "enableCache": true
-              },
-              "componentRef": {
-                "name": "comp-print-op"
-              },
-              "inputs": {
-                "parameters": {
-                  "msg": {
-                    "componentInputParameter": "pipelineparam--loop_arguments-loop-item"
-                  }
-                }
-              },
-              "taskInfo": {
-                "name": "print-op"
-              }
-            },
-            "print-op-2": {
-              "cachingOptions": {
-                "enableCache": true
-              },
-              "componentRef": {
-                "name": "comp-print-op-2"
-              },
-              "inputs": {
-                "parameters": {
-                  "msg": {
-                    "componentInputParameter": "pipelineparam--loop_arguments-loop-item",
-                    "parameterExpressionSelector": "parseJson(string_value)[\"A_a\"]"
-                  }
-                }
-              },
-              "taskInfo": {
-                "name": "print-op-2"
-              }
-            },
-            "print-op-3": {
-              "cachingOptions": {
-                "enableCache": true
-              },
-              "componentRef": {
-                "name": "comp-print-op-3"
-              },
-              "inputs": {
-                "parameters": {
-                  "msg": {
-                    "componentInputParameter": "pipelineparam--loop_arguments-loop-item",
-                    "parameterExpressionSelector": "parseJson(string_value)[\"B_b\"]"
-                  }
-                }
-              },
-              "taskInfo": {
-                "name": "print-op-3"
-              }
-            }
-          }
-        },
-        "inputDefinitions": {
-          "parameters": {
-            "pipelineparam--loop_arguments": {
-              "type": "STRING"
-            },
-            "pipelineparam--loop_arguments-loop-item": {
-              "type": "STRING"
-            }
-          }
-        }
-      },
-      "comp-print-op": {
-        "executorLabel": "exec-print-op",
-        "inputDefinitions": {
-          "parameters": {
-            "msg": {
-              "type": "STRING"
-            }
-          }
-        }
-      },
-      "comp-print-op-2": {
-        "executorLabel": "exec-print-op-2",
-        "inputDefinitions": {
-          "parameters": {
-            "msg": {
-              "type": "STRING"
-            }
-          }
-        }
-      },
-      "comp-print-op-3": {
-        "executorLabel": "exec-print-op-3",
-        "inputDefinitions": {
-          "parameters": {
-            "msg": {
-              "type": "STRING"
-            }
-          }
-        }
-      }
-    },
-    "deploymentSpec": {
-      "executors": {
-        "exec-print-op": {
-          "container": {
-            "args": [
-              "--msg",
-              "{{$.inputs.parameters['msg']}}"
-            ],
-            "command": [
-              "sh",
-              "-ec",
-              "program_path=$(mktemp)\nprintf \"%s\" \"$0\" > \"$program_path\"\npython3 -u \"$program_path\" \"$@\"\n",
-              "def print_op(msg):\n    print(msg)\n\nimport argparse\n_parser = argparse.ArgumentParser(prog='Print op', description='')\n_parser.add_argument(\"--msg\", dest=\"msg\", type=str, required=True, default=argparse.SUPPRESS)\n_parsed_args = vars(_parser.parse_args())\n\n_outputs = print_op(**_parsed_args)\n"
-            ],
-            "image": "python:3.7"
-          }
-        },
-        "exec-print-op-2": {
-          "container": {
-            "args": [
-              "--msg",
-              "{{$.inputs.parameters['msg']}}"
-            ],
-            "command": [
-              "sh",
-              "-ec",
-              "program_path=$(mktemp)\nprintf \"%s\" \"$0\" > \"$program_path\"\npython3 -u \"$program_path\" \"$@\"\n",
-              "def print_op(msg):\n    print(msg)\n\nimport argparse\n_parser = argparse.ArgumentParser(prog='Print op', description='')\n_parser.add_argument(\"--msg\", dest=\"msg\", type=str, required=True, default=argparse.SUPPRESS)\n_parsed_args = vars(_parser.parse_args())\n\n_outputs = print_op(**_parsed_args)\n"
-            ],
-            "image": "python:3.7"
-          }
-        },
-        "exec-print-op-3": {
-          "container": {
-            "args": [
-              "--msg",
-              "{{$.inputs.parameters['msg']}}"
-            ],
-            "command": [
-              "sh",
-              "-ec",
-              "program_path=$(mktemp)\nprintf \"%s\" \"$0\" > \"$program_path\"\npython3 -u \"$program_path\" \"$@\"\n",
-              "def print_op(msg):\n    print(msg)\n\nimport argparse\n_parser = argparse.ArgumentParser(prog='Print op', description='')\n_parser.add_argument(\"--msg\", dest=\"msg\", type=str, required=True, default=argparse.SUPPRESS)\n_parsed_args = vars(_parser.parse_args())\n\n_outputs = print_op(**_parsed_args)\n"
-            ],
-            "image": "python:3.7"
-          }
-        }
-      }
-    },
-    "pipelineInfo": {
-      "name": "pipeline-with-loop-parameter-args"
-    },
-    "root": {
-      "dag": {
-        "tasks": {
-          "for-loop-1": {
-            "componentRef": {
-              "name": "comp-for-loop-1"
-            },
-            "inputs": {
-              "parameters": {
-                "pipelineparam--loop_arguments": {
-                  "componentInputParameter": "loop_arguments"
-                }
-              }
-            },
-            "parameterIterator": {
-              "itemInput": "pipelineparam--loop_arguments-loop-item",
-              "items": {
-                "inputParameter": "pipelineparam--loop_arguments"
-              }
-            },
-            "taskInfo": {
-              "name": "for-loop-1"
-            }
-          }
-        }
-      },
-      "inputDefinitions": {
-        "parameters": {
-          "loop_arguments": {
-            "type": "STRING"
-          }
-        }
-      }
-    },
-    "schemaVersion": "2.0.0",
-    "sdkVersion": "kfp-1.7.2"
-  },
-  "runtimeConfig": {
-    "gcsOutputDirectory": "dummy_root",
-    "parameters": {
-      "loop_arguments": {
-        "stringValue": "[{\"A_a\": \"1\", \"B_b\": \"2\"}, {\"A_a\": \"10\", \"B_b\": \"20\"}]"
-      }
-    }
-  }
-}
\ No newline at end of file
diff --git a/sdk/python/kfp/v2/compiler_cli_tests/test_data/pipeline_with_loop_parameter.py b/sdk/python/kfp/v2/compiler_cli_tests/test_data/pipeline_with_loop_parameter.py
deleted file mode 100644
--- a/sdk/python/kfp/v2/compiler_cli_tests/test_data/pipeline_with_loop_parameter.py
+++ /dev/null
@@ -1,48 +0,0 @@
-# Copyright 2021 The Kubeflow Authors
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import json
-
-from kfp import components
-from kfp import dsl
-from kfp.v2 import compiler
-
-
-@components.create_component_from_func
-def print_op(msg: str):
-    print(msg)
-
-
-@dsl.pipeline(
-    name='pipeline-with-loop-parameter-args',
-    pipeline_root='dummy_root',
-)
-def my_pipeline(loop_arguments: str = json.dumps([{
-    'A_a': '1',
-    'B_b': '2'
-}, {
-    'A_a': '10',
-    'B_b': '20'
-}],
-                                                 sort_keys=True)):
-    with dsl.ParallelFor(loop_arguments) as item:
-        print_op(item)
-        print_op(item.A_a)
-        print_op(item.B_b)
-
-
-if __name__ == '__main__':
-    compiler.Compiler().compile(
-        pipeline_func=my_pipeline,
-        package_path=__file__.replace('.py', '.json'))
diff --git a/sdk/python/kfp/v2/compiler_cli_tests/test_data/pipeline_with_loop_static.json b/sdk/python/kfp/v2/compiler_cli_tests/test_data/pipeline_with_loop_static.json
deleted file mode 100644
--- a/sdk/python/kfp/v2/compiler_cli_tests/test_data/pipeline_with_loop_static.json
+++ /dev/null
@@ -1,182 +0,0 @@
-{
-  "pipelineSpec": {
-    "components": {
-      "comp-for-loop-2": {
-        "dag": {
-          "tasks": {
-            "print-op": {
-              "cachingOptions": {
-                "enableCache": true
-              },
-              "componentRef": {
-                "name": "comp-print-op"
-              },
-              "inputs": {
-                "parameters": {
-                  "msg": {
-                    "componentInputParameter": "pipelineparam--loop-item-param-1"
-                  }
-                }
-              },
-              "taskInfo": {
-                "name": "print-op"
-              }
-            },
-            "print-op-2": {
-              "cachingOptions": {
-                "enableCache": true
-              },
-              "componentRef": {
-                "name": "comp-print-op-2"
-              },
-              "inputs": {
-                "parameters": {
-                  "msg": {
-                    "componentInputParameter": "pipelineparam--loop-item-param-1",
-                    "parameterExpressionSelector": "parseJson(string_value)[\"A_a\"]"
-                  }
-                }
-              },
-              "taskInfo": {
-                "name": "print-op-2"
-              }
-            },
-            "print-op-3": {
-              "cachingOptions": {
-                "enableCache": true
-              },
-              "componentRef": {
-                "name": "comp-print-op-3"
-              },
-              "inputs": {
-                "parameters": {
-                  "msg": {
-                    "componentInputParameter": "pipelineparam--loop-item-param-1",
-                    "parameterExpressionSelector": "parseJson(string_value)[\"B_b\"]"
-                  }
-                }
-              },
-              "taskInfo": {
-                "name": "print-op-3"
-              }
-            }
-          }
-        },
-        "inputDefinitions": {
-          "parameters": {
-            "pipelineparam--loop-item-param-1": {
-              "type": "STRING"
-            }
-          }
-        }
-      },
-      "comp-print-op": {
-        "executorLabel": "exec-print-op",
-        "inputDefinitions": {
-          "parameters": {
-            "msg": {
-              "type": "STRING"
-            }
-          }
-        }
-      },
-      "comp-print-op-2": {
-        "executorLabel": "exec-print-op-2",
-        "inputDefinitions": {
-          "parameters": {
-            "msg": {
-              "type": "STRING"
-            }
-          }
-        }
-      },
-      "comp-print-op-3": {
-        "executorLabel": "exec-print-op-3",
-        "inputDefinitions": {
-          "parameters": {
-            "msg": {
-              "type": "STRING"
-            }
-          }
-        }
-      }
-    },
-    "deploymentSpec": {
-      "executors": {
-        "exec-print-op": {
-          "container": {
-            "args": [
-              "--msg",
-              "{{$.inputs.parameters['msg']}}"
-            ],
-            "command": [
-              "sh",
-              "-ec",
-              "program_path=$(mktemp)\nprintf \"%s\" \"$0\" > \"$program_path\"\npython3 -u \"$program_path\" \"$@\"\n",
-              "def print_op(msg):\n    print(msg)\n\nimport argparse\n_parser = argparse.ArgumentParser(prog='Print op', description='')\n_parser.add_argument(\"--msg\", dest=\"msg\", type=str, required=True, default=argparse.SUPPRESS)\n_parsed_args = vars(_parser.parse_args())\n\n_outputs = print_op(**_parsed_args)\n"
-            ],
-            "image": "python:3.7"
-          }
-        },
-        "exec-print-op-2": {
-          "container": {
-            "args": [
-              "--msg",
-              "{{$.inputs.parameters['msg']}}"
-            ],
-            "command": [
-              "sh",
-              "-ec",
-              "program_path=$(mktemp)\nprintf \"%s\" \"$0\" > \"$program_path\"\npython3 -u \"$program_path\" \"$@\"\n",
-              "def print_op(msg):\n    print(msg)\n\nimport argparse\n_parser = argparse.ArgumentParser(prog='Print op', description='')\n_parser.add_argument(\"--msg\", dest=\"msg\", type=str, required=True, default=argparse.SUPPRESS)\n_parsed_args = vars(_parser.parse_args())\n\n_outputs = print_op(**_parsed_args)\n"
-            ],
-            "image": "python:3.7"
-          }
-        },
-        "exec-print-op-3": {
-          "container": {
-            "args": [
-              "--msg",
-              "{{$.inputs.parameters['msg']}}"
-            ],
-            "command": [
-              "sh",
-              "-ec",
-              "program_path=$(mktemp)\nprintf \"%s\" \"$0\" > \"$program_path\"\npython3 -u \"$program_path\" \"$@\"\n",
-              "def print_op(msg):\n    print(msg)\n\nimport argparse\n_parser = argparse.ArgumentParser(prog='Print op', description='')\n_parser.add_argument(\"--msg\", dest=\"msg\", type=str, required=True, default=argparse.SUPPRESS)\n_parsed_args = vars(_parser.parse_args())\n\n_outputs = print_op(**_parsed_args)\n"
-            ],
-            "image": "python:3.7"
-          }
-        }
-      }
-    },
-    "pipelineInfo": {
-      "name": "pipeline-with-loop-static-args"
-    },
-    "root": {
-      "dag": {
-        "tasks": {
-          "for-loop-2": {
-            "componentRef": {
-              "name": "comp-for-loop-2"
-            },
-            "parameterIterator": {
-              "itemInput": "pipelineparam--loop-item-param-1",
-              "items": {
-                "raw": "[{\"A_a\": \"1\", \"B_b\": \"2\"}, {\"A_a\": \"10\", \"B_b\": \"20\"}]"
-              }
-            },
-            "taskInfo": {
-              "name": "for-loop-2"
-            }
-          }
-        }
-      }
-    },
-    "schemaVersion": "2.0.0",
-    "sdkVersion": "kfp-1.7.2"
-  },
-  "runtimeConfig": {
-    "gcsOutputDirectory": "dummy_root"
-  }
-}
\ No newline at end of file
diff --git a/sdk/python/kfp/v2/compiler_cli_tests/test_data/pipeline_with_loop_static.py b/sdk/python/kfp/v2/compiler_cli_tests/test_data/pipeline_with_loop_static.py
deleted file mode 100644
--- a/sdk/python/kfp/v2/compiler_cli_tests/test_data/pipeline_with_loop_static.py
+++ /dev/null
@@ -1,41 +0,0 @@
-# Copyright 2021 The Kubeflow Authors
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-from kfp import components
-from kfp import dsl
-from kfp.v2 import compiler
-
-
-@components.create_component_from_func
-def print_op(msg: str):
-    print(msg)
-
-
-@dsl.pipeline(
-    name='pipeline-with-loop-static-args',
-    pipeline_root='dummy_root',
-)
-def my_pipeline():
-
-    loop_args = [{'A_a': '1', 'B_b': '2'}, {'A_a': '10', 'B_b': '20'}]
-    with dsl.ParallelFor(loop_args) as item:
-        print_op(item)
-        print_op(item.A_a)
-        print_op(item.B_b)
-
-
-if __name__ == '__main__':
-    compiler.Compiler().compile(
-        pipeline_func=my_pipeline,
-        package_path=__file__.replace('.py', '.json'))
diff --git a/sdk/python/kfp/v2/compiler_cli_tests/test_data/pipeline_with_loops.json b/sdk/python/kfp/v2/compiler_cli_tests/test_data/pipeline_with_loops.json
new file mode 100644
--- /dev/null
+++ b/sdk/python/kfp/v2/compiler_cli_tests/test_data/pipeline_with_loops.json
@@ -0,0 +1,517 @@
+{
+  "pipelineSpec": {
+    "components": {
+      "comp-args-generator-op": {
+        "executorLabel": "exec-args-generator-op",
+        "outputDefinitions": {
+          "parameters": {
+            "Output": {
+              "type": "STRING"
+            }
+          }
+        }
+      },
+      "comp-for-loop-1": {
+        "dag": {
+          "tasks": {
+            "print-op": {
+              "cachingOptions": {
+                "enableCache": true
+              },
+              "componentRef": {
+                "name": "comp-print-op"
+              },
+              "inputs": {
+                "parameters": {
+                  "msg": {
+                    "componentInputParameter": "pipelineparam--loop_parameter-loop-item"
+                  }
+                }
+              },
+              "taskInfo": {
+                "name": "print-op"
+              }
+            }
+          }
+        },
+        "inputDefinitions": {
+          "parameters": {
+            "pipelineparam--loop_parameter": {
+              "type": "STRING"
+            },
+            "pipelineparam--loop_parameter-loop-item": {
+              "type": "STRING"
+            }
+          }
+        }
+      },
+      "comp-for-loop-2": {
+        "dag": {
+          "tasks": {
+            "print-op-2": {
+              "cachingOptions": {
+                "enableCache": true
+              },
+              "componentRef": {
+                "name": "comp-print-op-2"
+              },
+              "inputs": {
+                "parameters": {
+                  "msg": {
+                    "componentInputParameter": "pipelineparam--args-generator-op-Output-loop-item"
+                  }
+                }
+              },
+              "taskInfo": {
+                "name": "print-op-2"
+              }
+            },
+            "print-op-3": {
+              "cachingOptions": {
+                "enableCache": true
+              },
+              "componentRef": {
+                "name": "comp-print-op-3"
+              },
+              "inputs": {
+                "parameters": {
+                  "msg": {
+                    "componentInputParameter": "pipelineparam--args-generator-op-Output-loop-item",
+                    "parameterExpressionSelector": "parseJson(string_value)[\"A_a\"]"
+                  }
+                }
+              },
+              "taskInfo": {
+                "name": "print-op-3"
+              }
+            },
+            "print-op-4": {
+              "cachingOptions": {
+                "enableCache": true
+              },
+              "componentRef": {
+                "name": "comp-print-op-4"
+              },
+              "inputs": {
+                "parameters": {
+                  "msg": {
+                    "componentInputParameter": "pipelineparam--args-generator-op-Output-loop-item",
+                    "parameterExpressionSelector": "parseJson(string_value)[\"B_b\"]"
+                  }
+                }
+              },
+              "taskInfo": {
+                "name": "print-op-4"
+              }
+            }
+          }
+        },
+        "inputDefinitions": {
+          "parameters": {
+            "pipelineparam--args-generator-op-Output": {
+              "type": "STRING"
+            },
+            "pipelineparam--args-generator-op-Output-loop-item": {
+              "type": "STRING"
+            }
+          }
+        }
+      },
+      "comp-for-loop-4": {
+        "dag": {
+          "tasks": {
+            "print-op-5": {
+              "cachingOptions": {
+                "enableCache": true
+              },
+              "componentRef": {
+                "name": "comp-print-op-5"
+              },
+              "inputs": {
+                "parameters": {
+                  "msg": {
+                    "componentInputParameter": "pipelineparam--loop-item-param-3"
+                  }
+                }
+              },
+              "taskInfo": {
+                "name": "print-op-5"
+              }
+            },
+            "print-op-6": {
+              "cachingOptions": {
+                "enableCache": true
+              },
+              "componentRef": {
+                "name": "comp-print-op-6"
+              },
+              "inputs": {
+                "parameters": {
+                  "msg": {
+                    "componentInputParameter": "pipelineparam--loop-item-param-3",
+                    "parameterExpressionSelector": "parseJson(string_value)[\"A_a\"]"
+                  }
+                }
+              },
+              "taskInfo": {
+                "name": "print-op-6"
+              }
+            },
+            "print-op-7": {
+              "cachingOptions": {
+                "enableCache": true
+              },
+              "componentRef": {
+                "name": "comp-print-op-7"
+              },
+              "inputs": {
+                "parameters": {
+                  "msg": {
+                    "componentInputParameter": "pipelineparam--loop-item-param-3",
+                    "parameterExpressionSelector": "parseJson(string_value)[\"B_b\"]"
+                  }
+                }
+              },
+              "taskInfo": {
+                "name": "print-op-7"
+              }
+            }
+          }
+        },
+        "inputDefinitions": {
+          "parameters": {
+            "pipelineparam--loop-item-param-3": {
+              "type": "STRING"
+            }
+          }
+        }
+      },
+      "comp-print-op": {
+        "executorLabel": "exec-print-op",
+        "inputDefinitions": {
+          "parameters": {
+            "msg": {
+              "type": "STRING"
+            }
+          }
+        }
+      },
+      "comp-print-op-2": {
+        "executorLabel": "exec-print-op-2",
+        "inputDefinitions": {
+          "parameters": {
+            "msg": {
+              "type": "STRING"
+            }
+          }
+        }
+      },
+      "comp-print-op-3": {
+        "executorLabel": "exec-print-op-3",
+        "inputDefinitions": {
+          "parameters": {
+            "msg": {
+              "type": "STRING"
+            }
+          }
+        }
+      },
+      "comp-print-op-4": {
+        "executorLabel": "exec-print-op-4",
+        "inputDefinitions": {
+          "parameters": {
+            "msg": {
+              "type": "STRING"
+            }
+          }
+        }
+      },
+      "comp-print-op-5": {
+        "executorLabel": "exec-print-op-5",
+        "inputDefinitions": {
+          "parameters": {
+            "msg": {
+              "type": "STRING"
+            }
+          }
+        }
+      },
+      "comp-print-op-6": {
+        "executorLabel": "exec-print-op-6",
+        "inputDefinitions": {
+          "parameters": {
+            "msg": {
+              "type": "STRING"
+            }
+          }
+        }
+      },
+      "comp-print-op-7": {
+        "executorLabel": "exec-print-op-7",
+        "inputDefinitions": {
+          "parameters": {
+            "msg": {
+              "type": "STRING"
+            }
+          }
+        }
+      }
+    },
+    "deploymentSpec": {
+      "executors": {
+        "exec-args-generator-op": {
+          "container": {
+            "args": [
+              "--executor_input",
+              "{{$}}",
+              "--function_to_execute",
+              "args_generator_op"
+            ],
+            "command": [
+              "sh",
+              "-c",
+              "(python3 -m ensurepip || python3 -m ensurepip --user) && (PIP_DISABLE_PIP_VERSION_CHECK=1 python3 -m pip install --quiet                 --no-warn-script-location 'kfp==1.8.2' || PIP_DISABLE_PIP_VERSION_CHECK=1 python3 -m pip install --quiet                 --no-warn-script-location 'kfp==1.8.2' --user) && \"$0\" \"$@\"",
+              "sh",
+              "-ec",
+              "program_path=$(mktemp -d)\nprintf \"%s\" \"$0\" > \"$program_path/ephemeral_component.py\"\npython3 -m kfp.v2.components.executor_main                         --component_module_path                         \"$program_path/ephemeral_component.py\"                         \"$@\"\n",
+              "\nfrom kfp.v2.dsl import *\nfrom typing import *\n\ndef args_generator_op() -> List[str]:\n    return [{'A_a': '1', 'B_b': '2'}, {'A_a': '10', 'B_b': '20'}]\n\n"
+            ],
+            "image": "python:3.7"
+          }
+        },
+        "exec-print-op": {
+          "container": {
+            "args": [
+              "--executor_input",
+              "{{$}}",
+              "--function_to_execute",
+              "print_op"
+            ],
+            "command": [
+              "sh",
+              "-c",
+              "(python3 -m ensurepip || python3 -m ensurepip --user) && (PIP_DISABLE_PIP_VERSION_CHECK=1 python3 -m pip install --quiet                 --no-warn-script-location 'kfp==1.8.2' || PIP_DISABLE_PIP_VERSION_CHECK=1 python3 -m pip install --quiet                 --no-warn-script-location 'kfp==1.8.2' --user) && \"$0\" \"$@\"",
+              "sh",
+              "-ec",
+              "program_path=$(mktemp -d)\nprintf \"%s\" \"$0\" > \"$program_path/ephemeral_component.py\"\npython3 -m kfp.v2.components.executor_main                         --component_module_path                         \"$program_path/ephemeral_component.py\"                         \"$@\"\n",
+              "\nfrom kfp.v2.dsl import *\nfrom typing import *\n\ndef print_op(msg: str):\n    print(msg)\n\n"
+            ],
+            "image": "python:3.7"
+          }
+        },
+        "exec-print-op-2": {
+          "container": {
+            "args": [
+              "--executor_input",
+              "{{$}}",
+              "--function_to_execute",
+              "print_op"
+            ],
+            "command": [
+              "sh",
+              "-c",
+              "(python3 -m ensurepip || python3 -m ensurepip --user) && (PIP_DISABLE_PIP_VERSION_CHECK=1 python3 -m pip install --quiet                 --no-warn-script-location 'kfp==1.8.2' || PIP_DISABLE_PIP_VERSION_CHECK=1 python3 -m pip install --quiet                 --no-warn-script-location 'kfp==1.8.2' --user) && \"$0\" \"$@\"",
+              "sh",
+              "-ec",
+              "program_path=$(mktemp -d)\nprintf \"%s\" \"$0\" > \"$program_path/ephemeral_component.py\"\npython3 -m kfp.v2.components.executor_main                         --component_module_path                         \"$program_path/ephemeral_component.py\"                         \"$@\"\n",
+              "\nfrom kfp.v2.dsl import *\nfrom typing import *\n\ndef print_op(msg: str):\n    print(msg)\n\n"
+            ],
+            "image": "python:3.7"
+          }
+        },
+        "exec-print-op-3": {
+          "container": {
+            "args": [
+              "--executor_input",
+              "{{$}}",
+              "--function_to_execute",
+              "print_op"
+            ],
+            "command": [
+              "sh",
+              "-c",
+              "(python3 -m ensurepip || python3 -m ensurepip --user) && (PIP_DISABLE_PIP_VERSION_CHECK=1 python3 -m pip install --quiet                 --no-warn-script-location 'kfp==1.8.2' || PIP_DISABLE_PIP_VERSION_CHECK=1 python3 -m pip install --quiet                 --no-warn-script-location 'kfp==1.8.2' --user) && \"$0\" \"$@\"",
+              "sh",
+              "-ec",
+              "program_path=$(mktemp -d)\nprintf \"%s\" \"$0\" > \"$program_path/ephemeral_component.py\"\npython3 -m kfp.v2.components.executor_main                         --component_module_path                         \"$program_path/ephemeral_component.py\"                         \"$@\"\n",
+              "\nfrom kfp.v2.dsl import *\nfrom typing import *\n\ndef print_op(msg: str):\n    print(msg)\n\n"
+            ],
+            "image": "python:3.7"
+          }
+        },
+        "exec-print-op-4": {
+          "container": {
+            "args": [
+              "--executor_input",
+              "{{$}}",
+              "--function_to_execute",
+              "print_op"
+            ],
+            "command": [
+              "sh",
+              "-c",
+              "(python3 -m ensurepip || python3 -m ensurepip --user) && (PIP_DISABLE_PIP_VERSION_CHECK=1 python3 -m pip install --quiet                 --no-warn-script-location 'kfp==1.8.2' || PIP_DISABLE_PIP_VERSION_CHECK=1 python3 -m pip install --quiet                 --no-warn-script-location 'kfp==1.8.2' --user) && \"$0\" \"$@\"",
+              "sh",
+              "-ec",
+              "program_path=$(mktemp -d)\nprintf \"%s\" \"$0\" > \"$program_path/ephemeral_component.py\"\npython3 -m kfp.v2.components.executor_main                         --component_module_path                         \"$program_path/ephemeral_component.py\"                         \"$@\"\n",
+              "\nfrom kfp.v2.dsl import *\nfrom typing import *\n\ndef print_op(msg: str):\n    print(msg)\n\n"
+            ],
+            "image": "python:3.7"
+          }
+        },
+        "exec-print-op-5": {
+          "container": {
+            "args": [
+              "--executor_input",
+              "{{$}}",
+              "--function_to_execute",
+              "print_op"
+            ],
+            "command": [
+              "sh",
+              "-c",
+              "(python3 -m ensurepip || python3 -m ensurepip --user) && (PIP_DISABLE_PIP_VERSION_CHECK=1 python3 -m pip install --quiet                 --no-warn-script-location 'kfp==1.8.2' || PIP_DISABLE_PIP_VERSION_CHECK=1 python3 -m pip install --quiet                 --no-warn-script-location 'kfp==1.8.2' --user) && \"$0\" \"$@\"",
+              "sh",
+              "-ec",
+              "program_path=$(mktemp -d)\nprintf \"%s\" \"$0\" > \"$program_path/ephemeral_component.py\"\npython3 -m kfp.v2.components.executor_main                         --component_module_path                         \"$program_path/ephemeral_component.py\"                         \"$@\"\n",
+              "\nfrom kfp.v2.dsl import *\nfrom typing import *\n\ndef print_op(msg: str):\n    print(msg)\n\n"
+            ],
+            "image": "python:3.7"
+          }
+        },
+        "exec-print-op-6": {
+          "container": {
+            "args": [
+              "--executor_input",
+              "{{$}}",
+              "--function_to_execute",
+              "print_op"
+            ],
+            "command": [
+              "sh",
+              "-c",
+              "(python3 -m ensurepip || python3 -m ensurepip --user) && (PIP_DISABLE_PIP_VERSION_CHECK=1 python3 -m pip install --quiet                 --no-warn-script-location 'kfp==1.8.2' || PIP_DISABLE_PIP_VERSION_CHECK=1 python3 -m pip install --quiet                 --no-warn-script-location 'kfp==1.8.2' --user) && \"$0\" \"$@\"",
+              "sh",
+              "-ec",
+              "program_path=$(mktemp -d)\nprintf \"%s\" \"$0\" > \"$program_path/ephemeral_component.py\"\npython3 -m kfp.v2.components.executor_main                         --component_module_path                         \"$program_path/ephemeral_component.py\"                         \"$@\"\n",
+              "\nfrom kfp.v2.dsl import *\nfrom typing import *\n\ndef print_op(msg: str):\n    print(msg)\n\n"
+            ],
+            "image": "python:3.7"
+          }
+        },
+        "exec-print-op-7": {
+          "container": {
+            "args": [
+              "--executor_input",
+              "{{$}}",
+              "--function_to_execute",
+              "print_op"
+            ],
+            "command": [
+              "sh",
+              "-c",
+              "(python3 -m ensurepip || python3 -m ensurepip --user) && (PIP_DISABLE_PIP_VERSION_CHECK=1 python3 -m pip install --quiet                 --no-warn-script-location 'kfp==1.8.2' || PIP_DISABLE_PIP_VERSION_CHECK=1 python3 -m pip install --quiet                 --no-warn-script-location 'kfp==1.8.2' --user) && \"$0\" \"$@\"",
+              "sh",
+              "-ec",
+              "program_path=$(mktemp -d)\nprintf \"%s\" \"$0\" > \"$program_path/ephemeral_component.py\"\npython3 -m kfp.v2.components.executor_main                         --component_module_path                         \"$program_path/ephemeral_component.py\"                         \"$@\"\n",
+              "\nfrom kfp.v2.dsl import *\nfrom typing import *\n\ndef print_op(msg: str):\n    print(msg)\n\n"
+            ],
+            "image": "python:3.7"
+          }
+        }
+      }
+    },
+    "pipelineInfo": {
+      "name": "pipeline-with-loops"
+    },
+    "root": {
+      "dag": {
+        "tasks": {
+          "args-generator-op": {
+            "cachingOptions": {
+              "enableCache": true
+            },
+            "componentRef": {
+              "name": "comp-args-generator-op"
+            },
+            "taskInfo": {
+              "name": "args-generator-op"
+            }
+          },
+          "for-loop-1": {
+            "componentRef": {
+              "name": "comp-for-loop-1"
+            },
+            "inputs": {
+              "parameters": {
+                "pipelineparam--loop_parameter": {
+                  "componentInputParameter": "loop_parameter"
+                }
+              }
+            },
+            "parameterIterator": {
+              "itemInput": "pipelineparam--loop_parameter-loop-item",
+              "items": {
+                "inputParameter": "pipelineparam--loop_parameter"
+              }
+            },
+            "taskInfo": {
+              "name": "for-loop-1"
+            }
+          },
+          "for-loop-2": {
+            "componentRef": {
+              "name": "comp-for-loop-2"
+            },
+            "dependentTasks": [
+              "args-generator-op"
+            ],
+            "inputs": {
+              "parameters": {
+                "pipelineparam--args-generator-op-Output": {
+                  "taskOutputParameter": {
+                    "outputParameterKey": "Output",
+                    "producerTask": "args-generator-op"
+                  }
+                }
+              }
+            },
+            "parameterIterator": {
+              "itemInput": "pipelineparam--args-generator-op-Output-loop-item",
+              "items": {
+                "inputParameter": "pipelineparam--args-generator-op-Output"
+              }
+            },
+            "taskInfo": {
+              "name": "for-loop-2"
+            }
+          },
+          "for-loop-4": {
+            "componentRef": {
+              "name": "comp-for-loop-4"
+            },
+            "parameterIterator": {
+              "itemInput": "pipelineparam--loop-item-param-3",
+              "items": {
+                "raw": "[{\"A_a\": \"1\", \"B_b\": \"2\"}, {\"A_a\": \"10\", \"B_b\": \"20\"}]"
+              }
+            },
+            "taskInfo": {
+              "name": "for-loop-4"
+            }
+          }
+        }
+      },
+      "inputDefinitions": {
+        "parameters": {
+          "loop_parameter": {
+            "type": "STRING"
+          }
+        }
+      }
+    },
+    "schemaVersion": "2.0.0",
+    "sdkVersion": "kfp-1.8.2"
+  },
+  "runtimeConfig": {}
+}
\ No newline at end of file
diff --git a/sdk/python/kfp/v2/compiler_cli_tests/test_data/pipeline_with_loop_output.py b/sdk/python/kfp/v2/compiler_cli_tests/test_data/pipeline_with_loops.py
similarity index 58%
rename from sdk/python/kfp/v2/compiler_cli_tests/test_data/pipeline_with_loop_output.py
rename to sdk/python/kfp/v2/compiler_cli_tests/test_data/pipeline_with_loops.py
--- a/sdk/python/kfp/v2/compiler_cli_tests/test_data/pipeline_with_loop_output.py
+++ b/sdk/python/kfp/v2/compiler_cli_tests/test_data/pipeline_with_loops.py
@@ -14,41 +14,41 @@
 
 from typing import List
 
-from kfp import components
-from kfp import dsl
-from kfp.v2 import compiler
+from kfp.v2 import compiler, dsl
+from kfp.v2.dsl import component
 
 
-@components.create_component_from_func
-def args_generator_op() -> str:
-    import json
-    return json.dumps([{
-        'A_a': '1',
-        'B_b': '2'
-    }, {
-        'A_a': '10',
-        'B_b': '20'
-    }],
-                      sort_keys=True)
+@component
+def args_generator_op() -> List[str]:
+    return [{'A_a': '1', 'B_b': '2'}, {'A_a': '10', 'B_b': '20'}]
 
 
-@components.create_component_from_func
+@component
 def print_op(msg: str):
     print(msg)
 
 
-@dsl.pipeline(
-    name='pipeline-with-loop-output-args',
-    pipeline_root='dummy_root',
-)
-def my_pipeline():
+@dsl.pipeline(name='pipeline-with-loops')
+def my_pipeline(loop_parameter: List[str]):
 
+    # Loop argument is from a pipeline input
+    with dsl.ParallelFor(loop_parameter) as item:
+        print_op(item)
+
+    # Loop argument is from a component output
     args_generator = args_generator_op()
     with dsl.ParallelFor(args_generator.output) as item:
         print_op(item)
         print_op(item.A_a)
         print_op(item.B_b)
 
+    # Loop argument is a static value known at compile time
+    loop_args = [{'A_a': '1', 'B_b': '2'}, {'A_a': '10', 'B_b': '20'}]
+    with dsl.ParallelFor(loop_args) as item:
+        print_op(item)
+        print_op(item.A_a)
+        print_op(item.B_b)
+
 
 if __name__ == '__main__':
     compiler.Compiler().compile(
diff --git a/sdk/python/kfp/v2/compiler_cli_tests/test_data/pipeline_with_loops_and_conditions.json b/sdk/python/kfp/v2/compiler_cli_tests/test_data/pipeline_with_loops_and_conditions.json
--- a/sdk/python/kfp/v2/compiler_cli_tests/test_data/pipeline_with_loops_and_conditions.json
+++ b/sdk/python/kfp/v2/compiler_cli_tests/test_data/pipeline_with_loops_and_conditions.json
@@ -11,18 +11,28 @@
           }
         }
       },
+      "comp-args-generator-op-2": {
+        "executorLabel": "exec-args-generator-op-2",
+        "outputDefinitions": {
+          "parameters": {
+            "Output": {
+              "type": "STRING"
+            }
+          }
+        }
+      },
       "comp-condition-1": {
         "dag": {
           "tasks": {
-            "args-generator-op": {
+            "args-generator-op-2": {
               "cachingOptions": {
                 "enableCache": true
               },
               "componentRef": {
-                "name": "comp-args-generator-op"
+                "name": "comp-args-generator-op-2"
               },
               "taskInfo": {
-                "name": "args-generator-op"
+                "name": "args-generator-op-2"
               }
             },
             "for-loop-2": {
@@ -30,21 +40,27 @@
                 "name": "comp-for-loop-2"
               },
               "dependentTasks": [
-                "args-generator-op"
+                "args-generator-op-2"
               ],
               "inputs": {
                 "parameters": {
-                  "pipelineparam--args-generator-op-Output": {
+                  "pipelineparam--args-generator-op-2-Output": {
                     "taskOutputParameter": {
                       "outputParameterKey": "Output",
-                      "producerTask": "args-generator-op"
+                      "producerTask": "args-generator-op-2"
                     }
                   },
+                  "pipelineparam--args-generator-op-Output": {
+                    "componentInputParameter": "pipelineparam--args-generator-op-Output"
+                  },
                   "pipelineparam--flip-coin-op-Output": {
                     "componentInputParameter": "pipelineparam--flip-coin-op-Output"
                   },
-                  "pipelineparam--text_parameter": {
-                    "componentInputParameter": "pipelineparam--text_parameter"
+                  "pipelineparam--loop_parameter": {
+                    "componentInputParameter": "pipelineparam--loop_parameter"
+                  },
+                  "pipelineparam--msg": {
+                    "componentInputParameter": "pipelineparam--msg"
                   }
                 }
               },
@@ -60,12 +76,97 @@
             }
           }
         },
+        "inputDefinitions": {
+          "parameters": {
+            "pipelineparam--args-generator-op-Output": {
+              "type": "STRING"
+            },
+            "pipelineparam--flip-coin-op-Output": {
+              "type": "STRING"
+            },
+            "pipelineparam--loop_parameter": {
+              "type": "STRING"
+            },
+            "pipelineparam--msg": {
+              "type": "STRING"
+            }
+          }
+        }
+      },
+      "comp-condition-13": {
+        "dag": {
+          "tasks": {
+            "print-op-9": {
+              "cachingOptions": {
+                "enableCache": true
+              },
+              "componentRef": {
+                "name": "comp-print-op-9"
+              },
+              "inputs": {
+                "parameters": {
+                  "msg": {
+                    "runtimeValue": {
+                      "constantValue": {
+                        "stringValue": "1"
+                      }
+                    }
+                  }
+                }
+              },
+              "taskInfo": {
+                "name": "print-op-9"
+              }
+            }
+          }
+        },
         "inputDefinitions": {
           "parameters": {
             "pipelineparam--flip-coin-op-Output": {
               "type": "STRING"
             },
-            "pipelineparam--text_parameter": {
+            "pipelineparam--loop-item-param-11": {
+              "type": "STRING"
+            }
+          }
+        }
+      },
+      "comp-condition-15": {
+        "dag": {
+          "tasks": {
+            "for-loop-16": {
+              "componentRef": {
+                "name": "comp-for-loop-16"
+              },
+              "inputs": {
+                "parameters": {
+                  "pipelineparam--loop_parameter-loop-item": {
+                    "componentInputParameter": "pipelineparam--loop_parameter-loop-item"
+                  },
+                  "pipelineparam--loop_parameter-loop-item-subvar-B_b": {
+                    "componentInputParameter": "pipelineparam--loop_parameter-loop-item",
+                    "parameterExpressionSelector": "parseJson(string_value)[\"B_b\"]"
+                  }
+                }
+              },
+              "parameterIterator": {
+                "itemInput": "pipelineparam--loop_parameter-loop-item-subvar-B_b-loop-item",
+                "items": {
+                  "inputParameter": "pipelineparam--loop_parameter-loop-item-subvar-B_b"
+                }
+              },
+              "taskInfo": {
+                "name": "for-loop-16"
+              }
+            }
+          }
+        },
+        "inputDefinitions": {
+          "parameters": {
+            "pipelineparam--loop_parameter-loop-item": {
+              "type": "STRING"
+            },
+            "pipelineparam--loop_parameter-loop-item-subvar-A_a": {
               "type": "STRING"
             }
           }
@@ -84,7 +185,8 @@
               "inputs": {
                 "parameters": {
                   "msg": {
-                    "componentInputParameter": "pipelineparam--args-generator-op-Output-loop-item-subvar-A_a"
+                    "componentInputParameter": "pipelineparam--args-generator-op-Output-loop-item",
+                    "parameterExpressionSelector": "parseJson(string_value)[\"B_b\"]"
                   }
                 }
               },
@@ -96,6 +198,9 @@
         },
         "inputDefinitions": {
           "parameters": {
+            "pipelineparam--args-generator-op-Output-loop-item": {
+              "type": "STRING"
+            },
             "pipelineparam--args-generator-op-Output-loop-item-subvar-A_a": {
               "type": "STRING"
             },
@@ -118,7 +223,8 @@
               "inputs": {
                 "parameters": {
                   "msg": {
-                    "componentInputParameter": "pipelineparam--args-generator-op-Output-loop-item-subvar-B_b"
+                    "componentInputParameter": "pipelineparam--args-generator-op-Output-loop-item",
+                    "parameterExpressionSelector": "parseJson(string_value)[\"B_b\"]"
                   }
                 }
               },
@@ -130,7 +236,7 @@
         },
         "inputDefinitions": {
           "parameters": {
-            "pipelineparam--args-generator-op-Output-loop-item-subvar-B_b": {
+            "pipelineparam--args-generator-op-Output-loop-item": {
               "type": "STRING"
             },
             "pipelineparam--flip-coin-op-Output": {
@@ -148,6 +254,9 @@
               },
               "inputs": {
                 "parameters": {
+                  "pipelineparam--args-generator-op-Output-loop-item": {
+                    "componentInputParameter": "pipelineparam--args-generator-op-Output-loop-item"
+                  },
                   "pipelineparam--flip-coin-op-Output": {
                     "componentInputParameter": "pipelineparam--flip-coin-op-Output"
                   }
@@ -167,6 +276,9 @@
         },
         "inputDefinitions": {
           "parameters": {
+            "pipelineparam--args-generator-op-Output-loop-item": {
+              "type": "STRING"
+            },
             "pipelineparam--args-generator-op-Output-loop-item-subvar-A_a": {
               "type": "STRING"
             },
@@ -176,63 +288,185 @@
           }
         }
       },
-      "comp-condition-9": {
+      "comp-flip-coin-op": {
+        "executorLabel": "exec-flip-coin-op",
+        "outputDefinitions": {
+          "parameters": {
+            "Output": {
+              "type": "STRING"
+            }
+          }
+        }
+      },
+      "comp-for-loop-10": {
         "dag": {
           "tasks": {
-            "print-op-5": {
+            "print-op-7": {
               "cachingOptions": {
                 "enableCache": true
               },
               "componentRef": {
-                "name": "comp-print-op-5"
+                "name": "comp-print-op-7"
               },
               "inputs": {
                 "parameters": {
                   "msg": {
-                    "componentInputParameter": "pipelineparam--text_parameter-loop-item-subvar-p_a"
+                    "componentInputParameter": "pipelineparam--loop_parameter-loop-item"
+                  },
+                  "msg2": {
+                    "componentInputParameter": "pipelineparam--args-generator-op-2-Output-loop-item",
+                    "parameterExpressionSelector": "parseJson(string_value)[\"A_a\"]"
                   }
                 }
               },
               "taskInfo": {
-                "name": "print-op-5"
+                "name": "print-op-7"
               }
+            }
+          }
+        },
+        "inputDefinitions": {
+          "parameters": {
+            "pipelineparam--args-generator-op-2-Output": {
+              "type": "STRING"
             },
-            "print-op-6": {
+            "pipelineparam--args-generator-op-2-Output-loop-item": {
+              "type": "STRING"
+            },
+            "pipelineparam--flip-coin-op-Output": {
+              "type": "STRING"
+            },
+            "pipelineparam--loop_parameter-loop-item": {
+              "type": "STRING"
+            }
+          }
+        }
+      },
+      "comp-for-loop-12": {
+        "dag": {
+          "tasks": {
+            "condition-13": {
+              "componentRef": {
+                "name": "comp-condition-13"
+              },
+              "inputs": {
+                "parameters": {
+                  "pipelineparam--flip-coin-op-Output": {
+                    "componentInputParameter": "pipelineparam--flip-coin-op-Output"
+                  },
+                  "pipelineparam--loop-item-param-11": {
+                    "componentInputParameter": "pipelineparam--loop-item-param-11"
+                  }
+                }
+              },
+              "taskInfo": {
+                "name": "condition-13"
+              },
+              "triggerPolicy": {
+                "condition": "inputs.parameters['pipelineparam--loop-item-param-11'].string_value == '1'"
+              }
+            },
+            "print-op-8": {
               "cachingOptions": {
                 "enableCache": true
               },
               "componentRef": {
-                "name": "comp-print-op-6"
+                "name": "comp-print-op-8"
               },
               "inputs": {
                 "parameters": {
                   "msg": {
-                    "componentInputParameter": "pipelineparam--text_parameter-loop-item-subvar-p_b"
+                    "componentInputParameter": "pipelineparam--loop-item-param-11"
                   }
                 }
               },
               "taskInfo": {
-                "name": "print-op-6"
+                "name": "print-op-8"
               }
             }
           }
         },
         "inputDefinitions": {
           "parameters": {
-            "pipelineparam--text_parameter-loop-item-subvar-p_a": {
+            "pipelineparam--flip-coin-op-Output": {
               "type": "STRING"
             },
-            "pipelineparam--text_parameter-loop-item-subvar-p_b": {
+            "pipelineparam--loop-item-param-11": {
               "type": "STRING"
             }
           }
         }
       },
-      "comp-flip-coin-op": {
-        "executorLabel": "exec-flip-coin-op",
-        "outputDefinitions": {
+      "comp-for-loop-14": {
+        "dag": {
+          "tasks": {
+            "condition-15": {
+              "componentRef": {
+                "name": "comp-condition-15"
+              },
+              "inputs": {
+                "parameters": {
+                  "pipelineparam--loop_parameter-loop-item": {
+                    "componentInputParameter": "pipelineparam--loop_parameter-loop-item"
+                  },
+                  "pipelineparam--loop_parameter-loop-item-subvar-A_a": {
+                    "componentInputParameter": "pipelineparam--loop_parameter-loop-item",
+                    "parameterExpressionSelector": "parseJson(string_value)[\"A_a\"]"
+                  }
+                }
+              },
+              "taskInfo": {
+                "name": "condition-15"
+              },
+              "triggerPolicy": {
+                "condition": "inputs.parameters['pipelineparam--loop_parameter-loop-item-subvar-A_a'].string_value == 'heads'"
+              }
+            }
+          }
+        },
+        "inputDefinitions": {
           "parameters": {
-            "Output": {
+            "pipelineparam--loop_parameter": {
+              "type": "STRING"
+            },
+            "pipelineparam--loop_parameter-loop-item": {
+              "type": "STRING"
+            }
+          }
+        }
+      },
+      "comp-for-loop-16": {
+        "dag": {
+          "tasks": {
+            "print-op-10": {
+              "cachingOptions": {
+                "enableCache": true
+              },
+              "componentRef": {
+                "name": "comp-print-op-10"
+              },
+              "inputs": {
+                "parameters": {
+                  "msg": {
+                    "componentInputParameter": "pipelineparam--loop_parameter-loop-item-subvar-B_b-loop-item"
+                  }
+                }
+              },
+              "taskInfo": {
+                "name": "print-op-10"
+              }
+            }
+          }
+        },
+        "inputDefinitions": {
+          "parameters": {
+            "pipelineparam--loop_parameter-loop-item": {
+              "type": "STRING"
+            },
+            "pipelineparam--loop_parameter-loop-item-subvar-B_b": {
+              "type": "STRING"
+            },
+            "pipelineparam--loop_parameter-loop-item-subvar-B_b-loop-item": {
               "type": "STRING"
             }
           }
@@ -247,6 +481,9 @@
               },
               "inputs": {
                 "parameters": {
+                  "pipelineparam--args-generator-op-Output-loop-item": {
+                    "componentInputParameter": "pipelineparam--args-generator-op-Output-loop-item"
+                  },
                   "pipelineparam--args-generator-op-Output-loop-item-subvar-A_a": {
                     "componentInputParameter": "pipelineparam--args-generator-op-Output-loop-item",
                     "parameterExpressionSelector": "parseJson(string_value)[\"A_a\"]"
@@ -260,7 +497,7 @@
                 "name": "condition-3"
               },
               "triggerPolicy": {
-                "condition": "inputs.parameters['pipelineparam--flip-coin-op-Output'].string_value == 'heads'"
+                "condition": "inputs.parameters['pipelineparam--args-generator-op-Output-loop-item-subvar-A_a'].string_value == 'heads'"
               }
             },
             "condition-4": {
@@ -269,9 +506,8 @@
               },
               "inputs": {
                 "parameters": {
-                  "pipelineparam--args-generator-op-Output-loop-item-subvar-B_b": {
-                    "componentInputParameter": "pipelineparam--args-generator-op-Output-loop-item",
-                    "parameterExpressionSelector": "parseJson(string_value)[\"B_b\"]"
+                  "pipelineparam--args-generator-op-Output-loop-item": {
+                    "componentInputParameter": "pipelineparam--args-generator-op-Output-loop-item"
                   },
                   "pipelineparam--flip-coin-op-Output": {
                     "componentInputParameter": "pipelineparam--flip-coin-op-Output"
@@ -282,7 +518,7 @@
                 "name": "condition-4"
               },
               "triggerPolicy": {
-                "condition": "inputs.parameters['pipelineparam--flip-coin-op-Output'].string_value == 'tails'"
+                "condition": "inputs.parameters['pipelineparam--flip-coin-op-Output'].string_value == 'heads'"
               }
             },
             "condition-5": {
@@ -291,6 +527,9 @@
               },
               "inputs": {
                 "parameters": {
+                  "pipelineparam--args-generator-op-Output-loop-item": {
+                    "componentInputParameter": "pipelineparam--args-generator-op-Output-loop-item"
+                  },
                   "pipelineparam--args-generator-op-Output-loop-item-subvar-A_a": {
                     "componentInputParameter": "pipelineparam--args-generator-op-Output-loop-item",
                     "parameterExpressionSelector": "parseJson(string_value)[\"A_a\"]"
@@ -304,7 +543,83 @@
                 "name": "condition-5"
               },
               "triggerPolicy": {
-                "condition": "inputs.parameters['pipelineparam--args-generator-op-Output-loop-item-subvar-A_a'].string_value == '1'"
+                "condition": "inputs.parameters['pipelineparam--args-generator-op-Output-loop-item-subvar-A_a'].string_value == 'tails'"
+              }
+            },
+            "for-loop-12": {
+              "componentRef": {
+                "name": "comp-for-loop-12"
+              },
+              "inputs": {
+                "parameters": {
+                  "pipelineparam--flip-coin-op-Output": {
+                    "componentInputParameter": "pipelineparam--flip-coin-op-Output"
+                  }
+                }
+              },
+              "parameterIterator": {
+                "itemInput": "pipelineparam--loop-item-param-11",
+                "items": {
+                  "raw": "[\"1\", \"2\"]"
+                }
+              },
+              "taskInfo": {
+                "name": "for-loop-12"
+              }
+            },
+            "for-loop-8": {
+              "componentRef": {
+                "name": "comp-for-loop-8"
+              },
+              "inputs": {
+                "parameters": {
+                  "pipelineparam--args-generator-op-Output-loop-item": {
+                    "componentInputParameter": "pipelineparam--args-generator-op-Output-loop-item"
+                  },
+                  "pipelineparam--args-generator-op-Output-loop-item-subvar-B_b": {
+                    "componentInputParameter": "pipelineparam--args-generator-op-Output-loop-item",
+                    "parameterExpressionSelector": "parseJson(string_value)[\"B_b\"]"
+                  },
+                  "pipelineparam--flip-coin-op-Output": {
+                    "componentInputParameter": "pipelineparam--flip-coin-op-Output"
+                  }
+                }
+              },
+              "parameterIterator": {
+                "itemInput": "pipelineparam--args-generator-op-Output-loop-item-subvar-B_b-loop-item",
+                "items": {
+                  "inputParameter": "pipelineparam--args-generator-op-Output-loop-item-subvar-B_b"
+                }
+              },
+              "taskInfo": {
+                "name": "for-loop-8"
+              }
+            },
+            "for-loop-9": {
+              "componentRef": {
+                "name": "comp-for-loop-9"
+              },
+              "inputs": {
+                "parameters": {
+                  "pipelineparam--args-generator-op-2-Output": {
+                    "componentInputParameter": "pipelineparam--args-generator-op-2-Output"
+                  },
+                  "pipelineparam--flip-coin-op-Output": {
+                    "componentInputParameter": "pipelineparam--flip-coin-op-Output"
+                  },
+                  "pipelineparam--loop_parameter": {
+                    "componentInputParameter": "pipelineparam--loop_parameter"
+                  }
+                }
+              },
+              "parameterIterator": {
+                "itemInput": "pipelineparam--loop_parameter-loop-item",
+                "items": {
+                  "inputParameter": "pipelineparam--loop_parameter"
+                }
+              },
+              "taskInfo": {
+                "name": "for-loop-9"
               }
             },
             "print-op": {
@@ -317,7 +632,7 @@
               "inputs": {
                 "parameters": {
                   "msg": {
-                    "componentInputParameter": "pipelineparam--text_parameter"
+                    "componentInputParameter": "pipelineparam--msg"
                   }
                 }
               },
@@ -329,6 +644,9 @@
         },
         "inputDefinitions": {
           "parameters": {
+            "pipelineparam--args-generator-op-2-Output": {
+              "type": "STRING"
+            },
             "pipelineparam--args-generator-op-Output": {
               "type": "STRING"
             },
@@ -338,7 +656,10 @@
             "pipelineparam--flip-coin-op-Output": {
               "type": "STRING"
             },
-            "pipelineparam--text_parameter": {
+            "pipelineparam--loop_parameter": {
+              "type": "STRING"
+            },
+            "pipelineparam--msg": {
               "type": "STRING"
             }
           }
@@ -369,6 +690,9 @@
         },
         "inputDefinitions": {
           "parameters": {
+            "pipelineparam--args-generator-op-Output-loop-item": {
+              "type": "STRING"
+            },
             "pipelineparam--flip-coin-op-Output": {
               "type": "STRING"
             },
@@ -381,37 +705,105 @@
       "comp-for-loop-8": {
         "dag": {
           "tasks": {
-            "condition-9": {
+            "print-op-5": {
+              "cachingOptions": {
+                "enableCache": true
+              },
               "componentRef": {
-                "name": "comp-condition-9"
+                "name": "comp-print-op-5"
               },
               "inputs": {
                 "parameters": {
-                  "pipelineparam--text_parameter-loop-item-subvar-p_a": {
-                    "componentInputParameter": "pipelineparam--text_parameter-loop-item",
-                    "parameterExpressionSelector": "parseJson(string_value)[\"p_a\"]"
+                  "msg": {
+                    "componentInputParameter": "pipelineparam--args-generator-op-Output-loop-item-subvar-B_b-loop-item"
+                  }
+                }
+              },
+              "taskInfo": {
+                "name": "print-op-5"
+              }
+            }
+          }
+        },
+        "inputDefinitions": {
+          "parameters": {
+            "pipelineparam--args-generator-op-Output-loop-item": {
+              "type": "STRING"
+            },
+            "pipelineparam--args-generator-op-Output-loop-item-subvar-B_b": {
+              "type": "STRING"
+            },
+            "pipelineparam--args-generator-op-Output-loop-item-subvar-B_b-loop-item": {
+              "type": "STRING"
+            },
+            "pipelineparam--flip-coin-op-Output": {
+              "type": "STRING"
+            }
+          }
+        }
+      },
+      "comp-for-loop-9": {
+        "dag": {
+          "tasks": {
+            "for-loop-10": {
+              "componentRef": {
+                "name": "comp-for-loop-10"
+              },
+              "inputs": {
+                "parameters": {
+                  "pipelineparam--args-generator-op-2-Output": {
+                    "componentInputParameter": "pipelineparam--args-generator-op-2-Output"
                   },
-                  "pipelineparam--text_parameter-loop-item-subvar-p_b": {
-                    "componentInputParameter": "pipelineparam--text_parameter-loop-item",
-                    "parameterExpressionSelector": "parseJson(string_value)[\"p_b\"]"
+                  "pipelineparam--flip-coin-op-Output": {
+                    "componentInputParameter": "pipelineparam--flip-coin-op-Output"
+                  },
+                  "pipelineparam--loop_parameter-loop-item": {
+                    "componentInputParameter": "pipelineparam--loop_parameter-loop-item"
                   }
                 }
               },
+              "parameterIterator": {
+                "itemInput": "pipelineparam--args-generator-op-2-Output-loop-item",
+                "items": {
+                  "inputParameter": "pipelineparam--args-generator-op-2-Output"
+                }
+              },
               "taskInfo": {
-                "name": "condition-9"
+                "name": "for-loop-10"
+              }
+            },
+            "print-op-6": {
+              "cachingOptions": {
+                "enableCache": true
               },
-              "triggerPolicy": {
-                "condition": "inputs.parameters['pipelineparam--text_parameter-loop-item-subvar-p_a'].int_value > 0"
+              "componentRef": {
+                "name": "comp-print-op-6"
+              },
+              "inputs": {
+                "parameters": {
+                  "msg": {
+                    "componentInputParameter": "pipelineparam--loop_parameter-loop-item"
+                  }
+                }
+              },
+              "taskInfo": {
+                "name": "print-op-6"
               }
             }
           }
         },
         "inputDefinitions": {
           "parameters": {
-            "pipelineparam--text_parameter": {
+            "pipelineparam--args-generator-op-2-Output": {
               "type": "STRING"
             },
-            "pipelineparam--text_parameter-loop-item": {
+            "pipelineparam--flip-coin-op-Output": {
+              "type": "STRING"
+            },
+            "pipelineparam--loop_parameter": {
+              "type": "STRING"
+            },
+            "pipelineparam--loop_parameter-loop-item": {
               "type": "STRING"
             }
           }
@@ -427,6 +819,16 @@
           }
         }
       },
+      "comp-print-op-10": {
+        "executorLabel": "exec-print-op-10",
+        "inputDefinitions": {
+          "parameters": {
+            "msg": {
+              "type": "STRING"
+            }
+          }
+        }
+      },
       "comp-print-op-2": {
         "executorLabel": "exec-print-op-2",
         "inputDefinitions": {
@@ -476,6 +878,39 @@
             }
           }
         }
+      },
+      "comp-print-op-7": {
+        "executorLabel": "exec-print-op-7",
+        "inputDefinitions": {
+          "parameters": {
+            "msg": {
+              "type": "STRING"
+            },
+            "msg2": {
+              "type": "STRING"
+            }
+          }
+        }
+      },
+      "comp-print-op-8": {
+        "executorLabel": "exec-print-op-8",
+        "inputDefinitions": {
+          "parameters": {
+            "msg": {
+              "type": "STRING"
+            }
+          }
+        }
+      },
+      "comp-print-op-9": {
+        "executorLabel": "exec-print-op-9",
+        "inputDefinitions": {
+          "parameters": {
+            "msg": {
+              "type": "STRING"
+            }
+          }
+        }
       }
     },
     "deploymentSpec": {
@@ -483,14 +918,39 @@
         "exec-args-generator-op": {
           "container": {
             "args": [
-              "----output-paths",
-              "{{$.outputs.parameters['Output'].output_file}}"
+              "--executor_input",
+              "{{$}}",
+              "--function_to_execute",
+              "args_generator_op"
             ],
             "command": [
+              "sh",
+              "-c",
+              "(python3 -m ensurepip || python3 -m ensurepip --user) && (PIP_DISABLE_PIP_VERSION_CHECK=1 python3 -m pip install --quiet                 --no-warn-script-location 'kfp==1.8.2' || PIP_DISABLE_PIP_VERSION_CHECK=1 python3 -m pip install --quiet                 --no-warn-script-location 'kfp==1.8.2' --user) && \"$0\" \"$@\"",
               "sh",
               "-ec",
-              "program_path=$(mktemp)\nprintf \"%s\" \"$0\" > \"$program_path\"\npython3 -u \"$program_path\" \"$@\"\n",
-              "def args_generator_op():\n    import json\n    return json.dumps([{\n        'A_a': '1',\n        'B_b': '2'\n    }, {\n        'A_a': '10',\n        'B_b': '20'\n    }],\n                      sort_keys=True)\n\ndef _serialize_str(str_value: str) -> str:\n    if not isinstance(str_value, str):\n        raise TypeError('Value \"{}\" has type \"{}\" instead of str.'.format(\n            str(str_value), str(type(str_value))))\n    return str_value\n\nimport argparse\n_parser = argparse.ArgumentParser(prog='Args generator op', description='')\n_parser.add_argument(\"----output-paths\", dest=\"_output_paths\", type=str, nargs=1)\n_parsed_args = vars(_parser.parse_args())\n_output_files = _parsed_args.pop(\"_output_paths\", [])\n\n_outputs = args_generator_op(**_parsed_args)\n\n_outputs = [_outputs]\n\n_output_serializers = [\n    _serialize_str,\n\n]\n\nimport os\nfor idx, output_file in enumerate(_output_files):\n    try:\n        os.makedirs(os.path.dirname(output_file))\n    except OSError:\n        pass\n    with open(output_file, 'w') as f:\n        f.write(_output_serializers[idx](_outputs[idx]))\n"
+              "program_path=$(mktemp -d)\nprintf \"%s\" \"$0\" > \"$program_path/ephemeral_component.py\"\npython3 -m kfp.v2.components.executor_main                         --component_module_path                         \"$program_path/ephemeral_component.py\"                         \"$@\"\n",
+              "\nfrom kfp.v2.dsl import *\nfrom typing import *\n\ndef args_generator_op() -> list:\n    return [\n        {\n            'A_a': '1',\n            'B_b': ['2', '20'],\n        },\n        {\n            'A_a': '10',\n            'B_b': ['22', '222'],\n        },\n    ]\n\n"
+            ],
+            "image": "python:3.7"
+          }
+        },
+        "exec-args-generator-op-2": {
+          "container": {
+            "args": [
+              "--executor_input",
+              "{{$}}",
+              "--function_to_execute",
+              "args_generator_op"
+            ],
+            "command": [
+              "sh",
+              "-c",
+              "(python3 -m ensurepip || python3 -m ensurepip --user) && (PIP_DISABLE_PIP_VERSION_CHECK=1 python3 -m pip install --quiet                 --no-warn-script-location 'kfp==1.8.2' || PIP_DISABLE_PIP_VERSION_CHECK=1 python3 -m pip install --quiet                 --no-warn-script-location 'kfp==1.8.2' --user) && \"$0\" \"$@\"",
+              "sh",
+              "-ec",
+              "program_path=$(mktemp -d)\nprintf \"%s\" \"$0\" > \"$program_path/ephemeral_component.py\"\npython3 -m kfp.v2.components.executor_main                         --component_module_path                         \"$program_path/ephemeral_component.py\"                         \"$@\"\n",
+              "\nfrom kfp.v2.dsl import *\nfrom typing import *\n\ndef args_generator_op() -> list:\n    return [\n        {\n            'A_a': '1',\n            'B_b': ['2', '20'],\n        },\n        {\n            'A_a': '10',\n            'B_b': ['22', '222'],\n        },\n    ]\n\n"
             ],
             "image": "python:3.7"
           }
@@ -498,14 +958,19 @@
         "exec-flip-coin-op": {
           "container": {
             "args": [
-              "----output-paths",
-              "{{$.outputs.parameters['Output'].output_file}}"
+              "--executor_input",
+              "{{$}}",
+              "--function_to_execute",
+              "flip_coin_op"
             ],
             "command": [
+              "sh",
+              "-c",
+              "(python3 -m ensurepip || python3 -m ensurepip --user) && (PIP_DISABLE_PIP_VERSION_CHECK=1 python3 -m pip install --quiet                 --no-warn-script-location 'kfp==1.8.2' || PIP_DISABLE_PIP_VERSION_CHECK=1 python3 -m pip install --quiet                 --no-warn-script-location 'kfp==1.8.2' --user) && \"$0\" \"$@\"",
               "sh",
               "-ec",
-              "program_path=$(mktemp)\nprintf \"%s\" \"$0\" > \"$program_path\"\npython3 -u \"$program_path\" \"$@\"\n",
-              "def flip_coin_op():\n    \"\"\"Flip a coin and output heads or tails randomly.\"\"\"\n    import random\n    result = 'heads' if random.randint(0, 1) == 0 else 'tails'\n    return result\n\ndef _serialize_str(str_value: str) -> str:\n    if not isinstance(str_value, str):\n        raise TypeError('Value \"{}\" has type \"{}\" instead of str.'.format(\n            str(str_value), str(type(str_value))))\n    return str_value\n\nimport argparse\n_parser = argparse.ArgumentParser(prog='Flip coin op', description='Flip a coin and output heads or tails randomly.')\n_parser.add_argument(\"----output-paths\", dest=\"_output_paths\", type=str, nargs=1)\n_parsed_args = vars(_parser.parse_args())\n_output_files = _parsed_args.pop(\"_output_paths\", [])\n\n_outputs = flip_coin_op(**_parsed_args)\n\n_outputs = [_outputs]\n\n_output_serializers = [\n    _serialize_str,\n\n]\n\nimport os\nfor idx, output_file in enumerate(_output_files):\n    try:\n        os.makedirs(os.path.dirname(output_file))\n    except OSError:\n        pass\n    with open(output_file, 'w') as f:\n        f.write(_output_serializers[idx](_outputs[idx]))\n"
+              "program_path=$(mktemp -d)\nprintf \"%s\" \"$0\" > \"$program_path/ephemeral_component.py\"\npython3 -m kfp.v2.components.executor_main                         --component_module_path                         \"$program_path/ephemeral_component.py\"                         \"$@\"\n",
+              "\nfrom kfp.v2.dsl import *\nfrom typing import *\n\ndef flip_coin_op() -> str:\n    \"\"\"Flip a coin and output heads or tails randomly.\"\"\"\n    import random\n    result = 'heads' if random.randint(0, 1) == 0 else 'tails'\n    return result\n\n"
             ],
             "image": "python:3.7"
           }
@@ -513,14 +978,39 @@
         "exec-print-op": {
           "container": {
             "args": [
-              "--msg",
-              "{{$.inputs.parameters['msg']}}"
+              "--executor_input",
+              "{{$}}",
+              "--function_to_execute",
+              "print_op"
+            ],
+            "command": [
+              "sh",
+              "-c",
+              "(python3 -m ensurepip || python3 -m ensurepip --user) && (PIP_DISABLE_PIP_VERSION_CHECK=1 python3 -m pip install --quiet                 --no-warn-script-location 'kfp==1.8.2' || PIP_DISABLE_PIP_VERSION_CHECK=1 python3 -m pip install --quiet                 --no-warn-script-location 'kfp==1.8.2' --user) && \"$0\" \"$@\"",
+              "sh",
+              "-ec",
+              "program_path=$(mktemp -d)\nprintf \"%s\" \"$0\" > \"$program_path/ephemeral_component.py\"\npython3 -m kfp.v2.components.executor_main                         --component_module_path                         \"$program_path/ephemeral_component.py\"                         \"$@\"\n",
+              "\nfrom kfp.v2.dsl import *\nfrom typing import *\n\ndef print_op(msg: str, msg2: Optional[str] = None):\n    print(f'msg: {msg}, msg2: {msg2}')\n\n"
+            ],
+            "image": "python:3.7"
+          }
+        },
+        "exec-print-op-10": {
+          "container": {
+            "args": [
+              "--executor_input",
+              "{{$}}",
+              "--function_to_execute",
+              "print_op"
             ],
             "command": [
+              "sh",
+              "-c",
+              "(python3 -m ensurepip || python3 -m ensurepip --user) && (PIP_DISABLE_PIP_VERSION_CHECK=1 python3 -m pip install --quiet                 --no-warn-script-location 'kfp==1.8.2' || PIP_DISABLE_PIP_VERSION_CHECK=1 python3 -m pip install --quiet                 --no-warn-script-location 'kfp==1.8.2' --user) && \"$0\" \"$@\"",
               "sh",
               "-ec",
-              "program_path=$(mktemp)\nprintf \"%s\" \"$0\" > \"$program_path\"\npython3 -u \"$program_path\" \"$@\"\n",
-              "def print_op(msg):\n    print(msg)\n\nimport argparse\n_parser = argparse.ArgumentParser(prog='Print op', description='')\n_parser.add_argument(\"--msg\", dest=\"msg\", type=str, required=True, default=argparse.SUPPRESS)\n_parsed_args = vars(_parser.parse_args())\n\n_outputs = print_op(**_parsed_args)\n"
+              "program_path=$(mktemp -d)\nprintf \"%s\" \"$0\" > \"$program_path/ephemeral_component.py\"\npython3 -m kfp.v2.components.executor_main                         --component_module_path                         \"$program_path/ephemeral_component.py\"                         \"$@\"\n",
+              "\nfrom kfp.v2.dsl import *\nfrom typing import *\n\ndef print_op(msg: str, msg2: Optional[str] = None):\n    print(f'msg: {msg}, msg2: {msg2}')\n\n"
             ],
             "image": "python:3.7"
           }
@@ -528,14 +1018,19 @@
         "exec-print-op-2": {
           "container": {
             "args": [
-              "--msg",
-              "{{$.inputs.parameters['msg']}}"
+              "--executor_input",
+              "{{$}}",
+              "--function_to_execute",
+              "print_op"
             ],
             "command": [
+              "sh",
+              "-c",
+              "(python3 -m ensurepip || python3 -m ensurepip --user) && (PIP_DISABLE_PIP_VERSION_CHECK=1 python3 -m pip install --quiet                 --no-warn-script-location 'kfp==1.8.2' || PIP_DISABLE_PIP_VERSION_CHECK=1 python3 -m pip install --quiet                 --no-warn-script-location 'kfp==1.8.2' --user) && \"$0\" \"$@\"",
               "sh",
               "-ec",
-              "program_path=$(mktemp)\nprintf \"%s\" \"$0\" > \"$program_path\"\npython3 -u \"$program_path\" \"$@\"\n",
-              "def print_op(msg):\n    print(msg)\n\nimport argparse\n_parser = argparse.ArgumentParser(prog='Print op', description='')\n_parser.add_argument(\"--msg\", dest=\"msg\", type=str, required=True, default=argparse.SUPPRESS)\n_parsed_args = vars(_parser.parse_args())\n\n_outputs = print_op(**_parsed_args)\n"
+              "program_path=$(mktemp -d)\nprintf \"%s\" \"$0\" > \"$program_path/ephemeral_component.py\"\npython3 -m kfp.v2.components.executor_main                         --component_module_path                         \"$program_path/ephemeral_component.py\"                         \"$@\"\n",
+              "\nfrom kfp.v2.dsl import *\nfrom typing import *\n\ndef print_op(msg: str, msg2: Optional[str] = None):\n    print(f'msg: {msg}, msg2: {msg2}')\n\n"
             ],
             "image": "python:3.7"
           }
@@ -543,14 +1038,19 @@
         "exec-print-op-3": {
           "container": {
             "args": [
-              "--msg",
-              "{{$.inputs.parameters['msg']}}"
+              "--executor_input",
+              "{{$}}",
+              "--function_to_execute",
+              "print_op"
             ],
             "command": [
+              "sh",
+              "-c",
+              "(python3 -m ensurepip || python3 -m ensurepip --user) && (PIP_DISABLE_PIP_VERSION_CHECK=1 python3 -m pip install --quiet                 --no-warn-script-location 'kfp==1.8.2' || PIP_DISABLE_PIP_VERSION_CHECK=1 python3 -m pip install --quiet                 --no-warn-script-location 'kfp==1.8.2' --user) && \"$0\" \"$@\"",
               "sh",
               "-ec",
-              "program_path=$(mktemp)\nprintf \"%s\" \"$0\" > \"$program_path\"\npython3 -u \"$program_path\" \"$@\"\n",
-              "def print_op(msg):\n    print(msg)\n\nimport argparse\n_parser = argparse.ArgumentParser(prog='Print op', description='')\n_parser.add_argument(\"--msg\", dest=\"msg\", type=str, required=True, default=argparse.SUPPRESS)\n_parsed_args = vars(_parser.parse_args())\n\n_outputs = print_op(**_parsed_args)\n"
+              "program_path=$(mktemp -d)\nprintf \"%s\" \"$0\" > \"$program_path/ephemeral_component.py\"\npython3 -m kfp.v2.components.executor_main                         --component_module_path                         \"$program_path/ephemeral_component.py\"                         \"$@\"\n",
+              "\nfrom kfp.v2.dsl import *\nfrom typing import *\n\ndef print_op(msg: str, msg2: Optional[str] = None):\n    print(f'msg: {msg}, msg2: {msg2}')\n\n"
             ],
             "image": "python:3.7"
           }
@@ -558,14 +1058,19 @@
         "exec-print-op-4": {
           "container": {
             "args": [
-              "--msg",
-              "{{$.inputs.parameters['msg']}}"
+              "--executor_input",
+              "{{$}}",
+              "--function_to_execute",
+              "print_op"
             ],
             "command": [
+              "sh",
+              "-c",
+              "(python3 -m ensurepip || python3 -m ensurepip --user) && (PIP_DISABLE_PIP_VERSION_CHECK=1 python3 -m pip install --quiet                 --no-warn-script-location 'kfp==1.8.2' || PIP_DISABLE_PIP_VERSION_CHECK=1 python3 -m pip install --quiet                 --no-warn-script-location 'kfp==1.8.2' --user) && \"$0\" \"$@\"",
               "sh",
               "-ec",
-              "program_path=$(mktemp)\nprintf \"%s\" \"$0\" > \"$program_path\"\npython3 -u \"$program_path\" \"$@\"\n",
-              "def print_op(msg):\n    print(msg)\n\nimport argparse\n_parser = argparse.ArgumentParser(prog='Print op', description='')\n_parser.add_argument(\"--msg\", dest=\"msg\", type=str, required=True, default=argparse.SUPPRESS)\n_parsed_args = vars(_parser.parse_args())\n\n_outputs = print_op(**_parsed_args)\n"
+              "program_path=$(mktemp -d)\nprintf \"%s\" \"$0\" > \"$program_path/ephemeral_component.py\"\npython3 -m kfp.v2.components.executor_main                         --component_module_path                         \"$program_path/ephemeral_component.py\"                         \"$@\"\n",
+              "\nfrom kfp.v2.dsl import *\nfrom typing import *\n\ndef print_op(msg: str, msg2: Optional[str] = None):\n    print(f'msg: {msg}, msg2: {msg2}')\n\n"
             ],
             "image": "python:3.7"
           }
@@ -573,14 +1078,19 @@
         "exec-print-op-5": {
           "container": {
             "args": [
-              "--msg",
-              "{{$.inputs.parameters['msg']}}"
+              "--executor_input",
+              "{{$}}",
+              "--function_to_execute",
+              "print_op"
             ],
             "command": [
+              "sh",
+              "-c",
+              "(python3 -m ensurepip || python3 -m ensurepip --user) && (PIP_DISABLE_PIP_VERSION_CHECK=1 python3 -m pip install --quiet                 --no-warn-script-location 'kfp==1.8.2' || PIP_DISABLE_PIP_VERSION_CHECK=1 python3 -m pip install --quiet                 --no-warn-script-location 'kfp==1.8.2' --user) && \"$0\" \"$@\"",
               "sh",
               "-ec",
-              "program_path=$(mktemp)\nprintf \"%s\" \"$0\" > \"$program_path\"\npython3 -u \"$program_path\" \"$@\"\n",
-              "def print_op(msg):\n    print(msg)\n\nimport argparse\n_parser = argparse.ArgumentParser(prog='Print op', description='')\n_parser.add_argument(\"--msg\", dest=\"msg\", type=str, required=True, default=argparse.SUPPRESS)\n_parsed_args = vars(_parser.parse_args())\n\n_outputs = print_op(**_parsed_args)\n"
+              "program_path=$(mktemp -d)\nprintf \"%s\" \"$0\" > \"$program_path/ephemeral_component.py\"\npython3 -m kfp.v2.components.executor_main                         --component_module_path                         \"$program_path/ephemeral_component.py\"                         \"$@\"\n",
+              "\nfrom kfp.v2.dsl import *\nfrom typing import *\n\ndef print_op(msg: str, msg2: Optional[str] = None):\n    print(f'msg: {msg}, msg2: {msg2}')\n\n"
             ],
             "image": "python:3.7"
           }
@@ -588,14 +1098,79 @@
         "exec-print-op-6": {
           "container": {
             "args": [
-              "--msg",
-              "{{$.inputs.parameters['msg']}}"
+              "--executor_input",
+              "{{$}}",
+              "--function_to_execute",
+              "print_op"
+            ],
+            "command": [
+              "sh",
+              "-c",
+              "(python3 -m ensurepip || python3 -m ensurepip --user) && (PIP_DISABLE_PIP_VERSION_CHECK=1 python3 -m pip install --quiet                 --no-warn-script-location 'kfp==1.8.2' || PIP_DISABLE_PIP_VERSION_CHECK=1 python3 -m pip install --quiet                 --no-warn-script-location 'kfp==1.8.2' --user) && \"$0\" \"$@\"",
+              "sh",
+              "-ec",
+              "program_path=$(mktemp -d)\nprintf \"%s\" \"$0\" > \"$program_path/ephemeral_component.py\"\npython3 -m kfp.v2.components.executor_main                         --component_module_path                         \"$program_path/ephemeral_component.py\"                         \"$@\"\n",
+              "\nfrom kfp.v2.dsl import *\nfrom typing import *\n\ndef print_op(msg: str, msg2: Optional[str] = None):\n    print(f'msg: {msg}, msg2: {msg2}')\n\n"
+            ],
+            "image": "python:3.7"
+          }
+        },
+        "exec-print-op-7": {
+          "container": {
+            "args": [
+              "--executor_input",
+              "{{$}}",
+              "--function_to_execute",
+              "print_op"
+            ],
+            "command": [
+              "sh",
+              "-c",
+              "(python3 -m ensurepip || python3 -m ensurepip --user) && (PIP_DISABLE_PIP_VERSION_CHECK=1 python3 -m pip install --quiet                 --no-warn-script-location 'kfp==1.8.2' || PIP_DISABLE_PIP_VERSION_CHECK=1 python3 -m pip install --quiet                 --no-warn-script-location 'kfp==1.8.2' --user) && \"$0\" \"$@\"",
+              "sh",
+              "-ec",
+              "program_path=$(mktemp -d)\nprintf \"%s\" \"$0\" > \"$program_path/ephemeral_component.py\"\npython3 -m kfp.v2.components.executor_main                         --component_module_path                         \"$program_path/ephemeral_component.py\"                         \"$@\"\n",
+              "\nfrom kfp.v2.dsl import *\nfrom typing import *\n\ndef print_op(msg: str, msg2: Optional[str] = None):\n    print(f'msg: {msg}, msg2: {msg2}')\n\n"
+            ],
+            "image": "python:3.7"
+          }
+        },
+        "exec-print-op-8": {
+          "container": {
+            "args": [
+              "--executor_input",
+              "{{$}}",
+              "--function_to_execute",
+              "print_op"
             ],
             "command": [
+              "sh",
+              "-c",
+              "(python3 -m ensurepip || python3 -m ensurepip --user) && (PIP_DISABLE_PIP_VERSION_CHECK=1 python3 -m pip install --quiet                 --no-warn-script-location 'kfp==1.8.2' || PIP_DISABLE_PIP_VERSION_CHECK=1 python3 -m pip install --quiet                 --no-warn-script-location 'kfp==1.8.2' --user) && \"$0\" \"$@\"",
               "sh",
               "-ec",
-              "program_path=$(mktemp)\nprintf \"%s\" \"$0\" > \"$program_path\"\npython3 -u \"$program_path\" \"$@\"\n",
-              "def print_op(msg):\n    print(msg)\n\nimport argparse\n_parser = argparse.ArgumentParser(prog='Print op', description='')\n_parser.add_argument(\"--msg\", dest=\"msg\", type=str, required=True, default=argparse.SUPPRESS)\n_parsed_args = vars(_parser.parse_args())\n\n_outputs = print_op(**_parsed_args)\n"
+              "program_path=$(mktemp -d)\nprintf \"%s\" \"$0\" > \"$program_path/ephemeral_component.py\"\npython3 -m kfp.v2.components.executor_main                         --component_module_path                         \"$program_path/ephemeral_component.py\"                         \"$@\"\n",
+              "\nfrom kfp.v2.dsl import *\nfrom typing import *\n\ndef print_op(msg: str, msg2: Optional[str] = None):\n    print(f'msg: {msg}, msg2: {msg2}')\n\n"
+            ],
+            "image": "python:3.7"
+          }
+        },
+        "exec-print-op-9": {
+          "container": {
+            "args": [
+              "--executor_input",
+              "{{$}}",
+              "--function_to_execute",
+              "print_op"
+            ],
+            "command": [
+              "sh",
+              "-c",
+              "(python3 -m ensurepip || python3 -m ensurepip --user) && (PIP_DISABLE_PIP_VERSION_CHECK=1 python3 -m pip install --quiet                 --no-warn-script-location 'kfp==1.8.2' || PIP_DISABLE_PIP_VERSION_CHECK=1 python3 -m pip install --quiet                 --no-warn-script-location 'kfp==1.8.2' --user) && \"$0\" \"$@\"",
+              "sh",
+              "-ec",
+              "program_path=$(mktemp -d)\nprintf \"%s\" \"$0\" > \"$program_path/ephemeral_component.py\"\npython3 -m kfp.v2.components.executor_main                         --component_module_path                         \"$program_path/ephemeral_component.py\"                         \"$@\"\n",
+              "\nfrom kfp.v2.dsl import *\nfrom typing import *\n\ndef print_op(msg: str, msg2: Optional[str] = None):\n    print(f'msg: {msg}, msg2: {msg2}')\n\n"
             ],
             "image": "python:3.7"
           }
@@ -603,28 +1178,49 @@
       }
     },
     "pipelineInfo": {
-      "name": "pipeline-with-loops-and-conditions"
+      "name": "pipeline-with-loops-and-conditions-multi-layers"
     },
     "root": {
       "dag": {
         "tasks": {
+          "args-generator-op": {
+            "cachingOptions": {
+              "enableCache": true
+            },
+            "componentRef": {
+              "name": "comp-args-generator-op"
+            },
+            "taskInfo": {
+              "name": "args-generator-op"
+            }
+          },
           "condition-1": {
             "componentRef": {
               "name": "comp-condition-1"
             },
             "dependentTasks": [
+              "args-generator-op",
               "flip-coin-op"
             ],
             "inputs": {
               "parameters": {
+                "pipelineparam--args-generator-op-Output": {
+                  "taskOutputParameter": {
+                    "outputParameterKey": "Output",
+                    "producerTask": "args-generator-op"
+                  }
+                },
                 "pipelineparam--flip-coin-op-Output": {
                   "taskOutputParameter": {
                     "outputParameterKey": "Output",
                     "producerTask": "flip-coin-op"
                   }
                 },
-                "pipelineparam--text_parameter": {
-                  "componentInputParameter": "text_parameter"
+                "pipelineparam--loop_parameter": {
+                  "componentInputParameter": "loop_parameter"
+                },
+                "pipelineparam--msg": {
+                  "componentInputParameter": "msg"
                 }
               }
             },
@@ -646,45 +1242,50 @@
               "name": "flip-coin-op"
             }
           },
-          "for-loop-8": {
+          "for-loop-14": {
             "componentRef": {
-              "name": "comp-for-loop-8"
+              "name": "comp-for-loop-14"
             },
             "inputs": {
               "parameters": {
-                "pipelineparam--text_parameter": {
-                  "componentInputParameter": "text_parameter"
+                "pipelineparam--loop_parameter": {
+                  "componentInputParameter": "loop_parameter"
                 }
               }
             },
             "parameterIterator": {
-              "itemInput": "pipelineparam--text_parameter-loop-item",
+              "itemInput": "pipelineparam--loop_parameter-loop-item",
               "items": {
-                "inputParameter": "pipelineparam--text_parameter"
+                "inputParameter": "pipelineparam--loop_parameter"
               }
             },
             "taskInfo": {
-              "name": "for-loop-8"
+              "name": "for-loop-14"
             }
           }
         }
       },
       "inputDefinitions": {
         "parameters": {
-          "text_parameter": {
+          "loop_parameter": {
+            "type": "STRING"
+          },
+          "msg": {
             "type": "STRING"
           }
         }
       }
     },
     "schemaVersion": "2.0.0",
-    "sdkVersion": "kfp-1.7.2"
+    "sdkVersion": "kfp-1.8.2"
   },
   "runtimeConfig": {
-    "gcsOutputDirectory": "dummy_root",
     "parameters": {
-      "text_parameter": {
-        "stringValue": "[{\"p_a\": -1, \"p_b\": \"hello\"}, {\"p_a\": 2, \"p_b\": \"halo\"}, {\"p_a\": 3, \"p_b\": \"ni hao\"}]"
+      "loop_parameter": {
+        "stringValue": "[{\"A_a\": \"heads\", \"B_b\": [\"A\", \"B\"]}, {\"A_a\": \"tails\", \"B_b\": [\"X\", \"Y\", \"Z\"]}]"
+      },
+      "msg": {
+        "stringValue": "hello"
       }
     }
   }
diff --git a/sdk/python/kfp/v2/compiler_cli_tests/test_data/pipeline_with_loops_and_conditions.py b/sdk/python/kfp/v2/compiler_cli_tests/test_data/pipeline_with_loops_and_conditions.py
--- a/sdk/python/kfp/v2/compiler_cli_tests/test_data/pipeline_with_loops_and_conditions.py
+++ b/sdk/python/kfp/v2/compiler_cli_tests/test_data/pipeline_with_loops_and_conditions.py
@@ -11,33 +11,33 @@
 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 # See the License for the specific language governing permissions and
 # limitations under the License.
-import json
-from typing import List
 
-from kfp import components
-from kfp import dsl
-from kfp.v2 import compiler
+from typing import Optional
 
+from kfp.v2 import compiler, dsl
+from kfp.v2.dsl import component
 
-@components.create_component_from_func
-def args_generator_op() -> str:
-    import json
-    return json.dumps([{
-        'A_a': '1',
-        'B_b': '2'
-    }, {
-        'A_a': '10',
-        'B_b': '20'
-    }],
-                      sort_keys=True)
 
+@component
+def args_generator_op() -> list:
+    return [
+        {
+            'A_a': '1',
+            'B_b': ['2', '20'],
+        },
+        {
+            'A_a': '10',
+            'B_b': ['22', '222'],
+        },
+    ]
 
-@components.create_component_from_func
-def print_op(msg: str):
-    print(msg)
 
+@component
+def print_op(msg: str, msg2: Optional[str] = None):
+    print(f'msg: {msg}, msg2: {msg2}')
 
-@components.create_component_from_func
+
+@component
 def flip_coin_op() -> str:
     """Flip a coin and output heads or tails randomly."""
     import random
@@ -45,48 +45,62 @@ def flip_coin_op() -> str:
     return result
 
 
-@dsl.pipeline(
-    name='pipeline-with-loops-and-conditions',
-    pipeline_root='dummy_root',
-)
-def my_pipeline(text_parameter: str = json.dumps([
-    {
-        'p_a': -1,
-        'p_b': 'hello'
-    },
-    {
-        'p_a': 2,
-        'p_b': 'halo'
-    },
-    {
-        'p_a': 3,
-        'p_b': 'ni hao'
-    },
-],
-                                                 sort_keys=True)):
-
-    flip1 = flip_coin_op()
-
-    with dsl.Condition(flip1.output != 'no-such-result'):  # always true
-
-        args_generator = args_generator_op()
-        with dsl.ParallelFor(args_generator.output) as item:
-            print_op(text_parameter)
-
-            with dsl.Condition(flip1.output == 'heads'):
-                print_op(item.A_a)
-
-            with dsl.Condition(flip1.output == 'tails'):
+@dsl.pipeline(name='pipeline-with-loops-and-conditions-multi-layers')
+def my_pipeline(
+    msg: str = 'hello',
+    loop_parameter: list = [
+        {
+            'A_a': 'heads',
+            'B_b': ['A', 'B'],
+        },
+        {
+            'A_a': 'tails',
+            'B_b': ['X', 'Y', 'Z'],
+        },
+    ],
+):
+
+    flip = flip_coin_op()
+    outter_args_generator = args_generator_op()
+
+    with dsl.Condition(flip.output != 'no-such-result'):  # always true
+
+        inner_arg_generator = args_generator_op()
+
+        with dsl.ParallelFor(outter_args_generator.output) as item:
+
+            print_op(msg)
+
+            with dsl.Condition(item.A_a == 'heads'):
                 print_op(item.B_b)
 
-            with dsl.Condition(item.A_a == '1'):
-                with dsl.ParallelFor([{'a': '-1'}, {'a': '-2'}]) as item:
-                    print_op(item)
+            with dsl.Condition(flip.output == 'heads'):
+                print_op(item.B_b)
+
+            with dsl.Condition(item.A_a == 'tails'):
+                with dsl.ParallelFor([{'a': '-1'}, {'a': '-2'}]) as inner_item:
+                    print_op(inner_item)
+
+            with dsl.ParallelFor(item.B_b) as item_b:
+                print_op(item_b)
+
+            with dsl.ParallelFor(loop_parameter) as pipeline_item:
+                print_op(pipeline_item)
+
+                with dsl.ParallelFor(inner_arg_generator.output) as inner_item:
+                    print_op(pipeline_item, inner_item.A_a)
+
+            with dsl.ParallelFor(['1', '2']) as static_item:
+                print_op(static_item)
+
+                with dsl.Condition(static_item == '1'):
+                    print_op('1')
 
-    with dsl.ParallelFor(text_parameter) as item:
-        with dsl.Condition(item.p_a > 0):
-            print_op(item.p_a)
-            print_op(item.p_b)
+    # Reference loop item from grand child
+    with dsl.ParallelFor(loop_parameter) as item:
+        with dsl.Condition(item.A_a == 'heads'):
+            with dsl.ParallelFor(item.B_b) as item_b:
+                print_op(item_b)
 
 
 if __name__ == '__main__':
diff --git a/sdk/python/kfp/v2/compiler_cli_tests/test_data/pipeline_with_nested_loops.json b/sdk/python/kfp/v2/compiler_cli_tests/test_data/pipeline_with_nested_loops.json
--- a/sdk/python/kfp/v2/compiler_cli_tests/test_data/pipeline_with_nested_loops.json
+++ b/sdk/python/kfp/v2/compiler_cli_tests/test_data/pipeline_with_nested_loops.json
@@ -10,16 +10,19 @@
               },
               "inputs": {
                 "parameters": {
-                  "pipelineparam--text_parameter-loop-item-subvar-p_a": {
-                    "componentInputParameter": "pipelineparam--text_parameter-loop-item",
+                  "pipelineparam--loop_parameter-loop-item": {
+                    "componentInputParameter": "pipelineparam--loop_parameter-loop-item"
+                  },
+                  "pipelineparam--loop_parameter-loop-item-subvar-p_a": {
+                    "componentInputParameter": "pipelineparam--loop_parameter-loop-item",
                     "parameterExpressionSelector": "parseJson(string_value)[\"p_a\"]"
                   }
                 }
               },
               "parameterIterator": {
-                "itemInput": "pipelineparam--text_parameter-loop-item-subvar-p_a-loop-item",
+                "itemInput": "pipelineparam--loop_parameter-loop-item-subvar-p_a-loop-item",
                 "items": {
-                  "inputParameter": "pipelineparam--text_parameter-loop-item-subvar-p_a"
+                  "inputParameter": "pipelineparam--loop_parameter-loop-item-subvar-p_a"
                 }
               },
               "taskInfo": {
@@ -30,10 +33,10 @@
         },
         "inputDefinitions": {
           "parameters": {
-            "pipelineparam--text_parameter": {
+            "pipelineparam--loop_parameter": {
               "type": "STRING"
             },
-            "pipelineparam--text_parameter-loop-item": {
+            "pipelineparam--loop_parameter-loop-item": {
               "type": "STRING"
             }
           }
@@ -52,7 +55,7 @@
               "inputs": {
                 "parameters": {
                   "msg": {
-                    "componentInputParameter": "pipelineparam--text_parameter-loop-item-subvar-p_a-loop-item",
+                    "componentInputParameter": "pipelineparam--loop_parameter-loop-item-subvar-p_a-loop-item",
                     "parameterExpressionSelector": "parseJson(string_value)[\"q_a\"]"
                   }
                 }
@@ -65,10 +68,102 @@
         },
         "inputDefinitions": {
           "parameters": {
-            "pipelineparam--text_parameter-loop-item-subvar-p_a": {
+            "pipelineparam--loop_parameter-loop-item": {
               "type": "STRING"
             },
-            "pipelineparam--text_parameter-loop-item-subvar-p_a-loop-item": {
+            "pipelineparam--loop_parameter-loop-item-subvar-p_a": {
+              "type": "STRING"
+            },
+            "pipelineparam--loop_parameter-loop-item-subvar-p_a-loop-item": {
+              "type": "STRING"
+            }
+          }
+        }
+      },
+      "comp-for-loop-4": {
+        "dag": {
+          "tasks": {
+            "for-loop-6": {
+              "componentRef": {
+                "name": "comp-for-loop-6"
+              },
+              "inputs": {
+                "parameters": {
+                  "pipelineparam--loop-item-param-3": {
+                    "componentInputParameter": "pipelineparam--loop-item-param-3"
+                  }
+                }
+              },
+              "parameterIterator": {
+                "itemInput": "pipelineparam--loop-item-param-5",
+                "items": {
+                  "raw": "[100, 200, 300]"
+                }
+              },
+              "taskInfo": {
+                "name": "for-loop-6"
+              }
+            },
+            "print-op-2": {
+              "cachingOptions": {
+                "enableCache": true
+              },
+              "componentRef": {
+                "name": "comp-print-op-2"
+              },
+              "inputs": {
+                "parameters": {
+                  "msg": {
+                    "componentInputParameter": "pipelineparam--loop-item-param-3"
+                  }
+                }
+              },
+              "taskInfo": {
+                "name": "print-op-2"
+              }
+            }
+          }
+        },
+        "inputDefinitions": {
+          "parameters": {
+            "pipelineparam--loop-item-param-3": {
+              "type": "STRING"
+            }
+          }
+        }
+      },
+      "comp-for-loop-6": {
+        "dag": {
+          "tasks": {
+            "print-op-3": {
+              "cachingOptions": {
+                "enableCache": true
+              },
+              "componentRef": {
+                "name": "comp-print-op-3"
+              },
+              "inputs": {
+                "parameters": {
+                  "msg": {
+                    "componentInputParameter": "pipelineparam--loop-item-param-3"
+                  },
+                  "msg2": {
+                    "componentInputParameter": "pipelineparam--loop-item-param-5"
+                  }
+                }
+              },
+              "taskInfo": {
+                "name": "print-op-3"
+              }
+            }
+          }
+        },
+        "inputDefinitions": {
+          "parameters": {
+            "pipelineparam--loop-item-param-3": {
+              "type": "STRING"
+            },
+            "pipelineparam--loop-item-param-5": {
               "type": "STRING"
             }
           }
@@ -83,6 +178,29 @@
             }
           }
         }
+      },
+      "comp-print-op-2": {
+        "executorLabel": "exec-print-op-2",
+        "inputDefinitions": {
+          "parameters": {
+            "msg": {
+              "type": "STRING"
+            }
+          }
+        }
+      },
+      "comp-print-op-3": {
+        "executorLabel": "exec-print-op-3",
+        "inputDefinitions": {
+          "parameters": {
+            "msg": {
+              "type": "STRING"
+            },
+            "msg2": {
+              "type": "STRING"
+            }
+          }
+        }
       }
     },
     "deploymentSpec": {
@@ -90,14 +208,59 @@
         "exec-print-op": {
           "container": {
             "args": [
-              "--msg",
-              "{{$.inputs.parameters['msg']}}"
+              "--executor_input",
+              "{{$}}",
+              "--function_to_execute",
+              "print_op"
+            ],
+            "command": [
+              "sh",
+              "-c",
+              "(python3 -m ensurepip || python3 -m ensurepip --user) && (PIP_DISABLE_PIP_VERSION_CHECK=1 python3 -m pip install --quiet                 --no-warn-script-location 'kfp==1.8.2' || PIP_DISABLE_PIP_VERSION_CHECK=1 python3 -m pip install --quiet                 --no-warn-script-location 'kfp==1.8.2' --user) && \"$0\" \"$@\"",
+              "sh",
+              "-ec",
+              "program_path=$(mktemp -d)\nprintf \"%s\" \"$0\" > \"$program_path/ephemeral_component.py\"\npython3 -m kfp.v2.components.executor_main                         --component_module_path                         \"$program_path/ephemeral_component.py\"                         \"$@\"\n",
+              "\nfrom kfp.v2.dsl import *\nfrom typing import *\n\ndef print_op(msg: str, msg2: Optional[str] = None):\n    print(f'msg: {msg}, msg2: {msg2}')\n\n"
+            ],
+            "image": "python:3.7"
+          }
+        },
+        "exec-print-op-2": {
+          "container": {
+            "args": [
+              "--executor_input",
+              "{{$}}",
+              "--function_to_execute",
+              "print_op"
             ],
             "command": [
+              "sh",
+              "-c",
+              "(python3 -m ensurepip || python3 -m ensurepip --user) && (PIP_DISABLE_PIP_VERSION_CHECK=1 python3 -m pip install --quiet                 --no-warn-script-location 'kfp==1.8.2' || PIP_DISABLE_PIP_VERSION_CHECK=1 python3 -m pip install --quiet                 --no-warn-script-location 'kfp==1.8.2' --user) && \"$0\" \"$@\"",
               "sh",
               "-ec",
-              "program_path=$(mktemp)\nprintf \"%s\" \"$0\" > \"$program_path\"\npython3 -u \"$program_path\" \"$@\"\n",
-              "def print_op(msg):\n    print(msg)\n\nimport argparse\n_parser = argparse.ArgumentParser(prog='Print op', description='')\n_parser.add_argument(\"--msg\", dest=\"msg\", type=str, required=True, default=argparse.SUPPRESS)\n_parsed_args = vars(_parser.parse_args())\n\n_outputs = print_op(**_parsed_args)\n"
+              "program_path=$(mktemp -d)\nprintf \"%s\" \"$0\" > \"$program_path/ephemeral_component.py\"\npython3 -m kfp.v2.components.executor_main                         --component_module_path                         \"$program_path/ephemeral_component.py\"                         \"$@\"\n",
+              "\nfrom kfp.v2.dsl import *\nfrom typing import *\n\ndef print_op(msg: str, msg2: Optional[str] = None):\n    print(f'msg: {msg}, msg2: {msg2}')\n\n"
+            ],
+            "image": "python:3.7"
+          }
+        },
+        "exec-print-op-3": {
+          "container": {
+            "args": [
+              "--executor_input",
+              "{{$}}",
+              "--function_to_execute",
+              "print_op"
+            ],
+            "command": [
+              "sh",
+              "-c",
+              "(python3 -m ensurepip || python3 -m ensurepip --user) && (PIP_DISABLE_PIP_VERSION_CHECK=1 python3 -m pip install --quiet                 --no-warn-script-location 'kfp==1.8.2' || PIP_DISABLE_PIP_VERSION_CHECK=1 python3 -m pip install --quiet                 --no-warn-script-location 'kfp==1.8.2' --user) && \"$0\" \"$@\"",
+              "sh",
+              "-ec",
+              "program_path=$(mktemp -d)\nprintf \"%s\" \"$0\" > \"$program_path/ephemeral_component.py\"\npython3 -m kfp.v2.components.executor_main                         --component_module_path                         \"$program_path/ephemeral_component.py\"                         \"$@\"\n",
+              "\nfrom kfp.v2.dsl import *\nfrom typing import *\n\ndef print_op(msg: str, msg2: Optional[str] = None):\n    print(f'msg: {msg}, msg2: {msg2}')\n\n"
             ],
             "image": "python:3.7"
           }
@@ -105,7 +268,7 @@
       }
     },
     "pipelineInfo": {
-      "name": "pipeline-with-loops-and-conditions"
+      "name": "pipeline-with-nested-loops"
     },
     "root": {
       "dag": {
@@ -116,39 +279,52 @@
             },
             "inputs": {
               "parameters": {
-                "pipelineparam--text_parameter": {
-                  "componentInputParameter": "text_parameter"
+                "pipelineparam--loop_parameter": {
+                  "componentInputParameter": "loop_parameter"
                 }
               }
             },
             "parameterIterator": {
-              "itemInput": "pipelineparam--text_parameter-loop-item",
+              "itemInput": "pipelineparam--loop_parameter-loop-item",
               "items": {
-                "inputParameter": "pipelineparam--text_parameter"
+                "inputParameter": "pipelineparam--loop_parameter"
               }
             },
             "taskInfo": {
               "name": "for-loop-1"
             }
+          },
+          "for-loop-4": {
+            "componentRef": {
+              "name": "comp-for-loop-4"
+            },
+            "parameterIterator": {
+              "itemInput": "pipelineparam--loop-item-param-3",
+              "items": {
+                "raw": "[1, 2]"
+              }
+            },
+            "taskInfo": {
+              "name": "for-loop-4"
+            }
           }
         }
       },
       "inputDefinitions": {
         "parameters": {
-          "text_parameter": {
+          "loop_parameter": {
             "type": "STRING"
           }
         }
       }
     },
     "schemaVersion": "2.0.0",
-    "sdkVersion": "kfp-1.7.2"
+    "sdkVersion": "kfp-1.8.2"
   },
   "runtimeConfig": {
-    "gcsOutputDirectory": "dummy_root",
     "parameters": {
-      "text_parameter": {
-        "stringValue": "[{\"p_a\": [{\"q_a\":1}, {\"q_a\":2}], \"p_b\": \"hello\"}, {\"p_a\": [{\"q_a\":11},{\"q_a\":22}], \"p_b\": \"halo\"}]"
+      "loop_parameter": {
+        "stringValue": "[{\"p_a\": [{\"q_a\": 1}, {\"q_a\": 2}], \"p_b\": \"hello\"}, {\"p_a\": [{\"q_a\": 11}, {\"q_a\": 22}], \"p_b\": \"halo\"}]"
       }
     }
   }
diff --git a/sdk/python/kfp/v2/compiler_cli_tests/test_data/pipeline_with_nested_loops.py b/sdk/python/kfp/v2/compiler_cli_tests/test_data/pipeline_with_nested_loops.py
--- a/sdk/python/kfp/v2/compiler_cli_tests/test_data/pipeline_with_nested_loops.py
+++ b/sdk/python/kfp/v2/compiler_cli_tests/test_data/pipeline_with_nested_loops.py
@@ -12,28 +12,47 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-from kfp import components
-from kfp import dsl
-import kfp.v2.compiler as compiler
+from typing import Optional
 
+from kfp.v2 import compiler, dsl
+from kfp.v2.dsl import component
 
-@components.create_component_from_func
-def print_op(msg: str):
-    print(msg)
 
+@component
+def print_op(msg: str, msg2: Optional[str] = None):
+    print(f'msg: {msg}, msg2: {msg2}')
 
-@dsl.pipeline(
-    name='pipeline-with-loops-and-conditions',
-    pipeline_root='dummy_root',
-)
-def my_pipeline(
-    text_parameter:
-    str = '[{"p_a": [{"q_a":1}, {"q_a":2}], "p_b": "hello"}, {"p_a": [{"q_a":11},{"q_a":22}], "p_b": "halo"}]'
-):
-    with dsl.ParallelFor(text_parameter) as item:
+
+@dsl.pipeline(name='pipeline-with-nested-loops')
+def my_pipeline(loop_parameter: list = [
+    {
+        "p_a": [{
+            "q_a": 1
+        }, {
+            "q_a": 2
+        }],
+        "p_b": "hello",
+    },
+    {
+        "p_a": [{
+            "q_a": 11
+        }, {
+            "q_a": 22
+        }],
+        "p_b": "halo",
+    },
+]):
+    # Nested loop with withParams loop args
+    with dsl.ParallelFor(loop_parameter) as item:
         with dsl.ParallelFor(item.p_a) as item_p_a:
             print_op(item_p_a.q_a)
 
+    # Nested loop with withItems loop args
+    with dsl.ParallelFor([1, 2]) as outter_item:
+        print_op(outter_item)
+        with dsl.ParallelFor([100, 200, 300]) as inner_item:
+            print_op(outter_item, inner_item)
+
 
 if __name__ == '__main__':
     compiler.Compiler().compile(