Lesson 10 - For Loop

Goal

In this lesson we’ll learn how to use a for loop to create an iterative step.

Get Started

The idea here is to continually try the create_user_email subflow until it either creates an available address or fails. To do so, we should be able to leave the subflow as is and just work on the create_email_address step in new_hire.sl.

Loop Syntax

An iterative step looks very similar to a standard step that only runs once. To transform our create_email_address step into one that loops we’ll add the loop key along with a loop expression and indent the do and publish sections. For now, we’ll loop over a list of numbers.

- create_email_address:
    loop:
      for: attempt in [1,2,3,4]
      do:
        create_user_email:
          - first_name
          - middle_name
          - last_name
          - attempt: ${str(attempt)}
      publish:
        - address
        - password
    navigate:
      - CREATED: print_finish
      - UNAVAILABLE: print_fail
      - FAILURE: print_fail

Note

YAML Note: A list can be written using bracket ([]) notation instead of using indentation and hyphens (-).

For each item in our list the attempt loop variable is assigned the value and then passed to an iteration of the subflow call. All inputs must be strings. Therefore we convert the attempt value to a string using the Python str() function.

Since we’re assigning a value to attempt in the loop and not using it as flow input we can delete it from the flow’s input list.

For more information, see loop, for and publish in the DSL reference.

Default Behavior

We can save the file and run the flow now and see how the loop works. It won’t quite do what we want yet, but it will demonstrate what a loop’s default behavior is. Play around a bit with passing the optional middle name and not passing it to see what happens. Also try removing the last item in the loop expression’s list.

run --f <folder path>/tutorials/hiring/new_hire.sl --cp <folder path>/tutorials --i first_name=john,middle_name=e,last_name=doe

The first thing you’ll notice is that the subflow is being run several times. This is what our loop has done. Next, you’ll notice that depending on whether you’ve passed a middle name and how big the loop list is, different things will happen. This is due to the default behavior of loops and our create_user_email subflow.

By default a loop exits when either the list it is looping on has been exhausted or the operation or subflow called returns a result of FAILURE. This will explain the following cases:

case middle_name list iterations flow result
1 no [1,2,3,4] 3 FAILURE
2 yes [1,2,3,4] 4 FAILURE
3 no [1,2,3] 3 FAILURE
4 yes [1,2,3] 3 FAILURE or SUCCESS

For all cases: For attempt 1 and 2 the create_user_email subflow runs it will return a result of either CREATED or UNAVAILABLE because the generate_user_email operation will return a result of SUCCESS. Since neither of those are FAILURE, the loop will continue to run.

Case 1: Since the middle_name is not present, the generate_user_email operation will return a result of FAILURE when the 3 is passed to its attempt input. The loop exits on the FAILURE result by default and goes to its navigate section which forwards it to print_fail. Since print_fail is the on_failure step, it ends the flow with a result of FAILURE.

Case 2: This case is very similar to the previous one. The only difference is that the generate_user_email operation will return a result of FAILURE when the 4, not 3, is passed to its attempt input.

Case 3: This case is even more similar to the first case. The first case never got to the 4th iteration of the loop, so we can expect that it we removed the 4th item from the list the same thing will happen.

Case 4: This time we have a middle_name so the create_user_email subflow will run successfully all three times, returning results of either CREATED or UNAVAILABLE. Since neither of those are FAILURE, the loop will only exit when the list is exhausted. At that point the result from the last iteration of the step will be used by the navigation to see where the flow goes next. If the last iteration’s result is CREATED, the print_finish step will run and the flow will end with a result of SUCCESS. If the last iteration’s result is UNAVAILABLE, the print_fail step will run and the flow will end with a result of FAILURE.

Custom Break

Now that we understand what happens in the default case, let’s put in a custom break so the loop will do what we want it to. We want the loop to stop when we’ve either found a suitable email address or something has gone wrong, so we’ll add a break key with a list of results we want to break on, which in our case is CREATED or FAILURE.

- create_email_address:
    loop:
      for: attempt in [1,2,3,4]
      do:
        create_user_email:
          - first_name
          - middle_name
          - last_name
          - attempt
      publish:
        - address
        - password
      break:
        - CREATED
        - FAILURE
    navigate:
      - CREATED: print_finish
      - UNAVAILABLE: print_fail
      - FAILURE: print_fail

In a case where we want the loop to continue no matter what happens, we would have to override the default break on a result of failure by mapping the break key to an empty list ([]).

The published address variable will contain the address value from the last iteration of the loop. We can use at the same way published variables are used in regular steps. However, when using loops, you often want to aggregate the published output. We will do that in the next lesson.

For more information, see break in the DSL reference.

List Types

One last thing we can change to improve our flow is the loop’s list. Right now we’re using a literal list, but we can use any Python expression that results in a list instead. So here we can substitute [1,2,3,4] with range(1,5). We could also use a comma delimited strings which would be split automatically into a list.

Run It

Everything should be working as expected now. We can save our file and run the flow with or without a middle name. To test a result of FAILURE it’s best not to pass a middle name and run the flow several times.

run --f <folder path>/tutorials/hiring/new_hire.sl --cp <folder path>/tutorials --i first_name=john,last_name=doe

Download the Code

Lesson 10 - Complete code

Up Next

In the next lesson we’ll write another loop and aggregate the information that is output.

New Code - Complete

new_hire.sl

namespace: tutorials.hiring

imports:
  base: tutorials.base

flow:
  name: new_hire

  inputs:
    - first_name
    - middle_name:
        required: false
    - last_name

  workflow:
    - print_start:
        do:
          base.print:
            - text: "Starting new hire process"
        navigate:
          - SUCCESS: create_email_address

    - create_email_address:
        loop:
          for: attempt in range(1,5)
          do:
            create_user_email:
              - first_name
              - middle_name
              - last_name
              - attempt: ${str(attempt)}
          publish:
            - address
            - password
          break:
            - CREATED
            - FAILURE
        navigate:
          - CREATED: print_finish
          - UNAVAILABLE: print_fail
          - FAILURE: print_fail

    - print_finish:
        do:
          base.print:
            - text: "${'Created address: ' + address + ' for: ' + first_name + ' ' + last_name}"
        navigate:
          - SUCCESS: SUCCESS

    - on_failure:
      - print_fail:
          do:
            base.print:
              - text: "${'Failed to create address for: ' + first_name + ' ' + last_name}"