> For the complete documentation index, see [llms.txt](https://docs.augelab.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.augelab.com/french/key-features/headless/scripting-reference.md).

# Référence de scripting

Cette page documente l'API publique de scripting en mode headless exposée par le package Python `studio`.

Elle se concentre sur ces symboles publics :

* `StudioScenario`
* `install_logging_event`
* `enable_logging_stdout`
* `disable_logging_stdout`

Si vous n'avez jamais exécuté un scénario en mode headless, lisez d'abord la section Headless puis revenez ici pour les détails de l'API.

***

## Importations

Toutes les API documentées ici sont exportées depuis le package de haut niveau `studio` :

```python
from studio import (
	StudioScenario,
	install_logging_event,
	enable_logging_stdout,
	disable_logging_stdout,
)
```

***

## Stubs de type

La surface publique headless est la suivante :

```python
from __future__ import annotations

from typing import Any, Callable, Optional


def enable_logging_stdout() -> None: ...
def disable_logging_stdout() -> None: ...
def install_logging_event(event: Callable[[str], None]) -> None: ...


class StudioScenario:
	def __init__(self, *, verification_code: str = "") -> None: ...

	def load_scenario(self, path: str) -> Optional[StudioScenario]: ...
	def load_scenario_raw(self, content: str) -> bool: ...
	def save_scenario(self, path: str) -> bool: ...
	def clear_scenario(self) -> bool: ...
	def disable_load_errors(self) -> StudioScenario: ...
	def load_custom_nodes(self, path: str) -> bool: ...

	def get_ports(
		self,
		input_op_title: str = "Subsystem In",
		output_op_title: str = "Subsystem Out",
	) -> dict[str, list[str]]: ...
	def get_name(self) -> str: ...

	def run(self, args: tuple[Any, ...] = ()) -> tuple[list[Any], ...]: ...
	def run_server(
		self,
		inputs: tuple[Any, ...] = (),
		host: str = "127.0.0.1",
		port: int = 8080,
		display_controls: bool = False,
		header: str = "Scenario Server",
	) -> None: ...
	def cleanup(self) -> bool: ...

	def enable_logging_stdout(self) -> StudioScenario: ...
	def disable_logging_stdout(self) -> StudioScenario: ...
	def install_logging_hook(self, hook: Callable[[str, int], None], level: int = 0) -> StudioScenario: ...

	def installStartEvent(self, event: Callable[..., None]) -> StudioScenario: ...
	def installEndEvent(self, event: Callable[..., None]) -> StudioScenario: ...
	def installStepStartEvent(self, event: Callable[..., None]) -> StudioScenario: ...
	def installStepEndEvent(self, event: Callable[..., None]) -> StudioScenario: ...
```

***

## `StudioScenario`

`StudioScenario` est le point d'entrée principal pour charger et exécuter un fichier `.pmod` sans l'interface graphique.

### Constructeur

```python
scenario = StudioScenario(verification_code="...")
```

#### `verification_code`

Cette valeur est utilisée pour configurer la licence lors d'une exécution headless.

Usages courants :

* Machine locale / serveur : passer la chaîne du verification code.
* Docker / CI : monter un fichier de licence dans le conteneur et passer son **chemin** comme `verification_code`.

> ⚠️ Pour les environnements non interactifs (Docker/CI/services), passez toujours `verification_code` explicitement.

***

### Cycle de vie typique

Dans la plupart des scripts, vous ferez :

1. Créer le scénario (`StudioScenario(...)`)
2. Charger un `.pmod` (`load_scenario(...)`)
3. Lancer l'exécution (`run(...)`)
4. Nettoyer (`cleanup()`)

```python
import os

from studio import StudioScenario, enable_logging_stdout


def main() -> None:
	# Optionnel : affiche les logs runtime sur stdout (utile pour le débogage).
	enable_logging_stdout()

	verification_code = os.environ.get("AUGELAB_VERIFICATION_CODE", "")
	scenario = StudioScenario(verification_code=verification_code)

	try:
		loaded = scenario.load_scenario("your_scenario.pmod")
		if loaded is None:
			raise FileNotFoundError("Could not load .pmod file")

		# Si votre scénario a N ports d'entrée, vous devez passer exactement N args.
		result = scenario.run(args=())
		print("Scenario result:", result)
	finally:
		scenario.cleanup()


if __name__ == "__main__":
	main()
```

> ℹ️ `cleanup()` est important dans les processus de longue durée (services, lots) pour libérer la mémoire et réinitialiser l'état d'exécution.

***

### Chargement d'un `.pmod`

```python
scenario.load_scenario("path/to/scenario.pmod")
```

* Retourne `StudioScenario` en cas de succès.
* Retourne `None` si le chemin n'existe pas.

Si votre scénario référence des ressources externes, conservez la même arborescence de fichiers utilisée sur le poste de travail (voir la section “Transferring .pmod files”).

***

### Exécuter un scénario

```python
outputs = scenario.run(args=(... ,))
```

Le runtime vérifie que le nombre d'arguments correspond au nombre d'entrées du scénario.

Si votre `.pmod` a :

* 0 entrées : appelez `scenario.run()` ou `scenario.run(())`
* 1 entrée : appelez `scenario.run((value,))`
* 2 entrées : appelez `scenario.run((value1, value2))`

> ⚠️ `args` doit être un tuple. Pour une seule entrée, n'oubliez pas la virgule de fin : `(value,)`.

***

## Autres méthodes de `StudioScenario`

Cette section couvre des méthodes publiques supplémentaires utiles pour l'automatisation et le débogage.

### Charger sans échouer sur les ressources manquantes

Si vous déplacez des scénarios entre machines/conteneurs et que certaines ressources peuvent être manquantes, vous pouvez charger plus permissivement :

```python
from studio import StudioScenario

scenario = StudioScenario(verification_code="YOUR_CODE")
scenario.disable_load_errors()
scenario.load_scenario("scenario.pmod")
```

### Charger / sauvegarder

```python
scenario.load_scenario_raw(content="...")
scenario.save_scenario("out.pmod")
scenario.clear_scenario()
```

### Nœuds personnalisés

Charger des modules Python de nœuds personnalisés depuis un dossier :

```python
scenario.load_custom_nodes("path/to/custom_nodes")
```

> ⚠️ Cela importe dynamiquement des fichiers Python. Chargez uniquement du code de confiance.

### Nom du scénario

```python
name = scenario.get_name()
```

### Ports (entrées/sorties du scénario)

Vous pouvez interroger les noms des ports d'entrée/sortie :

```python
ports = scenario.get_ports()
print("inputs:", ports["inputs"])
print("outputs:", ports["outputs"])
```

### Lancer un serveur web (optionnel)

Si vous souhaitez exposer un exécuteur de scénario via HTTP :

```python
scenario.run_server(host="0.0.0.0", port=8080)
```

### Événements

Installer des callbacks sur le cycle de vie de l'exécution :

```python
def on_start() -> None:
	print("Scenario started")


scenario.installStartEvent(on_start)
```

***

## Aides à la journalisation

AugeLab Studio dispose d'un logger runtime utilisé par les blocs et les scénarios. En mode headless, vous voudrez généralement :

* afficher les logs sur stdout pendant le développement
* rediriger les logs vers votre propre système de journalisation

Ces aides sont globales (elles affectent le logger runtime utilisé par votre scénario headless).

***

### `enable_logging_stdout()`

Active l'affichage "amical" des logs runtime vers stdout.

Utilisez ceci lorsque vous voulez voir les logs des blocs/scénarios dans la console :

```python
from studio import enable_logging_stdout

enable_logging_stdout()
```

***

### `disable_logging_stdout()`

Désactive l'envoi des logs runtime vers stdout.

Utile lorsque :

* vous utilisez `install_logging_event(...)` et voulez éviter les logs dupliqués
* vous voulez une sortie propre (seulement vos propres `print(...)` / logger)

```python
from studio import disable_logging_stdout

disable_logging_stdout()
```

***

### `install_logging_event(event: Callable[[str], None])`

Installe un callback qui reçoit les messages de log runtime.

C'est le moyen le plus simple d'intégrer les logs AugeLab dans votre propre framework de journalisation.

```python
import logging

from studio import install_logging_event, disable_logging_stdout

logger = logging.getLogger("augelab")


def forward_to_python_logging(msg: str) -> None:
	logger.info(msg)


disable_logging_stdout()
install_logging_event(forward_to_python_logging)
```

### `StudioScenario.install_logging_hook(hook, level=0)`

Si vous préférez installer un hook par scénario (avec filtrage par niveau), utilisez :

```python
def hook(msg: str, level: int) -> None:
	print(level, msg)


scenario.install_logging_hook(hook, level=20)
```

> ⚠️ Gardez votre callback rapide.\
> Si vous devez effectuer un travail lourd (I/O, réseau), envisagez de bufferiser les messages et de les traiter dans un autre thread/processus.

***

## Schémas recommandés

### 1) Variable d'environnement pour la licence

Pour les scripts qui s'exécutent dans différents environnements (local vs CI vs Docker), passer `verification_code` via une variable d'environnement rend le code portable :

```python
import os
from studio import StudioScenario

scenario = StudioScenario(verification_code=os.environ["AUGELAB_VERIFICATION_CODE"])
```

### 2) Toujours appeler cleanup avec try/finally

```python
from studio import StudioScenario

scenario = StudioScenario(verification_code="YOUR_CODE")
try:
	scenario.load_scenario("scenario.pmod")
	scenario.run(())
finally:
	scenario.cleanup()
```
