Skip to main content

Python Scripting

Python scripting allows you to extend your configuration models with custom logic, external API integrations, and dynamic behavior. Scripts are executed server-side in a sandboxed Python environment and can be used across four different contexts: value expressions, model scripts, text expressions, and dynamic groups.

How it works

Every Python script must define an invoke function that accepts a single args parameter. This function is called by the runtime, and args is a dictionary containing the variables you've configured in the expression editor.

def invoke(args):
return args['Width']['value'] * args['Height']['value']

The args dictionary keys correspond to the tags you assign to variables in the expression editor. Each variable is a dictionary containing properties like value, selected, and any custom feature properties.

Script structure

The invoke function

Every script must define an invoke function. This is the entry point that receives the configured variables and returns a result.

def invoke(args):
# args is a dictionary of variables
# Return a value appropriate for the expression type
return result

The return type depends on where the script is used:

Expression typeExpected return type
Value expressionfloat or int (numeric)
Text expressionstr (string)
Dynamic grouplist of dictionaries
Model scriptAny (return value is ignored)

Accessing variables

Variables are passed as dictionaries with the following properties:

def invoke(args):
feature = args['MyFeature']

feature['value'] # Numeric value (decimal)
feature['selected'] # Boolean - whether the feature is selected
feature['textValue'] # Text value (string or None)
feature['id'] # Node ID (GUID string)
feature['featureId'] # Feature ID (GUID string)
feature['code'] # Article code
feature['name'] # Feature name

Custom feature properties are also included by name:

def invoke(args):
feature = args['MyFeature']
weight = feature['Weight'] # Numeric property
color = feature['ColorName'] # Text property

Child variables

When a variable is configured with Children enabled, it becomes a list of dictionaries instead of a single dictionary:

def invoke(args):
children = args['Options'] # List of child feature dictionaries

total = 0
for child in children:
if child['selected']:
total += child['value']
return total

Multilevel children

When Multilevel is enabled (model scripts only), child variables include a _children property for recursive traversal:

def invoke(args):
def sum_tree(node):
total = node['value']
for child in node.get('_children', []):
total += sum_tree(child)
return total

return sum_tree(args['Root'])

Value expressions

Value expressions compute a numeric value for a feature based on other features in the model. The script must return a number.

def invoke(args):
width = args['Width']['value']
height = args['Height']['value']
return width * height

The returned value is used as the derived value for the feature the expression is attached to. Value expressions are evaluated during the solving process whenever referenced variables change.

Pure functions

Value expressions can be marked as pure functions in the expression editor. A pure function always returns the same result for the same inputs, which allows the system to cache results and improve performance. Mark your expression as pure if it doesn't depend on external state (no API calls, no randomness).

Model scripts

Model scripts run during the solving process and can modify feature values, text values, and image values using the assign_property function. Unlike value expressions, model scripts don't return a meaningful value - they operate through side effects.

def invoke(args):
quantity = args['Quantity']['value']
price_per_unit = args['PricePerUnit']['value']

total = quantity * price_per_unit

assign_property(args['TotalPrice'], 'value', total)
assign_property(args['Summary'], 'textValue', f'Total: €{total:.2f}')

The assign_property function

assign_property is a built-in function available in all Python scripts. It issues a command to update a feature's property.

assign_property(variable, property_name, value)
ParameterTypeDescription
variabledictA variable from args (must contain an id field)
property_namestrThe property to set
valueanyThe value to assign

Built-in property names:

PropertyDescription
valueSets the numeric value of the feature
textValueSets the text value of the feature
imageValueSets the image URL of the feature

You can also assign custom properties by name. These become available to other expressions that reference the feature:

def invoke(args):
data = fetch_external_data()
assign_property(args['Feature'], 'ExternalWeight', data['weight'])
assign_property(args['Feature'], 'ExternalPrice', data['price'])

Logging

Use print() to send log messages to the configurator. Messages printed to stdout appear as info-level logs, and messages printed to stderr appear as error-level logs.

import sys

def invoke(args):
print('Processing configuration...') # Info level
print('Something went wrong', file=sys.stderr) # Error level

Text expressions

Text expressions generate dynamic text for features. The script must return a string.

def invoke(args):
color = args['Color']['name']
size = args['Size']['value']
return f'{color} - Size {size}'

