pypyr.steps.switch permalink

conditional branching permalink

Control-of-flow statement that lets you conditionally select between execution branches depending on whether the controlling case expression evaluates to True.

You can conceptualize this as an IF-THEN-ELSE or IF-ELIF-ELSE style conditional branch. If you’re not branching between different execution paths, but instead just want to control whether an individual step runs, you can use the run or skip decorators instead.

# ./my-pipeline.yaml
context_parser: pypyr.parser.string
steps:
  - name: pypyr.steps.switch
    comment: argString is set by the pypyr.parser.string context_parser
    in:
      switch:
        - case: !py argString == 'a'
          call: A
        - case: !py argString == 'b'
          call: B
        - case: !py argString == 'bc'
          call: [B, C] # multiple groups, will run in order
        - default: D

  - name: pypyr.steps.echo
    in:
      echoMe: done!

A:
  - name: pypyr.steps.echo
    in:
      echoMe: A

B:
  - name: pypyr.steps.echo
    in:
      echoMe: B

C:
  - name: pypyr.steps.echo
    in:
      echoMe: C

D:
  - name: pypyr.steps.echo
    in:
      echoMe: D

Running this pipeline will look like this:

term
$ pypyr my-pipeline # default case, argString is empty ''
D
done!

$ pypyr my-pipeline a
A
done!

$ pypyr my-pipeline b
B
done!

$ pypyr my-pipeline bc
B
C
done!

$ pypyr my-pipeline xyz # no matches, default
D
done!

When pypyr finds a matching case, it will call the step-groups you set in call. Once the called step-group(s) complete, pypyr will complete the switch step and move on to the next step in the pipeline after the switch.

pypyr only calls the first matching case, it does not fall-through to the following case statements.

You can optionally specify default to execute if none of the case expressions match.

If no case expressions match and there is no default, pypyr will continue to the next step in the pipeline without executing any of the blocks in the switch.

All inputs support string substitutions.

call input permalink

The input to call can be

  • a simple string: run a single step-group
  • a list of string: run a sequence of step-groups in order
  • a dict for expanded syntax: set success and failure groups for the case.
    • The groups input can be a string for a single-step group, or a list of string to run a sequence of groups in order.
- name: pypyr.steps.switch
  in:
    switch:
      - case: !py argString == 'a'
        call: A # call single group `A`

      - case: !py argString == 'bc'
        call: [B, C] # call sequence of groups in order

      - case: !py argString == 'c'
        call: # expanded syntax to set a failure handler explicitly
          groups: C # can also be a list to call a sequence of groups in order
          failure: handle_error

      - default: D

The expanded input for default is the same as for call:

switch:
  - case: !py argString == 'a'
    call:
      groups: A
      failure: handle_error
  - case: !py argString == 'b'
    call: B
  - case: !py argString == 'bc'
    call: [B, C]
  - default:
      groups: D
      failure: some_error_handling_group

Like call, switch only runs success or failure groups if you actually specify these. You don’t have to specify success or failure groups - if you don’t, pypyr will just run the group(s) you explicitly specified and continue when done without trying to run any success or failure groups. It will still fall back to the containing success or failure group settings for the pipeline itself or the step-group the switch lives in.

failure handler permalink

With expanded syntax, you can add a failure handler if you want to do some specific handling if the called step-groups for a case raise an error.

Notice in the A step-group this pipeline deliberately raises an error to illustrate the principle:

context_parser: pypyr.parser.string
steps:
  - name: pypyr.steps.switch
    in:
      switch:
        - case: !py argString == 'a'
          call:
            groups: A
            failure: handle_error
        - case: !py argString == 'b'
          call: B
        - case: !py argString == 'bc'
          call: [B, C]
        - default: D

A:
  - name: pypyr.steps.echo
    in:
      echoMe: A
  
  - name: pypyr.steps.assert
    comment: arbitrarily raise an error here
    in:
      assert: False

B:
  - name: pypyr.steps.echo
    in:
      echoMe: B

C:
  - name: pypyr.steps.echo
    in:
      echoMe: C

D:
  - name: pypyr.steps.echo
    in:
      echoMe: D

handle_error:
  - name: pypyr.steps.echo
    in:
      echoMe: error handling stuff here

  # swallow error so pipeline does not quit reporting failure
  - pypyr.steps.stopstepgroup

Assuming you saved this pipeline as ./my-pipeline.yaml, you can run it like this:

term
$ pypyr my-pipeline # default case, argString is empty ''
D
done!

$ pypyr my-pipeline a
A
Error while running step pypyr.steps.assert at pipeline yaml line: 40, col: 5
Something went wrong. Will now try to run handle_error.
error handling stuff here

combine with decorators permalink

As with any pypyr step, you can apply any of the decorators to the switch step.

Here we’re running switch in a while loop, using the while iterator {whileCounter} to make decisions on each loop iteration:

steps:
  - name: pypyr.steps.switch
    while:
      max: 100
    in:
      switch:
        - case: !py whileCounter % 15 == 0
          call: FizzBuzz

        - case: !py whileCounter % 3 == 0
          call: Fizz

        - case: !py whileCounter % 5 == 0
          call: Buzz

        - default: just_print

Fizz:
  - name: pypyr.steps.echo
    in:
      echoMe: Fizz

Buzz:
  - name: pypyr.steps.echo
    in:
      echoMe: Buzz

FizzBuzz:
  - name: pypyr.steps.echo
    in:
      echoMe: FizzBuzz

just_print:
  - name: pypyr.steps.echo
    in:
      echoMe: '{whileCounter}'

You could of course use a foreach loop to achieve the same effect. In this case the iterator is i:

- name: pypyr.steps.switch
  foreach: !py range(0, 100)
  in:
    switch:
      - case: !py i % 15 == 0
        call: FizzBuzz

      - case: !py i % 3 == 0
        call: Fizz

      - case: !py i % 5 == 0
        call: Buzz

      - default: just_print

format expressions permalink

The examples so far have used py strings. You can use a standard formatting expression for truthy-style evaluation instead:

# ./switch-truthy
steps:
  - name: pypyr.steps.set
    comment: somewhat arbitrarily set some variables in context
    in:
      set:
        A: arb string
        B: False
        D: 1 

  - name: pypyr.steps.switch
    in:
      switch:
        - case: '{A}'
          call: A
        - case: '{B}'
          call: [B, C]
        - case: '{D}'
          call: D

A:
  - name: pypyr.steps.echo
    in:
      echoMe: A

B:
  - name: pypyr.steps.echo
    in:
      echoMe: B

C:
  - name: pypyr.steps.echo
    in:
      echoMe: C

D:
  - name: pypyr.steps.echo
    in:
      echoMe: D

Running this pipeline will result in:

term
$ pypyr switch-truthy
D

truthy strings permalink

Note that pypyr truthy bool evaluation evaluates strings somewhat atypically: case insensitive string “true”, “1” & “1.0” are True, while all other string values, including empty string, evaluate to False. This makes it easier for you to work with string inputs from cli arguments without having to do special casting first.

So if you changed the input in the example to this:

- name: pypyr.steps.set
  comment: somewhat arbitrarily set some variables in context
  in:
    set:
      A: "TRUE" # notice this is a string, not a bool
      B: False
      D: 1 

The value of {A} will evaluate to boolean True and in the switch statement pypyr will run the A group instead of D.

see also

last updated on .