---
title: "Python Execution API"
description: "Documentation and guidance for building tools and integrations with the Disguise platform."
url: https://developer.disguise.one/python-api/execution-api/
---

# Python Execution API

-   [Overview](#Overview)
-   [Architecture](#Architecture)
-   [API](#API)
    -   [execute](#execute)
    -   [registermodule](#registermodule)
-   [The d3 package](#the-d3-package)
    -   [Importing d3 in execute](#importing-d3-in-execute)
    -   [Importing d3 in registered modules](#importing-d3-in-registered-modules)
-   [Returning values](#Returning-values)
-   [Logging](#Logging)
-   [Run time and timeouts](#Run-time-and-timeouts)
-   [Writing out scripts](#Writing-out-scripts)
-   [Undo](#undo)
-   [Module naming](#Module-naming)
-   [Using registered modules in other modules](#Using-modules-in-other-modules)
-   [Loading modules on startup](#Loading-modules-on-startup)

## Overview

This is a set of API endpoints allowing Python 2.7 scripts to be executed in a Designer session remotely. This new functionality allows programmers to control and automate Designer using Python and the [`d3`](#d3-module) module, opening up a wide range of possibilities for show control, automation and integration with other systems.

The API consists of two endpoints:

-   `execute` - This endpoint allows users to execute a block of Python code in the current Designer session. No state is preserved between calls to this endpoint.
-   `registermodule` - This endpoint allows users to register a Python module that can be used in subsequent calls to the `execute` endpoint by referencing the module name.

The execute endpoint is the primary method for running code in Designer however when more complex scripts are required, the `registermodule` endpoint can be used to preload modules into the environment that can be referenced in the `execute` endpoint.

This API **has not** been optimised for performance-related tasks such as executing on every frame or providing telemetry metrics, users are encouraged to use other tools such as the [Live Update API](../api/session/liveupdate) for these tasks.

For the avoidance of doubt, this endpoint **MUST NOT** be polled, and is for functional changes such as object creation.

## Architecture

![Plugins python request](/assets/plugins-python-architecture.png)

The above diagram shows a high level overview of the Python execution architecture.

When a request is made to the `execute` endpoint, the stringified Python 2.7 code is unpacked into a internal `runner` function. The `runner` function is then executed with the Designer embedded python in a specific module namespace if a `moduleName` is provided, or in a default namespace if no module name is provided. More information on these namespaces can be found in the following sections.

The return value of this `runner` function is then converted to JSON and returned in the response body along with any logging information.

The wrapping of the user code into a `runner` function means that all code is executed in a function scope, this means that global variables cannot be created in the `execute` endpoint. This also allows for some additional guardrails to be put in place to prevent stability issues such as:

-   Preventing long running code by adding timeouts (see [Run time and timeouts](#Run-time-and-timeouts) section)
-   Allowing isolated logging
-   Preventing certain statements that are not valid in the plugin context (see [Python Environment](/python-api/environment#python-restrictions) page for more information)

However some statements are **not valid** in the context of a function body, such as `from [package] import *` and users should avoid using these statements in their code.

### Module namespaces

The `registermodule` endpoint allows users to register a Python module that can be used in subsequent calls to the `execute` endpoint. This is useful for more complex scripts that require multiple functions or classes to be defined but also acts as a namespace to contain plugins from each other.

These registered modules are managed by Designer and when used in the `execute` endpoint are used as the execution namespace, meaning all variables, methods and classes defined in the module are immediately accessible without needing an import statement.

The module contents themselves are compiled as a normal Python module, so they do **not** receive the `d3` import that `execute` scripts get for free. Each registered module is responsible for its own imports — see [The d3 package](#the-d3-package).

### Default namespace

When no `moduleName` is provided in the `execute` endpoint, the code is executed in the default plugin namespace. This namespace can’t be added to but has some additional utility functions to help with common tasks. More information on these utility functions can be found on the [Python Environment](/python-api/environment#execution-utility-functions) page.

## API

### _execute_

`POST api/session/python/execute`

Execute a block of python code, no state is preserved after execution

#### Request

The request body is structured as follows:

```json
{
  "moduleName": "string", # Optional
  "script": "string"
}
```

**moduleName** - The registered module the script will be executed in. For one-off scripts, this field can be omitted or an empty string.

**script** - The Python code to be executed. This needs to be valid Python 2.7 code, using the correct indentation and stringified.

#### Response

```json
{
  "status": {
    "code": 0,
    "message": "string",
    "details": [
      {
        "type_url": "string",
        "value": "string"
      }
    ]
  },
  "d3Log": "string",
  "pythonLog": "string",
  "returnValue": "string"
}
```

Note that calling this endpoint too frequently or during a show is not a supported workflow - this is intended for show programming tasks, not during production.

An example of using the `execute` endpoint is shown below using Python:

```python
import requests

response = requests.post(
    url="http://localhost/api/session/python/execute", 
    json={
        "script": "print('Hello Disguise Designer!')"
    }
)
```

The response to this statement would look as follows:

```json
{
    "status": {
        "code": 0,
        "message": "",
        "details": []
    },
    "d3Log": "Hello Disguise Designer!\nPython script took 37.352000000000004ms\n",
    "pythonLog": "Hello Disguise Designer!\n",
    "returnValue": "null"
}
```

### _registermodule_

`POST api/session/python/registermodule`

Register a python module to be used in later execute calls.

#### Request

```json
{
  "moduleName": "string",
  "contents": "string",
}
```

**moduleName** - This is the name the module will be referred to in Python. This string will need to be parsed in the `execute` request in the `moduleName` field in order to load the module. See the [Module naming](#Module-naming) section for more information on valid module names.

**contents** - This is the contents of the Python module in a single string. Classes, functions and variables can be defined here to be used in the `execute`. Unlike the `execute` endpoint, no imports are added for you — if the module uses Designer types or globals it must start with `from d3 import *`. See [Importing d3 in registered modules](#importing-d3-in-registered-modules).

#### Response

```json
{
  "status": {
    "code": 0,
    "message": "string",
    "details": [
      {
        "type_url": "string",
        "value": "string"
      }
    ]
  }
}
```

An example of registering a Python module using the `registermodule` endpoint is shown below:

```python
import requests

response = requests.post(
    url="http://localhost/api/session/python/registermodule", 
    json={
        "moduleName": "screen_utils",
        "contents": """
from d3 import *

def createLEDScreen():
    # Create a new LED screen resource
    screen = resourceManager.loadOrCreate(
        'objects/ledscreen/ledscreen_1.apx',
        LedScreen
    )

    # Set the physical size in meters
    screen.scale = Vec(1, 2, 0)

    # Set the resolution in pixels
    screen.resolution = Vec(1920, 1080)

    # Center the screen at origin
    screen.offset = Vec(0, 0, 0)

    return screen
"""
    }
)
```

Giving the following response:

```json
{
    "status": {
        "code": 0,
        "message": "",
        "details": []
    }
}
```

If errors are experienced whilst compiling the given module, errors will be given in the `message` field.

To use the above module in the `execute`, the module name must be passed into the `moduleName` field, the request would look as follows:

```python
import requests

response = requests.post(
    url="http://localhost/api/session/python/execute", 
    json={
        "moduleName": "screen_utils",
        "script": "createLEDScreen()"
    }
)
```

## The d3 package

The `d3` package an interface to Designer object types, resources and session state. A full breakdown of the different classes and functions can be found on the [Python Reference](/python-api/docs) page.

The two endpoints treat this package differently, and the difference is the most common cause of `NameError` exceptions when a working script is formalised into a registered module.

### Importing d3 in execute

Before a `script` is run, the `execute` endpoint imports the whole of the `d3` package into the namespace the script executes in — the equivalent of `from d3 import *`. Designer types such as `LedScreen` and `Vec`, and the `d3` globals such as `state`, `guisystem` and `resourceManager`, are therefore already in scope and a `d3` import statement is not required.

This happens on every call to `execute`, whether or not a `moduleName` is supplied.

The import is performed at namespace level rather than inside your script, and you should not add it yourself. As described in the [Architecture](#Architecture) section, an `execute` script is wrapped in a function, and `from [package] import *` is not valid in a function body.

### Importing d3 in registered modules

The `contents` sent to the `registermodule` endpoint are compiled as an ordinary Python module and are **not** given the automatic import. A registered module must import everything it uses, including the `d3` package:

```python
from d3 import *

def createLEDScreen():
    screen = resourceManager.loadOrCreate(
        'objects/ledscreen/ledscreen_1.apx',
        LedScreen
    )
    screen.scale = Vec(1, 2, 0)
    return screen
```

Module contents are at module level, so `from d3 import *` is valid here even though it is not valid inside an `execute` script.

This import is required in **every** registered module that references Designer types or globals. It is not inherited from the `execute` call that later runs against the module, and it is not shared between registered modules — registering `screen_utils` with the import does not make `d3` available inside `serialise_helpers`.

⚠️

A script that runs correctly through `execute` can fail with an error such as `NameError: name 'LedScreen' is not defined` when the same code is pasted into a registered module. Add `from d3 import *` to the top of the module `contents`.

The [execution utility functions](/python-api/environment#execution-utility-functions), such as `trackTime()` and `runningTime()`, are not part of the `d3` package. They are only available to `execute` calls made without a `moduleName` and cannot be imported into a registered module.

Some elements of the `d3` package are not available through the plugin system, such as GUI elements, to prevent stability issues. For more information, please read the [Python Environment](/python-api/environment#python-restrictions) page.

## Returning values

It is possible to return values from the `execute` endpoint. As mentioned in the [Architecture](#Architecture) section, all execution code is run within the `runner` function, by adding a `return` statement the script will return values from the `runner` function.

The returned values will be serialised as JSON using the Python `json` package.

From r34.2, [enums](/python-api/guides/utility#enums) are their own types rather than integers, and cannot be serialised. Returning one, or a dict or list containing one, raises a `TypeError`; return `int(value)` or `value.toString()` instead.

An example of returning a value is shown below

```python
import requests

response = requests.post(
    url="http://localhost/api/session/python/execute", 
    json={
        "script": "return {'a': [], 'b':2}"
    }
)

print(response.json())
```

Gives the response:

```json
{
    "status": {
        "code": 0,
        "message": "",
        "details": []
    },
    "d3Log": "Python script took 3.1447ms\n",
    "pythonLog": "",
    "returnValue": "{\"a\": [], \"b\": 2}"
}
```

## Logging

As always logging is important but it is even more crucial when running commands remotely. To help with this, two logging fields are provided in the response.

-   `d3Log` - The D3 log field captures the console log for the time the Python script is executing, recording any output generated by running a Python command, including the time taken to execute the Python command. As this captures the Designer console output, it is possible other threads may write to this output during this period causing additional irrelevant output.
    
-   `pythonLog` - This output field is the pure Python output, recording any print statements or warnings occurring during the execution of the script. This output will also appear in the `d3Log` field, however, it may be mixed with other application output.
    

## Run time and timeouts

The execution of Python scripts through the `execute` occurs on the main application thread and causing the application to pause whilst the script executes. To avoid cases where scripts run longer than expected, a timeout interrupt has been implemented. The length of this timeout is dictated by the experimental option `pythonApiExecutionTimeout` which can be set through the d3Manager window. When a script exceeds this value, a `KeyboardInterrupt` is executed, and the Python execution stops and returns a `TimeoutError` in the response.

## Writing out scripts

Scripts sent to the `execute` endpoint can be written out using the experimental `pythonApiOutputRequestsToProject` flag through d3Manager. When this is set to `True` all scripts sent to the `execute` endpoint will be written to the project `internal\\PythonApiHistory` directory. This can be useful for debugging issues as well as keeping a record of the scripts executed in a project. However, this should be used with caution as executing many requests will cause the project size to grow quickly.

## Undo

Undo is supported when executing scripts through the `execute` endpoint.

When an exception is thrown during execution or a script exceeds the timeout, the operation is automatically rolled back by undoing any changes the script made before the error occurred. This means the session state is restored to what it was before the script ran, allowing scripts to fail safely and be retried without worrying about operating on a potentially unknown or partially modified state.

In the unlikely event that the rollback itself fails, the error message will include details of the undo failure:

```text
Failed to run plugin: default_plugin
Error code: 0, Error message: RuntimeError: ...
Warning: rollback failed: <undo error details>
```

## Module naming

As plugins are imported as Python modules, plugin names need to follow the same naming conventions:

-   Module names cannot be empty
-   Module names include alphanumeric and . - \_ characters
-   Module names cannot start or end with . - or \_

## Using modules in other modules

Registering a Python plugin adds the defined module to the internal Designer Python system, meaning these modules can be used inside other Python plugin modules. Modules, when registered, are prepended with `user_module_` to protect core modules. To use a registered module in another module, this would need to be added to the module name.

Here’s an example of creating another plugin module:

```python
import requests

response = requests.post(
    url="http://localhost/api/session/python/registermodule", 
    json={
        "moduleName": "serialise_helpers",
        "contents": """
def serialiseLED(ledScreen):
    return {'name':ledScreen.path , 'uid': ledScreen.uid}
"""
    }
)
```

This could then be used when executing in another module namespace by importing the module:

```python
import requests

response = requests.post(
    url="http://localhost/api/session/python/execute", 
    json={
        "moduleName": "screen_utils",
        "script": """
from user_module_serialise_helpers import serialiseLED
return serialiseLED(createLEDScreen())
"""
    }
)
```

## Loading modules on startup

Another option for loading modules is to have the modules registered when the Designer application is started. This can be done by adding the modules to the project `{project_root}/plugins` directory. These will be loaded using the following rules:

-   Any directory within the `plugins` directory with a `__init__.py` will be loaded as a plugin using the directory name as the plugin name.
-   Any “.py” files within the `plugins` directory will be loaded using the file name as the plugin name.
