blob: 040caedfaef8f38dc45fb1606443c24878010330 [file]
= Python 3 Language
:doctitle: Python 3
:shortname: python3
:artifactid: camel-python3
:description: Evaluates a Python 3 expression
:since: 4.23
:supportlevel: Preview
:tabs-sync-option:
*Since Camel {since}*
Camel allows https://www.graalvm.org/python/[Python 3] (GraalPy) to be
used as an xref:manual::expression.adoc[Expression] or xref:manual::predicate.adoc[Predicate]
in Camel routes.
This language is distinct from xref:python-language.adoc[Python], which uses Jython and is limited to Python 2.7.
For example, you can use Python 3 in a xref:manual::predicate.adoc[Predicate]
with the xref:eips:choice-eip.adoc[Content-Based Router] EIP.
== Python 3 Options
// language options: START
include::partial$language-options.adoc[]
// language options: END
== Variables
The following variables are bound by default:
[width="100%",cols="10%,10%,80%",options="header",]
|=======================================================================
|Variable |Type |Description
|body |Object |the message body
|header |Map |the message headers (same as `headers`)
|headers |Map |the message headers
|exchangeProperty |Map |the exchange properties (same as `exchangeProperties`)
|exchangeProperties |Map |the exchange properties
|variable |Map |the exchange variables (same as `variables`)
|variables |Map |the exchange variables
|exchangeId |String |the exchange id
|=======================================================================
The variables have the same names as in the xref:groovy-language.adoc[Groovy] language. In default mode only the
data variables above are bound; the Camel host objects are bound only with trusted host access.
By default, Python can index Java maps and lists (for example `headers['foo']`
or `variables['foo']`) but cannot invoke methods on host objects.
`variables` holds the exchange-scoped variables (for example set with the
xref:eips:setVariable-eip.adoc[Set Variable] EIP). Global and route variables,
such as `global:foo`, are not in this map, so in default mode a script cannot read
them; with trusted host access they can be read with `exchange.getVariable(...)`.
Like `headers`, it is the exchange's own map rather than a copy, so an assignment
such as `variables['foo'] = 'bar'` updates the exchange. Such an assignment does not
go through `Exchange.setVariable`, so `variables['global:foo'] = 1` sets an exchange
variable named `global:foo`, not a global one. Setting a variable to `None` fails,
because the variables map does not accept null values.
`exchange`, `camelContext`, `message`, `request` and `exception` are not
bound in default mode. Scripts that refer to them raise a Python `NameError`. Those
variables are available only when you opt in to trusted host access, as described in
<<_trusted_host_access>>.
== Security
The default GraalPy context does **not** use `HostAccess.ALL` or `allowAllAccess(true)`.
Host method calls on bound objects are denied unless you opt in.
`SandboxPolicy.CONSTRAINED` was evaluated and is **not** the default: it rejects the Java
`Map` host access needed for `headers['foo']` and requires redirecting stdout/stderr on both
the engine and the context. Primitive-only scripts can use it in a custom `Python3Language`.
The GraalPy context sets `python.PosixModuleBackend` to `java`, so POSIX operations are
backed by Java file and system APIs. File I/O, process creation, and socket operations
remain restricted because IO is not enabled (`allowIO` is false). That setting is **not**
a hard security sandbox. HostAccess restrictions are the security boundary.
=== Trusted host access
To allow Python to call public methods on host objects, and to expose Camel host
objects as variables, bind a language created with `createWithHostAccess()` before
the first usage.
This is a trusted host-access mode, **not** a sandbox. `HostAccess.ALL` lets Python
call public methods and fields on bound Java objects. It does **not** enable
`allowAllAccess`, Java class lookup, host IO, or process creation. Use it only when
you trust the scripts.
[WARNING]
====
Trusted host access lets Python call public methods on bound Camel objects such as
`camelContext`, `exchange`, and `message`. That includes destructive operations, for
example `camelContext.stop()`, `camelContext.getRegistry().bind(...)`, and
`exchange.getContext().getExecutorService(...)`. Enable this mode only for fully
trusted scripts. Do not use it with Python code taken from untrusted or external
input.
====
In this mode the default variables above remain available, plus:
[width="100%",cols="10%,10%,80%",options="header",]
|=======================================================================
|Variable |Type |Description
|exchange |Exchange |the Exchange
|camelContext |CamelContext |the CamelContext
|message |Message |the message
|request |Message |the message (same as `message`)
|exception |Exception |the exception if the exchange failed (or the caught exception in an error handler), otherwise `None`
|=======================================================================
[source,java]
----
Python3Language python3 = Python3Language.createWithHostAccess();
camelContext.getRegistry().bind("python3", python3);
----
== Usage
[tabs]
====
Java::
+
[source,java]
----
import static org.apache.camel.language.python3.Python3Language.python3;
public class MyRouteBuilder extends RouteBuilder {
@Override
public void configure() {
from("direct:start")
.choice()
.when().python3("body == 'Hello'").to("mock:hello")
.otherwise().to("mock:other");
}
}
----
XML::
+
[source,xml]
----
<route>
<from uri="direct:start"/>
<choice>
<when>
<python3>body == 'Hello'</python3>
<to uri="mock:hello"/>
</when>
<otherwise>
<to uri="mock:other"/>
</otherwise>
</choice>
</route>
----
YAML::
+
[source,yaml]
----
- route:
from:
uri: direct:start
steps:
- choice:
when:
- expression:
python3:
expression: 'body == ''Hello'''
steps:
- to:
uri: mock:hello
otherwise:
steps:
- to:
uri: mock:other
----
====
Python 3 syntax is supported, including f-strings:
[source,python]
----
f'Hello {body}'
----
You can load the script from an external resource with the
`resource:scheme:location` syntax, for example `resource:classpath:myscript.py`
or `resource:file:/path/to/script.py`.
[WARNING]
====
Do not derive `resource:classpath:` or `resource:file:` paths from untrusted
input such as message headers or query parameters. A path taken from untrusted
data can cause Camel to load and evaluate an unexpected Python script.
====
== Dependencies
To use Python 3 in your Camel routes, you need to add the dependency on
*camel-python3*, which implements the Python 3 language with GraalPy.
The GraalPy runtime (language + standard library + Truffle) is large, on the order of
100+ MB of JARs. That is expected for embedding CPython-compatible Python 3 on the JVM.
If you use Maven, you could add the following to your `pom.xml`,
substituting the version number for the latest release.
[source,xml]
----
<dependency>
<groupId>org.apache.camel</groupId>
<artifactId>camel-python3</artifactId>
<version>x.x.x</version>
</dependency>
----
GraalPy 25.x embeds CPython 3.12. On JDK 17 it typically runs in interpreter-only mode;
on JDK 21+ and on a GraalVM JDK it can use the optimizing runtime. On JDK 24+ you may
need `--enable-native-access=ALL-UNNAMED` (the unit tests already set this).
GraalPy is skipped on `s390x` and `ppc64le` in the same way as camel-javascript.
GraalPy is licensed under MIT, the Python Software Foundation License, and the Universal
Permissive License (UPL), which are ASF Category A licenses.