foreach
repeat step for each item in list
Run the step once for each item in the list.
The iterator is context['i']
. If you want to use the iterator value in your
step with a substitution expression, you’d use {i}
.
foreach
takes any iterable. In your pipeline yaml, you can specify this as a
list []
in two ways:
foreach: [item 1, item 2, item 3]
or
foreach:
- item 1
- item 2
- item 3
loop static input list
The foreach
input here is a standard list.
# ./foreach-example.yaml
steps:
- name: pypyr.steps.echo
foreach:
- one
- two
- three at last
in:
echoMe: "{i}"
$ pypyr foreach-example
one
two
three at last
loop run-time list
You can use substitutions to assign dynamic values to the list to iterate.
# ./foreach-substitution-example.yaml
context_parser: pypyr.parser.list
steps:
- name: pypyr.steps.echo
foreach: '{argList}'
in:
echoMe: "this time it's: {i}"
This example just uses the argList
from the pypyr.parser.list context parser, but you can use any list you
might have in your context.
$ pypyr foreach-substitution-example eggs bacon ham
this time it's: eggs
this time it's: bacon
this time it's: ham
loop over a mapping
You can run your foreach
loop on any iterable, including mapping or dictionary
types. Depending on your input to foreach
, you can either iterate only on the
keys, or on the keys and values together:
steps:
- name: pypyr.steps.set
in:
set:
my_mapping:
a: b
c: d
- name: pypyr.steps.echo
description: --> loop over a mapping will just return the keys
foreach: '{my_mapping}'
in:
echoMe: key is {i}
- name: pypyr.steps.echo
description: --> loop over a mapping items will return keys + values
foreach: !py my_mapping.items()
in:
echoMe: key is {i[0]} & value is {i[1]}
This will output:
--> loop over a mapping will just return the keys
key is a
key is c
--> loop over a mapping items will return keys + values
key is a & value is b
key is c & value is d
run a sequence of steps in a loop
You can loop on any step, whether it is your own or a built-in pypyr step. If
you loop on a call step, you will run your
foreach
loop on the entire sequence of steps inside a step-group. This lets
you loop over multiple steps as a unit.
If you want to loop over an entire pipeline, use pype to invoke the pipeline and decorate the pype
step in
the same way.
# ./foreach-call.yaml
steps:
- name: pypyr.steps.echo
in:
echoMe: begin
- name: pypyr.steps.call
comment: run entire sequence of steps
in loop_me step-group in a loop.
foreach:
- item 1
- item 2
- item 3
in:
call: loop_me
- name: pypyr.steps.echo
in:
echoMe: end
loop_me:
- name: pypyr.steps.echo
in:
echoMe: this is foreach {i} executing like a boss.
- name: pypyr.steps.cmd
comment: you prob want to do something useful here
in:
cmd: echo yourcmd --dothing {i}
This results in:
$ pypyr foreach-call
begin
this is foreach item 1 executing like a boss.
yourcmd --dothing item 1
this is foreach item 2 executing like a boss.
yourcmd --dothing item 2
this is foreach item 3 executing like a boss.
yourcmd --dothing item 3
end
conditional execution inside loop
The run
, skip
& swallow
decorators evaluate dynamically on each iteration.
So if during an iteration the step’s logic sets run=False
, the step will not
execute on the next iteration.
The loop will run to completion, though, so if a subsequent iteration sets
run=True
again, the step will execute again on the next loop iteration.
# ./foreach-run.yaml
steps:
- name: pypyr.steps.echo
in:
echoMe: begin
- name: pypyr.steps.set
in:
set:
myThings:
- name: thing 1
isReady: True
- name: thing 2
isReady: False
- name: thing 3
isReady: True
- name: pypyr.steps.echo
comment: only run for thing when isReady.
foreach: '{myThings}'
run: '{i[isReady]}'
in:
echoMe: do something with {i[name]}
- name: pypyr.steps.echo
in:
echoMe: end!
When you run this pipeline, notice that the 2nd iteration skips execution, but it continues with the 3rd iteration:
$ pypyr foreach-run
begin
do something with thing 1
do something with thing 3
end!
nesting loops
You can nest foreach
loops. You can either call a step with a foreach
inside
a called group with a foreach
, loop pipelines calling other pipelines with
loops in them, or you can flatten the loop by getting the product of lists you
want to iterate.
nesting loops with call
When you call
a group that contains another loop, pypyr will iterate the inner
loop for each invocation of the outer. Pay attention to the value of i
-
because i
will always be the iterator value of the current, innermost loop,
grab the value of the parent’s iterator with set
before the inner loop
starts.
steps:
- name: pypyr.steps.call
foreach: ['A', 'B', 'C']
in:
call: looping_group
- name: pypyr.steps.echo
in:
echoMe: done!
looping_group:
- name: pypyr.steps.set
in:
set:
current_parent_iterator: '{i}'
- name: pypyr.steps.echo
foreach: ['one', 'two', 'three']
in:
echoMe: '{current_parent_iterator}.{i}'
This will output:
A.one
A.two
A.three
B.one
B.two
B.three
C.one
C.two
C.three
done!
nesting loops with pype
When you loop when you invoke a child pipeline from a parent with pype, the child pipeline itself can also contain loops.
So given a parent pipeline:
steps:
- name: pypyr.steps.pype
foreach: ['A', 'B', 'C']
in:
pype:
name: foreach-nest-pype-child
- name: pypyr.steps.echo
in:
echoMe: done!
Calling a child pipeline that looks like this:
# ./foreach-nest-pype-child.yaml
steps:
- name: pypyr.steps.set
in:
set:
current_parent_iterator: '{i}'
- name: pypyr.steps.echo
foreach: ['one', 'two', 'three']
in:
echoMe: '{current_parent_iterator}.{i}'
This will output:
A.one
A.two
A.three
B.one
B.two
B.three
C.one
C.two
C.three
done!
As with nesting loops with call
, pay attention to the value of i
- because
i
will always be the iterator value of the current, innermost loop, grab the
value of the parent’s iterator with set
before the inner loop starts.
flatten nested loops
You can achieve the same thing by using the built-in itertools’ handy product function. This is especially handy to simplify your pipeline structure if you are nesting several levels deep.
- name: pypyr.steps.pyimport
in:
pyImport: from itertools import product
- name: pypyr.steps.set
in:
set:
list_a: ['A', 'B', 'C']
list_b: ['one', 'two', 'three']
- name: pypyr.steps.echo
foreach: !py product(list_a, list_b)
in:
echoMe: '{i[0]}.{i[1]}'
- name: pypyr.steps.echo
in:
echoMe: done!
As before, this will output:
A.one
A.two
A.three
B.one
B.two
B.three
C.one
C.two
C.three
done!
Notice in this case i
is the tuple that product
returns, so you can use the
tuple index to access the current values of the iterator without having to grab
a reference to the parent iterator first with set
as in the nested
call
or pype
examples above.