---
id: scripting--python-script
title: "Python script"
summary: "Runs your own Python code (CPython embedded through pythonnet) for every message that reaches the component. Use it for transformations or when you need Python packages installed on the host."
audience: user
keywords: "input, output, script"
---
# Python script

Runs your own Python code (CPython embedded through pythonnet) for every message that reaches the component. Use it for transformations or when you need Python packages installed on the host.

## Pins

| Pin | Kind | Types | Description |
|---|---|---|---|
| Input | input | any | The data from the previous component. Its type is the type of the input connection. |
| Output | output | any | Whatever the script passes to `services.Next`. The template creates an object of the output connection's type. |

## Configuration

| Field | Type | Default | Description |
|---|---|---|---|
| `Script` | code (Python) | empty | The user script: a function `execute(metaData, inputData)` plus any imports, functions and classes you need. Empty means the generated template is used. |

## Behaviour

- Before your code the component generates a Python class for every type of the flow (same class and property names as in the flow, e.g. `inputData.Name`), the classes `FlowMetaData`, `NoInputData`, `AnyInputData` and `ViaFileInfo`, and the global `services`. `base64`, `datetime`, `uuid` and the `viaducta` package are already imported. This part is hidden in the editor; your code starts with the `#viaducta.python.code` marker, and the line numbers of errors and of the debugger are the lines of your script.
- The script is executed once when the flow starts (module-level code runs then); for every message the component calls `execute(metaData, inputData)`, where `inputData` already is an instance of the input connection's class (`NoInputData` when nothing is connected). Module-level variables keep their values between messages. Messages are processed one at a time per component.
- The generated template:

  ```python
  def execute(metaData: FlowMetaData, inputData: Customer):
      outputData = CustomerDto()
      services.Next(metaData, outputData)
  ```

  `CustomerDto` is the output connection's type; without an output connection the template calls `services.Next(metaData, inputData)`.
- `services` is the component's flow service (`IFlowService`) with the C# member names, the same services a C# script has: `services.Log`, `services.Messaging`, `services.Settings`, `services.Component`, `services.Flow`, `services.Profiler`, `services.Batches`, `services.Storage`, `services.Repository` and the rest. The `viaducta` package (see Notes) gives the editor their types.
- `services.Next(metaData, obj)` sends `obj` to the components connected to `Output`; `obj` must be an instance of one of the generated flow classes. It can be called zero, one or many times. `services.Next(metaData, obj, connectionId)` sends to one connection only. The next components run on a separate task, so `Next` returns before they finish.
- Frequently used services:
  - `services.Log.Information("...")` (also `Debug`, `Warning`, `Error`, `Fatal`) writes to the flow log.
  - `services.Messaging.Information("...")` (also `Warning`, `Error`, `Trace`) writes to the designer console; `UpdateStatusMessage("...")`, `UpdateProgress(0.5)` (0 to 1), `StopProgress()` and `Clear()` update the component.
  - `services.Settings.Get("Package", "Name")` reads an expression setting.
  - `services.Component.Id` / `.Name`; `services.Profiler.UpdateRecordsProduced(services.Component.Id, 1)`.
  - `services.Batches.Begin(metaData, "label")` opens a batch: `with services.Batches.Begin(metaData, fileName) as batch:` send the records with `batch.MetaData` and call `batch.Complete()`; leaving the block without `Complete()` fails the batch.
  - `services.HandleExceptionAsync(metaData, inputData, ex)` hands a caught exception to the flow's error handling and carries on.
- List properties are `ViaProxyArray` objects: index, `len()`, `in`, iteration, `append`, `extend`, assignment by index and `del` are written through to the .NET list. Date properties are returned as `datetime.datetime`. Decimal properties are returned as `float` and accept `float`, `int` and `decimal.Decimal` (a `Decimal` keeps its full precision).
- Leading tabs are converted to 4 spaces before execution; don't mix tabs and spaces differently inside a block.
- An exception that leaves `execute` goes to the flow's error handling as `PythonScriptException`: the message is `ValueError: ...`, the stack trace is the Python traceback from your code on, and the error line is marked in the editor.
- In debug mode the script is traced line by line while `execute` runs: the debugger stops before a line runs and shows its local variables (flow objects and other .NET objects as .NET objects, Python values as a dump of at most 1000 values). Module-level code is not traced.

## Notes

- Editor support: the `viaducta` package (`viaducta-<version>-py3-none-any.whl`, built with the `Viaducta.Python` project) contains type stubs for `services` and everything it returns. Install it into the Python of your editor (`pip install viaducta-...whl`) for completion and type checking outside the designer. The host doesn't need it: the component brings its own copy and puts it first on `sys.path`.
- `print` output goes to the host's stdout, not to the designer; use `services.Messaging.Information` or `services.Log`.
- The Python configured as `Python.RuntimeDll` (default `python312.dll`, 3.11 or newer) must be available on the host; extra packages must be installed into that Python.
- The Python engine is shared by the whole host process: it starts once and stays up until the process ends, stopping a component only releases its own module.
- The template is generated from the connections that exist when it is created; connect first, then open the editor (or clear the script).

## Example

Input connection type `Customer`, output connection type `CustomerDto`:

```python
#viaducta.python.code
import re

def execute(metaData: FlowMetaData, inputData: Customer):
    if not inputData.Email:
        return  # filter: nothing is sent on
    outputData = CustomerDto()
    outputData.Name = inputData.Name.title()
    outputData.Phone = re.sub(r"\D", "", inputData.Phone or "")
    services.Messaging.UpdateStatusMessage("Last: " + inputData.Email)
    services.Next(metaData, outputData)
```

Database reader → **Python script** → Json serializer → File writer
