Overview
Etlworks includes built-in support for Python as a scripting language. Anywhere Etlworks offers a scripting field, you can choose Python instead of JavaScript and write inline scripts for flows, mappings, conditions, and transformations.
Breaking change in Etlworks 10.x: Built-in Python in the Java 25 runtime now uses GraalPy with Python 3.13. Jython is no longer included in this runtime. Existing Jython/Python 2.7 scripts must be reviewed and, where necessary, converted before upgrading.
What this change means:
- The scripting language in the editor is still Python — but selecting Python on the new runtime executes Python 3.
- There is no Jython compatibility switch in the Java 25 runtime, and existing scripts are not automatically translated. This is a change to the execution engine, not simply an editor syntax change.
- Scripts using ordinary syntax shared by Python 2 and Python 3 may already work as-is — not every script requires rewriting. Use the migration checklist to review each one.
- Java integration remains available, but the documented class-loading pattern changes (see Java interoperability).
Integration Agents: for flows executed by an Integration Agent, the executing Agent's engine determines Python behavior. Upgrading the application alone does not convert an older Agent's Python runtime — a 9.x Agent still runs Jython. With mixed application/Agent versions, test scripts on the actual execution target.
External Python is a separate feature: Python executed through a local process or SSH with Execute Script Local or Remote via SSH uses that external interpreter and is unaffected by this change.
What the new built-in Python gives you
- Modern Python 3.13 syntax — f-strings, modern exception handling, comprehensions, functions, and the other supported Python 3 language features.
- Bundled Python standard library — ordinary imports such as json, re, datetime, decimal, and pathlib work out of the box.
- No separate Python installation for embedded scripts — the runtime is packaged with the Java 25 application and Agent.
- Direct access to Etlworks objects and Java APIs — existing bindings such as etlConfig, and context-specific objects such as the mapping objects, remain available.
- Pythonic results — an evaluated final expression can supply the script result, like in JavaScript.
- Backward-compatible result callback — scriptResult.setResult(...) remains supported.
- Reusable Python modules — compatible local pure-Python modules can be imported from a configured search path.
- Evaluation isolation — fresh Python contexts prevent accidental sharing of interpreter globals between independent evaluations.
- Error information — errors retain script source names and line locations when available.
- Cached script infrastructure — the adapter supports compiled scripts and shares its underlying engine and source infrastructure.
Keep expectations accurate:
- No performance promise is made versus Jython or CPython. The default runtime uses GraalPy's portable interpreter, not an optimizing GraalVM runtime — for Python invoked per row, run a representative performance test.
- Compatibility with all CPython packages is not guaranteed (see third-party packages).
- The embedded runtime is not a security sandbox — scripts have host access, like JavaScript scripts do.
- Use the terms embedded Python (this article) and external Python (a separate interpreter) — both are real Python; they serve different workloads.
Where built-in Python is available
Python can be used anywhere Etlworks provides a scripting field:
- In the Execute JavaScript or Python flow
- In Mapping, to calculate field values
- In scripting transformations (before row, after row, for each row)
- In conditions and loop conditions inside a nested flow
- In exception handlers, Execute before, and other scripting fields
How to write built-in Python scripts
Step 1. Open the scripting editor
Click Open in Editor near any scripting text area.
Step 2. Choose Python
In the editor, click Select Language and choose Python.
Step 3. Write Python 3 code
Enter your Python script and test it in its actual flow or mapping context — the objects available to the script depend on where it runs. Do not assume every scripting field receives every object; each scripting article documents its own bindings. For example, in Mapping, dataSet and currentRow provide field access and row is the current 0-based row number — see Set and Get a field's value in Mapping.
Mapping example (calculating a field value):
value = dataSet.getFieldValue(currentRow, "amount") * 1.2
scriptResult.setResult(value)Use etlConfig.log(...) for messages intended for the Flow log. Python print(...) is supported and goes through the script output writer, but not every scripting location routes it to the Flow log identically — prefer etlConfig.log for operational messages.
Returning values from Python
Both result forms are supported in the 10.x runtime.
A final expression (like JavaScript):
mapped = str(fieldValue).strip().upper()
mappedThe existing callback (backward compatible with Jython-era scripts):
mapped = str(fieldValue).strip().upper()
scriptResult.setResult(mapped)A null-safe variant:
mapped = None if fieldValue is None else str(fieldValue).strip().upper()
scriptResult.setResult(mapped)Precedence: a non-null evaluated result takes precedence; when the evaluation returns no value (or None), the Etlworks wrapper falls back to the scriptResult value. Avoid mixing both mechanisms with conflicting values — for example:
scriptResult.setResult("fallback")
"final expression"returns "final expression" in the new runtime.
Two cautions: a trailing assignment (mapped = ...) is not a trailing expression — end with the bare expression when you want it returned; and top-level return statements are not valid outside a function. When converting an existing script, preserve its result contract rather than changing the mechanism for style.
Java interoperability
Python scripts can use any Java class available to the executing Etlworks runtime. In the 10.x runtime, load classes with java.type(...) — the Jython-style from com.example import SomeClass no longer works:
import java
Math = java.type("java.lang.Math")
String = java.type("java.lang.String")
SystemConfig = java.type("com.toolsverse.config.SystemConfig")
etlConfig.log("value=" + str(String.valueOf(Math.max(4, 7))))
config = SystemConfig.instance()
etlConfig.log(
"tenant=" + str(config.getProperty("request_tenant_name"))
)
etlConfig.log(
"user=" + str(config.getProperty("request_user_name"))
)Notes on this example:
- The Java classes must be available to the executing Etlworks runtime — java.type(...) does not download dependencies.
- Wrap returned Java values in str(...) before Python string operations.
- The tenant and user values depend on the current execution context and are not guaranteed to exist in every execution; an absent property renders as None here. Handle that explicitly when you need an empty string instead.
For ordinary Python calculations, Java is unnecessary:
etlConfig.log(f"value={max(4, 7)}")When a Java API expects a Java collection, build the Java collection rather than assuming a Python list is interchangeable:
import java
ArrayList = java.type("java.util.ArrayList")
values = ArrayList()
values.add("first")
values.add("second")
etlConfig.setValue("values", values)Do not store Python functions, module objects, or other context-bound Python objects for use after the evaluation has ended — each evaluation runs in a fresh Python context. For state shared between scripts, use the documented Etlworks mechanisms (global variables, Flow variables, key/value storage) with the scope you need.
Importing modules
Bundled standard-library modules
No installation is required for the included Python standard library:
import json
from datetime import date
payload = {
"processed_on": date.today().isoformat(),
"status": "ready"
}
etlConfig.log(json.dumps(payload))Customer pure-Python modules
A local .py module can be imported when its directory is on sys.path. Example module file, etl_helpers.py:
def normalize_name(value):
if value is None:
return ""
return str(value).strip().upper()Example embedded script:
import sys
sys.path.insert(0, "/opt/etlworks-python/modules")
from etl_helpers import normalize_name
etlConfig.log(normalize_name(" Etlworks "))On Windows:
sys.path.insert(0, r"C:\etlworks-python\modules")What to know:
- These are example directories, not Etlworks-created module directories. Adding a directory to sys.path does not download or install anything.
- Place the module on the machine that actually executes the script: for a flow running on an Integration Agent, on that Agent; in Docker, in a persistent mounted directory, using the path visible inside the container; for multiple execution nodes, on every relevant node. The service identity must have read access.
- Repeat the sys.path setup and imports in each independent evaluation — interpreter state does not persist between evaluations.
- {app.data} and other Etlworks tokens do not expand automatically inside an arbitrary Python string — use real paths.
- Hosted customers must use a location and deployment method available in their environment; there is no unrestricted server filesystem administration on hosted instances.
Third-party packages
- The current embedded Etlworks runtime does not bundle pip or expose a managed package-installation workflow.
- Compatible pure-Python packages can potentially be deployed as files with their dependencies and imported through sys.path. Compatibility must be checked against Python 3.13 and the embedded runtime.
- Installing a package into the host's system Python or a CPython virtual environment does not make it available to embedded Etlworks Python. Do not run pip install from mapping scripts, and do not modify Etlworks runtime JARs to add packages.
- GraalPy's broader package capabilities do not establish that a package works in this Etlworks embedding. Native extensions and CPython binary wheels need particular care — upstream documentation explains that native packages require GraalPy-compatible builds; see the GraalPy documentation.
- No bundled or supported NumPy, pandas, SciPy, boto3, or other packages are promised without product-specific validation.
External Python: when to use a separate interpreter
For workloads needing an independently managed interpreter, a virtual environment, native dependencies, or standalone programs, use Execute Script Local or Remote via SSH and its Python execution guide. External Python runs your own interpreter (with pip, virtual environments, and any packages it supports), locally or over SSH.
Choose by workload, not by Python version — external execution is not required simply to use Python 3: embedded Python is Python 3.13 on the 10.x runtime. Use embedded Python for inline logic close to your data and flows; use external Python for package-heavy programs and independent processes.
Migrating Jython (Python 2.7) scripts to 10.x
Review every existing Python script before upgrading the runtime that executes it. Common conversions:
| Legacy pattern | Python 3 migration |
|---|---|
| from com.example import SomeClass | import java, then SomeClass = java.type("com.example.SomeClass") |
| print value | print(value) |
| except Exception, ex: | except Exception as ex: |
| xrange(...) | range(...) |
| mapping.iteritems() | mapping.items() for a Python dictionary |
| unicode(...) | Usually str(...); review byte decoding separately |
| basestring checks | Review and normally use str; distinguish bytes |
| Integer / assumptions | / performs true division; use // when integer floor division is intended |
| Python 2 byte/string assumptions | Review str, bytes, .encode(...), and .decode(...) |
| Jython-specific modules and APIs | Replace with supported Python 3 or Java APIs |
| Interpreter globals used across evaluations | Use documented Etlworks state/variable APIs with the required scope |
Also review:
- map, filter, zip, and dictionary views where code expects a materialized list.
- Java strings, collections, overloaded methods, and numeric conversions.
- Python None versus Java null.
- Exception handling involving Java methods.
- Third-party dependencies.
- Return-value behavior (preserve each script's result contract).
- The application versus Agent execution location.
A textual search-and-replace is not sufficient — test each converted script in its actual execution context.
Legacy: built-in Python on 9.x (Jython)
This section applies to 9.x runtimes and 9.x Integration Agents only. There, built-in Python is powered by Jython (Python 2.7): no third-party packages, no Python 3 syntax, Java classes imported Jython-style (from java.util import ArrayList), and script results returned with scriptResult.setResult(...). Scripts written for 9.x keep working on 9.x; convert them with the checklist above when the executing runtime moves to 10.x.
Summary
- On the 10.x Java 25 runtime, built-in (embedded) Python is GraalPy / Python 3.13; Jython is gone and there is no compatibility switch.
- Standard-library imports and local pure-Python modules work; pip and managed package installation are not part of the embedded runtime.
- Results come from the final expression or from scriptResult.setResult(...); a non-null expression wins.
- Java interop uses java.type(...).
- The executing engine — application or Agent — determines Python behavior; test on the actual execution target.
- External Python via Execute Script Local or Remote via SSH remains the right tool for package-heavy, independently managed Python programs.