while permalink

repeat step(s) in while loop permalink

Repeat step until stop is True, or until you reach a configurable maximum iterations. You have to specify at least one of either max or stop.

If you specify both max and stop, the loop exits when stop is True as long as it’s still under max iterations.

max will exit the loop even if stop is still False. If you want to error and stop processing when max exhausts set errorOnMax to True. For example, maybe you are waiting for stop to reach True but want to timeout after max.

The iterator is context['whileCounter']. If you want to use the iterator value in your step with a substitution expression, you’d use {whileCounter}.

These are all the options to configure a while loop:

while: # optional. repeat step until stop is True or max iterations reached.
  stop: '{keyhere}' # loop until this evaluates True.
  max: 1 # max loop iterations to run. integer. Defaults None (infinite).
  sleep: 0 # sleep between iterations, in seconds. Decimals allowed. Defaults 0.
  errorOnMax: False # raise error if max reached. Defaults False.

All the inputs support substitutions, so you can assign values to these properties dynamically at run-time. stop evaluates on each loop iteration.

loop until stop condition true permalink

The stop condition re-evaluates on every loop iteration. This means that you can signal from your step you want to break out of the loop reporting success.

If you use any context variables in your stop condition, these need to exist before the loop starts. You can initialize these in the in arguments for the same step, or alternatively somewhere in a preceding step like contextcopy or set.

Remember that you can use py strings for the stop condition. This allows you to use more advanced python expressions for your stop condition.

stop: !py responseCode == "OK"

This example uses an inline py step to execute some arbitrary python that changes the variable that the while stop condition evaluates:

# ./while-stop.yaml
steps:
  - name: pypyr.steps.echo
    in:
      echoMe: begin
  - name: pypyr.steps.py
    comment: while with a stop condition
             the step changes the value
             the stop condition checks for
             on the 3rd iteration.
    in:
      # initialize the stop boolean here
      stopWhenImTrue: False
      py: |
        print(f"this is while {whileCounter} executing like a boss.")
        if whileCounter == 3:
          stopWhenImTrue = True
          save('stopWhenImTrue')        
    while:
      stop: '{stopWhenImTrue}'
  - name: pypyr.steps.echo
    in:
      echoMe: end

This will result in:

$ pypyr while-stop
begin
this is while 1 executing like a boss.
this is while 2 executing like a boss.
this is while 3 executing like a boss.
end

This is a contrived example to show looping on a single step. See calling multiple steps in a loop for a pipeline that does exactly the above, but with using multiple steps instead.

loop until maximum iteration count permalink

You can set max to a static positive integer, or you can use a formatting expression to set the loop upper bound dynamically.

You can’t change max mid-loop, however. max evaluates at the start of the while loop and becomes fixed at that point.

# ./while-max.yaml
context_parser: pypyr.parser.string
steps:
  - name: pypyr.steps.echo
    comment: loop 5X
    while:
      max: 5    
    in:
      echoMe: Static Max -> this is step {whileCounter}
  - name: pypyr.steps.echo
    comment: get loop counter from cli arg input
             pypyr makes the string an int under
             the covers for you.
             argString comes from the cli arg.
    while:
      max: '{argString}'
    in:
      echoMe: Dynamic Max -> this is step {whileCounter}

This results in:

$ pypyr while-max 3
Static Max -> this is step 1
Static Max -> this is step 2
Static Max -> this is step 3
Static Max -> this is step 4
Static Max -> this is step 5
Dynamic Max -> this is step 1
Dynamic Max -> this is step 2
Dynamic Max -> this is step 3

$

combine max & stop condition permalink

You can set a maximum loop iteration count in addition to a stop condition. This is pretty useful for situations where you want your task to repeat but stop after a certain number of iterations.

#  ./while-max-stop.yaml
steps:
  - name: pypyr.steps.echo
    in:
      echoMe: begin
  - name: pypyr.steps.set
    comment: arbitrary context key with value "actual".
             the loop stop condition will be looking for
             value "expected" to stop the loop.
    in:
      set:
        arbKey: actual
  - name: pypyr.steps.echo
    comment: stop evaluates on every loop iteration.
             If arbKey becomes 'expected' during 
             loop iteration, will stop loop.
             Since arbKey never becomes expected, 
             loop will exhaust on 3X.
    while: 
      max: 3
      stop: !py arbKey == 'expected'
    in:
      echoMe: counter {whileCounter} - arbKey is "{arbKey}", not "expected"
  - name: pypyr.steps.echo
    in:
      echoMe: end!

This will result in:

