Skip to content

SDK and PXFLOW Studio

PXFLOW Studio opens a Flow from one of two authoring owners. An SDK-linked Flow is owned by its Python source and dependency lock; Studio opens the validated Flow revision materialized from that source. A PXFLOW-native Flow is owned by its .pxflow revision and can be authored directly on the Canvas. Report Workbench uses either Flow through public inputs and results while keeping Report mapping in .pxreport. The PXFLOW action in Project Home opens Studio.

Current Python facade and Component boundary

The current public facade provides px.component, px.Results, px.Flow, and px.Client. It does not provide px.input, px.result, px.setting, or px.client(). Current Function and Component ports come from ordinary Python parameter annotations and annotated px.Results fields.

@px.component currently registers process-local companion metadata; it does not compile a portable declaration or executor, resolve an installed release, or create a durable ComponentRef. Existing V1 presentation and settings wire remains compatible, and an already-saved exact target is preserved as read-only unsupported. Production exposes no new third-party Component placement. The current internal/test flow.node(...) resolver primitive can use a live px.Client to persist an exact ComponentRef, but that primitive is not production third-party admission. Caller and release policy keep it unexposed until both P0 closures are complete. The successor V2 Canvas mounts only the host-owned component.declarative@2; settings are declared through component-declared-settings@1, at most eight per declaration, and the host draws each control from its own registry. Rich surfaces belong to component-rich-surface-declaration@1, the RFC-016 successor, whose bridge grants reading the Node context and proposing a settings patch and nothing more. Exact ProjectScope data-path isolation is implemented, but third-party Component activation remains blocked until authenticated-principal authorization, execution-trust closure, and the explicit production gates are implemented.

In the SDK, flow.node(...) places a Node and flow.connect(...) defines a connection from a source port to a destination input. Studio displays the same Nodes and connections on the Canvas.

Use two-level SDK names

Every SDK example begins with from pipelinexlab import px. Current PipelineXLab root types and decorators keep the px. prefix: px.Flow, px.Report, px.Client, px.Results, @px.function, and @px.component. Once a root object exists, its methods state which document or operation they change:

python
from pipelinexlab import px


flow = px.Flow(
    "girder_review",
    label="Girder review",
    description="Checks girder demand against resistance.",
)
span = flow.input("span", float, unit="m", description="Clear span.")

report = px.Report("girder_report", title="Girder report")
basis = report.section("basis", title="Design basis")
report_span = basis.value("span", 12.0, unit="m")
connection = report.connect_flow("strength_check", flow)
connection.bind("report_span_to_span", report_span, to=flow.inputs.span)
report.save()

client = px.Client(workspace="engineering", project="bridge_project")
run = client.run(connection)

Here flow, report, basis, connection, and client are variables that hold objects created on the preceding lines. The fixed SDK names use px.*; follow-up work is called on the object that owns it. Imported package targets keep their package namespace, for example midas_components.model_viewer; this makes project code visibly different from installed capabilities. Every current public Python surface, including a hosted app, creates px.Client(workspace="engineering", project="bridge_project") with both scopes explicit. A host-injected lowercase px.client() convenience remains a planned contract.

Keep definitions, placements, and values separate

ConceptSDKPXFLOW Studio
Function@px.functionDefinitions → Functions
Component companioncurrent process-local @px.component metadatano new production placement; already-saved exact refs remain unsupported
Python helperplain Python def; a Function declares it via helpers=[...] so it travels in the Function revision, a Component keeps it in its executor modulenot a Canvas object
Nodeflow.node("node_key", target)placed Canvas instance
Flowflow = px.Flow("flow_key", ...)Canvas, interface, saved revision
Structured value · planned@px.record contractRecord schema in port details after marker delivery

@px.function registers an executable definition. Current @px.component registers only process-local companion metadata and annotation-derived ports. Successor V2 presentation is the host-owned component.declarative@2 projection only. Function Nodes come from explicit flow.node(...) placement. New third-party Component placement is blocked in production; the current internal/test resolver primitive can resolve and persist an exact ref, but its existence does not authorize production use. Only after both P0 closures may the product expose that spelling for an admitted exact release in the same Project. Plain Python helper functions -- declared on the Function via helpers=[...] so their source is committed into its revision -- are not placement targets.

One .py Flow module exports exactly one public module-level flow object. Use a separate module for each additional Flow. Complex Flows continue with top-level declarative placement and wiring statements.

Code-to-canvas example

The two flow.connect(...) declarations connect a Flow input or named Node result to a Node input. flow.result(...) connects a named Node result to a public Flow result.
python
from pipelinexlab import px


class GirderResults(px.Results):
    utilization: float


def calculate_utilization(
    span: float,
    permanent_load: float,
    variable_load: float,
) -> float:
    governing_load = permanent_load + variable_load
    return governing_load / (span * 100.0)


@px.function(
    key="girder_resistance",
    version="1.0.0",
    description="Calculates girder utilization from two design loads.",
)
def girder_resistance(
    span: float,
    permanent_load: float,
    variable_load: float,
) -> GirderResults:
    return GirderResults(
        utilization=calculate_utilization(span, permanent_load, variable_load)
    )


