Conditional Logic (If/Else)
Conditional Logic (If/Else) enables dynamic branching within workflows, allowing different execution paths to be taken based on evaluated conditions. This capability is crucial for building flexible and responsive workflows that adapt to varying inputs or runtime states.
Defining Conditions and Branches
Conditional logic is initiated using a conditional construct, which provides methods to define if_, elif_, and else_ branches. Each branch specifies a condition (except else_) and an action to perform if that condition is met.
Syntax and Chaining
A conditional block starts with if_, followed by an expression. Subsequent alternative conditions are defined using elif_, and a default path is specified with else_. Each conditional statement must conclude with a then() call, which defines the output or action for that specific branch.
# Example: Basic conditional structure
from flytekit import conditional, task, workflow
@task
def task_a(x: int) -> str:
return f"Task A executed with {x}"
@task
def task_b(x: int) -> str:
return f"Task B executed with {x}"
@workflow
def my_workflow(input_val: int) -> str:
result = (
conditional("my_condition")
.if_(input_val > 10)
.then(task_a(x=input_val))
.else_()
.then(task_b(x=input_val))
)
return result
Supported Expressions
Conditions within if_ and elif_ statements must evaluate to a boolean and are constructed using specific expression types:
-
Comparison Expressions: These involve standard comparison operators:
>(greater than)<(less than)>=(greater than or equal to)<=(less than or equal to)==(equal to)!=(not equal to)- Example:
input_val > 10,input_str == "hello"
-
Conjunction Expressions: These combine multiple comparison expressions using bitwise operators:
&(logical AND)|(logical OR)- Example:
(input_val > 5) & (input_val < 15)
Unsupported Expressions
It is important to note the following limitations for expressions:
- No Python Logical Keywords: Standard Python
and,or,is, andnotkeywords are not supported within conditional expressions. Use&for AND and|for OR. - No Unary Expressions: Conditions cannot be formed directly from a single input value or a
Promisewithout a comparison. For example,if_(x)wherexis an input or aPromiseis not allowed. A comparison must always be present, such asif_(x == True).
Specifying Branch Outcomes (then(), fail())
Each if_, elif_, or else_ branch must conclude with a then() call. The then() method accepts a Promise or a Tuple[Promise] representing the output of a task or a set of tasks to be executed if that branch is taken.
Alternatively, a branch can explicitly fail() by providing an error message. This immediately terminates the workflow execution with the specified error if that branch is chosen.
# Example: Using elif_ and returning multiple outputs
from typing import Tuple
@task
def task_c(x: int) -> Tuple[str, int]:
return f"C: {x}", x * 2
@task
def task_d(x: int) -> Tuple[str, int]:
return f"D: {x}", x + 10
@workflow
def complex_workflow(input_val: int) -> Tuple[str, int]:
message, value = (
conditional("complex_condition")
.if_(input_val < 0)
.then(task_c(x=input_val))
.elif_((input_val >= 0) & (input_val < 10))
.then(task_d(x=input_val))
.else_()
.fail("Input value is too large!") # Example of failing a branch
)
return message, value
Managing Outputs
Conditional blocks are designed to produce a consistent set of outputs across all possible branches.
Consistent Output Signatures
The system automatically computes the minimum common set of output variables across all then() branches within a ConditionalSection. This ensures that regardless of which branch is executed, the conditional block always presents a unified output signature to subsequent workflow steps. If branches return different numbers or types of outputs, the system attempts to find the intersection of variables. If no common variables exist, or if some branches return outputs while others do not, this can lead to unexpected behavior or compilation errors.
Void Outputs
If a branch does not produce any explicit output (e.g., it calls a task that returns None or is a VoidPromise), the conditional block can be configured to return a VoidPromise. This indicates that the conditional block itself does not contribute any data outputs to the workflow.
Execution Behavior
Conditional logic behaves differently depending on the execution context:
- Compilation Mode (
ConditionalSection): During workflow compilation, theConditionalSectionconstruct builds aBranchNodein the workflow graph. This node encapsulates allif_,elif_, andelse_branches, along with their respective conditions and outputs. The actual evaluation of conditions and selection of a branch occurs at runtime on the orchestration engine. - Local Execution (
LocalExecutedConditionalSection): When a workflow is executed locally, theLocalExecutedConditionalSectionevaluates the conditions in order. The first condition that evaluates toTrueis selected, and only the tasks within that specific branch are executed. This short-circuits the evaluation, preventing unnecessary local task execution. - Skipped Branches (
SkippedConditionalSection): For nested conditionals, if an outer conditional branch is not taken, any innerConditionalSectionwithin that untaken branch will be represented by aSkippedConditionalSection. This ensures that tasks within the skipped nested conditional are not evaluated or executed, optimizing performance and preventing side effects.
Practical Applications
Conditional logic is a powerful tool for building sophisticated workflows:
- Dynamic Task Selection: Execute different data processing tasks based on the characteristics of input data (e.g., process images with one model if they are JPEGs, another if they are PNGs).
- A/B Testing: Route users or data through different experimental paths in a workflow to compare outcomes.
- Resource Optimization: Choose between a lightweight task and a resource-intensive task based on the scale of the input or available resources.
- Fallback Mechanisms: Implement error handling or alternative processing paths if a primary task fails or a condition is not met.
Important Considerations
- Workflow Context: Conditional logic must always be defined within the context of a workflow. It cannot be used as a standalone Python
if/elseoutside of workflow definitions. - Expression Limitations: Always adhere to the supported
ComparisonExpressionandConjunctionExpressiontypes. Using standard Python logical keywords (and,or,not) or unary expressions will result in runtime errors. - Consistent Outputs: Design all branches to return a consistent set of outputs to avoid ambiguity and ensure predictable workflow behavior. If branches have truly disparate outputs, consider structuring the workflow to handle these differences explicitly after the conditional block.