Python SDK 문법
API 이름으로 바로 찾고 짧은 예제부터 보려면 Python SDK API reference를 사용하세요. 이 페이지는 API가 함께 지켜야 하는 정확한 authoring 계약과 저장 경계를 설명합니다.
현재 Python facade와 계획 문법의 경계
현재 공개 facade는 px.component, px.Results, px.Record, px.Settings, px.Flow, px.Client와 metadata marker px.input, px.result, px.setting을 제공하지만 px.client()는 제공하지 않습니다. 현재 Function과 Component port의 type은 callable의 일반 Python parameter annotation과 px.Results subclass의 annotated field가 정하고, marker가 그 선언부에 description·unit·required를 더합니다. px.setting은 component-declared-settings@1이 소유하는 instance setting 선언 문법입니다.
또한 @px.component는 process-local descriptor를 등록할 뿐 portable Component container의 declaration·executor·서명 입력을 자동 생성하지 않습니다. Flow reader의 기본 projection은 exact Component target을 unsupported로 보존합니다. production host가 명시적으로 연결한 componentPresentations resolver가 같은 exact installed target을 찾으면 host-owned component.declarative@2로 ready projection을 반환하고, declaration이 없거나 잘못됐으면 unsupported를 유지합니다. package UI는 Canvas에 마운트하지 않으며 settings는 component-declared-settings@1이, rich surface는 RFC-016의 component-rich-surface-declaration@1이 소유합니다.
가장 짧은 사용 흐름
pipelinexlab package 하나가 Flow와 Report API를 함께 제공합니다.
pip install pipelinexlabfrom pipelinexlab import px계산 구조는 px.Flow, 문서와 Flow 연결은 px.Report로 작성합니다. 두 객체는 같은 Python 파일에 둘 수 있으며 별도 package나 별도 설치가 필요하지 않습니다.
| SDK 객체 | 작성하는 것 | 저장 결과 |
|---|---|---|
px.Flow(...) | public input/result, Node와 내부 연결 | .pxflow |
px.Report(...) | 본문, 값·표, Connected Flow와 result 위치 | .pxreport |
SDK 이름을 읽는 규칙
PipelineXLab SDK의 이름은 두 단계로 읽습니다. px.*는 PipelineXLab이 제공하는 정의이고, 그 정의로 만든 객체의 작업은 해당 객체 이름 뒤에서 찾습니다.
| 생성 코드 | 반환 객체·handle | 소유한 메서드·접근자 |
|---|---|---|
flow = px.Flow(...) | Flow 객체 flow | .input(), .node(), .connect(), .result(), .group(), .subflow() |
report = px.Report(...) | Report 객체 report | .section(), .connect_flow(), .save() |
section = report.section(...) | Section handle section | .text(), .value(), .table(), .result_slot() |
connection = report.connect_flow(...) | Connected Flow handle connection | .bind(), .show() |
client = px.Client(workspace=..., project=...) | Client 객체 client | .run(), .run_many(), .run_batch() |
node = flow.node(...) | Node handle node | node.inputs.span, node.results.utilization |
표의 flow, report, section, connection, client, node는 사용자가 정하는 변수 이름입니다. 고정 SDK 이름은 px.*로 표시하며, 객체를 받은 뒤에는 그 변수에서 메서드를 호출합니다.
따라서 Flow module에서 flow.node(...)를 읽으면 어느 Flow를 바꾸는지 바로 보입니다. IDE도 flow. 뒤에는 Flow 작성 기능을, report. 뒤에는 Report 작성 기능을 제안할 수 있습니다. 숨은 current Flow나 current Project를 선택하도록 px root에 global node·connect·run을 두지 않습니다. core symbol은 package root에서 개별 import하지 않고 from pipelinexlab import px 아래에서 사용합니다.
설치한 Component target은 제공 package의 이름을 유지합니다.
from pipelinexlab import px
import midas_civil.components as midas_components
flow = px.Flow(
"model_review",
label="Model review",
description="Reviews one structural model.",
)
viewer = flow.node("model_view", midas_components.model_viewer)
client = px.Client(workspace="engineering", project="bridge_design")현재 공개 facade에는 px.client() 편의 함수가 없습니다. hosted surface를 포함한 공개 Python 호출은 px.Client(workspace="engineering", project="bridge_design")처럼 Workspace와 Project 범위를 명시합니다.
Pure bytes codec와 exact public signature index
아래 spelling은 RFC-001이 승인한 V1 codec implementation입니다. sdk/python의 pure Python wrapper가 private native adapter를 통해 같은 Rust core를 호출하며 Python codec fallback은 없습니다.
px.loads(data: bytes) -> object
px.dumps(document) -> bytespx.loads는 path, file object나 stream이 아니라 bytes만 받습니다.px.dumps는 generated direct document만 받고 bytes만 반환합니다.- file lock, atomic save, custody, revision commit과 recovery는 Host가 소유합니다.
- codec은 legacy YAML·manifest를 추측해 읽거나 remote service로 fallback하지 않습니다.
- 지원하는 direct document는
.pxflow와 저장된 Report revision인.pxreport입니다.report.document()가 정확한 저장 revision을 읽고,px.dumps는 그 불변 값을 변환합니다. - 두 spelling은 현재 public facade와 API index에
implemented로 노출합니다.
Machine profile은 Flow뿐 아니라 공개 Report·Runtime signature도 모두 기록합니다. 변수별 예제 이름을 API 이름으로 만들지 않고 section, table, connection, report, client receiver로 정규화합니다.
px.Report(report_key: str, *, title: str)
px.Report.open(report_key: str, *, workspace: str, project: str, revision: str | None = None) -> 'Report'
report.section(section_key: str, title: str) -> '_Section'
section.text(block_key: str, text: str) -> '_ReportBlock'
section.value(value_key: str, value: bool | int | float | str | decimal.Decimal, unit: str) -> '_ReportValue'
section.table(table_key: str, rows: list | tuple, *, row_key: str) -> '_Table'
table.select(*, rows: list | tuple, fields: list | tuple) -> '_TableSelection'
section.result_slot(slot_key: str, label: str = ...) -> '_ReportResultSlot'
section.result_table(table_key: str, *, row_key: str, fields: list | tuple) -> '_ReportResultTable'
section.artifact_slot(slot_key: str, *, label: str, accepts: list | tuple) -> '_ReportArtifactSlot'
section.attachment(attachment_key: str, source: str, *, media_type: str) -> '_ReportAttachment'
report.connect_flow(connection_key: str, flow: 'Flow') -> '_FlowConnection'
report.connection(connection_key: str) -> '_FlowConnection'
connection.bind(mapping_key: str, source: object, *, to: '_Port', fields: dict | None = None, row_key: dict | None = None) -> None
connection.show(mapping_key: str, source: '_Port', *, at: object, fields: dict | None = None, row_key: dict | None = None) -> None
connection.remove_mapping(mapping_key: str) -> None
report.move(block_key: str, *, to: str | None = None, before: str | None = None, after: str | None = None) -> None
report.remove(block_key: str) -> None
report.remove_connection(connection_key: str) -> None
report.save() -> ReportRef
report.plan_pages(page_setup: dict | None = None) -> PagePlan
report.export_pdf(path: str | os.PathLike, page_setup: dict | None = None, on_existing: str = "refuse") -> PdfExport
report.preview_html(path: str | os.PathLike) -> str
px.Client(*, workspace: str, project: str, timeout: float | None = ..., service_url: str | None = None, access_token: str | None = None, organization: str | None = None)
client.create_project(*, name: str, command_id: str) -> str
client.search_catalog(*, workspace_ref: str, name_prefix: str | None = None, kind: str | None = None, sort: str = "nameAscending", limit: int = 50, cursor: str | None = None) -> dict
client.save(flow: object) -> FlowRef | ReportRef
client.plan_flow_change(flow: FlowRef, *, commands: list | tuple) -> dict
client.apply_flow_change(plan: dict, *, command_id: str) -> FlowRef
client.search_functions(*, function_key_prefix: str | None = None, sort: str = "functionKeyAscending", limit: int = 50, cursor: str | None = None) -> dict
client.describe_function(function_key: str, *, version: str | None = None) -> dict
client.plan_function_source_change(function_key: str, *, version: str, source: str) -> dict
client.apply_function_change(plan: dict, *, command_id: str) -> dict
client.read_flow_input_draft(flow: FlowRef) -> dict
client.save_flow_input_draft(flow: FlowRef, values: dict, *, expected_version: str | None, command_id: str | None = None) -> dict
client.get_run(run_key: str) -> Run
client.open_report(report_key: str, *, revision: str | None = None) -> Report
client.run(flow: object, *, run_key: str | None = None, command_id: str | None = None, **inputs: object) -> Run
client.run(connection, *, run_key: str | None = None, command_id: str | None = None) -> Run
client.run_many(connections: list | tuple) -> list[Run]
client.run_batch(connection: object, cases: list | tuple, *, case_key: str, inputs: dict, result_mapping: str | None = None) -> BatchRun
run.read_result(name: str, *, max_bytes: int) -> bytes
run.read_inputs(*, max_bytes: int) -> bytes
px.Artifact.from_bytes(data: bytes, *, media_type: str) -> 'Artifact'
report.block(block_key: str) -> '_ReportBlock'
report.result_slot(slot_key: str) -> '_ReportResultSlot'
report.document() -> 'ReportDocument'
report.table(table_key: str) -> '_Table'
table.cell(row: str | int, field: str) -> dict
table.set_cell(row: str | int, field: str, value: bool | int | float | str | decimal.Decimal, *, expected: dict | None = None) -> None
selection.read() -> str
selection.replace(text: str) -> None
client.read_report(report_key: str, *, revision: str, blocks: list | tuple = (), tables: dict | None = None, attachments: list | tuple = ()) -> dict
client.read_attachment(report_key: str, attachment_key: str, *, revision: str, max_bytes: int = 8388608) -> bytes
report.capture_context(*, reads: list | tuple | None, writes: list | tuple) -> dict
report.apply_edits(edits: dict, *, context: dict, values: dict | None = None, cells: list | tuple = ()) -> None
attachment.capture(*, max_bytes: int = 8388608) -> dict
block.to_dict() -> dict
block.replace(node: dict, *, expected: dict | None = None) -> None
block.set_value(field_key: str, value: bool | int | float | str | decimal.Decimal, *, unit: str) -> '_ReportValue'
block.select(start: int, end: int) -> '_ReportTextSelection'
block.insert_result(offset: int, slot: '_ReportResultSlot') -> None
selection.format(*, bold: bool | None = None, italic: bool | None = None, underline: bool | None = None, font_family: str | None = None, font_size: str | None = None, color: str | None = None) -> None
selection.as_value(field_key: str, *, type: type, unit: str) -> '_ReportValue'
client.export_documents(output: 'str | os.PathLike', *, flows: dict | None = None, reports: dict | None = None) -> dict
px.ReportDocument(data: bytes)
px.ReportDocument.from_wire(document: dict) -> 'ReportDocument'
px.ProjectPdfFiles(directory: str | os.PathLike, *, workspace: str, project: str)
pdf_files.read(key: str) -> dict | None
pdf_files.list() -> list[dict]
pdf_files.remember(key: str, *, name: str, content_ref: str, size: int, expected_revision: str | None) -> dict
pdf_files.import_pdf(key: str, path: str | os.PathLike, *, expected_revision: str | None, name: str | None = None) -> dict
pdf_files.open_pdf(key: str) -> pathlib.Path
px.DocumentAnalysis(data: bytes)
px.DocumentAnalysis.from_wire(document: dict) -> 'DocumentAnalysis'
analysis.to_dict() -> dict
analysis.citation(segment_keys: list[str]) -> 'DocumentCitation'
px.DocumentCitation(data: bytes)
px.DocumentCitation.from_wire(document: dict) -> 'DocumentCitation'
citation.to_dict() -> dict
citation.resolve(analysis: 'DocumentAnalysis') -> list[dict]
px.DocumentTranslation(data: bytes)
px.DocumentTranslation.from_wire(document: dict) -> 'DocumentTranslation'
translation.to_dict() -> dict
translation.resolve(analysis: 'DocumentAnalysis') -> dict
document.to_dict() -> dict
report.settings() -> dict
report.set_settings(value: dict, *, expected_version: str | None) -> dict
client.import_documents(path: 'str | os.PathLike', *, reports: list | tuple | None = None, command_id: 'str | None' = None) -> dict
client.retain_document(value: 'str | DocumentAnalysis | DocumentCitation | DocumentTranslation', *, source: 'Report | None' = None) -> str
client.document_job(job_ref: str) -> dict
client.cancel_document_job(job_ref: str) -> dict
client.review_document_job(job_ref: str, *, result_ref: str, decision: str) -> dict
client.apply_document_result(report: 'Report', *, job_ref: str, result_ref: str, command_id: str) -> dict
client.document_application(command_id: str) -> dict
client.resume_document_application(command_id: str) -> dict
client.submit_document_job(request: dict, *, command_id: str) -> dict
client.document_submission(command_id: str) -> dict
client.document_job_result(job_ref: str) -> 'DocumentAnalysis | DocumentTranslation'
px.DocumentTranslatedPdf(data: bytes)
px.DocumentTranslatedPdf.from_wire(document: dict) -> 'DocumentTranslatedPdf'
translated_pdf.to_dict() -> dict
translated_pdf.resolve(analysis: 'DocumentAnalysis', translation: 'DocumentTranslation') -> dict
client.retain_translated_pdf(value: 'DocumentTranslatedPdf', *, pdf: 'str | os.PathLike') -> strreport.connection(key)는 이미 연결된 Flow를 조회합니다. 같은 Flow를 두 번 연결했다면 각 connection_key로 구분하며, 연결에 저장된 정확한 Flow revision의 connection.inputs 및 connection.results 포트를 제공합니다. 조회·포트 선택은 저장이나 실행을 하지 않습니다. 새 연결은 기존 report.connect_flow(...)로 추가합니다.
기존 입력을 수정할 때는 block.set_value(...)를 사용합니다. 등록된 field_key와 단위를 명시하면 본문 값과 입력 registry가 같은 UpsertReportValue operation으로 갱신됩니다. 일반 텍스트는 먼저 select(...).as_value(...)로 등록합니다. block.replace(...)로 본문만 바꾸는 것은 입력 저장소 수정의 대체 경로가 아닙니다.
from pipelinexlab import px
with px.Client(workspace="dev", project="section_properties") as client:
with client.open_report("section_calculation_note") as report:
report.block("width").set_value("width", 250.0, unit="mm")
connection = report.connection("section_properties")
client.save(report)
print(report.document().freshness)
run = client.run(connection).wait(timeout_seconds=45)이 예제의 block·field·connection key는 저장 문서에 있어야 합니다. 반환된 입력 handle은 같은 Report의 connection.bind(..., to=connection.inputs.width)에 사용할 수 있습니다. 복원된 포트로 새 mapping을 저장해도 Flow를 재생성하거나 최신 revision으로 바꾸지 않습니다. 실행은 저장 후 명시적으로 요청하며, 닫힌 Report의 연결을 실행하려면 Report를 다시 엽니다. 결과 projection은 실행 후 별도로 적용되므로 최신 결과 조회는 client.open_report(...)로 현재 Report head를 다시 읽습니다. 기존 Report handle은 저장된 자기 revision을 유지합니다.
실행 중 관계없는 본문이나 다른 표 영역을 편집해 저장해도, Runtime은 최신 Report에 해당 결과만 적용할 수 있습니다. 표는 rowKey × fieldKey 쓰기 영역을 비교합니다. 같은 목적지나 실행 입력·연결·Flow pin이 바뀌면 결과 적용은 거절하고 실행 snapshot은 보존합니다. Batch는 다른 case의 적용을 허용하되 initial revision의 Report 입력과 자신의 case 영역이 변하지 않았는지 확인합니다. 충돌 후에는 최신 Report를 열어 확인하고 명시적으로 재실행합니다. Run.state == "Succeeded"는 결과 계산 성공이며 Report 적용 성공을 대신하지 않습니다. 적용 여부는 run = run.refresh() 후 run.projection으로 관측합니다: durable data.projection sidecar를 읽은 불변 RunProjection | None 값이며, state는 closed 3종 pending·applied·refused, applied는 실제 만들어진 exact report_revision_ref를, refused는 closed refusal_reason과 schema-valid diagnostics를 가집니다. projection이 없는 일반 Flow Run은 None입니다. 적용 거절은 이미 생성된 계산 snapshot을 삭제하지 않으므로 run.result()는 그대로 읽힙니다.
Run, RunProjection, BatchRun, PagePlan, PdfExport는 위 호출이 반환하는 output-only 불변 값입니다. public constructor로 직접 만들 수 없습니다. Run.refresh()·wait()·cancel()·result()는 Run을 만든 원래 Client를 빌려 쓰며, 그 Client가 닫혔거나 없는 경우 다른 Client를 암묵적으로 선택하지 않고 공개 Error로 거부합니다.
run.read_result(name: str, *, max_bytes: int) -> bytes는 선택한 Run의 named result 내용을 읽습니다. Run.result()의 참조 목록은 그대로 유지합니다. 일반 결과는 canonical JSON bytes이며 json.loads(...)로 해석합니다(int64/decimal 등은 기존 canonical 문자열 표현을 유지). 생성 artifact/binary 결과는 원본 파일 bytes를 반환합니다. 필요한 max_bytes는 전체 내용의 허용 크기이며 0부터 64 MiB까지입니다. 빈 파일도 검증하고, 한 번에 64 KiB씩 동일 snapshot과 content digest를 확인합니다. Run을 만든 Client가 닫히면 거절하며 조회는 계산을 실행하지 않습니다. 누락된 결과, 크기 초과, 읽을 수 없는 내용은 px.Error와 structured diagnostic으로 구분합니다. 생성 파일 metadata가 없는 과거 digest-only artifact는 임의 CAS 접근으로 해석하지 않습니다.
이 호출은 product.run.read_result_chunk를 사용하며 fixed 21-Tool catalog를 변경하지 않습니다. 서버/SDK의 product schema digest가 다르면 기존 handshake에서 명시적으로 거절합니다. SDK 반환량·전송량 한도와 저장소의 메모리 예산은 구분합니다. SQLite/PostgreSQL snapshot reader는 검증 중 할당을 포함해 저장소 인스턴스당 snapshot 버퍼 64 MiB·16개를 공유하고 큰 파일은 임시 디스크를 사용합니다. 원본은 새 조회에서 전체 검증하며 후속 chunk는 검증한 복사본에서 읽습니다. 복사 버퍼·응답·SDK 결과 조립은 별도이므로 서버 전체 메모리가 max_bytes 이하라는 보장은 아닙니다. 디스크 사용량과 첫 전체 검증 비용, snapshot 회수 조건은 Run 조회를 따릅니다. 생성 파일은 px.Artifact.from_bytes로 반환합니다. 이 공통 경로의 지원이 sectionproperties 환경·사례 완료를 뜻하지 않습니다.
Run.diagnostics는 tuple[Diagnostic, ...]인 불변 snapshot입니다. run.refresh()가 run_get의 /data/diagnostics를 읽으며, polling한 run.wait()도 이 값을 보존합니다. 실패가 확정된 현재 Attempt의 진단만 반환합니다. 이전 재시도 진단과 성공한 Run의 진단은 섞지 않으며, 필드가 없는 기존 Runtime 응답은 빈 tuple입니다. /data/run의 닫힌 필드와 Run.to_dict()는 유지합니다. 진단 직렬화에는 각 Diagnostic.to_dict()를 사용합니다.
report.document().freshness는 ReportFreshness | None인 불변 조회 값입니다. Runtime이 /data/freshness에 반환한 정확한 저장 revision의 판정만 읽으며 SDK가 별도 계산하지 않습니다. source_revision_ref는 판정 대상 revision이고 results는 tuple[ReportResultFreshness, ...]입니다. 두 타입은 output-only이며 직접 생성하지 않습니다. 각 결과는 snapshot_ref, mapping_key, port_key, value_ref, connection_key, status, reason을 가집니다. 같은 결과의 여러 본문 배치는 한 판정을 재사용합니다.
| status | reason | 의미 |
|---|---|---|
current | matches | 현재 저장 입력·고정 Flow·mapping이 실행 당시와 일치 |
stale | inputsChanged, mappingChanged, flowChanged, connectionRemoved | 이전 결과는 유지하지만 현재 연결/입력의 결과로 쓰면 안 됨 |
unknown | provenanceUnavailable, sourceUnavailable | 판정 근거를 읽거나 검증할 수 없음. 성공·최신으로 추정하지 않음 |
미저장 편집 내용의 판정이 아닙니다. 고정된 이전 문서는 그 문서의 입력을 기준으로 판단하며 프로젝트의 최신 Flow revision을 자동으로 따라가지 않습니다. Batch 결과는 snapshot에 고정된 case 입력과 현재 Report 소유 입력을 구분합니다. 다른 case의 적용 자체로 stale이 되지 않습니다. 오프라인 px.loads/ReportDocument.from_wire와 구 Runtime 응답은 freshness=None이며, 실행 결과가 없는 최신 응답은 results=()입니다. 둘은 서로 다른 상태입니다. freshness.to_dict()로 sidecar만 직렬화할 수 있고, px.dumps(document)와 document.to_dict()는 기존 문서만 반환합니다. 읽기나 보기 전환이 저장·실행을 발생시키지 않습니다.
callee별 구현 상태
위 문법은 승인된 authoring 설계 전체입니다. 아래 표는 각 callee가 현재 빌드에 구현돼 있는지를 밝힙니다. implemented는 searched Python package에서 그 callee의 마지막 이름 정의가 resolve되고 별도 signature checker가 실제 receiver·signature를 확인한다는 뜻이고, declared는 승인된 문법이되 아직 빌드되지 않아 호출이 어디에도 resolve되지 않는다는 뜻입니다. declared는 거부나 폐기가 아닙니다.
이 표는 machine contract인 authoring-profile.v1.jsonpython.exactGrammar 각 entry의 implementationStatus와 같은 값을 담습니다. npm run docs:contract가 두 문서를 양방향으로 비교하므로 한쪽만 고칠 수 없고, check-authoring-grammar-reality.cjs가 그 값을 검색 대상으로 고정된 Python package의 실제 tree와 다시 비교하므로 어느 쪽도 tree보다 앞서 나갈 수 없습니다.
client.search_catalog는 명시한 불투명 Workspace 참조의 접근 가능한 항목을 기존 project_searchworkspaceItems 범위로 한 페이지씩 읽습니다. name_prefix의 공백·대소문자는 보존하며, 다음 커서는 같은 범위·필터·정렬에만 사용합니다. 반환 사전의 items와 nextCursor는 서비스의 페이지이며 전체 개수나 검색 시점의 고정 스냅샷을 의미하지 않습니다.
| callee | implementationStatus |
|---|---|
px.Flow | implemented |
flow.input | implemented |
flow.node | implemented |
flow.connect | implemented |
flow.result | implemented |
flow.group | implemented |
flow.viewer | implemented |
flow.subflow | implemented |
flow.preview_html | implemented |
px.function | implemented |
px.component | implemented |
px.loads | implemented |
px.dumps | implemented |
px.Report | implemented |
px.Report.open | implemented |
report.section | implemented |
section.text | implemented |
section.value | implemented |
section.table | implemented |
table.select | implemented |
section.result_slot | implemented |
section.result_table | implemented |
section.artifact_slot | implemented |
section.attachment | implemented |
report.connect_flow | implemented |
report.connection | implemented |
connection.bind | implemented |
connection.show | implemented |
connection.remove_mapping | implemented |
report.move | implemented |
report.remove | implemented |
report.remove_connection | implemented |
report.save | implemented |
report.plan_pages | implemented |
report.export_pdf | implemented |
report.preview_html | implemented |
px.Client | implemented |
client.create_project | implemented |
client.search_catalog | implemented |
client.save | implemented |
client.plan_flow_change | implemented |
client.apply_flow_change | implemented |
client.search_functions | implemented |
client.describe_function | implemented |
client.plan_function_source_change | implemented |
client.apply_function_change | implemented |
client.read_flow_input_draft | implemented |
client.save_flow_input_draft | implemented |
client.get_run | implemented |
client.open_report | implemented |
client.run | implemented |
client.run_many | implemented |
client.run_batch | implemented |
run.read_result | implemented |
run.read_inputs | implemented |
px.Artifact.from_bytes | implemented |
report.block | implemented |
report.result_slot | implemented |
report.document | implemented |
report.table | implemented |
table.cell | implemented |
table.set_cell | implemented |
selection.read | implemented |
selection.replace | implemented |
client.read_report | implemented |
client.read_attachment | implemented |
report.capture_context | implemented |
report.apply_edits | implemented |
attachment.capture | implemented |
block.to_dict | implemented |
block.replace | implemented |
block.set_value | implemented |
block.select | implemented |
block.insert_result | implemented |
selection.format | implemented |
selection.as_value | implemented |
client.export_documents | implemented |
px.ReportDocument | implemented |
px.ReportDocument.from_wire | implemented |
px.ProjectPdfFiles | implemented |
pdf_files.read | implemented |
pdf_files.list | implemented |
pdf_files.remember | implemented |
pdf_files.import_pdf | implemented |
pdf_files.open_pdf | implemented |
px.DocumentAnalysis | implemented |
px.DocumentAnalysis.from_wire | implemented |
analysis.to_dict | implemented |
analysis.citation | implemented |
px.DocumentCitation | implemented |
px.DocumentCitation.from_wire | implemented |
citation.to_dict | implemented |
citation.resolve | implemented |
px.DocumentTranslation | implemented |
px.DocumentTranslation.from_wire | implemented |
translation.to_dict | implemented |
translation.resolve | implemented |
document.to_dict | implemented |
report.settings | implemented |
report.set_settings | implemented |
client.import_documents | implemented |
client.retain_document | implemented |
client.document_job | implemented |
client.cancel_document_job | implemented |
client.review_document_job | implemented |
client.apply_document_result | implemented |
client.document_application | implemented |
client.resume_document_application | implemented |
client.submit_document_job | implemented |
client.document_submission | implemented |
client.document_job_result | implemented |
px.DocumentTranslatedPdf | implemented |
px.DocumentTranslatedPdf.from_wire | implemented |
translated_pdf.to_dict | implemented |
translated_pdf.resolve | implemented |
client.retain_translated_pdf | implemented |
implemented가 뜻하는 범위는 좁습니다. 그 callee의 마지막 이름을 가진 def나 class가 searched package 안에 있다는 것이지, receiver·signature·필수 keyword가 이 문서대로라는 뜻은 아닙니다.
DocumentAnalysis와 DocumentCitation의 저장 형식·원본 좌표·정확한 인용 해석은 PDF 원본 분석과 인용을 따릅니다.
처음에는 다음 순서로 작성합니다.
- Canvas에서 실행할 계산은
@px.function으로 등록합니다. - Function이 부르는 일반 Python
def는@px.function(..., helpers=[...])로 선언합니다. 선언한 helper의 소스는 Function revision에 함께 저장되고, 선언하지 않은 module-level 함수는 실행 시 존재하지 않습니다. Component 구현 module 안의 helper는 module이 통째로 실행되므로 선언 없이 사용합니다. - 설치형 Component의 companion target은
@px.component로 등록합니다. 후속 V2 Canvas는 host-ownedcomponent.declarative@2만 사용하고 settings를component-declared-settings@1로 선언당 최대 8개까지 받고 host control registry가 그립니다. - Flow를 작성하는
.pymodule에는 publicflow = px.Flow("flow_key", ...)객체를 하나만 만들고, 그 아래에flow.input(...),flow.node(...),flow.connect(...),flow.result(...)를 작성합니다. - 같은 Flow의 시각적 묶음은
flow.group(...), 다른 module의 Flow 재사용은 importedflowobject를 받는flow.subflow(...)로 구분합니다. - Report Workbench와
px.Report가 사용할 경계는flow.input(...)과flow.result(...)로 공개합니다. report = px.Report(...)로 Report 객체를 만들고section = report.section(...)으로 받은 handle에서.text(),.value(),.table(),.result_slot()을 호출합니다.connection = report.connect_flow(...)로 받은 handle에서connection.bind(...)와connection.show(...)를 호출한 뒤report.save()로 canonical.pxreportrevision을 만듭니다. 실제 계산은client = px.Client(workspace=..., project=...)로 만든 객체의.run(),.run_many(),.run_batch()에서 시작합니다.
from pipelinexlab import px
class CheckResults(px.Results):
utilization: float
@px.function(
key="check_member",
version="1.0.0",
description="Checks member utilization.",
)
def check_member(
demand: float,
capacity: float,
) -> CheckResults:
return CheckResults(utilization=demand / capacity)
flow = px.Flow(
"member_check",
label="Member check",
description="Runs one member check.",
)
demand = flow.input("demand", float, unit="kN", description="Design demand.")
capacity = flow.input(
"capacity",
float,
unit="kN",
description="Design capacity.",
)
utilization_check = flow.node("utilization_check", check_member)
flow.connect(demand, utilization_check.inputs.demand)
flow.connect(capacity, utilization_check.inputs.capacity)
flow.result("utilization", utilization_check.results.utilization)
report = px.Report(
"member_review",
title="Member review",
)
basis = report.section("design_basis", title="Design basis")
report_demand = basis.value("demand", 120.0, unit="kN")
report_capacity = basis.value("capacity", 180.0, unit="kN")
summary = report.section("summary", title="Check result")
utilization_slot = summary.result_slot(
"utilization",
label="Utilization",
)
member_check = report.connect_flow("member_check", flow)
member_check.bind(
"report_demand_to_demand",
report_demand,
to=flow.inputs.demand,
)
member_check.bind(
"report_capacity_to_capacity",
report_capacity,
to=flow.inputs.capacity,
)
member_check.show(
"utilization_to_summary",
flow.results.utilization,
at=utilization_slot,
)
client = px.Client(workspace="engineering", project="bridge_design")
report.save()작성한 뒤 다음 명령으로 확인합니다.
pxlab package check .
pxlab flow materialize . --flow member_check --output member_check.pxflow
pxlab studio open . --flow member_checkStudio에서는 공유 Function을 Definitions → Functions → Place Node에서 선택합니다. Components dock은 host-owned V2 discovery를 위한 계획 surface이며, 현재 production reader는 저장된 Component target을 unsupported로 표시합니다. flow.node("node_key", target)의 첫 번째 인수는 같은 Flow 안에서 겹치지 않는 Node의 고정 이름이고, 두 번째 인수는 실행할 @px.function 또는 @px.component target입니다.
SDK Flow와 Report가 만나는 지점
SDK는 Flow의 public boundary와 내부 연결을 정의합니다.
flow.connect(...)는 current Flow 안에서 사용합니다. Python Flow module은 public port와 내부 연결을 정의합니다. Report Workbench와 px.Report는 materialize된 exact public port 계약을 읽어 Report block·table·result 위치의 mapping을 같은 .pxreport.flowConnections에 저장합니다.
span = flow.input("span", float, unit="m", description="Clear span.")
check = flow.node("resistance_check", check_resistance)
flow.connect(span, check.inputs.span)
flow.result("utilization", check.results.utilization)| SDK 선언 | Studio Canvas | Report Workbench·px.Report |
|---|---|---|
flow.input("span", ...) | public input port | authored value·table의 destination |
flow.connect(span, check.inputs.span) | Flow 내부 연결선 | Overview에서 읽기 전용 확인 |
flow.result("utilization", ...) | public result port | result projection의 source |
자세한 저장·실행 계약은 Report와 Flow 공개 포트 연결을 따릅니다.
Report document 작성
px.Report는 Report Workbench와 같은 ReportDocument command를 사용하는 Python authoring API입니다. 새 Report를 코드로 만든 뒤 report.save()를 호출하면 .pxreport revision이 canonical 원본이 됩니다. Python source와 .pxreport를 동시에 독립적으로 편집하는 두 원본으로 유지하지 않습니다.
Connected Flow mapping은 다음 signature로 stable identity를 명시합니다.
connection.bind(mapping_key, source, *, to, fields=None, row_key=None)
connection.show(mapping_key, source, *, at, fields=None, row_key=None)mapping_key는 connection 안에서 유일한 lower_snake_case key이며 source나 target label에서 자동 생성하지 않습니다. Table과 structured value를 연결할 때 fields와 row_key로 명시적인 field·row mapping을 전달합니다.
report = px.Report("bridge_review", title="Bridge review")
basis = report.section("design_basis", title="Design basis")
basis.text("scope", "Check the governing girder load cases.")
span = basis.value("span", 24.0, unit="m")
cases = basis.table(
"load_cases",
rows=load_case_rows,
row_key="case_key",
)
summary = report.section("summary", title="Summary")
utilization = summary.result_slot("utilization", label="Utilization")
check = report.connect_flow("girder_check", girder_flow)
check.bind("report_span_to_span", span, to=girder_flow.inputs.span)
check.bind("report_cases_to_cases", cases, to=girder_flow.inputs.cases)
check.show(
"utilization_to_summary",
girder_flow.results.utilization,
at=utilization,
)
report.save()| 소유 객체·handle | 호출 | 저장되는 사용자 의미 |
|---|---|---|
PipelineXLab SDK px | report = px.Report(...) | Report 하나와 이름·제목 |
Report 객체 report | basis = report.section(...) | 본문의 이름 있는 Section handle |
Section handle basis | basis.text(...) | 작성 가능한 문단 |
Section handle basis | basis.value(...) | type·unit을 가진 작성값 |
Section handle basis | basis.table(...) | stable row key를 가진 작성 table |
Section handle summary | summary.result_slot(...) | Flow Result를 읽기 전용으로 표시할 위치 |
Report 객체 report | check = report.connect_flow(...) | 저장할 Flow 대상을 가진 Connected Flow draft handle |
Connected Flow handle check | check.bind(...) | Report value/table → Flow public input mapping |
Connected Flow handle check | check.show(...) | Flow public result → Report result position mapping |
Report 객체 report | report.save() | Flow revision을 pin하고 검증·저장한 새 .pxreport revision |
Report 객체 report | report.plan_pages() | 저장된 revision의 결정적 page plan (pageSize·orientation·margins를 가진 page_setup으로 상자를 바꿉니다) |
Report 객체 report | report.export_pdf("최종.pdf") | 저장된 revision을 Runtime이 조판해 그 경로에 쓴 PDF의 path·byteLength·sha256·pageCount |
report.connect_flow(...)와 mapping 작성은 현재 Report의 draft만 바꾸며 Run을 만들지 않습니다. report.save()가 exact Flow revision을 resolve·pin하고 connection을 .pxreport에 저장합니다. 실제 실행은 저장한 Report revision과 Connected Flow를 대상으로 합니다.
client = px.Client(workspace="engineering", project="bridge_design")
# Report 연결 없이 Flow를 직접 실행
direct_run = client.run(
flow,
demand=120.0,
capacity=180.0,
)
# report.connect_flow(...)가 반환한 저장 connection 실행
report_run = client.run(check)
runs = client.run_many([check, serviceability_check])
# Batch 전용 connection은 case가 채울 demand·capacity를 Report source에 bind하지 않습니다.
batch_report = px.Report("member_batch_review", title="Member batch review")
batch_check = batch_report.connect_flow("member_check_cases", flow)
batch_report.save()
check_cases = [
{"case_key": "uls_01", "demand": 120.0, "capacity": 180.0},
{"case_key": "uls_02", "demand": 145.0, "capacity": 180.0},
]
batch = client.run_batch(
batch_check,
check_cases,
case_key="case_key",
inputs={
"demand": flow.inputs.demand,
"capacity": flow.inputs.capacity,
},
)client.run(flow, *, run_key=None, command_id=None, **inputs)는 Flow public input을 직접 받아 ordinary Run 하나를 만듭니다.run_key와command_id를 생략하면 SDK가 호출마다 새 값을 만들고 그 호출은 새 Run입니다. 둘을 호출자가 제공하면 같은 두 값의 재제출은 같은 의도의 멱등 재시도이며 두 번째 Run을 만들지 않습니다.run_key와command_id는 실행 식별자 전용 예약어이므로 Flow public input 이름으로 쓸 수 없습니다. 같은 의도의 반복인지 새 의도인지 아는 쪽은 호출자뿐이고 Apply도 이미commandId를 호출자에게 받습니다. supervisor 선발급은 발급 뒤 제출 전에 연결이 끊기면 주인 없는 Run 자리를 만들므로 사용하지 않습니다.client.run(connection)은 저장한 Report connection의 input mapping으로 ordinary Run 하나를 만들고, result mapping을 통해 Report 결과 위치를 갱신합니다.client.run_many(...)는 각 Connected Flow에 독립 Run을 만들며 Runtime resource budget 안에서 bounded parallel로 실행할 수 있습니다.client.run(connection, run_key=..., command_id=...)는 같은 고정 revision·입력·식별자로 재시도합니다. 둘을 생략하면 새 실행입니다. 제출 오류의run_key,command_id,outcome_unknown으로 응답 유실을 구분합니다. 원래 connection 객체를 보관하면 같은 Project의 새 Client로 재접속할 수 있지만, 호출자 프로세스 재시작 후 연결 핸들 복원·자동 재제출은 지원하지 않습니다.client.run_many(...)자체를 다시 호출하면 새 실행이므로 오류의failed_index에 해당하는 연결만client.run(...)에 원래 식별자를 전달해 복구합니다.- supervisor transport는 한 connection에서 request 하나만 in-flight로 허용하므로
client.run_many(...)는 입력 전체를 먼저 확인한 뒤 인증된product.report.run_connection제출 응답만 목록 순서대로 하나씩 받습니다. Run 완료를 기다리지는 않으므로 accepted Run은 Runtime에서 독립적으로 진행할 수 있습니다. 제출 하나가 거부되면 앞서 accepted된 Run은 rollback하지 않고 이후 connection은 제출하지 않습니다. - connection 실행은 exact saved Report revision의
inputProjections에서reportValue,reportTableSelection,reportAttachmentsource를 그대로 나릅니다. Table은 source Record field descriptor, row-key와 complete row를, attachment는 artifact type, RFC-002 content reference, media type과 supervisor가 잰 size를 포함합니다. facade가 destination Flow type을 source type으로 대체하지 않으며 core만 exact saved Flow revision과 비교해 이름 있는 type/unit/field/row-key refusal을 판정합니다. 일치한 ordinaryRunInputPayload값은 filesystem CAS에 저장되고 참조만 Runtime으로 흐릅니다.resultMappings가 있는 connection은 같은 인증 경로의 projection intent와 completion CAS를 사용합니다. - 같은 Flow revision도 Report 목적·입력·결과 위치가 다르면 서로 다른 Connected Flow로 여러 번 사용할 수 있습니다.
client.run_batch(...)는 같은 Connected Flow를 여러 scalar input set으로 반복하며 stablecaseKey마다 ordinary child Run 하나를 만듭니다.inputs에는 해당 connection의 저장된 Report input mapping이 비워 둔 Flow port만 지정합니다. 같은 port를 저장 mapping과 Batch case가 함께 제공하면 덮어쓰지 않고 active producer 충돌로 거부합니다.List<T>나Table<Record>하나를 collection input으로 보내면 row 수와 관계없이 Run 한 번입니다.- Flow B가 Flow A의 result를 필요로 하면 Report의 연결 순서에 의존하지 않고 parent Flow 안에서 Node 또는 Subflow로 연결합니다.
Report Workbench의 Connected Flows, Run this Flow, Run selected, Batch Run은 각각 connect_flow, run, run_many, run_batch와 같은 의미를 사용합니다. Project Activity는 이 Run들을 한곳에서 확인하는 화면이며 SDK의 별도 실행 객체나 transaction boundary가 아닙니다.
core 문법을 읽는 열두 문장
- decorator는 재사용할 기능이나 data schema를 등록합니다. 이 단계에서는 Node가 생기거나 계산이 실행되지 않습니다.
@px.function은 Definitions에서 관리하는 재사용 계산을 등록합니다.- decorator 없는
def를 Function에서 쓰려면helpers=[...]로 선언합니다. Component 구현 module 안의 helper는 선언 없이 사용합니다. - 현재
@px.component는 Python annotation에서 읽은 port shape와 기본 metadata를 process-local registry에 등록합니다. 후속 V2 Canvas UI는 host-ownedcomponent.declarative@2만 사용합니다. flow.node("node_key", target)만 Canvas Node를 배치합니다. target은@px.function또는 imported@px.component중 하나입니다.flow.input(...)과flow.result(...)는 Flow의 공개 interface를,flow.connect(...)는 Flow 내부 연결을 명시합니다.flow.group(...)은 같은 Flow Node의 presentation membership만 바꿉니다.- 다른 module이 export한
flowobject는flow.subflow("subflow_key", imported_flow)로 배치하며 caller에서는 읽기 전용으로 열고 Edit source Flow로 이동합니다. - 현재
px.Recordsubclass는 data schema이고, 계획된 app/paper decorator는 surface/template definition입니다. 어느 것도 배치 전에는 Node가 아닙니다. px.Report(...)는 Report document를 시작하고 section·value·table·result 위치를 작성합니다.report.connect_flow(...)는 Connected Flow draft를 추가할 뿐 실행하지 않으며,bind와show가 input/result mapping을 만듭니다.report.save()가 exact Flow revision을 pin하고 connection을 저장합니다.- Run은
px.Client의 실행 API에서만 시작합니다. 여러 connection은run_many, 같은 connection의 여러 scalar case는 explicitrun_batch를 사용합니다.
업무별 기능은 Component로 추가합니다. 제공 Component는 Components 사용하기에서 찾습니다. Component와 별개인 Extension template·app surface·adapter는 Extensions와 Integrations에서 해당 SDK 문서로 이동합니다. Component rich editor는 RFC-016의 component-rich-surface-declaration@1이 소유하는 격리 surface입니다.
한 .py Flow module은 top-level public flow object를 정확히 하나 export합니다. 복잡한 Flow도 큰 decorated 함수 하나로 감싸지 않고 Node 배치와 연결을 top-level declarative composition으로 유지합니다. Project에 Flow가 여러 개면 Flow마다 module을 나눕니다. 일반 Python helper와 target body의 문장은 Canvas 객체가 아닙니다.
현재 사용자는 Function과 Component target에서 일반 Python annotation과 px.Results의 annotated field로 type을 정하고 px.input(...)·px.result(...) marker로 description·unit을 더합니다. px.setting(...)은 px.Settings field의 선언 문법입니다. px.field(...) marker는 아래에 보존한 계획 문법입니다. Flow boundary는 현재도 flow.input(...)과 flow.result(...)가 직접 선언합니다.
| 정의하거나 배치할 대상 | 공개 문법 |
|---|---|
| 재사용 Function definition | @px.function(...) |
| 현재 reusable Component companion target | @px.component(...) |
| 후속 V2 Component Canvas UI | host-owned component.declarative@2 · contract_only |
| Component settings | px.Settings subclass의 px.setting(...) field · 선언당 최대 8개 |
| rich surface | RFC-016의 component-rich-surface-declaration@1 소유 |
| Function helper | 일반 def를 helpers=[...]로 선언; Function revision에 함께 저장, Node로 배치하지 않음 |
| Component 내부 helper | executor module 안의 일반 def; module이 통째로 실행되므로 선언 불필요 |
| 실제 Canvas Node | flow.node("node_key", target) |
| Flow input | flow.input("port_key", ValueType, ...) |
| 명시적 연결 | flow.connect(source, node.inputs.port_key) |
| Flow result | flow.result("port_key", source, description=None) |
| 같은 Flow의 presentation group | flow.group("group_key", members=[...], label=...) |
| 외부 Flow | flow.subflow("subflow_key", imported_flow) |
| 현재 target input | span: float |
| Component instance settings | @px.component(..., settings=BeamSettings); V2 저장값은 선언된 setting key만 |
| 현재 target의 이름 있는 결과 | px.Results subclass의 utilization: float |
| 여러 field를 묶은 현재 data schema | class LoadCase(px.Record): 아래 annotated field |
| 현재 Record field | case_key: str |
| 현재 collection type | 지원되는 list[T] annotation |
| Report document | report = px.Report("report_key", title=...) 또는 Report Workbench |
| Report section·content | section = report.section(...) 뒤 Section handle의 .text(), .value(), .table(), .result_slot() |
| Report source/result 위치와 public port mapping | connection = report.connect_flow(...) 뒤 Connected Flow handle의 .bind(), .show() 또는 Report Workbench의 Use report input / Show result |
| Report 저장 | Report 객체의 report.save() → canonical .pxreport revision |
| Flow 직접 1회 실행 | client = px.Client(workspace=..., project=...) 뒤 client.run(flow, *, run_key=None, command_id=None, **inputs) |
| Connected Flow 1회 실행 | Client 객체의 client.run(connection) |
| 여러 Connected Flow 실행 | Client 객체의 client.run_many([connection_a, connection_b]) |
| 같은 connection의 여러 scalar case | 저장 mapping이 없는 port를 inputs로 고른 뒤 client.run_batch(connection, cases, case_key=..., inputs=...) |
Component는 reusable catalog definition을 뜻합니다. flow.node(...)가 Component target을 배치하면 durable document에는 exact Component Node 사용처가 생깁니다. reader는 resolver 없이 이를 unsupported로 보존하고, production host의 명시적 installed-presentation resolver가 exact target을 찾으면 host-owned declarative Node로 투영합니다.
실행 target과 Python helper
현재 decorator는 reusable definition의 process-local metadata를 Python symbol 옆에 등록합니다. port는 Python annotation에서 읽으며 decorator 등록은 target을 호출하거나 Canvas 객체, portable declaration 또는 executor entry를 만들지 않습니다. decorator와 package artifact 사이의 build-time discovery·generation은 계획 계약입니다.
| 작성 대상 | 문법 | 등록하는 것 | Canvas placement |
|---|---|---|---|
| 재사용 계산 | @px.function | versioned Function definition과 실행 metadata | flow.node(...) 뒤 Function Reference Node |
| 현재 Component companion target | @px.component | key·label·description·category와 annotation 기반 port shape | flow.node(...) 뒤 exact ref; resolver 성공 시 host projection, 실패 시 unsupported |
| Component Canvas UI | component.declarative@2 | host-owned declaration projection | production renderer/bridge |
| Component settings | px.Settings subclass의 px.setting(...) field | component-declared-settings@1 선언 · 선언당 최대 8개 | host control registry가 그리는 control |
| rich surface | component-rich-surface-declaration@1 | RFC-016이 소유하는 격리 editor surface | Canvas에는 mount하지 않음 |
| 내부 계산 helper | plain def | Function은 helpers=[...]로 선언, Component는 module 동거 | Canvas에 나타나지 않음 |
| 현재 구조화 값 | px.Record subclass | annotated-field data schema | Node가 아님 |
| App Builder 화면 | @px.app | surface definition | Node가 아님 |
| Engineering Paper template | 문서 Extension이 제공하는 @px.paper | optional template definition | Node가 아님 |
decorator argument에는 nodeKey, Canvas layout, connection과 Run trigger를 넣지 않습니다. nodeKey는 flow.node(...), wiring은 flow.connect(...), layout은 Canvas presentation, 실행은 사용자가 시작한 Run이 각각 소유합니다.
Extension SDK와 Adapter는 core 문법과 분리합니다
외부 화면이나 artifact 형식은 Extension이 category·format 계약으로 제공합니다. 이는 Extension discovery·host 계약이며 SDK↔PXFLOW core의 새 제품 축이 아닙니다.
SDK Adapter System
└─ App Surface Adapter category (`app_surface`)
├─ PXFLOW App format (`pxflow_app`)
└─ Streamlit format (`streamlit`)Adapter 분류는 기존 화면을 찾고 열고 상태·권한을 확인하는 공통 계약입니다. 계산과 문서의 실제 의미는 기존 SDK 객체가 계속 소유합니다.
| SDK에서 작성하는 것 | 생성되는 Adapter 분류 | 의미를 소유하는 객체 |
|---|---|---|
@px.app | app_surface + pxflow_app | App layout direct object와 target FlowDocument |
| Streamlit source와 package surface 선언 | app_surface + streamlit | FlowDocument, Run, ResultSnapshot |
Extension별 editor와 template
core SDK 문서는 @px.component, flow.node(...) placement와 host-owned component.declarative@2 경계까지만 정의합니다. rich editor의 package field, entry와 message shape는 RFC-016의 component-rich-surface-declaration@1이 정의하고, command shape는 열지 않습니다. Component와 별개인 Extension template·app surface 문서는 Extensions와 Integrations에서 확인합니다.
한 portable Component container가 Component 선언을 여러 개 나르는 형식은 현재 V1이 아닌 node-presentation-profile.v2.json의 contract_only successor입니다. Python package/distribution의 현재 다중 Component export와 다른 축이며, V1 authoring 문법을 V2 container 형식으로 소급해 해석하지 않습니다.
Streamlit은 package에서 format을 선택합니다
Streamlit 화면은 package의 공통 surface table에서 format, entry와 사용할 typed target을 선언합니다.
[tool.pipelinexlab.surfaces.bearing_check]
format = "streamlit"
format-contract = 1
title = "Bearing check"
description = "Interactive bearing pressure check."
entry = { kind = "python_module", module = "structural_checks.apps.bearing" }
targets = [{ kind = "flow", key = "bearing_review" }]
host-capabilities = []adapterCategory = app_surface
formatKey = streamlit
surfaceKey = bearing_checkStandalone Streamlit source는 px.Client(workspace="engineering", project="bridge_design")로 만든 client의 run(flow, **inputs)로 선언된 Flow를 실행하고, Function port, unit, Flow 구조와 result schema는 연결한 Flow contract에서 읽습니다.
계획된 PXFLOW App은 @px.app으로 정의합니다
@px.app은 기존 Flow의 input과 result를 Builder page에 배치할 planned surface definition입니다. 현재 public facade에는 없습니다. 이 예제는 contract_only이며 현재 SDK에서 실행할 수 없습니다. decorator 자체는 Canvas Node를 배치하거나 계산을 실행하지 않고, 계산·port type·unit은 연결한 exported Flow object의 contract에서 읽는 목표 계약입니다.
from structural_checks.flows.girder_design import flow as girder_design_flow
@px.app(
flow=girder_design_flow,
title="Girder design",
description="Enter design data and review the named results.",
)
def girder_design_app(app: px.App):
main = app.page("main", title="Design")
main.input(girder_design_flow.inputs.span)
main.input(girder_design_flow.inputs.design_cases)
main.result(girder_design_flow.results.resistance_summary)
main.result(girder_design_flow.results.governing_utilization)- 함수 이름
girder_design_app이 기본surfaceKey입니다. main은 stablepageKey입니다.girder_design_flow.inputs.<portKey>와girder_design_flow.results.<portKey>는 generated typed reference입니다.- Builder에는
connection=Trueport만 배치 후보로 표시됩니다. - materialize와 build는 선언만 읽고, 실제 계산은 사용자가 Run을 시작할 때 실행됩니다.
- Builder 변경은 SDK-linked source diff와 Apply/Cancel을 거쳐 같은 direct object로 다시 materialize됩니다.
독립 .pxflow는 이 정의를 선택적인 appSurfaces collection으로 직접 저장합니다. 화면 작성부터 배포까지는 이 collection과 App Builder와 배포의 절차를 사용합니다.
다른 category도 같은 원칙을 사용합니다. data_source, artifact, document_codec는 각 category의 typed schema와 등록된 format key로 작성합니다. 전체 형식과 지원 format은 SDK Adapter 체계를 따릅니다.
@px.component
현재 @px.component는 Python callable을 reusable Component target으로 process-local registry에 등록합니다. componentKey, label, description, 선택적 category/subcategory와 일반 Python annotation에서 읽은 input/result 이름·type shape가 등록 범위입니다. 이 decorator는 Canvas Node를 만들거나 실행하지 않으며 nodeKey, layout, wiring, instance setting schema와 presentation을 소유하지 않습니다.
@px.component(*, key, label, description, category=None, subcategory=None)key,label과description은 필수입니다.category는 선택적이지만 값을 쓸 때는 닫힌 primary task registry의input,transform,calculate,review,deliver,control여섯 개 중 하나입니다. packaged Component declaration이 쓰는 registry와 같은 여섯 key이며, 그 밖의 값은 decoration에서 거부합니다.subcategory는 별개 축입니다. 닫힌 category 아래에서 publisher가 직접 정하는 선택적lower_snake_casekey입니다.- 값이 없으면 해당 member를 생략하고
subcategory는category가 있을 때만 허용합니다. key가 stablecomponentKey입니다. Python 함수 이름은 구현 symbol이며 identity가 아닙니다.
class ModelViewerResults(px.Results):
selection: str
@px.component(
key="model_viewer",
label="Model viewer",
description="Displays a connected structural model.",
category="review",
subcategory="model",
)
def model_viewer(
model: str,
) -> ModelViewerResults:
... # companion descriptor symbol; packaged executor is a separate artifact entry- current input은 function signature annotation, result는
px.Resultssubclass의 annotated field에 한 번만 선언합니다. - 이 process-local descriptor는 portable container declaration이나 executor payload가 아닙니다. decorator에서
content/component.json, executor entry, manifest, digest 또는 signature input으로 자동 변환하는 public bridge는 아직 없습니다. - admitted Component의 durable identity는 exact 7-field
ComponentRef의 publisher,packageKey, exact package version/digest,componentKey와componentContractVersion이 소유합니다. - live
px.Client와 installed declaration resolution은 exact ProjectScope isolation과 execution-trust closure 이후 successor의 target semantics입니다. production third-party Component install admission과 activation을 차단하고, 저장된 exact target·raw settings·usage-derived partial ports를 read-onlyunsupported로 보존합니다. internal/test V1 wire probe를 public placement 절차로 쓰지 않습니다. - 현재 production Flow reader는 저장된 Component target을
unsupported로 표시합니다. package UI는 마운트하지 않습니다.
후속 V2 Canvas와 settings 경계
후속 V2 Canvas는 admitted declaration을 host-owned component.declarative@2으로만 투영하며 package renderer·modal·settings schema를 받지 않습니다. portable Component V2의 settings를 component-declared-settings@1로 선언당 최대 8개까지 받고 host control registry가 그립니다. rich surface는 RFC-016의 component-rich-surface-declaration@1이 소유하며 이 문서는 그 entry·message 형식을 다시 정의하지 않습니다.
Component package는 pyproject.toml의 public-module에서 Component symbol과 generated type stub을 export할 수 있습니다. Python package/distribution 하나가 Component symbol을 여러 개 export하는 것과 portable Component container의 declaration 개수는 서로 다른 축입니다. 현재 V1 portable container는 content/component.json 하나를 운반하며, 이 artifact는 decorator와 별도 단계에서 만듭니다. 아래 placement는 두 P0 closure 뒤 제품 경로를 보여 줍니다. current internal/test compatibility resolver primitive가 exact ref를 저장할 수 있어도 production third-party 절차가 아닙니다. 이 예제는 contract_only이며 현재 SDK에서 실행할 수 없습니다.
import midas_civil.components as midas_components
flow = px.Flow(
"model_review",
label="Model review",
description="Reviews one structural model.",
)
model = flow.input("model", str, description="Structural model reference.")
view = flow.node(
"model_view",
midas_components.model_viewer,
)
flow.connect(model, view.inputs.model)
flow.result("selection", view.results.selection)이때 durable document에는 model_view Component Node target이 생기지만 현재 production Flow surface는 이를 unsupported로 표시합니다. import와 @px.component 선언 자체는 Canvas 객체가 아닙니다. 설치, dependency와 missing-package 동작은 Component 만들기와 배포를 따릅니다.
Function·Flow 선언 예제
아래의 calculate_resistance는 개발자가 작성한 domain 계산 helper입니다. Canvas에는 flow.node(...) placement만 표시되고 helper는 Function 구현 안에 유지됩니다. 이 fence는 planned Record·Table·port metadata 문법까지 함께 보여 주는 목표 계약입니다. 이 예제는 contract_only이며 현재 SDK에서 실행할 수 없습니다.
from pipelinexlab import px
@px.record(
"load_case",
description="One named design load case.",
row_key="case_key",
)
class LoadCase:
case_key: str = px.field(
description="Stable case key used to join inputs and results."
)
permanent_g: float = px.field(unit="kN", description="Permanent load.")
variable_q: float = px.field(unit="kN", description="Variable load.")
@px.record(
"check_result",
description="Resistance result for one design case.",
row_key="case_key",
)
class CheckResult:
case_key: str = px.field(description="Source load-case key.")
utilization: float = px.field(
unit="1",
description="Demand-to-resistance ratio.",
)
class GirderResults(px.Results):
resistance_summary: px.Table[CheckResult] = px.result(
description="Resistance check for each design case."
)
governing_utilization: float = px.result(
unit="1",
description="Largest demand-to-resistance ratio.",
)
@px.function(
key="girder_resistance",
version="4.1.0",
description="Checks girder resistance for the supplied design cases.",
)
def girder_resistance(
span: float = px.input(
unit="m",
description="Clear span measured between bearing centre-lines.",
constraints={"exclusiveMinimum": 0},
),
design_cases: px.Table[LoadCase] = px.input(
description="Design load cases to check."
),
) -> GirderResults:
summary = calculate_resistance(span, design_cases)
return GirderResults(
resistance_summary=summary,
governing_utilization=max(row.utilization for row in summary),
)
flow = px.Flow(
"girder_design",
label="Girder design",
description="Runs the girder resistance check.",
)
span = flow.input(
"span",
float,
unit="m",
description="Clear span measured between bearing centre-lines.",
constraints={"exclusiveMinimum": 0},
)
design_cases = flow.input(
"design_cases",
px.Table[LoadCase],
description="Design load cases to check.",
)
resistance_check = flow.node("resistance_check", girder_resistance)
flow.connect(span, resistance_check.inputs.span)
flow.connect(design_cases, resistance_check.inputs.design_cases)
flow.result(
"resistance_summary",
resistance_check.results.resistance_summary,
)
flow.result(
"governing_utilization",
resistance_check.results.governing_utilization,
)Extension별 SDK 상세 문서
core 문법은 Function·Component definition, explicit Node placement, Flow 내부 연결과 public input/result까지 정의합니다. 전용 editor, template, 외부 API와 app surface는 설치한 Extension의 문서에서 이어서 설명합니다.
@px.function
무엇을 위한 문법인가요?
@px.function은 일반 Python 계산 함수를 PipelineXLab의 재사용 가능한 Function으로 등록합니다. Function은 정해진 입력을 받아 계산하고, 이름이 붙은 결과를 반환하는 실행 기능입니다. decorator는 definition만 등록하며 함수를 호출하거나 Canvas Node를 배치하지 않습니다.
계산 함수에 이 decorator를 붙이면 SDK가 함수의 입력, 결과, 단위, 설명과 실행 조건을 읽습니다. 이 정보는 PXFLOW Studio, MCP와 Runtime이 같은 Function 계약을 사용하는 근거가 됩니다. Report Workbench는 Function을 직접 연결하지 않고 Flow가 공개한 input/result 계약을 사용합니다.
등록하면 어디에서 사용할 수 있나요?
Function으로 등록하면 다음 위치에서 같은 계산을 사용할 수 있습니다.
- PXFLOW Studio의 Definitions → Functions에서 찾아 Place Node로 Function Reference Node를 배치합니다.
- 다른 Flow에서 같은 Function을 다시 사용합니다.
- Function을 배치한 Flow가 public input/result를 공개하면 Report Workbench에서 그 Flow boundary를 연결할 수 있습니다.
- MCP와 AI가 이름, 설명, 타입과 단위를 읽고 정확한 기능을 선택합니다.
- Runtime이 고정된 version과 실행 정책에 따라 계산합니다.
언제 사용하나요?
다른 Flow에서도 다시 사용하거나 독립적으로 시험·배포할 계산에 사용합니다. 하중 조합, 단면 검토, 수량 계산과 파일 변환처럼 입력과 결과가 분명한 계산이 Function에 해당합니다.
PXFLOW Studio에서 Function Reference Node로 배치할 계산에는 @px.function을 붙입니다. Function 내부에서만 쓰는 작은 helper, 문자열 정리와 단순 수학 보조 함수는 일반 Python 함수로 두고 @px.function(..., helpers=[...])에 선언합니다. 선언한 helper의 소스는 Function revision에 함께 저장되어 실행 시 존재하고, 선언하지 않은 함수는 실행 시 존재하지 않습니다.
기본 작성 형태
@px.function(*, key, version, description, policy=..., capabilities=...)| 항목 | 사용자가 정하는 내용 | 사용되는 곳 |
|---|---|---|
key | Function을 찾고 연결할 때 사용하는 stable lower_snake_case identity | SDK, Studio와 MCP |
| Python 함수 이름 | Function body를 구현하는 Python symbol | source와 stack trace |
version | 입력·결과와 계산 의미의 version | 저장된 Flow, 실행 이력과 결과 출처 |
description | 무엇을 계산하고 언제 쓰는지 설명하는 한 문장 | Project Definitions, 도움말과 AI 검색 |
policy | 재시도, cache와 병렬 실행이 안전한지 여부 | Runtime 실행 계획 |
capabilities | 파일, network, secret과 GPU 중 필요한 권한의 상한 | 실행 전 권한 확인 |
key, version과 description은 필수입니다. 명시한 key가 functionKey이며 Python 함수 이름을 바꿔도 identity는 유지됩니다. package 이름은 pyproject.toml에서 한 번 선언합니다. 사용자는 input/result 목록을 decorator에 다시 만들지 않습니다.
함수 본문은 실제 계산을 수행합니다. -> SomeResults return annotation은 named result schema를 선택하는 타입 계약이고, 현재 registry는 SomeResults의 annotated field를 result port로 읽습니다. Materializer는 함수 본문의 Python 문장을 개별 Node로 바꾸지 않으며, decorated Function 전체와 flow.node(...) placement 하나가 Canvas Node 하나에 대응합니다.
이름과 version
version은 유효한 SemVer입니다. package version에서 자동으로 가져오지 않습니다.- catalog에서 Function은
packageKey/functionKey@version으로 구분합니다. functionKey는 명시한key이며 stablelower_snake_case를 사용합니다.description은 사용자, IDE 도움말과 LLM이 함께 읽는 설명입니다.- 같은 의미를 담는
summary,help,definition을 따로 만들지 않습니다. - 현재 입력과 결과는 decorator에 목록으로 반복하지 않고 parameter annotation과
px.Resultsannotated field에서 읽으며,px.input(...)·px.result(...)marker가 같은 선언부에서 description과 unit을 더합니다.
Package metadata
표준 pyproject.toml을 사용하며 별도 handwritten Flow manifest를 만들지 않습니다.
구현 경계
아래 [tool.pipelinexlab] metadata와 decorator를 읽어 descriptor, portable declaration, executor entry, SBOM과 signing input을 한 번에 만드는 build pipeline은 계획 계약입니다. 현재 @px.function·@px.component registration만으로 package artifact가 생성되지 않습니다.
[project]
name = "pipelinexlab-structural-checks"
version = "2.3.0"
requires-python = ">=3.12,<3.13"
[tool.pipelinexlab]
contract-version = 1
package-key = "structural_checks"
entry-module = "structural_checks"
public-module = "structural_checks"[project].version은 설치·배포 package version입니다.- 각 Function의 public 계약 key와 version은
@px.function(key=..., version=..., description=...)이 별도로 소유합니다. - 계획된
entry-module은 build-time descriptor extraction에 사용하는 승인된 module root입니다. - 계획된
public-module은 다른 사용자의 SDK 코드와 generated type stub이 사용하는 안정적인 Python import module입니다. Python export symbol은 구현 이름이며 explicitfunctionKey또는componentKey가 저장 identity를 소유합니다. - 향후 build가 FunctionDescriptor·Component declaration, host-declarative presentation metadata, dependency lock, SBOM, artifact digest와 signing input을 생성합니다. 현재 decorator와 이 metadata 사이에는 자동 package bridge가 없습니다.
- successor Component placement는 authorization·execution-trust P0 closure를 통과한 live
px.Client가 같은 Project의 admitted exact release를 resolve해야 합니다. current V1은 명시적으로 수용한 unsigned row를absent·not-checked로 저장하고 signed input은 trust authority 없이 기록하지 않습니다. 이 저장 evidence는 production install 또는 execution 승인이 아닙니다. - PXFLOW App 화면을 SDK source로 관리하면
@px.app에서 Flow의 generated input/result reference를 배치합니다. - Streamlit 사용자 화면이 있으면
[tool.pipelinexlab.surfaces.<surface_key>]에format="streamlit", entry module과 허용할 Flow를 선언합니다. Flow port와 unit은 target Flow contract에서 읽습니다. 자세한 구조는 SDK Adapter 체계와 Streamlit 사용자 화면 만들기를 따릅니다. - Component rich editor의 field·entry·message shape는 RFC-016의
component-rich-surface-declaration@1이 정의합니다. Component와 별개인 Extension template·import/export contribution은 각 versioned contract와 trust admission이 정의하며 core Flow 문법을 바꾸지 않습니다.
실행 정책
현재 facade는 @px.function(policy={...})의 plain mapping을 받습니다. 아래 px.policy(...) helper는 planned grammar입니다. 이 예제는 contract_only이며 현재 SDK에서 실행할 수 없습니다. 기본값 외의 policy 선언은 실행이 아직 지키지 않으므로 저장 시 이름 있는 거부를 받습니다 (집행 슬라이스 전까지).
px.policy(
deterministic=True,
parallel_safe=True,
side_effect="none", # none | local_write | external
cache="content", # none | content
retry="safe", # never | safe
timeout_seconds=60,
)기본값은 deterministic=True, parallel_safe=True, side_effect="none", cache="content", retry="safe"입니다. Function과 Component target 모두 capability나 실제 동작과 모순되면 validation error입니다. 외부 effect가 있는 target은 retry="never", cache="none"으로 설정하고 Manual Run에서 실행합니다.
capability
현재 facade는 @px.function(capabilities={...})의 plain mapping을 받습니다. 아래 기본값(files: "none", 빈 network/secrets, gpu: False) 외의 capability 선언은 실행이 아직 지키지 않으므로 저장 시 이름 있는 거부를 받습니다. dependencies의 exact pin은 저장할 수 있지만 실행에는 host가 준비한 검증된 보관 환경이 필요합니다. 패키지 누락·버전 불일치·보관 환경 부재는 source 실행 전에 거절하며 실행 중 자동 설치하지 않습니다. px.capabilities(...) helper는 planned grammar입니다. 이 예제는 contract_only이며 현재 SDK에서 실행할 수 없습니다.
px.capabilities(
files="selected_read", # none | selected_read | project_read | project_write
network=("api.example.com",),
secrets=("structural_api",),
gpu=False,
)이 선언은 target이 요청할 수 있는 권한의 상한선입니다. Runtime이 현재 principal과 deployment policy로 실제 허용 범위를 결정합니다.
px.input, flow.input과 result
target input의 type은 일반 parameter annotation, named result의 type은 px.Results subclass의 annotated field에서 읽습니다. 아래 marker는 같은 선언부에서 description과 unit을 한 번만 선언하는 current 문법이며 description, unit, required keyword만 받습니다.
px.input(*, description, unit=None, required=True)
px.result(*, description, unit=None)- 계획 계약에서도 target value type은 왼쪽 Python annotation이 소유합니다.
px.input(...)은 target parameter에만 사용합니다.px.result(...)는px.Resultsfield에만 사용합니다.- 숫자 공학 값에는 canonical unit이 필요하며 dimensionless는
"1"입니다. connection=True가 기본입니다.connection=False는 일반 picker에서 숨기지만 같은 Flow의 명시적 연결에는 사용할 수 있습니다. ACL은 별도 Project 권한에서 관리합니다.
아래 required/default 표도 marker가 구현된 뒤 적용할 계획 계약입니다.
| 공개 문법 | required | nullable | default |
|---|---|---|---|
x: T = px.input(...) | true | false | 없음 |
x: T = px.input(..., default=value) | false | false | value |
x: T | None = px.input(...) | true | true | 없음 |
x: T | None = px.input(..., default=None) | false | true | null |
Flow boundary는 함수 parameter로 추론하지 않습니다. module-level Flow object에 stable key와 type을 직접 전달합니다.
span = flow.input(
"span",
float,
unit="m",
description="Clear span.",
)flow.input(...)은 source handle을 반환합니다. target input과 연결할 때는 flow.connect(span, node.inputs.span)처럼 destination을 명시합니다. 공개 result는 flow.result("result_key", node.results.result_key)로 선언합니다.
Report Workbench는 이 public input/result만 연결 대상으로 보여 줍니다. Report authored source → Flow input과 Flow result → Report position mapping은 .pxreport가 소유하므로 flow.connect(...)로 표현하지 않습니다. flow.connect(...)는 Canvas에 보이는 current Flow 내부 연결과 1:1로 대응합니다.
px.setting 경계
px.setting(...)은 px.Settings subclass의 field 하나가 선언하는 description, unit, requiredness와 default를 소유합니다. settingKey는 그 field 이름이고, 선언한 closed type에서 host control registry가 control을 그리며 package renderer나 migration schema는 받지 않습니다. portable Component V2 placement의 settings는 component-declared-settings@1이 소유하고 선언당 최대 8개까지 받으며 host control registry가 그립니다. 현재 V1 placement의 기존 non-empty settings wire는 호환을 위해 그대로 읽으며 V2 규칙으로 소급해 좁히지 않습니다.
settings 계약과 rich surface 계약은 분리합니다. settings owner가 승인돼도 package renderer나 modal을 허용한다는 뜻이 아닙니다. rich surface의 entry·message 형식은 RFC-016의 component-rich-surface-declaration@1이 정의합니다.
IDE와 정적 분석
- target body 안에서 parameter는 annotation의 Python type으로 인식됩니다.
- port의 type은 일반 annotation이 정합니다.
px.input(...),px.result(...)과px.setting(...)은 type을 바꾸지 않는 metadata marker이고 public stub에 포함합니다. 계획된px.field(...)도 구현될 때 같은 원칙을 따릅니다. - 현재 공개 stub은 구현된 facade만 제안해야 하며 계획 marker를 runnable API로 노출하지 않습니다.
- decorator는 definition을 등록할 뿐 호출하거나 배치하지 않습니다. target body는 배치된 Node를 Run할 때만 실행됩니다.
- 저장 형식에는 Python typing 표현이 아니라 정규화된
TypeDescriptor가 들어갑니다.
@px.record, px.field와 px.Table
이 절의 decorator @px.record, px.field와 px.Table 문법은 contract_only 계획이며 현재 public facade에 없습니다. 현재 record schema는 px.Record subclass의 annotated field로 선언하고, collection port는 지원되는 원소 annotation의 list[T]를 사용합니다. planned Record는 여러 field를 하나의 port 값으로 묶을 때 선택합니다. scalar port는 float, str, bool 같은 일반 Python type만으로 선언하고 @px.record를 요구하지 않습니다. 예를 들어 한 하중 case의 이름, 고정하중과 활하중을 함께 전달할 때 LoadCase Record를 정의합니다. Table은 같은 Record를 여러 행으로 전달할 때 사용합니다. @px.record는 data schema만 등록하며 Canvas Node나 실행 target이 아닙니다.
Record와 Table은 placed Node 또는 Flow의 input/result port를 지나가는 값의 구조입니다. Canvas topology는 explicit flow.node(...) placement와 flow.connect(...)로 구성하고, PDF·image·큰 binary는 접근 권한이 확인되는 artifact reference로 전달합니다.
Report 연결 주소는 top-level flow.input(...)·flow.result(...) port가 소유합니다. Record는 그 port로 전달할 값 하나가 여러 field를 가질 때의 schema를 소유합니다.
@px.record(key, *, description, row_key=None)
px.field(*, description, unit=None, constraints=None, default=px.REQUIRED)key에는 explicit stablelower_snake_caserecordKey를 입력합니다.row_key는 이 Record를 Table row로 사용할 때의 exactfieldKey입니다. 지정하면 해당 field는 required·non-nullstr또는int여야 하며 TypeDefinition의rowKeyField로 materialize됩니다.- field name이
fieldKey입니다. - field type과 nullability는 왼쪽 annotation이 소유하며 required/default는
px.field(...)의default유무로 결정합니다. px.field는 nested value의 설명·단위·제약을 소유하고, 외부 연결 여부는 top-level port가 결정합니다.- materializer는
px.field(...)로 선언한 field를 record schema로 읽고 method와 계산 body는 구현으로 유지합니다.
Collection type은 다음처럼 씁니다.
이 예제는 contract_only이며 현재 SDK에서 실행할 수 없습니다. list[float]는 current이지만 같은 fence의 px.Table[LoadCase]는 planned grammar입니다.
px.Table[LoadCase] # 같은 record schema의 row table
list[float] # 같은 item type의 ordered listpx.Table[T] 하나는 하나의 typed input/result 값이며 target을 한 번 실행합니다. 전체 Table result를 Report result table에 표시하려면 row Record에 row_key=가 있어야 합니다. 그 key로 Report의 explicit target table과 field mapping을 정의합니다.
Python type과 wire type
현재 facade가 materialize하는 type은 scalar, Literal[...], date/time/duration과 지원되는 원소의 list[T] 및 생성 결과용 px.Artifact입니다. @px.record, px.Table, px.Binary, px.Evidence와 px.CalculationTrace 행은 contract_only 계획이며 current SDK에서 실행할 수 없습니다. 목표 Public type은 다음 표의 closed shape를 사용합니다.
| Python type | TypeDescriptor kind | 규칙 |
|---|---|---|
bool | boolean | int로 암묵 변환하지 않음 |
int | int64 | signed 64-bit 범위를 벗어나면 validation error |
float | float64 | finite 값만 허용 |
decimal.Decimal | decimal | canonical decimal string |
str | string | UTF-8 string |
Literal[...] | enum | literal wire value를 stable key로 사용 |
datetime.date | date | ISO 8601 date |
datetime.datetime | timestamp | timezone-aware 값만 받고 UTC RFC 3339로 정규화 |
datetime.timedelta | duration | canonical ISO 8601 duration |
@px.record class | record | explicit recordKey로 resolve |
list[T] | list | ordered homogeneous item |
px.Table[T] | table | T는 @px.record row이며 전체 Table이 한 값 |
px.Binary | binary | content-addressed bounded binary reference |
px.Artifact | artifact | 현재 Run 생성 PNG·binary 결과. 외부 파일 입력 reader는 planned |
px.Evidence | evidence | provenance와 digest를 가진 immutable evidence reference |
px.CalculationTrace | calculation_trace | 수식·대입·solver·iteration·check와 provenance를 가진 immutable 계산 근거 |
현재 Project Function 실행은 고정 Function revision의 입력 선언을 읽어 wire 값을 Python 인수로 복원합니다. int64는 int, decimal은 Decimal, date/timestamp/duration은 각각 date/UTC datetime/timedelta로 전달하며 지원되는 중첩 list[T]에도 적용합니다. str로 선언한 숫자 문자열은 문자열로 유지합니다. 저장된 숫자·시간의 wire spelling과 revision은 바뀌지 않으며 Python timedelta로 정확하게 표현할 수 없는 기간은 반올림하지 않고 거부합니다. 명시적인 null은 nullable 입력에만 허용하고, 연결하지 않은 Python default는 유지합니다. 이 Python 객체 복원은 Project Function 호출 경계에 한정됩니다. Component의 execute(inputs, settings) 입력·출력은 선언 기반 canonical wire 표현을 사용합니다. int64·decimal·날짜·시간·기간은 wire 문자열, list/record/table은 그 재귀 구조를 받습니다. Flow 입력에서 왔는지 다른 Function/Component에서 왔는지에 따라 표현이 바뀌지 않습니다. settings와 선언된 default 전달 규칙은 유지합니다. Function과 Component의 선언된 모든 출력은 반환 직후, 다음 Node 실행·캐시 기록 전에 검증합니다. cache hit에도 같은 검증을 적용합니다. private worker protocol은 일반 결과는 pxflow-python-worker/2, 생성 파일은 /3이며 typed Component 선언을 제공하는 Runtime과 SDK를 함께 갱신해야 합니다. 기존 파일·revision은 바뀌지 않고, 잘못된 출력에 의존하던 실행은 발생 Node에서 거부됩니다.
public port에는 위 표의 closed type을 사용합니다. 큰 payload는 px.Binary 또는 px.Artifact, 시간 값은 timezone-aware datetime으로 선언합니다.
고정 선택지는 Python Literal로 선언합니다. 각 literal value가 stable wire value이며 화면 label은 별도 번역 projection입니다.
from typing import Literal
SteelGrade = Literal["s355", "s460"]px.Artifact와 큰 파일
현재 px.Artifact.from_bytes(data: bytes, *, media_type: str)는 Function/Component 내부에서 만든 불변 bytes를 결과로 반환합니다. MIME은 image/png와 application/octet-stream, 파일당 32 MiB, Attempt당 64 MiB·32개까지입니다. 생성은 저장이나 읽기 권한 부여가 아니며 Runtime이 파일의 크기·digest와 PNG 형식/디코딩 한도를 검사한 뒤 같은 Run snapshot에 확정합니다. Run.read_result로 원본 bytes를 다시 읽습니다. 같은 객체를 여러 결과로 반환하면 한 번 전송합니다. 최상위 named artifact 결과를 지원하며 복합 list/record 내부의 artifact 결과는 명시적으로 거절합니다. 파일이 포함된 결과는 일반 JSON 결과 캐시에 기록하지 않습니다. 직접 생성자 호출은 지원하지 않습니다. 자세한 예제와 제한은 Artifact API를 따릅니다.
planned 계약에서는 PDF, image, Arrow와 큰 binary를 path string이 아니라 authorized object reference로 받습니다. 이 예제는 contract_only이며 현재 SDK에서 실행할 수 없습니다.
pdf_source: px.Artifact = px.input(
description="PDF source document.",
constraints={"mediaTypes": ["application/pdf"]},
)계획된 외부 입력 reader는 host path나 unrestricted bytes를 제공하지 않습니다. 이 reader의 capability·byte 정책 집행은 후속 범위이며 현재 생성 결과 API에 포함되지 않습니다.
px.Results
px.Results는 @px.function 또는 @px.component가 반환할 결과에 이름을 붙입니다. 결과가 하나뿐이어도 이름을 명시하면 Report Workbench와 AI가 순서나 화면 label을 추측하지 않고 정확한 결과를 선택할 수 있습니다. 결과가 여러 개라면 각 field가 독립적인 result port가 됩니다.
Node target은 tuple 또는 이름 없는 list가 아니라 px.Results subclass를 반환합니다. Flow는 flow.result(...)로 이 named result handle을 공개합니다.
class SolverResults(px.Results):
displacement: float
calculation_trace: strannotated field의 이름과 type이 result key와 타입을 정하고, unit·description을 field에 더하는 px.result(...)는 current marker입니다. connection metadata는 계획 계약으로 남습니다.
planned px.CalculationTrace는 FormulaExpression과 계산 항목을 포함한 typed result입니다. Flow가 named result로 공개하면 Report Workbench는 immutable ResultSnapshot에서 읽어 수식과 계산 근거를 표시할 수 있습니다. optional Extension의 확장 표현은 해당 Extension SDK가 정의합니다.
field name이 portKey입니다. output0, 첫 번째 result 추측과 single-result alias는 금지합니다.
px.Flow와 명시적 Node 배치
Flow는 decorated function이 아니라 module top level의 declarative object입니다.
이 절의 문법은 공개 문서의 사람용 정확 문법 SSOT입니다. 아래 일곱 Flow call을 포함한 Flow·decorator·codec·Report·Runtime의 exact public signature 35개와 필수 keyword 목록은 machine contract인 authoring-profile.v1.json의 python.exactGrammar에도 저장됩니다. 각 entry는 implementationStatus로 그 callee가 오늘 빌드되어 있는지도 함께 밝히며, 같은 값이 callee별 구현 상태 표에 있습니다. 두 계약을 함께 변경한 뒤 npm run docs:contract를 통과해야 API 문서와 예제에 반영할 수 있습니다.
px.Flow(flow_key: str, *, label: str, description: str)
flow.input(input_key: str, ValueType: object, *, description: str, unit: str | None = None, constraints: dict | None = None) -> '_Port'
flow.node(node_key: str, target: object, *, settings: dict | None = None, client: 'Client | None' = None) -> '_Node'
flow.connect(source: '_Port', destination: '_Port') -> None
flow.result(result_key: str, source: '_Port', *, description: str | None = None) -> '_Port'
flow.group(group_key: str, *, members: list | tuple, label: str | None = None) -> None
flow.viewer(viewer_key: str, *, results: list | tuple, x: float = 0.0, y: float = 0.0) -> None
flow.subflow(subflow_key: str, imported_flow: 'Flow', *, label: str | None = None, client: 'Client | None' = None) -> '_Subflow'
flow.preview_html(path: str | os.PathLike) -> str
@px.function(*, key: str, version: str, description: str, policy: dict | None = None, capabilities: dict | None = None, helpers: list | None = None, dependencies: dict | None = None)
@px.component(*, key: str, label: str, description: str, category: str | None = None, subcategory: str | None = None, contract_version: int = 1, executor_entry: str | None = None, icon: str | None = None, summary_results: list | tuple | None = None, settings: type | None = None, deterministic: bool | None = None, cache: str | None = None)flow.input(..., constraints=...)는 direct schema의 closed grammar v1 object를 그대로 저장하며, 현재 승인된 predicate kind는 allowedValues 하나입니다. 예: constraints={"version": 1, "allowedValues": ["s355", "s460"]}. 임의 expression이나 code는 허용하지 않고 kind 목록은 schema·core·contract lock을 함께 갱신하는 additive update로만 늘립니다. 이 exact signature에는 requiredness 인자가 없으므로 Python으로 선언한 input은 모두 required이며 direct document의 required omission도 기존 V1 의미대로 true입니다.
한 .py Flow module은 public flow object를 정확히 하나 export합니다. 복잡한 Flow도 큰 decorated function body로 감싸지 않고 placement와 connection을 top level에 나열합니다. Flow 선언을 읽는 과정은 계산을 실행하지 않습니다.
flow.node("node_key", target, settings={...})는 Function의 durable Node placement와 Component target variant의 wire representation을 만듭니다. 반환된 handle의 inputs.<portKey>는 connection destination이고 results.<portKey>는 source입니다. Function target은 production Canvas Node로 표시합니다. Component target은 exact reference/raw settings recovery만 유지한 read-only unsupported이며 production third-party install admission·activation을 뜻하지 않습니다.
flow.node(...) target | 저장 target variant | Canvas 표현 | 재사용 경계 |
|---|---|---|---|
같은 Project의 @px.function metadata | ProjectFunctionRef | read-only Function Reference Node | Project Function version이 소유 |
installed package의 @px.function metadata | FunctionRef | read-only Function Reference Node | exact installed release가 소유 |
imported @px.component companion metadata | ComponentRef | 현재 production reader에서 read-only unsupported | exact saved 7-field ref가 소유; installed release resolution은 successor gate 이후 |
일반 Python def는 target resolution에서 거부합니다. Function이 부를 helper는 helpers=[...]로 선언해 revision에 싣고, Component 구현 module 안의 helper는 module과 함께 실행됩니다. @px.function은 executable definition을 등록하지만 @px.component는 current V1 companion metadata만 등록하며 executor·package declaration·install 권한을 만들지 않습니다. 어느 decorator도 Node 자체를 만들지 않습니다. Imported px.Flow object는 flow.node(...) target resolution에서 거부합니다. external Flow는 flow.subflow(...)로 배치합니다.
# 아래 두 이름은 explicit @px.function target으로 resolve됩니다.
flow = px.Flow(
"bridge_review",
label="Bridge review",
description="Combines loads and checks resistance.",
)
span = flow.input("span", float, unit="m", description="Clear span.")
load_cases = flow.input(
"load_cases",
list[float],
description="Design load cases.",
)
combinations = flow.node("load_combinations", combine_loads)
resistance = flow.node("resistance_check", girder_resistance)
flow.connect(load_cases, combinations.inputs.load_cases)
flow.connect(span, resistance.inputs.span)
flow.connect(
combinations.results.design_cases,
resistance.inputs.design_cases,
)
flow.result(
"governing_utilization",
resistance.results.governing_utilization,
)flow_key,node_key, input key와 result key는 stablelower_snake_case입니다.node_key는 target name, local variable name, source line과 Canvas 좌표에서 파생하지 않습니다.- source는 Flow input handle 또는 placed Node/subflow의 named result handle입니다.
- destination은 placed Node/subflow의 exact input handle입니다.
settings={...}는 Component Node instance setting을 저장하지만, 현재@px.componentfacade에는 이를 선언·검증하는 setting schema가 없습니다. settings-bearing V2 Component를 발행하지 않습니다.- 모든 Flow 내부 value connection은
flow.connect(...)로 드러냅니다. placement keyword나 Python assignment로 숨은 input binding을 만들지 않습니다. - Node layout은 Canvas presentation state가 소유하며 decorator와 target은 소유하지 않습니다.
Materialize와 source write-back 계약
materialize는 SDK source에서 direct Flow를 만드는 정방향 projection입니다. local registration, Flow input/result, Node placement, exact target ref, connection, Group과 Component setting을 읽으며 target의 계산 body나 Run을 실행하지 않습니다.
이 Flow projection은 portable Component declaration·executor container를 만드는 package build가 아닙니다. @px.component에서 package artifact로 이어지는 자동 bridge는 현재 없습니다.
SDK-linked Flow에서 Studio 변경을 source에 반영할 때는 기존 Python module에 승인된 code action을 적용합니다. source write, rematerialize와 semantic parity 검사는 하나의 작업으로 완료되어야 하며, 다시 만든 Flow가 사용자가 검토한 candidate Flow와 의미상 같을 때만 새 revision을 저장합니다.
PXFLOW-native Flow는 .pxflow direct object가 작성 원본입니다. 여기서 표시하는 SDK 코드는 Flow 구조를 설명하는 파생 preview이며 source owner가 아닙니다. direct Flow에는 Python body, helper, import 구성, 주석과 formatting이 들어 있지 않고 여러 Python source가 같은 Flow를 만들 수 있으므로 기존 source 전체를 역산해 덮어쓰지 않습니다. 사용자 절차와 변경별 편집 위치는 Studio에서 편집한 내용을 저장하는 방법을 따릅니다.
Plain Python helper
일반 Python 함수는 Function 구현을 나누는 helper로 작성하고 @px.function(..., helpers=[...])에 선언합니다. helper에는 public port marker를 붙이지 않고 flow.node(...)에 직접 전달하지 않습니다.
def calculate_section_utilization(section):
return max(section) / 250.0
@px.function(
key="custom_section_check",
version="1.0.0",
description="Checks one section and returns its utilization.",
helpers=[calculate_section_utilization],
)
def custom_section_check(section: list[float]) -> SectionCheckResults:
return SectionCheckResults(
utilization=calculate_section_utilization(section)
)
check = flow.node("section_check", custom_section_check)
flow.connect(section, check.inputs.section)
flow.result("utilization", check.results.utilization)Canvas에는 custom_section_check Function Reference Node만 보입니다. calculate_section_utilization의 소스는 선언에 따라 같은 Function revision에 저장되어 실행 시 함께 존재합니다. 선언하지 않은 module-level 함수는 revision에 저장되지 않으므로 실행 시 존재하지 않습니다.
flow.group(...)
flow.group(...)은 같은 Flow에 이미 배치된 Node의 presentation membership만 선언합니다. 실행 unit, connection, public interface와 ownership은 바뀌지 않습니다.
flow.group(
"member_check",
members=[combinations, resistance],
label="Member check",
)group_key는 Flow 안에서 unique한 stablelower_snake_case입니다.members는 같은 Flow에 배치된 Node handle입니다.- Group은 Node나 nested Flow가 아니며 실행 순서와 data dependency를 만들지 않습니다.
- group/ungroup은 기존 Node와 connection을 그대로 둔 채 presentation membership만 바꿉니다.
flow.subflow(...)
다른 Flow를 재사용할 때는 owning module이 export한 flow object를 일반 Python import한 뒤 flow.subflow("subflow_key", imported_flow)로 배치합니다.
from structural_checks.flows.section_properties import flow as section_properties_flow
section_properties = flow.subflow(
"section_properties",
section_properties_flow,
)
flow.connect(geometry, section_properties.inputs.geometry)
flow.result("area", section_properties.results.area)외부 Flow는 caller에서 읽기 전용인 nested Flow입니다. 내부를 바꾸려면 Edit source Flow로 owning module로 이동합니다. same-Flow Group과 달리 별도 source와 Flow revision을 소유합니다. imported Flow를 flow.node(...) target으로 전달하지 않습니다.
Table과 여러 input set 실행
Table port는 Flow 안에서 다른 값과 같은 단일 typed value입니다. Table binding 하나는 target을 한 번 실행합니다. Node body가 Table의 행을 공학 의미에 맞게 처리합니다.
같은 Flow를 여러 scalar input set으로 실행해야 하면 batch caller가 saved Flow revision에 각 input set을 명시적으로 제출합니다. 이는 Canvas Node나 숨겨진 문서 연결을 만들지 않으며, 각 실행은 독립된 Run과 ResultSnapshot을 가집니다.
예외
planned exception 계약에서는 예측 가능한 입력 오류를 stable code를 가진 px.InputError로 반환합니다. 이 예제는 contract_only이며 현재 SDK에서 실행할 수 없습니다.
raise px.InputError(
code="SECTION_NOT_FOUND",
path="/inputs/section_name",
params={"sectionName": section_name},
)Runtime은 @px.function body 실패를 PX_FUNCTION_FAILED, @px.component target body 실패를 PX_COMPONENT_FAILED로 정규화합니다. params에는 실제 functionKey 또는 componentKey를 담고, path는 고정 Flow의 /nodes/<index>/target을 가리킵니다. 외부 Subflow 내부 실패는 호출한 Flow의 /externalSubflows/<index>/target을 가리킵니다. 진단을 Attempt와 함께 저장하며 Runtime 재시작 후에도 run.refresh().diagnostics로 확인할 수 있습니다. 원문 예외·스택은 공개 진단에 포함하지 않습니다. diagnosticId는 제공되는 경우에만 로그 연결에 사용합니다.
Exact grammar implementation status
Approved client spelling은 px.Client(workspace=..., project=...)이고 px.client()는 implementation claim이 아닌 named contract mismatch입니다. Exact grammar 표의 human notation은 callee | implementationStatus입니다. implemented는 callee final segment와 같은 def 또는 class가 검색 대상으로 고정된 named Python package 중 하나에 존재한다는 뜻만 가지며 receiver, signature, keyword conformance를 증명하지 않습니다. Reality baseline은 searched packages와 resolution pattern만 pin하고 callee별 truth는 authoring-profile.v1.json만 소유합니다. 이 implementationStatus는 exact Python authoring grammar에 local한 contract이고 unrelated public contract에 자동 확장되지 않습니다.
공식 작성 형태 빠른 확인
| 작성할 내용 | 사용할 문법 |
|---|---|
| 현재 Function·Component 입력 | 일반 Python parameter annotation + px.input(...) marker |
| Flow 입력 | flow.input("port_key", ValueType, ...) |
| 현재 이름 있는 결과 | px.Results subclass의 annotated field + px.result(...) marker |
| 현재 record schema | px.Record subclass의 annotated field |
| Canvas Node instance와 stable identity | flow.node("node_key", target); settings schema는 component-declared-settings@1 |
| Component package version과 digest | 설치된 container resolver가 반환한 exact ComponentRef |
| 현재 Component definition | @px.component의 process-local metadata; 자동 package bridge 없음 |
| 후속 V2 Component Canvas UI | host-owned component.declarative@2; package UI 없음 |
| Component settings | px.Settings subclass의 px.setting(...) field · component-declared-settings@1 선언 · 선언당 최대 8개 |
| rich surface | RFC-016의 component-rich-surface-declaration@1 소유 |
| 같은 Flow 안의 presentation 묶음 | flow.group(...) |
| 외부 Flow 배치 | flow.subflow("subflow_key", imported_flow) |
| Report authored source·result 위치와 public port 연결 | px.Report 또는 Report Workbench의 .pxreport.flowConnections |
| SDK source에서 Flow 저장 파일 생성 | pxlab flow materialize ... --output <name>.pxflow |
| Python helper 구현 | Function은 helpers=[...]로 선언해 revision에 저장, Component는 executor module 동거; Node target 아님 |
Report authored numbers use int for signed int64, decimal.Decimal for exact decimal, and float for float64. For example section.value("tolerance", Decimal("1e-18"), unit="1") uses Core canonical decimal storage; import Decimal from Python's decimal module. A raw string such as "1.00" remains a string. section.table(...) applies the same scalar codec to new cells and preserves the chosen row identity. A saved Report's computationScale controls subsequent Decimal writes, while displayScale never changes stored execution values.
Descriptor 등록 실패의 공개 분기
from pipelinexlab import px로 가져온 px.Error를 잡고 diagnostics의 code와 params로 분기합니다. 예외 subclass 이름은 private implementation detail입니다.
| 등록 실패 | Diagnostic.code | Diagnostic.params |
|---|---|---|
| annotation 누락 | PX_REQUEST_SHAPE_INVALID | member="annotation", reason="missingAnnotation" |
| 지원하지 않는 annotation | PX_REQUEST_SHAPE_INVALID | member="annotation", reason="unsupportedAnnotation" |
| 같은 종류의 descriptor key 중복 | PX_KEY_DUPLICATE | key=<등록한 key> |
| decorator/port/setting metadata 오류 | PX_REQUEST_SHAPE_INVALID | member="metadata", reason="invalidMetadata" |
from pipelinexlab import px
try:
@px.function(key="missing_annotation", version="1.0.0", description="Annotation example.")
def calculate(value) -> None:
pass
except px.Error as refusal:
for diagnostic in refusal.diagnostics:
if diagnostic.code == "PX_REQUEST_SHAPE_INVALID":
print(diagnostic.params["member"], diagnostic.params["reason"])메시지와 private 클래스명은 분기 계약에 포함되지 않습니다. 등록 단계는 함수 본문을 실행하지 않습니다.
번역 결과의 원문 연결·읽기 순서·구간 대응은 DocumentTranslation을 참고합니다.
프로젝트 PDF 기록과 로컬 원본 복사는 ProjectPdfFiles를 사용합니다. Web의 remember는 원본 업로드가 아니며, 재선택은 같은 key의 revision을 갱신합니다.
부분 편집과 제한된 Report·이미지 읽기
report.table(key).cell(row, field)는 canonical kind/value/unit 복사본을 반환합니다. set_cell(row, field, value, expected=before)는 같은 타입·단위를 유지하고 기존 셀만 수정합니다. 행 ID 열은 수정하지 않습니다. 여러 셀 변경은 기존 report.save()에서 함께 검증·저장됩니다.
block.select(start, end).read()와 .replace(text)는 Unicode code point 범위를 사용합니다. 빈 범위는 삽입이고 빈 문자열 교체는 삭제입니다. inline 입력/결과 노드를 포함한 범위는 거절합니다. 주변 텍스트/서식은 유지하고 새 텍스트는 선택 첫 글자의 marks, 삽입이면 앞 텍스트의 marks를 사용합니다. 선택한 블록이 바뀌면 해당 선택을 다시 사용하지 못합니다.
block.replace(node, expected=before)는 캡처한 로컬 초안과 현재 블록이 같은 경우에만 교체합니다. expected를 생략하면 기존 교체 동작입니다. 저장 revision CAS와 별개이며, 하나의 Report 객체에 여러 스레드가 동시에 쓰거나 다른 저장본의 변경을 자동 병합하는 기능은 아닙니다.
client.read_report는 exact revision과 blocks, tables={key: {"rows": [...], "fields": [...]}}, attachments로 지정한 부분만 반환합니다. 결과의 target은 scope/report/revision을 보존하며 selection.blocks에는 canonical block/values, selection.tables에는 식별자와 선택된 canonical cell, selection.attachments에는 첨부 메타데이터가 있습니다. 수정 가능한 Report 파일이나 저장 명령이 아닙니다. 블록 64개, 표 16개, 표당 행 256개·열 64개, 전체 셀 4096개, 첨부 64개, selection JSON 256 KiB가 상한입니다. 빈 요청·중복·없는 대상·상한 초과는 거절하며 자동으로 잘라서 반환하지 않습니다. 서버는 현재 전체 저장본을 읽고 검증한 뒤 추출하지만, SDK에 전체 문서를 전송하지는 않습니다.
이미지는 기존 section.attachment(key, path, media_type="image/png")로 저장합니다. PNG/JPEG/SVG 메타데이터 조회와 원본 바이트 읽기는 구분됩니다. client.read_attachment(report_key, attachment_key, revision=..., max_bytes=8388608)는 명시적으로 요청한 원본만 읽고 크기·MIME·offset·digest를 검증합니다. 큰 파일은 기존 export_documents로 내보냅니다. 이미지 생성 서비스가 만든 파일도 같은 attachment 경로로 추가할 수 있습니다. 이 API는 이미지 이해, OCR, 이미지 생성 모델 호출을 실행하지 않으며 임시 URL이나 이미지 base64를 Report 본문에 저장하지 않습니다.
flow.viewer(...)
공개 result handle 목록과 위치를 Flow V2 presentation에 저장합니다. 같은 Flow의 공개 결과만 연결하며 계산 노드는 추가하지 않습니다. flow.viewer("figures", results=[flow.results.geometry, flow.results.mesh, flow.results.centroids])처럼 여러 그림 결과를 하나의 뷰어에 연결할 수 있습니다. 선택 Run은 session 상태이며 문서에 저장하지 않습니다. 기존 viewer 없는 Flow는 V1을 유지합니다. SDK 작성·codec·저장 계약과 제품 화면 연결의 제공 상태는 API 안내를 따릅니다.
저장된 Flow의 Plan/Apply
client.plan_flow_change(flow: FlowRef, *, commands: list | tuple) -> dict
client.apply_flow_change(plan: dict, *, command_id: str) -> FlowRef
client.search_functions(*, function_key_prefix: str | None = None, sort: str = "functionKeyAscending", limit: int = 50, cursor: str | None = None) -> dict
client.describe_function(function_key: str, *, version: str | None = None) -> dict
client.plan_function_source_change(function_key: str, *, version: str, source: str) -> dict
client.apply_function_change(plan: dict, *, command_id: str) -> dict기존 Flow 편집은 정확한 저장 revision에 대한 공통 Plan/Apply를 사용합니다. 뷰어 편집, 명령 묶음, 응답 유실 후 재시도는 Flow 변경 API를 따릅니다. client.export_documents의 flows 값은 작성한 Flow와 저장된 FlowRef를 받습니다.
저장된 FlowRef의 실행
client.run은 작성한 Flow, 정확한 저장 FlowRef 또는 Report connection을 받습니다. FlowRef 경로는 Project 범위와 저장 revision의 canonical bytes를 검증하고 해당 입력 타입·단위를 기존 제출 경로에 연결합니다. 현재 head 선택이나 암묵적 저장을 수행하지 않습니다. 같은 주소·입력·run_key·command_id를 보관하면 새 Client/프로세스에서도 기존 Run을 재조회합니다. 상세 사용법은 Run API를 따릅니다. 공개 signature는 바뀌지 않습니다.
저장된 Run과 제출 입력 조회
client.get_run(run_key: str) -> Run은 현재 Client의 Project에서 기존 run_get을 호출한다. 새 실행을 제출하지 않으므로 원래 Flow 객체·입력·command ID가 없어도 저장된 Run을 읽는다. 없는 Run은 PX_TARGET_NOT_FOUND이며 조회로 생성하지 않는다. 반환 Run은 이 Client를 빌려 쓴다.
run.read_inputs(*, max_bytes: int) -> bytes는 그 Run의 canonical typed input JSON bytes를 읽는다. product.run.read_inputs_chunk가 Project/Run 소속을 확인하며 저장된 input payload digest를 따른다. 형태는 입력 key → {type, unit?, value}이고 정수·Decimal의 wire 문자열을 보존한다. 0–64 MiB 반환 상한, 64 KiB chunk, 매 chunk의 Run·Flow revision·input digest 일치와 최종 digest를 검사한다. 실행 전/실패 Run에도 저장 입력은 조회할 수 있다. 파일이 없으면 명시적으로 거절하며 현재 초안이나 새 Flow head의 값으로 대체하지 않는다. UI 초안 비교는 별도 소비자 작업이다.
Flow 입력 초안 읽기와 저장
client.read_flow_input_draft(flow: FlowRef) -> dict와 client.save_flow_input_draft(flow: FlowRef, values: dict, *, expected_version: str | None, command_id: str | None = None) -> dict는 인증된 principal·Project·Flow에 속한 입력 문자열을 저장하고 읽는다. Runtime operation은 product.flow.read_input_draft와 product.flow.save_input_draft다. 해당 Flow revision이 실제로 존재해야 하며 서버는 현재 권한을 확인한다. 읽기는 ArtifactRead, 저장은 ArtifactRead와 RunExecute를 요구한다.
values는 선언된 입력 key에서 정확한 문자열로의 dict다. 빈 문자열과 "1e" 같은 미완성 입력은 유효한 초안이며 계산 입력으로 자동 변환되지 않는다. 저장은 Flow 문서·head·기존 Run을 바꾸지 않는다. 실행은 기존 client.run(flow_ref, **typed_inputs)에서 별도로 검증하고 제출한다.
응답은 {draft, inputSchemaRef}다. 없는 초안은 draft=None; 저장된 초안은 schemaVersion=1, sourceRevisionRef, inputSchemaRef, values, 양의 문자열 version을 가진다. 바깥의 inputSchemaRef는 선택한 정확한 Flow의 입력 선언 digest다. 안쪽 digest가 같을 때만 자동 복원하며, 배치만 변경된 revision은 이를 유지한다. 입력 선언이 달라져도 기존 초안을 덮거나 삭제하지 않는다.
새 저장은 expected_version=None, 수정은 읽어 둔 draft["version"]을 전달한다. 충돌을 최신 버전으로 몰래 덮어쓰지 않는다. 응답 유실 시 원래 FlowRef·values·expected_version·command_id로 재시도하면 그 요청의 저장 receipt를 받는다. 이후 저장이 있어도 이전 receipt는 원래 버전을 반환한다. 자동 생성한 command_id도 오류의 command_id에 보존하며 outcome_unknown으로 미확정 여부를 표시한다. 구체적 사용 예시는 Flow 변경 API를 따른다.