flow = px.Flow(
    "girder_review",
    label="Girder review",
    description="Checks girder utilization for design load cases.",
)
span = flow.input("span", float, unit="m", description="Clear span.")
permanent_load = flow.input(
    "permanent_load", float, unit="kN", description="Permanent design load."
)
variable_load = flow.input(
    "variable_load", float, unit="kN", description="Variable design load."
)
resistance_check = flow.node("resistance_check", girder_resistance)
flow.connect(span, resistance_check.inputs.span)
flow.connect(permanent_load, resistance_check.inputs.permanent_load)
flow.connect(variable_load, resistance_check.inputs.variable_load)
flow.result("utilization", resistance_check.results.utilization)
SDK declarationPXFLOW Studio
@px.functionshared Function in Definitions
flow = px.Flow(...)one Flow and saved revision stream
flow.node("resistance_check", ...)Function Reference Node
flow.connect(...)typed input connections
flow.result(...)public typed Flow Result
px.Results fieldsnamed result ports
@px.record · plannedstructured value schema after marker delivery

Each flow.connect(source, destination) call remains one addressed typed connection. When one structured public input feeds several Nodes, the Canvas may collapse the parallel lines into a boundary summary with its exact binding count. This reduces visual noise without merging the connections; Python still shows and locates every flow.connect(...) declaration.

The SDK-authored Flow shown as Nodes and typed connections in PXFLOW

Use the Flow from a Report

Dotted lines are Report-to-Flow mappings owned by `.pxreport`; the solid line is the internal Flow connection owned by `.pxflow` and `flow.connect(...)`. Creating a Report connection does not change the Flow Python source.

The SDK and PXFLOW Studio author the calculation structure. px.Report or Report Workbench defines where a Report value enters the public Flow boundary and where a public Result appears in the Report. In Python, create report = px.Report(...), then receive connection = report.connect_flow(...); .bind() and .show() belong to that Connected Flow handle.

RelationshipDefine or edit it hereOwner
Public Flow inputSDK flow.input(...) or the Studio Flow interfaceSDK-linked Python source or PXFLOW-native .pxflow
Internal port connectionSDK flow.connect(...) or a Studio Canvas connectionSDK-linked Python source or PXFLOW-native .pxflow
Public Flow resultSDK flow.result(...) or the Studio Flow interfaceSDK-linked Python source or PXFLOW-native .pxflow
Report-authored value → public Flow inputConnected Flow handle .bind() or Report Workbench Use report input.pxreport
Public Flow result → Report positionConnected Flow handle .show() or Report Workbench Show result.pxreport

Adding a connection from Report Workbench selects the saved public-port contract and updates the Report mapping. The SDK-linked Python source and the internal Canvas connections keep the same structure. Several Reports can therefore reuse one Flow with different authored values and result positions.

The connection Overview in Report Workbench traces the selected route from a public input through the internal Nodes to a public result as a read-only view. Choose Open in PXFLOW Studio when the Flow itself needs editing.

The two Node target kinds

An explicit flow.node(...) placement accepts:

  1. an @px.function target for a Function Reference Node; or
  2. after both P0 closures, an imported @px.component companion matched to an admitted exact release in the same Project.

Current production does not expose the second form for new third-party placement. It preserves any already-saved exact ComponentRef as unsupported. Successor V2 uses only the host-owned declarative Node projection.

Each decorator requires an explicit stable lower_snake_case key. Renaming the Python implementation symbol does not rename the registered target or its placed Nodes.

flow.group(...) stores editable visual membership between existing same-Flow Node handles. flow.subflow("subflow_key", imported_flow) places an imported separate Flow as a read-only External reference; Edit source Flow opens its owner.

python
from structural_sections.flows import section_properties as section_properties_module


geometry = flow.input(
    "geometry",
    SectionGeometry,
    description="Section geometry.",
)
section_properties = flow.subflow(
    "section_properties",
    section_properties_module.flow,
)
flow.connect(geometry, section_properties.inputs.geometry)
flow.result("area", section_properties.results.area)

The caller connects the External reference interface while the imported Flow retains ownership of its internal Nodes and source.

Choose a value shape

Value sent through one portAnnotation
scalarfloat, str, bool
one structured value · plannedan @px.record type
one list valuelist[T]
one typed table value · plannedpx.Table[RecordType]

A Table binding is one value and one Flow Run input. For several scalar input sets, open Run → New batch, review the complete input sets, and start explicit Runs against the same saved Flow revision.

Choose the authoring owner

Studio edits follow the authoring owner. SDK-linked uses a source-safe diff, Apply, and rematerialization; PXFLOW-native saves a .pxflow revision and is the only mode that offers SDK code preview.

SDK-linked

Python source and its dependency lock are the owner.

  • Studio shows a reviewed source diff for supported edits.
  • Apply stages the candidate diff, validates and materializes it, then commits the source and Flow revisions together after semantic parity passes.
  • Unsupported source-safe edits offer code editing or an explicit PXFLOW-native copy.
  • A failed validation or parity check keeps the previous source and Flow revisions.

