Builder
In Slyme, as business logic complexity increases, you often need to compose many Nodes (such as @node, @expression, @wrapper) to build a complex execution tree (Node tree). To better manage and reuse these building logics, Slyme introduced the concept of Builder and provides the @builder decorator.
Simply put, Builder is a factory function specifically for instantiating and assembling Nodes.
@builder Decorator
The @builder decorator's core responsibility is to wrap your assembly logic and perform a series of safety checks when the function returns, ensuring you build a legal and robust Node tree.
Basic Usage
You can define a Builder like a normal function, just add the @builder decorator:
from slyme.builder import builder
from slyme.node import sequential
from slyme.context import Ref
# Assume nodes are already defined
# from my_nodes import load_data, process_data, save_data
@builder
def create_data_pipeline(source_path: str):
scope = {
"config": Ref("process_config"),
"output": Ref("output_path"),
}
# 1. Instantiate each Node (Def phase)
load_node = load_data(path=source_path)
process_node = process_data(scope)
save_node = save_data(scope)
# 2. Assemble and return a complete Node tree
return sequential(nodes=[load_node, process_node, save_node])Calling a Builder function does not execute the Node — it merely executes the internal assembly logic and returns the outermost Node Def instance:
pipeline_def = create_data_pipeline("/path/to/data")
# Convert to Exec (execution phase)
pipeline_exec = pipeline_def.prepare()
# ctx = pipeline_exec(ctx)TIP
@builder automatically checks the function's return value. If you forget the return when writing complex branching logic (causing it to return None), the framework will raise a clear ValueError exception, reminding you to return the built Node instance.
Structure Validation
By default, @builder automatically calls internal check_node_structure for deep structural legality validation of the entire Node tree before returning. As mentioned in the Node Structure Validation chapter, Slyme has strict constraints on the mutual holding relationships between different Node types (for example, @wrapper can only be mounted as middleware and cannot be passed as a parameter to @node, etc.).
If in some special scenarios (like an extremely frequently called internal sub-Builder, for performance reasons) you need to turn off this structural validation, you can explicitly pass check_structure=False:
from slyme.builder import builder
@builder(check_structure=False)
def fast_internal_builder():
# The Node returned here will skip structure validation
return load_data(path="...")Composition and Dynamic Modification
Builder's biggest advantage is reusability. You can have one Builder call another Builder, and thanks to Slyme Node's modifiable nature before .prepare(), you can easily fine-tune existing building results.
This is very useful when building different variants of pipelines, avoiding a lot of repetitive template code:
from slyme.builder import builder
from slyme.node import sequential
from slyme.context import Ref
def base_scope():
return {
"config": Ref("default_config"),
}
@builder
def base_pipeline():
scope = base_scope()
return sequential(nodes=[
load_data(path="default_path"),
process_data(scope),
])
@builder
def custom_pipeline(new_path: str):
my_scope = {"output": Ref("output_path")}
# 1. Get the base Node tree
pipeline = base_pipeline()
# 2. Dynamically modify specific Node's build-time parameters
pipeline["nodes"][0]["path"] = new_path
pipeline["nodes"].append(
save_data(my_scope)
)
return pipelineThrough this approach, you can combine small Builder blocks into large systems like building with LEGO, while maintaining extreme flexibility.