Aller au contenu

Ports d'E/S typés

Chaque fonction expose des ports d’entrée et de sortie nommés. Les ports portent des types issus du catalogue canonique ; l’éditeur de workflow s’en sert pour valider les connexions au tracé, et le worker s’en sert pour acheminer les valeurs vers les bons emplacements dans le conteneur.

inputs: et outputs: sous une fonction sont des dictionnaires dont chaque clé est un nom de port et chaque valeur décrit le port :

functions:
size:
handler: bolts:size
inputs:
diameter:
type: Length
description: "Diamètre nominal du boulon (m)."
required: true # défaut : true
load:
type: Force
required: false # entrée optionnelle
outputs:
stress:
type: Stress
description: "Contrainte équivalente de von Mises."
report:
type: File[pdf]
required: false # les sorties File peuvent être optionnelles

Chaque entrée a la même forme :

ChampTypeRequisNotes
typechaîne ou mappingouiExpression de type — voir ci-dessous.
descriptionchaînenonTexte libre, affiché dans l’éditeur et l’AutoForm.
requiredbooléennonDéfaut : true. Sémantique détaillée plus bas.
symbolchaînenonNotation du document source (LaTeX accepté, ex. "F_0^{max}") — affichée en badge à côté du nom du port.
unitchaînenonUnité de la grandeur (ex. "N*m") — affichée en badge ; purement informative, aucune conversion automatique.

Le nom du port est la clé du dictionnaire — il doit être une chaîne non vide, typiquement en snake_case. C’est l’argument nommé que le runner passe à votre handler en mode A, et la clé JSON dans /workspace/in/data.json en modes B/C.

Chaque valeur de type: est analysée contre le catalogue de types. Le parseur accepte :

type: Length
type: Force
type: Stress
type: Integer
type: String

Tous les noms canoniques du catalogue. Le sous-typage s’applique à la connexion — Force → Numeric passe, Force → Length est refusé.

type: File # toute extension
type: File[pdf] # uniquement .pdf
type: File[step,iges,brep] # intersection autorisée

Les extensions sont insensibles à la casse et n’incluent jamais le point de tête (File[.pdf] est refusé — écrivez File[pdf]). À la validation, la plateforme compare l’extension réelle du fichier téléversé à la liste déclarée.

type: list[File] # toute extension, plusieurs fichiers
type: list[File[mmed,med]] # intersection d'extensions contrainte

À utiliser quand une fonction accepte un ensemble non borné de fichiers auxiliaires sous un même port logique — maillages, includes, tables de données, etc. — sans avoir à inventer une liste fixe de ports nommés. Le frontend affiche un sélecteur multi-fichiers (un upload par fichier avec un bouton « ajouter un fichier ») ; l’API transmet chaque upload sous le même var_name.

Différence de staging avec un port File unique. Les fichiers d’une collection sont écrits sous leur nom d’origine dans /workspace/in/files/<original>, pas renommés en <port_name>.<ext>. Cela préserve les références par nom de fichier dans les payloads utilisateur (par exemple un .export code_aster écrit F mmed forma01a.mmed D 20 se résout tel quel — la plateforme ne le renomme pas en mesh.mmed). Quand deux uploads partagent le même nom d’origine, le second reçoit un suffixe _2 avant la dernière extension, et ainsi de suite.

Sorties. Une fonction peut aussi déclarer un port sortie list[File]. Elle écrit la collection dans le sous-dossier out/files/<port>/ — un fichier par membre, sous n’importe quel nom. Une sortie list[File] required est satisfaite dès que ce dossier contient au moins un fichier. Les nœuds de workflow en aval se lient à toute la collection par une seule arête, exactement comme pour une entrée list[File].

type: list[Force] # liste homogène
type: list[Vector3]
type: list[Torsor]

Le sous-typage s’applique élément par élément : list[Force] → list[Numeric] ✅, list[Force] → list[Length] ❌.

type: dict[str, Length]
type: dict[str, Force]

Toujours indexé par chaîne. Seul le type de valeur est analysé — la clé est figée à str.

type: literal[8.8, 10.9, 12.9] # seules ces valeurs sont valides

Utile pour les énumérations comme les classes de qualité. Toute valeur hors liste échoue à la validation.

Forme chaîne ou forme YAML mapping, équivalentes :

# forme chaîne
type: "{F: Force3, M: Moment3}"
# forme YAML
type:
F: Force3
M: Moment3