$ pypyr while-max-stop
begin
counter 1 - arbKey is "actual", not "expected"
counter 2 - arbKey is "actual", not "expected"
counter 3 - arbKey is "actual", not "expected"
end!

$ echo $?
0

raise error on max iteration count permalink

If you want your loop to raise an error if it exhausts the available maximum iterations, set errorOnMax: True. By default this is False.

This is frequently useful when you combine max and stop and you want to stop processing after a maximum amount of iterations when the stop condition never evaluates to True.

Here is a simple example. In the real world, stop is likely to use a {formatting expression} that while evaluate dynamically on each loop iteration, rather than just a hard-coded False.

#  ./while-max-stop-err.yaml
steps:
  - name: pypyr.steps.echo
    in:
      echoMe: begin
  - name: pypyr.steps.echo
    comment: stop will never be True.
             loop will exhaust on 3X.
             And raise an error.
    while: 
      max: 3
      stop: False
      errorOnMax: True
    in:
      echoMe: counter {whileCounter}
  - name: pypyr.steps.echo
    in:
      echoMe: end!
$ pypyr while-max-stop-err
begin
counter 1
counter 2
counter 3
ERROR:pypyr.dsl:while_loop: exhausted 3 iterations of while loop, and errorOnMax is True.
ERROR:pypyr.stepsrunner:run_step_groups: Something went wrong. Will now try to run on_failure.

LoopMaxExhaustedError: while loop reached 3.

$ echo $?
255

sleep between while iterations permalink

Use sleep to set the interval in seconds to wait in between step iterations.

If you don’t set sleep it defaults to 0, which is no wait at all.

# ./while-sleep
steps:
  - name: pypyr.steps.echo
    comment: loop 5 times with 2 seconds sleep 
             in between executions.
    in:
      echoMe: this is step {whileCounter}. ctrl-c if you're bored.
    while:
      max: 5
      sleep: 2

This results in:

$ pypyr while-sleep
this is step 1. ctrl-c if you're bored.
this is step 2. ctrl-c if you're bored.
this is step 3. ctrl-c if you're bored.
this is step 4. ctrl-c if you're bored.
this is step 5. ctrl-c if you're bored.

$

infinite loop permalink

You can run an infinite loop by never having stop always evaluate to False.

# ./while-infinite.yaml
steps:
  - name: pypyr.steps.echo
    description: runs infinitely
    comment: 3s sleep between iterations
    in:
      echoMe: this is a deliberate infinite loop. ctrl+c to break.
    while:
      stop: False
      sleep: 3

This will result in:

$ pypyr while-infinite
pypyr.steps.echo: runs infinitely
this is a deliberate infinite loop. ctrl+c to break.
this is a deliberate infinite loop. ctrl+c to break.
this is a deliberate infinite loop. ctrl+c to break.
this is a deliberate infinite loop. ctrl+c to break.
this is a deliberate infinite loop. ctrl+c to break.
this is a deliberate infinite loop. ctrl+c to break.
this is a deliberate infinite loop. ctrl+c to break.
^C

$

nesting foreach in while permalink

A foreach on the same step will run the entire foreach loop once for each while iteration.

 # ./while-foreach
steps:
  - name: pypyr.steps.echo
    comment: loop 3X while.
             the entire foreach runs
             for each while iteration.
    in:
      echoMe: this is {whileCounter} - {i}
    foreach: ['eggs', 'bacon', 'spam']
    while:
      max: 3
  - name: pypyr.steps.echo
    in:
      echoMe: done!

This results in output:

$ pypyr while-foreach
this is 1 - eggs
this is 1 - bacon
this is 1 - spam
this is 2 - eggs
this is 2 - bacon
this is 2 - spam
this is 3 - eggs
this is 3 - bacon
this is 3 - spam
done!

run a sequence of steps in a loop permalink

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 while 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.

# ./while-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.
    in:
      # initialize the stop boolean here
      stopWhenImTrue: False
      call: loop_me
    while:
      stop: '{stopWhenImTrue}'
  - name: pypyr.steps.echo
    in:
      echoMe: end

loop_me:
  - name: pypyr.steps.echo
    in:
      echoMe: this is while {whileCounter} executing like a boss.
  - name: pypyr.steps.set
    comment: set stopWhenImTrue to True on the 3rd iteration
             you're likely to do more meaningful work in your
             own pipelines.
    run: !py whileCounter == 3
    in:
      set:
        stopWhenImTrue: True

This will result in:

$ pypyr while-call
begin
this is while 1 executing like a boss.
this is while 2 executing like a boss.
this is while 3 executing like a boss.
end

see also

last updated on .