Action Flow Port Connectivity
This example checks whether every port (in item / out item) on each sub-action
inside an action definition is covered by at least one item flow. Unconnected ports are
reported so that incomplete wiring can be caught early.
This example uses the same DataPipeline model as Action Flow Type Compatibility and
Action Flow Latency Analysis Labs. In this model, the Publish action has an in item tag :
Tag port that is intentionally left without a flow.
Concepts Used
API |
Purpose |
|---|---|
Port endpoints of an item flow ( |
|
Owning action usages at each end of the flow ( |
|
Item ports declared on an action usage |
|
Direction of a port feature ( |
Example Model
package DataPipeline {
private import ISQBase::DurationValue;
private import SI::min;
item def RawData;
item def CleanData;
item def AnalysisResult;
item def Report;
item def Tag;
action def Ingest {
doc /* Read raw data from a source. */
out item raw : RawData;
attribute latency : DurationValue = 2 [min];
}
action def Clean {
doc /* Remove noise and normalize the raw data. */
in item raw : RawData;
out item clean : CleanData;
attribute latency : DurationValue = 5 [min];
}
action def Analyze {
doc /* Run statistical analysis on clean data. */
in item clean : CleanData;
out item result : AnalysisResult;
attribute latency : DurationValue = 10 [min];
}
action def Publish {
doc /* Format and publish the result.
Expects a Report but receives an AnalysisResult — a type mismatch. */
in item report : Report;
in item tag : Tag; // intentionally left unconnected
attribute latency : DurationValue = 1 [min];
}
action def Pipeline {
first start;
then action ingest : Ingest;
then action clean : Clean;
then action analyze : Analyze;
then action publish : Publish;
flow from ingest.raw to clean.raw;
flow from clean.clean to analyze.clean;
// Type mismatch: AnalysisResult is not a subtype of Report
flow from analyze.result to publish.report;
// publish.tag is intentionally left without a flow
}
}
Example Script
import pathlib
import syside
EXAMPLE_DIR = pathlib.Path(__file__).parent
MODEL_FILE_PATH = EXAMPLE_DIR / "example_model.sysml"
type ConnectedPort = tuple[str, str]
def check_port_connectivity(model: syside.Model, action_def_name: str) -> None:
"""Check whether every port on each sub-action inside a named action
definition is covered by at least one item flow. Report any port that
is not connected."""
# Build a set of (action qualified name, port name) pairs that appear
# as endpoints in at least one FlowUsage.
connected: set[ConnectedPort] = set()
for flow in model.nodes(syside.FlowUsage):
owning_def = flow.owning_definition
if owning_def is None or owning_def.name != action_def_name:
continue
# Record source port
src_action = flow.source_feature
src_port = flow.source_output_feature
if (
src_action is not None
and src_port is not None
and src_port.name is not None
):
connected.add((str(src_action.qualified_name), src_port.name))
# Record target port
for tgt_action in flow.target_features.collect():
tgt_port = flow.target_input_feature
if tgt_port is not None and tgt_port.name is not None:
connected.add((str(tgt_action.qualified_name), tgt_port.name))
# Compare each sub-action's ports against the connected set.
unconnected_found = False
for action in model.nodes(syside.ActionUsage):
owning_def = action.owning_definition
if owning_def is None or owning_def.name != action_def_name:
continue
ports = {*action.inputs.collect(), *action.outputs.collect()}
for port in ports:
if port.name is None:
continue
action_qname = str(action.qualified_name)
key: ConnectedPort = (action_qname, port.name)
if key not in connected:
direction = "in"
if port.direction == syside.FeatureDirectionKind.Out:
direction = "out"
elif port.direction == syside.FeatureDirectionKind.Inout:
direction = "inout"
print(
f" [UNCONNECTED] {action.name}.{port.name} ({direction})"
)
unconnected_found = True
if not unconnected_found:
print(" All ports are connected.")
def main() -> None:
(model, diagnostics) = syside.load_model([MODEL_FILE_PATH])
assert not diagnostics.contains_errors(warnings_as_errors=True)
action_def = "Pipeline"
print(f"Port connectivity report for {action_def}:")
check_port_connectivity(model, action_def)
if __name__ == "__main__":
main()
Output
Port connectivity report for Pipeline:
[UNCONNECTED] publish.tag (in)
Download
Download this example here.