Les structures inline correspondent champ par champ à la connexion — ajouter ou retirer un champ change le type. Pour des structures récurrentes, définissez-les dans le catalogue (ou utilisez le canonique Torsor pour {F, M}).

La sémantique de required dépend du côté et du type du port :

Type de portrequired: truerequired: false
EntréeL’appelant doit fournir la valeur ; absence → 422 à la soumission.Peut être omise ; absente des kwargs / in/data.json.
Sortie (File)Le worker vérifie que le fichier est dans out/files/ ; absence → run en échec.Le worker accepte présence comme absence.
Sortie (list[File])Satisfaite ssi out/files/<port>/ contient au moins un fichier ; vide → run en échec.Le worker accepte une collection vide ou absente.
Sortie (non-File)La fonction doit retourner cette clé dans son dictionnaire de sortie.n/a — les sorties non-File sont toujours requises, le parseur refuse required: false dessus.

Le défaut est true partout. Ne déclarez required: false que quand l’absence de la valeur porte un sens réel (un fichier de diagnostic optionnel, une entrée par défaut).

Object est le sommet de la hiérarchie : il accepte n’importe quelle forme JSON. Il est utilisable des deux côtés — entrée et sortie.

inputs:
config: { type: Object } # forme libre côté appelant
outputs:
result: { type: Object } # le contenu dépend du calcul

Quand l’utiliser. Pour des fonctions méta-paramétrées dont la forme exacte de la sortie dépend d’une entrée — par exemple un solveur Excel dont les clés de sortie sont choisies par la table de correspondance fournie par l’appelant. Les connexions de workflow issues d’une sortie Object se branchent sur des ports d’entrée acceptant Object (sommet du treillis), donc le typage statique côté workflow reste cohérent.

Quand l’éviter. Si la sortie a une forme connue, préférez un type nominal, un dict[str, T] typé, ou plusieurs sorties distinctes. Un port Object est opaque aux nœuds en aval — l’éditeur ne peut pas proposer d’autocomplétion des champs ni détecter une erreur de branchement avant l’exécution.

Pour les entrées File, le fichier est préparé à /workspace/in/files/<port_name>.<ext><ext> provient du fichier téléversé par l’appelant. Le runner du mode A passe un pathlib.Path indexé par le nom du port ; les modes B/C le lisent eux-mêmes.

Pour les entrées collection File (list[File]), chaque upload est préparé à /workspace/in/files/<nom_original> (sans renommage vers <port_name>). Utilisez cette forme quand le payload utilisateur référence des noms de fichiers spécifiques (par exemple un .export code_aster déclarant F mmed forma01a.mmed D 20). Voir le contrat de runtime pour la disposition exacte des dossiers.

Pour les sorties File, le worker attend exactement un fichier sous /workspace/out/files/ dont le stem correspond au nom du port — out/files/report.pdf pour une sortie nommée report. L’extension est préservée verbatim dans la réponse.

La contrainte d’extension déclarée dans le manifest (File[pdf]) est vérifiée contre :

  • l’extension réelle des fichiers d’entrée à la soumission,
  • l’extension réelle écrite sous out/files/ après exécution.

En mode A, la signature annotée du handler est lue à la lecture du manifest pour produire le schéma du formulaire d’exécution (AutoForm). C’est utile pour qu’un utilisateur puisse lancer la fonction depuis l’interface sans connaître son catalogue.

def size(diameter: float, load: float) -> dict:
return {"stress": load / area(diameter)}

Sans inputs: dans mecapy.yml, l’introspection produit deux champs numériques (diameter, load) dans l’AutoForm. Le retour dict non typé tombe sur un schéma permissif.

L’introspection ne nourrit pas les ports typés du workflow. Pour des fonctions destinées à être composées dans des workflows, déclarez explicitement les ports avec les types du catalogue :

functions:
stress_bolt:
handler: handler:stress_bolt
inputs:
torsor: { type: Torsor, description: "Torseur force/moment dans la section du boulon." }
diameter: { type: Length, description: "Diamètre nominal du boulon (m)." }
pitch: { type: Length, description: "Pas du filetage (m)." }
outputs:
sigma_eq: { type: Stress, description: "Contrainte équivalente de von Mises (Pa)." }

En modes B et C, inputs: / outputs: sont obligatoires : il n’y a pas de signature Python à lire.

Une fois lus, les ports typés sont stockés sur la version de fonction et exposés par l’API en lecture. Le formulaire d’exécution et le vérificateur de connexions de l’éditeur de workflow les consomment — les déclarer correctement, c’est ce qui débloque l’expérience riche en aval.