| = 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. |