Typed I/O ports
Each function exposes named input and output ports. Ports carry types from the canonical catalog; the workflow editor uses them to validate connections at draw time, and the worker uses them to route values into the right slots inside the container.
Declaration shape
Section intitulée « Declaration shape »inputs: and outputs: under a function are dicts where each key is
a port name and each value describes the port:
functions: size: handler: bolts:size inputs: diameter: type: Length description: "Bolt nominal diameter in metres." required: true # default: true load: type: Force required: false # optional input outputs: stress: type: Stress description: "Von Mises equivalent stress." report: type: File[pdf] required: false # File outputs may be optionalEach entry has the same shape:
| Field | Type | Required | Notes |
|---|---|---|---|
type | string or mapping | yes | Type expression — see below. |
description | string | no | Free text, surfaced in the editor and AutoForm. |
required | boolean | no | Defaults to true. See semantics below. |
The port name is the dict key — it must be a non-empty string,
typically snake_case. It becomes the keyword argument the runner
passes to your handler in mode A, and the JSON key in
/workspace/in/data.json in modes B/C.
Type expressions
Section intitulée « Type expressions »Every type: value is parsed against the
type catalog. The parser accepts:
Nominal types
Section intitulée « Nominal types »type: Lengthtype: Forcetype: Stresstype: Integertype: StringAll canonical names from the catalog. Sous-typage applies on
connection — Force → Numeric is fine, Force → Length is rejected.
type: File # any extensiontype: File[pdf] # exactly .pdftype: File[step,iges,brep] # intersection allowedExtensions are case-insensitive and never include the leading dot
(File[.pdf] is rejected — write File[pdf]). When validating an
actual uploaded file, the platform checks the file’s extension against
the list.
type: list[Force] # homogeneous listtype: list[Vector3]type: list[Torsor]Sous-typage applies element-wise: list[Force] → list[Numeric]
✅, list[Force] → list[Length] ❌.
type: dict[str, Length]type: dict[str, Force]Always string-keyed. The value type is the only one parsed — the
key is fixed to str.
Literals
Section intitulée « Literals »type: literal[8.8, 10.9, 12.9] # only these values are validUseful for enumerations like quality classes. Any value not in the list fails validation.
Inline structs
Section intitulée « Inline structs »Either string form or YAML mapping form, both equivalent:
# string formtype: "{F: Force3, M: Moment3}"
# YAML formtype: F: Force3 M: Moment3Inline structs match field-by-field on connection — adding or
removing a field changes the type. For repeated structs, define them
in the catalog (or use the canonical Torsor for {F, M}).
required semantics
Section intitulée « required semantics »The semantics of required are mode-asymmetric:
| Port kind | required: true | required: false |
|---|---|---|
| Input | Caller must supply the value; missing → 422 at submit time. | May be omitted; absent from the kwargs / in/data.json. |
| Output (File) | Worker validates the file is present in out/files/; missing → run failure. | Worker accepts both presence and absence. |
| Output (non-File) | The function must return the key in its output dict. | n/a — non-File outputs are always required and the parser rejects required: false on them. |
The default is true everywhere. Only declare required: false
when the absence of the value carries real meaning (an optional
diagnostic file, a fall-through input).
Object is input-only
Section intitulée « Object is input-only »The Object catalog type is a one-way door. It accepts arbitrary
JSON shapes on the input side, but outputs cannot use Object
(directly or nested) — the parser rejects them at deploy time.
The reason is workflow typing: a function returning Object is
opaque to downstream nodes. If you genuinely have arbitrary
output, refactor: return a typed struct, a dict[str, T] for some
known T, or split the result into multiple typed outputs.
File ports — extension semantics
Section intitulée « File ports — extension semantics »For File inputs, the file is staged at
/workspace/in/files/<port_name>.<ext> where <ext> comes from the
caller’s uploaded file. Mode A’s runner passes a pathlib.Path keyed
by the port name; modes B/C read it themselves.
For File outputs, the worker expects exactly one file under
/workspace/out/files/ whose stem matches the port name —
out/files/report.pdf for an output called report. The extension
is preserved verbatim in the response.
The extension constraint declared in the manifest (File[pdf]) is
checked against:
- the actual extension of input files at submit time,
- the actual extension written under
out/files/post-execution.
Mode A introspection
Section intitulée « Mode A introspection »Mode A is the only mode where you can omit inputs: / outputs:.
The deploy parser imports your handler, reads its signature, and
infers ports from type hints:
def size(diameter: float, load: float) -> dict: return {"stress": load / area(diameter)}Without an inputs: block in mecapy.yml, the parser infers
diameter: Float and load: Float. The output uses a
permissive result: object because dict (untyped) can’t be
narrowed.
For workflow-eligible functions, prefer explicit declaration — introspection falls back to permissive types when the signature is ambiguous, and the workflow editor needs precise types to validate connections. The pedagogical example below shows the recommended shape:
functions: stress_bolt: handler: handler:stress_bolt inputs: torsor: { type: Torsor, description: "Force/moment torsor at the bolt section." } diameter: { type: Length, description: "Bolt nominal diameter (m)." } pitch: { type: Length, description: "Thread pitch (m)." } outputs: sigma_eq: { type: Stress, description: "Von Mises equivalent stress (Pa)." }In modes B and C inputs: / outputs: are mandatory because there
is no Python signature to read.
Persistence and exposure
Section intitulée « Persistence and exposure »Once parsed, the typed I/O is stored on FunctionVersion.io_spec and
exposed via GET /function-versions/{id} as a FunctionIoSpec
object. The frontend’s typed AutoForm and the workflow connection
checker both consume this spec — declaring it correctly is what
unlocks rich UX downstream.