Author a Flow and Report with Python
Write calculations and reports in Python instead of on screen. What you save is the same thing the screens produce, so you can open it afterwards in PXFLOW Studio and Report Workbench and keep editing there.
One pipelinexlab SDK authors both parts.
| What you author | Object | What it does | Saved file | Where it opens |
|---|---|---|---|---|
| Calculation | px.Flow | Takes input values, calculates, and exposes results | .pxflow | PXFLOW Studio |
| Report | px.Report | Holds body text, values, tables, and the positions that display results | .pxreport | Report Workbench |
Both objects can sit in one Python file, and saving still writes two documents. To use one calculation in several Reports, author a single px.Flow and connect it from each Report, then vary only the Report content.
pip install pipelinexlabfrom pipelinexlab import pxRead every SDK call by its owner
The public SDK uses two levels of names. Start PipelineXLab declarations with px., then call actions on the object that owns the change:
| Create it first | Object or handle returned | Methods available there |
|---|---|---|
flow = px.Flow(...) | Flow object flow | .input(), .node(), .connect(), .result() |
report = px.Report(...) or px.Report.open(...) | Report object report | .section(), .connect_flow(), .save() |
summary = report.section(...) | Section handle summary | content, attachment, and result-position methods |
connection = report.connect_flow(...) | Connected Flow handle connection | .bind(), .show() |
client = px.Client(workspace=..., project=...) | Client object client | .run(), .run_many(), .run_batch() |
This keeps each call explicit without putting unrelated operations into one flat px.* namespace. Do not import PipelineXLab core types as unqualified names; begin examples with from pipelinexlab import px. @px.function registers a reusable calculation target, while @px.component currently registers V1 companion descriptor metadata only. The Component decorator does not materialize a package declaration or executor, grant production install admission, or make a third-party Flow Node executable. Undecorated functions remain implementation helpers.
Component successor only
The code below is a successor V2 contract-only example, not a current production procedure. Exact ProjectScope data-path isolation is implemented. Production third-party Component activation and install admission stay blocked until Project authorization and execution-trust closure are complete. The current reader preserves the exact target as read-only unsupported. A future Canvas projection uses only the host-owned component.declarative@2 renderer; settings are declared through component-declared-settings@1, at most eight per declaration, and the host draws each control from its own registry, and package-owned rich UI belongs to component-rich-surface-declaration@1 (RFC-016). See Component container V2 and third-party Node UI.
Only in an approved successor does a package Component target keep its public package namespace:
from midas_civil.components import model_viewer
viewer = flow.node("model_viewer", model_viewer)2
3
All public Python calls, including PipelineXLab-hosted surfaces, use px.Client(workspace="...", project="...") with an explicit Workspace and Project scope. The lowercase px.client() convenience is reserved for a planned host-scope contract and is not part of the current facade.
Know which document you are authoring
| Python object | What you author | Saved document | Visual editor |
|---|---|---|---|
px.Flow | public inputs, Nodes, Node connections, public results | .pxflow | PXFLOW Studio |
px.Report | sections, text, values, tables, result positions, Connected Flows | .pxreport | Report Workbench |
Python, Report Workbench, and AI editing commands update the same Report document. They do not create separate kinds of Report.
One Flow module exports exactly one public flow object. A small Project may keep that Flow and one Report in the same .py file. Multiple Flows use separate modules and are imported where needed. In both layouts, report.connect_flow(...) creates the relationship in the Report draft; report.save() pins the exact Flow revision and persists the connection.
Author the complete path
This example creates a Flow, authors two Report values, connects those values to Flow inputs, chooses a Report result position, saves the documents, and then starts the saved connection.
from pipelinexlab import px
class CheckResults(px.Results):
utilization: float
@px.function(
key="check_resistance",
version="1.0.0",
description="Checks girder demand against resistance.",
)
def check_resistance(
span: float,
demand: float,
) -> CheckResults:
resistance = span * 100.0
return CheckResults(utilization=demand / resistance)
# 1. Calculation Flow
flow = px.Flow(
"girder_check",
label="Girder check",
description="Checks girder demand against resistance.",
)
span = flow.input("span", float, unit="m", description="Clear span.")
demand = flow.input(
"demand",
float,
unit="kN.m",
description="Design demand.",
)
check = flow.node("resistance_check", check_resistance)
flow.connect(span, check.inputs.span)
flow.connect(demand, check.inputs.demand)
flow.result("utilization", check.results.utilization)
# 2. Report content and authored values
report = px.Report("girder_report", title="Girder review")
basis = report.section("design_basis", title="Design basis")
basis.text("purpose", "Review the governing girder check.")
report_span = basis.value("span", 12.0, unit="m")
report_demand = basis.value("demand", 860.0, unit="kN.m")
summary = report.section("check_summary", title="Check result")
utilization_slot = summary.result_slot(
"utilization",
label="Utilization",
)
# 3. Report–Flow connection
strength = report.connect_flow("strength_check", flow)
strength.bind("span_input", report_span, to=flow.inputs.span)
strength.bind("demand_input", report_demand, to=flow.inputs.demand)
strength.show(
"utilization_result",
flow.results.utilization,
at=utilization_slot,
)
# Keep import and discovery free of save and run side effects.
if __name__ == "__main__":
# 4. Open the session that saves and runs
with px.Client(workspace="engineering", project="bridge_project") as client:
# 5. Save the Report and its exact Flow reference
report_revision = report.save()
# 6. Start the saved connection
run = client.run(strength)2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
Steps 1–5 are authoring operations. They validate and save the Flow and Report but do not perform the calculation. client.run(...) starts the calculation and returns the Run you can follow in Project Activity.
When a new Flow object and Report are authored in the same script, the authoring runner validates and saves the Flow first. It then saves the Report with the exact Flow revision and public port keys selected by the connection.
Match Python to the screen
| Screen action | Owner and Python call | What it creates |
|---|---|---|
| New Flow | SDK constructor flow = px.Flow(...) | a Flow object being authored |
| Place and connect Nodes | flow.node(...), flow.connect(...) | the Flow calculation structure |
| Add a Flow result | flow.result(...) | a result other documents can connect to |
| New Report | SDK constructor report = px.Report(...) | a Report object being authored |
| Add section | Report object summary = report.section(...) | one Section handle |
| Add text, value, or table | Section handle .text(), .value(), .table() | Report content |
| Add a result position | Section handle .result_slot() | a position that displays a calculation result |
| Connected Flows → Add Flow | Report object connection = report.connect_flow(...) | one Connected Flow handle |
| Use report input | Connected Flow handle .bind() | Report value → Flow input |
| Show result | Connected Flow handle .show() | Flow result → Report position |
| Save | Report object .save() | a new .pxreport revision |
| connection Run | Client object .run(connection) | one Flow Run |
| Run selected | client.run_many(connections) | one independent Run per connection |
| Batch Run | client.run_batch(...) | one child Run per scalar case |
The variable returned by report.connect_flow(...), such as strength, is a draft connection handle. After report.save() pins and persists it, pass that handle to client.run(...).
Split the Flow and Report into separate files
Export the flow object from the Flow module:
# project_flows/girder_check.py
from pipelinexlab import px
flow = px.Flow(
"girder_check",
label="Girder check",
description="Checks girder demand against resistance.",
)
# Declare inputs, Nodes, connections, and results here.2
3
4
5
6
7
8
9
Import that object into the Report module:
# project_reports/girder_report.py
from pipelinexlab import px
from project_flows.girder_check import flow
report = px.Report("girder_report", title="Girder review")
connection = report.connect_flow("strength_check", flow)
# Bind Report values and choose result positions here.
if __name__ == "__main__":
report.save()2
3
4
5
6
7
8
9
10
Splitting files changes only the Python layout. The saved relationship remains one .pxreport connection to an exact .pxflow revision.
Run several connected Flows
Running one Report–Flow connection creates one independent Run. Pass several connections to client.run_many(...) to start them together. The Runtime observes the Project's concurrency limit, and completion order may differ from the list order.
The same Flow may appear in several connections. Each connection keeps its own input mappings, result positions, current state, and Run history.
strength_uls = report.connect_flow("strength_uls", flow)
strength_sls = report.connect_flow("strength_sls", flow)
report.save()
runs = client.run_many([strength_uls, strength_sls])2
3
4
5
Project Activity shows these Runs together. Cancelling or retrying one Run does not change the other connections.
Choose one collection Run or a Batch
Connecting a typed List or Table to one Flow input sends the complete collection as one value and creates one Run. Use a Batch when the same scalar calculation must run once for each case.
# Leave span and demand unbound in this connection; each case supplies them.
strength_cases = report.connect_flow("strength_cases", flow)
cases = [
{"case_key": "uls_01", "span": 12.0, "demand": 860.0},
{"case_key": "uls_02", "span": 12.0, "demand": 920.0},
]
if __name__ == "__main__":
report.save()
client = px.Client(workspace="engineering", project="bridge_project")
batch = client.run_batch(
strength_cases,
cases,
case_key="case_key",
inputs={
"span": flow.inputs.span,
"demand": flow.inputs.demand,
},
)2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
A Batch creates one child Run for each case_key. Child Runs may execute in parallel up to the Project limit, and each child keeps its own status and Result. Ports supplied through inputs are left unbound in the saved strength_cases connection. To project child results into a Report Table, save a result-table mapping and pass its stable key as result_mapping; completion order never defines row identity.
Put calculation dependencies inside a Flow
When Flow B needs a result from Flow A, connect them inside a parent Flow instead of relying on the order of two Report connections.
pipeline = px.Flow(
"design_pipeline",
label="Design pipeline",
description="Runs section properties before the strength check.",
)
section = pipeline.subflow("section_properties", section_properties_flow)
strength_check = pipeline.subflow("strength_check", strength_check_flow)
pipeline.connect(
section.results.section,
strength_check.inputs.section,
)
pipeline.result(
"utilization",
strength_check.results.utilization,
)2
3
4
5
6
7
8
9
10
11
12
13
14
15
Connect the completed parent Flow to the Report once. PXFLOW Studio and the Runtime then use the connections inside that Flow as the calculation order.
Edit a draft without saving first
Use client.read_report(...) for a selected part of an exact saved revision, or report.capture_context(reads=..., writes=...) for current unsaved blocks and inputs. Include every referenced block in reads and each edit target in writes. report.apply_edits(edits, context=context, values=..., cells=...) validates body, input, and table edits together before changing the draft. Changed references or targets cause refusal; review the current content before trying again. Persist accepted edits with report.save().
Call attachment.capture() to freeze an unsaved image's bytes for later reads and saving, even if the original file changes. This does not upload the file or provide restart recovery. Context capture does not read image bytes automatically. These APIs do not run an AI model. See the linked API reference for examples, size limits, and conflict rules.