Step Fixture
Use Step Fixtures to Access the Stack from within a Step.
In general, when defining steps, you usually can only supply inputs that have been output by previous steps. However, there are some exceptions.
  • An object which is a subclass of BaseStepConfig: This object is used to pass run-time parameters to a pipeline run. It can be used to send parameters to a step that are not artifacts. You learned about this one already in the chapter on Step Configuration
  • A StepContext object: This object gives access to the active stack, materializers, and special integration-specific libraries.
These special parameters are comparable to Pytest fixtures, hence the name 'Step fixtures'.
Step fixtures are simple to use. Simply pass a parameter in with the right type hint as follows:

Using fixtures in the Functional API

1
@step
2
def my_step(
3
config: SubClassBaseStepConfig, # subclass of `BaseStepConfig`
4
context: StepContext,
5
artifact: str, # all other parameters are artifacts, coming from upstream steps
6
):
7
...
Copied!
Please note that the name of the parameter can be anything, but the type hint is what is important.

Using the BaseStepConfig

BaseStepConfig instances can be passed when creating a step.
We first need to sub-class it:
1
from zenml.steps import BaseStepConfig
2
3
class MyConfig(BaseStepConfig):
4
param_1: int = 1
5
param_2: str = "one"
6
param_3: bool = True
Copied!
Behind the scenes, this class is essentially a Pydantic BaseModel. Therefore, any type that Pydantic supports is supported.
You can then pass it in a step as follows:
1
from zenml.steps import step
2
@step
3
def my_step(
4
config: MyConfig,
5
...
6
):
7
config.param_1, config.param_2, config.param_3
Copied!
If all properties in MyConfig have default values, then that is already enough. If they don't all have default values, then one must pass the config during pipeline run time. You can also override default values here and therefore dynamically parameterize your pipeline runs.
1
from zenml.pipelines import pipeline
2
@pipeline
3
def my_pipeline(my_step):
4
...
5
6
pipeline = my_pipeline(
7
my_step=my_step(config=MyConfig(param_1=2)),
8
)
Copied!

Using the StepContext

Unlike BaseStepConfig, we can use the StepContext simply by adding it to our step function signature and ZenML will take care of passing the right thing when executing your step.
The StepContext provides additional context inside a step function. It can be used to access artifacts directly from within the step.
You do not need to create a StepContext object yourself and pass it when creating the step, as long as you specify it in the signature ZenML will create the StepContext and automatically pass it when executing your step.
When using a StepContext inside a step, ZenML disables caching for this step by default as the context provides access to external resources which might influence the result of your step execution. To enable caching anyway, explicitly enable it in the @step decorator or when initializing your custom step class.
Within a step, there are many things that you can use the StepContext object for. For example materializers, artifact locations, etc ...
1
from zenml.steps import StepContext, step
2
3
@step
4
def my_step(
5
context: StepContext,
6
):
7
context.get_output_materializer() # Returns a materializer for a given step output.
8
context.get_output_artifact_uri() # Returns the URI for a given step output.
9
context.metadata_store # Access to the [Metadata Store](https://apidocs.zenml.io/latest/api_docs/metadata_stores/)
Copied!
For more information, check the API reference
The next section will directly address one important use for the Step Context.