Skip to content

Execution flow of a composed app

How data flows through a composed pipeline, step by step.

Consider two apps composed into a pipeline:

from scinexus import define_app


@define_app(app_type="loader")
def read_json(path: str) -> dict:
    import json

    with open(path) as f:
        return json.load(f)


@define_app
def validate(data: dict, required_field: str) -> dict:
    if required_field not in data:
        raise ValueError(f"missing {required_field!r} field")
    return data


app = read_json() + validate(required_field="name")

Composing with + creates a new app where validate is the outermost app and read_json is stored as its .input attribute. When you call app(filepath), execution begins at the outermost app and works inward.

The execution flow when you call app(filepath)

flowchart TD
    entry["Executes scinexus __call__(val)"] --> none{val is None?}
    none -- yes --> nc_none[create and return NotCompleted ERROR, recording current app as origin]
    none -- no --> nc{val is NotCompleted?}
    nc -- yes --> nc_return[returns same NotCompleted]
    nc -- no --> has_input{has an input app?}
    has_input -- yes --> call_input["call input(val), which enters the top of this chart"]
    call_input --> input_nc{result is NotCompleted?}
    input_nc -- yes --> nc_input[return same NotCompleted]
    input_nc -- no --> type_check
    has_input -- no --> type_check{val type is valid for self}
    type_check -- fail --> nc_type[create and return NotCompleted ERROR]
    type_check -- pass --> main["main(val)"]
    main -- exception --> nc_main[NotCompleted ERROR]
    main -- success --> result["return result (which may be NotCompleted FAIL)"]

    classDef errorNode fill:#fde0c8,stroke:#333
    classDef successNode fill:#c8e0fd,stroke:#333
    class nc_none,nc_return,nc_input,nc_type,nc_main errorNode
    class result successNode

This is the same sequence for every composed app, regardless of pipeline length. Each app in the chain runs the same __call__ checks, so NotCompleted propagation and exception handling are consistent throughout. See Runtime type checking for details on how type validation works and how to disable it.