While Flow is selected, use the separate Visual / Python switch in the top toolbar. Visual edits the Canvas; Python edits the same Flow module, including px.Flow, public boundaries, Node placements, connections, Groups, and Subflows. Switching views does not save. Update Flow stages and validates the candidate, materializes a candidate Flow, checks semantic parity, and then commits the source revision and Flow revision together before replacing the Canvas. A failed update keeps the Python draft, last valid source, and last valid Visual Flow. While a dirty or invalid Python draft exists, Visual identifies the last applied Flow and locks structural editing until the user chooses Update Flow or Discard. Selecting a Node in the Flow outline locates its Canvas Node in Visual or its flow.node(...) declaration in Python.

Shared @px.function bodies remain in Project Definitions, and installed Component implementation remains with its owning package.

PXFLOW-native

The .pxflow direct object is the owner.

  • Place shared Functions or imported Flows. Component discovery and placement in the dock are planned.
  • Edit Node keys, Groups, and typed bindings. V2 settings are declared through component-declared-settings@1, at most eight per declaration.
  • Save creates a new Flow revision.

Studio-to-SDK write-back for an SDK-linked Flow is a source-safe code action against the existing Python module, followed by forward rematerialization and semantic comparison with the reviewed candidate Flow. A PXFLOW-native Flow can show derived SDK code as a structural preview, while its .pxflow direct object remains the authoring owner. Python bodies, helpers, import layout, comments, and formatting stay in Python source, and multiple Python programs can materialize the same Flow. Studio therefore preserves an existing source and applies reviewed source-safe edits to it.

Use semantic keys

  • Use stable lower_snake_case Flow, Function, Node, Group, and port keys.
  • Keep each Node and Group key unique within its Flow.
  • Treat a key rename as an impact-reviewed change.
  • Use semantic keys for connection addresses; labels, UUIDs, local paths, source positions, variable names, and canvas coordinates remain presentation or implementation details.

Declare ports once

For a current Function or Component companion, the parameter name and annotation own an input key and type. An annotated px.Results field owns a result key and type. px.input(...) and px.result(...) are current markers that add description and unit in the default-value position; connection=False metadata stays a contract_only plan. px.setting(...) declares one px.Settings field, and component-declared-settings@1 owns that schema. Project ACLs and execution policy remain separate from port discovery.

Component packages use the same Flow contract

A Python distribution may export several @px.component companion targets. The current portable V1 container carries one declaration and executor entry; there is no automatic decorator to package compiler. The current CLI install path is internal/test-only for third-party containers: its contribution data path has exact Project isolation, but it has neither authenticated-principal authorization nor an inactive quarantine state, so it is not a supported production admission procedure. The saved exact ComponentRef remains readable but unsupported in the production Flow reader. Import and placement become supported only after the authorization and execution-trust closure described above and the explicit production gates are implemented.

Multi-declaration package compilation and Components-dock discovery belong to the versioned portable-container V2. Its Canvas surface is host-owned component.declarative@2, with settings declared through component-declared-settings@1. Package renderer/modal/editor contributions are not V2 fields; rich surfaces belong to component-rich-surface-declaration@1, the RFC-016 successor.

Run and Result

SDK client.run(...) after client = px.Client(workspace="engineering", project="bridge_project"), PXFLOW Run, and MCP pxflow_run execute the same supported saved Flow revision and produce typed Results. Current Component targets stop at the production reader's unsupported state. Exact ProjectScope data-path isolation is implemented, but Component execution is not a supported third-party capability: Validate and Run remain blocked until Project authorization and execution-trust closure are implemented. Rich artifact presentation is outside V2 and belongs to component-rich-surface-declaration@1, the RFC-016 successor.

Continue

Create a personal service Project

Connect a px.Client to the service with workspace="personal", the new Project key, service_url, access_token, and the selected organization, then call:

python
project_ref = client.create_project(
    name="Section analysis",
    command_id="create_section_analysis",
)
# Use the same Client for the existing save(), run(), and open_report() APIs.

create_project(*, name: str, command_id: str) -> str returns an opaque catalog reference; it does not replace the SDK Project key. Keep the same request ID, scope and name when retrying after losing a response. A replay returns the original Project without duplicating grants or audit records. Changing the request body under that ID or adopting an existing Project key is refused. The current personal account, memberships, V1 eligibility and explicit creation authority must still permit the request. Login and replay never restore revoked creation authority.

The token must permit product.runtime.handshake, project_search, and product.project.create. Existing save/run operations need their own token scopes as well as Project capabilities. For example, Flow saving calls pxflow_validate, pxflow_describe, pxflow_plan_change, and pxflow_apply_change. Catch px.Error and inspect its diagnostics. The Client reference lists the conflict and permission diagnostics and the UTF-8 input limits.

This operation creates a personal service Project. Local-only SDK authoring and execution retain their existing behavior and do not require it. A compatible SDK/server operation descriptor is required; check operation availability and account provisioning on the service you use.