Text expressions are evaluated after the solver completes, in dependency order. If one text expression references a variable whose text value is set by another text expression, the referenced expression is evaluated first.

Find and replace expressions

In addition to Python, text expressions support a simpler find and replace syntax using @{expression} placeholders:

The selected color is @{Color} with size @{Size.Value * 2}

This syntax uses dynamic LINQ expressions and is useful for simple text formatting without writing a full Python script.

Dynamic groups

Dynamic groups use Python scripts to generate features dynamically, often based on external data sources. The script must return a list of dictionaries, where each dictionary represents a feature to create.

import requests

def invoke(args):
response = requests.get('https://api.example.com/products')
products = response.json()

return [
{
'id': product['sku'],
'name': product['name'],
'price': product['price'],
'description': product['description']
}
for product in products
]

Return format

Each dictionary in the returned list must include:

FieldRequiredTypeDescription
idYesstrUnique identifier for the feature
nameYesstrDisplay name of the feature
(other fields)NovariesBecome feature properties

Property types are inferred from the value type:

Value typeFeature property type
strText property
int or floatInput (numeric) property
dict with id fieldAssociated feature property

Accessing configuration state

Dynamic group scripts receive the same variable format as other scripts, plus a CONFIGURATION_ID constant:

def invoke(args):
config_id = args['CONFIGURATION_ID']
selected_category = args['Category']

if selected_category['selected']:
return fetch_products(selected_category['code'])
return []

Environment variables

Scripts can access environment variables (secrets) that are configured in the Elfsquad Management System. These are useful for storing API keys and other sensitive data.

Environment variables are injected into os.environ before script execution:

import os
import requests

def invoke(args):
api_key = os.environ.get('API_KEY')
response = requests.get(
'https://api.example.com/data',
headers={'Authorization': f'Bearer {api_key}'}
)
return response.json()

Available libraries

The Python runtime includes the following libraries in addition to the Python standard library:

LibraryDescription
requestsHTTP client for making API calls
numpyNumerical computing
scipyScientific computing
pandasData analysis and manipulation
pulpLinear and integer programming
matplotlibData visualization

You can import any of these in your scripts:

import numpy as np
import pandas as pd

def invoke(args):
values = [child['value'] for child in args['Items'] if child['selected']]
return float(np.mean(values)) if values else 0.0

Examples

Fetching prices from an external API

import os
import requests

def invoke(args):
api_url = os.environ.get('PRICING_API_URL')
product_code = args['Product']['code']

response = requests.get(f'{api_url}/prices/{product_code}')
data = response.json()

assign_property(args['Product'], 'value', data['price'])
assign_property(args['Product'], 'textValue', data['currency'])

Calculating a weighted total

def invoke(args):
total = 0
for option in args['Options']:
if option['selected']:
total += option['value'] * option['Weight']
return total

Generating options from a database

import os
import requests

def invoke(args):
api_key = os.environ.get('DB_API_KEY')
category = args['Category']['code']

response = requests.get(
f'https://api.example.com/items?category={category}',
headers={'X-Api-Key': api_key}
)

return [
{
'id': item['id'],
'name': item['name'],
'price': item['unit_price'],
'lead_time': item['lead_time_days']
}
for item in response.json()
]

Conditional text generation

def invoke(args):
parts = []
for option in args['SelectedOptions']:
if option['selected']:
parts.append(f"{option['name']} (x{option['value']:.0f})")

if not parts:
return 'No options selected'

return ', '.join(parts)

Linear optimization with PuLP

import pulp

def invoke(args):
items = args['Items']

prob = pulp.LpProblem('Optimize', pulp.LpMinimize)
quantities = {
i: pulp.LpVariable(f'q_{i}', lowBound=0)
for i in range(len(items))
}

# Minimize total cost
prob += pulp.lpSum(
quantities[i] * items[i]['UnitCost']
for i in range(len(items))
)

# Meet minimum quantity
prob += pulp.lpSum(quantities.values()) >= args['MinQuantity']['value']

prob.solve(pulp.PULP_CBC_CMD(msg=0))

for i, item in enumerate(items):
assign_property(item, 'value', quantities[i].varValue or 0)

return pulp.value(prob.objective)