Skip to content

multi-file Workflow Example

Tip

This example uses the echo-multi app, make sure to read that reference documentation first.

This example showcases how to use multi-file applications within a Nextpipe workflow. A multi-file application differs from a json application in that it accepts a directory of files as input and produces a directory of files as output. Note that the workflow itself is also a multi-file application, however, this is a user choice (i.e., you could also create a json workflow that uses multi-file sub-applications).

Add the ann app.yaml manifest to run this example as a multi-file application.

app.yaml
type: python
runtime: ghcr.io/nextmv-io/runtime/python:3.11
python:
  pip-requirements: pyproject.toml
files:
  - main.py
configuration:
  content:
    format: multi-file
    multi-file:
      input:
        path: inputs
      output:
        solutions: outputs

Make sure that you have an inputs directory with some sample inputs files.

id,name,value
jumping,jack,10
running,jill,20
I have a rabbit and a hat.

Find the workflow code below (mind the comments explaining each step):

main.py
import os
import shutil

import nextmv
from nextpipe import FlowSpec, app, log, needs, step

manifest = nextmv.Manifest.from_yaml()


# >>> Workflow definition
class Flow(FlowSpec):
    # The first step receives the path to the input files directly (see main()) and
    # automatically zips the directory and passes it to the 'echo-multi' sub-app.
    @app(app_id="echo-multi")
    @step
    def solve1():
        """Runs a multi-file model."""
        pass

    # The second step receives the path to the output files from the first step. This path
    # will point to a temporary directory containing the output files from the first step.
    @needs(predecessors=[solve1])
    @step
    def transform(result_path: str):
        """Transforms the result for the next step."""
        # Just list the content of the result directory.
        log(f"Contents of result directory {result_path}:")
        for file_name in os.listdir(result_path):
            full_file_name = os.path.join(result_path, file_name)
            if os.path.isfile(full_file_name):
                log(f"- {file_name}")

        # Add a new file to the result for demonstration purposes.
        new_file_path = os.path.join(result_path, "additional_file.txt")
        with open(new_file_path, "w") as f:
            f.write("This is an additional file added in the transform step.\n")
        log(f"Added new file: {new_file_path}")

        return result_path

    # The third step receives the (modified) directory from the transform step and runs
    # another multi-file app on it.
    @app(
        app_id="echo-multi",
        # We specify the content type explicitly here. This is normally done via the app's
        # manifest, but we can do it explicitly like this too.
        run_configuration=nextmv.RunConfiguration(
            format=nextmv.Format(
                format_input=nextmv.FormatInput(
                    input_type=nextmv.InputFormat.MULTI_FILE
                ),
                format_output=nextmv.FormatOutput(
                    output_type=nextmv.OutputFormat.MULTI_FILE
                ),
            )
        ),
        full_result=True,
    )
    @needs(predecessors=[transform])
    @step
    def solve2(result: nextmv.RunResult):
        """Runs another multi-file model."""
        pass

    # The final step receives the output from 'solve2' as a full result object (see
    # 'full_result=True' above). In this case, the path to the output files is available
    # via 'result.output'.
    @needs(predecessors=[solve2])
    @step
    def prepare_output(result: nextmv.RunResult):
        """Transforms the result for the next step."""
        # Extract the path to the output files.
        result_path = result.output
        # Simply copy the files from the given directory to the expected output
        # directory.
        output_dir = manifest.configuration.content.multi_file.output.solutions
        os.makedirs(output_dir, exist_ok=True)
        for file_name in os.listdir(result_path):
            full_file_name = os.path.join(result_path, file_name)
            if os.path.isfile(full_file_name):
                shutil.copy(full_file_name, output_dir)


def main():
    # Run workflow (simply provide the path to the multi-file input)
    flow = Flow("DecisionFlow", manifest.configuration.content.multi_file.input.path)
    flow.run()
    # The last step of the flow already prepares the output in the requested directory,
    # so no need to do anything here anymore.


if __name__ == "__main__":
    main()

Run the example:

uv run main.py
[nextpipe] No application ID or run ID found, uplink is inactive.
[nextpipe] Flow: Flow
[nextpipe] nextpipe: v0.7.0
[nextpipe] nextmv: 1.10.1
[nextpipe] Flow graph steps:
[nextpipe] Step:
[nextpipe]   Definition: Step(solve1, StepRun(echo-multi, , {}, False))
[nextpipe]   Docstring: Runs a multi-file model.
[nextpipe] Step:
[nextpipe]   Definition: Step(transform, StepNeeds(solve1))
[nextpipe]   Docstring: Transforms the result for the next step.
[nextpipe] Step:
[nextpipe]   Definition: Step(solve2, StepNeeds(transform), StepRun(echo-multi, , {}, True))
[nextpipe]   Docstring: Runs another multi-file model.
[nextpipe] Step:
[nextpipe]   Definition: Step(prepare_output, StepNeeds(solve2))
[nextpipe]   Docstring: Transforms the result for the next step.
[nextpipe] Mermaid diagram:
[nextpipe] graph LR
  solve1(solve1)
  solve1 --> transform
  transform(transform)
  transform --> solve2
  solve2(solve2)
  solve2 --> prepare_output
  prepare_output(prepare_output)

[nextpipe] Mermaid URL: https://mermaid.ink/svg/Z3JhcGggTFIKICBzb2x2ZTEoc29sdmUxKQogIHNvbHZlMSAtLT4gdHJhbnNmb3JtCiAgdHJhbnNmb3JtKHRyYW5zZm9ybSkKICB0cmFuc2Zvcm0gLS0+IHNvbHZlMgogIHNvbHZlMihzb2x2ZTIpCiAgc29sdmUyIC0tPiBwcmVwYXJlX291dHB1dAogIHByZXBhcmVfb3V0cHV0KHByZXBhcmVfb3V0cHV0KQo=?theme=dark
[nextpipe] Running node solve1_0
[nextpipe] Started app step solve1_0 run, find it at https://cloud.nextmv.io/app/echo-multi/run/latest-ddBWdlEDR?view=details
[nextpipe] Running node transform_0
[transform_0] Contents of result directory /var/folders/_m/5zls419934123jw62wrrv_6h0000gn/T/nextpipe_output_jquiow53:
[transform_0] - input.csv
[transform_0] - input.txt
[transform_0] Added new file: /var/folders/_m/5zls419934123jw62wrrv_6h0000gn/T/nextpipe_output_jquiow53/additional_file.txt
[nextpipe] Running node solve2_0
[nextpipe] Started app step solve2_0 run, find it at https://cloud.nextmv.io/app/echo-multi/run/latest-qbkGdlPvR?view=details
[nextpipe] Running node prepare_output_0

Content of the output directory:

outputs
├── additional_file.txt
├── input.csv
└── input.txt

The resulting Mermaid diagram for this flow looks like this:

Mermaid Mermaid