Getting Started with GraalPy on the JVM

You can use GraalPy with GraalVM JDK, Oracle JDK, or OpenJDK. You can easily add GraalPy to your Java application using Maven or Gradle build tools as shown below. Other build systems (Ant, Make, CMake, and so on) can also be used with a bit more manual work.

Maven

GraalPy can generate a Maven project that embeds Python packages into a Java application using Maven artefacts.

  1. GraalPy project publishes a Maven archetype to generate a starter project:
    mvn archetype:generate \
      -DarchetypeGroupId=org.graalvm.python \
      -DarchetypeArtifactId=graalpy-archetype-polyglot-app \
      -DarchetypeVersion=24.1.0
    
  2. Build a native executable using the GraalVM Native Image “tool” plugin that was added for you automatically:
     mvn -Pnative package
    
  3. Once completed, run the executable:
     ./target/polyglot_app
    

    The application prints “hello java” to the console.

The project uses the GraalVM Polyglot API with additional features to manage Python virtual environments and integrate Python package dependencies with a Maven workflow. The Java code and the pom.xml file are heavily documented and the generated code describes available features.

See also Embedding Build Tools for more information about the GraalPy Maven Plugin.

Creating Cross-platform JARs with Native Python Packages

The generated project uses the GraalPy Maven plugin, which makes it easy to add Python dependencies. However, Python packages may have native components that are specific to the build system. In order to distribute the resulting application for other systems, follow these steps:

  1. Build the project on each deployment platform. Rename JAR files so they each have a platform-specific name and move them to a temporary directory on the same machine.

  2. Unzip each of the JAR files (substituting the correct names for the JAR files). A special file, vfs/fileslist.txt needs to be concatenated from each JAR file. Finally, create a new combined.jar from the combination of all files and with the concatenated fileslist.txt.

     unzip linux.jar -d combined
     mv combined/vfs/fileslist.txt fileslist-linux.txt
     unzip windows.jar -d combined
     mv combined/vfs/fileslist.txt fileslist-windows.txt
     cat fileslist-linux.txt fileslist-windows.txt > combined/vfs/fileslist.txt
     cd combined
     zip -r ../combined.jar *
    

Gradle

  1. Create a Java application with Gradle using the command below and follow the prompts (select the Groovy build script language, select a test framework, and so on):
     gradle init --type java-application \
                 --project-name interop  \
                 --package interop \
                 --no-split-project
    

    The project is generated in the current working directory with the following structure:

     └── app
         ├── build.gradle
         └── src
             └── main
                 ├── java
                 │   └── interop
                 │       └── App.java
                 └── resources
    
  2. Open your project configuration file, app/build.gradle, and modify it as follows.
    • Include the GraalPy support and the GraalVM Polyglot API in the dependencies section:
        implementation("org.graalvm.polyglot:polyglot:24.1.2")
        implementation("org.graalvm.polyglot:python:24.1.2")
      
  3. Finally, replace the code in the file named App.java as follows for a small Python embedding:
     package interop;
    
     import org.graalvm.polyglot.*;
    
     class App {
         public static void main(String[] args) {
             try (var context = Context.create()) {
                 System.out.println(context.eval("python", "'Hello Python!'").asString());
             }
         }
     }
    
  4. Run the application with Gradle:
     ./gradlew run
    

    The application prints “Hello Python!” to the console.

    Note: The performance of the GraalPy runtime depends on the JDK in which you embed it. For more information, see Runtime Optimization Support.

  5. Optionally, you can also use a third-party Python package.

    5.1. In app/build.gradle:

    • add the graalpy-gradle-plugin to the plugins section:
      id "org.graalvm.python" version "24.1.2"
      
    • configure the GraalPy Gradle plugin:
      graalPy { 
       packages = ["termcolor==2.2"]
      }
      

    5.2. In settings.gradle, add the following pluginManagement configuration.

    pluginManagement {
       repositories {
          gradlePluginPortal()        
       }
    }
    

    5.3. Update the file named App.java as follows:

       package interop;
       
       import org.graalvm.polyglot.*;
       import org.graalvm.python.embedding.GraalPyResources;
       
       class App {
       ...
       public static void main(String[] args) {
           try (Context context = GraalPyResources.createContext()) {
               String src = """
               from termcolor import colored
               colored_text = colored("hello java", "red", attrs=["reverse", "blink"])
               print(colored_text)
               """;
               context.eval("python", src);
           }
       }
    

See also Embedding Build Tools for more information about the GraalPy Gradle Plugin.

Ant, CMake, Makefile or Other Build Systems Without Direct Support for Maven Dependencies

Some (often older) projects may be using Ant, Makefiles, CMake, or other build systems that do not directly support Maven dependencies. Projects such as Apache Ivy™ enable such build systems to resolve Maven dependencies, but developers may have reasons not to use them. GraalPy comes with a tool to obtain the required JAR files from Maven.

  1. Assuming there is some directory where third-party dependencies are stored for the project and that the build system is set up to put any JAR files there on the classpath, the project directory tree might look similar to this:

     ├───lib
     │   └─── ... *.jar dependencies are here
     └───src
         └─── ... *.java files and resources are here
    
  2. Install GraalPy for your system and ensure you have graalpy on your PATH. Open a command-line interface and enter your project directory. Then, as appropriate for your system, run one of the following commands:

    In a POSIX shell:

     export GRAALPY_HOME=$(graalpy -c 'print(__graalpython__.home)')
     "${GRAALPY_HOME}/libexec/graalpy-polyglot-get" -a python -o lib -v "24.1.0"
    

    In PowerShell:

     $GRAALPY_HOME = graalpy -c "print(__graalpython__.home)"
     & "$GRAALPY_HOME/libexec/graalpy-polyglot-get" -a python -o lib -v "24.1.0"
    

    These commands download all GraalPy dependencies into the lib directory.

  3. Provided that your build system is set up to pick up the JAR files from lib, the GraalPy embedding code below should work if put in an appropriate place in the project to run as the main class.

     import org.graalvm.polyglot.*;
    
     public class Hello {
         public static void main(String[] args) {
             try (var context = Context.newBuilder().option("engine.WarnInterpreterOnly", "false").build()) {
                 System.out.println(context.eval("python", "'Hello Python!'").asString());
             }
         }
     }
    

Testing Python Applications and Packages on GraalPy

Go here to get a CPython compatible distribution of GraalPy to test Python applications and packages.

Testing Python Applications and Packages on GraalPy

Choosing the GraalPy Runtime

GraalPy provides a Python 3.11 compliant runtime. A primary goal is to support PyTorch, SciPy, and their constituent libraries, as well as to work with other data science and machine learning libraries from the rich Python ecosystem. GraalPy is distributed as an ahead-of-time compiled native executable, compact in size.

GraalPy provides the following capabilities:

  • CPython-compatible distribution. This is the most compatible option to test Python code on GraalPy, since it most closely resembles the structure of CPython distributions.
  • Unique deployment mode for Python applications. Compile a Python application on GraalPy to a single native binary that embeds all needed resources.
  • Access to GraalVM’s language ecosystems and tools. GraalPy can run many standard Python tools as well as tools from the GraalVM ecosystem.

GraalPy Distributions

GraalPy is available as GraalPy built on Oracle GraalVM and GraalPy Community.

  • GraalPy built on top of Oracle GraalVM provides the best experience: it comes with additional optimizations, is significantly faster and more memory-efficient. It is licensed under the GraalVM Free Terms and Conditions (GFTC) license, same as Oracle GraalVM, which permits use by any user including commercial and production use. Redistribution is permitted as long as it is not for a fee.

  • GraalPy Community is built on top of GraalVM Community Edition, and is fully open-source.

Two language runtime options are available for the Oracle and Community distributions of GraalPy:

  • Native
    • GraalPy is compiled ahead-of-time to a native executable.
    • This means that you do not need a JVM to run GraalPy and it is compact in size.
  • JVM
    • You can easily exploit Java interoperability.
    • Peak performance may be higher than the native option.

GraalPy Identification

The four GraalPy runtimes are identified as follows, using the general pattern graalpy(-community)(-jvm)-<version>-<os>-<arch>:

  Oracle Community
Native graalpy-<version>-<os>-<arch> graalpy-community-<version>-<os>-<arch>
JVM graalpy-jvm-<version>-<os>-<arch> graalpy-community-jvm-<version>-<os>-<arch>

Comparison

Runtime Native (default) JVM
Time to start faster slower
Time to reach peak performance faster slower
Peak performance (also considering GC) good best
Java interoperability needs configuration works

Installing GraalPy

NOTE: There will be a delay between GraalPy release and its availability on Pyenv.

Linux

The easiest way to install GraalPy on Linux is to use Pyenv (the Python version manager). To install version 24.1.0 using Pyenv, run the following commands:

pyenv install graalpy-24.1.0
pyenv shell graalpy-24.1.0

Before running pyenv install, you may need to update pyenv to include the latest GraalPy versions.

Alternatively, you can download a compressed GraalPy installation file from GitHub releases.

  1. Find the download that matches the pattern graalpy-XX.Y.Z-linux-amd64.tar.gz or graalpy-XX.Y.Z-linux-aarch64.tar.gz (depending on your platform) and download.
  2. Uncompress the file and update your PATH environment variable to include to the graalpy-XX.Y.Z-linux-amd64/bin (or graalpy-XX.Y.Z-linux-aarch64/bin) directory.

macOS

The easiest way to install GraalPy on macOS is to use Pyenv (the Python version manager). To install version 24.1.0 using Pyenv, run the following commands:

pyenv install graalpy-24.1.0
pyenv shell graalpy-24.1.0

Before running pyenv install, you may need to update pyenv to include the latest GraalPy versions.

Alternatively, you can download a compressed GraalPy installation file from GitHub releases.

  1. Find the download that matches the pattern graalpy-XX.Y.Z-macos-amd64.tar.gz or graalpy-XX.Y.Z-macos-aarch64.tar.gz (depending on your platform) and download.
  2. Remove the quarantine attribute.
     sudo xattr -r -d com.apple.quarantine /path/to/graalpy
    

    For example:

     sudo xattr -r -d com.apple.quarantine ~/.pyenv/versions/graalpy-24.1.0
    
  3. Uncompress the file and update your PATH environment variable to include to the graalpy-XX.Y.Z-macos-amd64/bin (or graalpy-XX.Y.Z-macos-aarch64/bin) directory.

Windows

  1. Find and download a compressed GraalPy installation file from GitHub releases that matches the pattern graalpy-XX.Y.Z-windows-amd64.tar.gz.
  2. Uncompress the file and update your PATH variable to include to the graalpy-XX.Y.Z-windows-amd64/bin directory.
Windows Limitations

The Windows distribution of GraalPy has more limitations than its Linux or macOS counterpart, so not all features and packages may be available.

It has the following known issues:

  • JLine treats Windows as a dumb terminal, with no autocomplete and limited editing capabilities in the REPL
  • Interactive help() in the REPL doesn’t work
  • Inside a virtual environment:
    • graalpy.cmd and graalpy.exe are broken
    • pip.exe cannot be used directly
    • pip has trouble with cache file loading, use --no-cache-dir
    • Only pure Python binary wheels can be installed, no native extensions or source builds
    • To install a package, use myvenv/Scripts/python.exe -m pip --no-cache-dir install <pkg>
  • Running from PowerShell works better than running from CMD, various scripts will fail on the latter

Installing Packages

The best way of using GraalPy is from a venv virtual environment. This generates wrapper scripts and makes the implementation usable from a shell as the standard Python interpreter.

  1. Create a virtual environment with GraalPy by running the following command:
     graalpy -m venv <venv-dir>
    

    For example:

     graalpy -m venv ~/.virtualenvs/graalpy-24.1.0
    
  2. Activate the environment in your shell session:
     source <venv-dir>/bin/activate
    

    For example:

     source ~/.virtualenvs/graalpy-24.1.0/bin/activate
    

Multiple executables are available in the virtual environment, including: python, python3, and graalpy.

Note: To deactivate the Python environment (and return to your shell), run deactivate.

The pip package installer is available when using a virtual environment. The GraalPy implementation of pip may choose package versions other than the latest in cases where it ships patches to make these work better.

Python Performance

Execution Performance

GraalPy uses the state-of-the-art just-in-time (JIT) compiler of GraalVM. When JIT compiled, GraalPy runs Python code ~4x faster than CPython on the official Python Performance Benchmark Suite.

These benchmarks can be run by installing the pyperformance package and calling pyperformance run on each of CPython and GraalPy. To get the Jython numbers we adapted the harness and benchmarks because of missing Python 3 support in Jython. The speedup was then calculated by taking the pair-wise intersection of working benchmarks and calculating the geomean.

Without a JIT compiler, GraalPy currently executes pure Python code around ~4x slower than CPython. This means that very short running scripts or scripts running without the Graal compiler on Oracle JDK or OpenJDK are expected to be slower.

Many Python packages from the machine learning or data science ecosystems contain C extension code. This code benefits little from GraalPy’s JIT compilation and suffers from having to emulate CPython implementation details on GraalPy. When many C extensions are involved, performance can vary a lot depending on the specific interactions of native and Python code.

Code Loading Performance and Footprint

It takes time to parse Python code so when using GraalPy to embed another language in Python, observe the general advice for embedding Graal languages related to code caching. Furthermore, some Python libraries require loading a large amount of code on startup before they can do any work. Due to the design of the Python language, incremental parsing is not possible and for some scripts, the parser may represent a significant fraction of runtime and memory. To mitigate this, GraalPy can cache the bytecode generated during parsing in .pyc files, if appropriate file system access is configured.

Creating and Managing .pyc Files

GraalPy automatically creates a .pyc file when there is an invalid or absent .pyc file that matches a corresponding .py file.

When GraalPy imports a Python source file (module) during an execution for the first time, it automatically creates a corresponding .pyc file. If GraalPy imports the same module again, then it uses the existing .pyc file. That means that there are no .pyc files for source files that were not yet executed (imported). GraalPy creates .pyc files entirely through the FileSystem API, so that a Java application with embedded Python code can manage file system access.

Note: GraalPy never deletes a .pyc file.

Every time GraalPy subsequently executes a script, it reuses the existing .pyc file, or creates a new one. GraalPy recreates a .pyc file if the timestamp or hashcode of the original source file is changed. GraalPy generates the hashcode based only on the Python source file by calling source.hashCode(), which is the JDK hash code over the array of source file bytes, calculated with java.util.Arrays.hashCode(byte[]).

GraalPy also recreates .pyc files if a magic number in the Python parser is changed. The magic number is hard-coded in the source of Python and can not be changed by the user (unless of course that user has access to the bytecode of Python).

The developers of GraalPy change the magic number when the bytecode format changes. This is an implementation detail, so the magic number does not have to correspond to the version of GraalPy (as in CPython). The magic number of pyc is a function of the actual Python runtime Java code that is running. Changes to the magic number are communicated in the release notes so that developers or system administrators can delete old .pyc files when upgrading.

Note that if you use .pyc files, you must allow write-access to GraalPy at least when switching versions or modifying the original source code file. Otherwise, the regeneration of source code files will fail and every import will have the overhead of accessing each old .pyc file, parsing the code, serializing it, and trying (and failing) to write out a new .pyc file.

GraalPy creates the following directory structure for .pyc files:

top_directory/
  __pycache__/
    sourceA.graalpy.pyc
    sourceB.graalpy.pyc
  sourceA.py
  sourceB.py
  sub_directory/
    __pycache__/
      sourceX.graalpy.pyc
    sourceX.py

By default, GraalPy creates the __pycache__ directory on the same directory level as a source code file and in this directory all .pyc files from the same directory are stored. This directory may store .pyc files created with different versions of Python (including, for example, CPython), so the user may see files ending in .cpython3-6.pyc, for example.

.pyc files are largely managed automatically by GraalPy in a manner compatible with CPython. GraalPy provides options similar to CPython to specify the location of t_.pyc_ files, and if they should be written at all, and both of these options can be changed by guest code.

The creation of .pyc files can be controlled in the same way as CPython:

  • The GraalPy launcher (graalpy) reads the PYTHONDONTWRITEBYTECODE environment variable. If this is set to a non-empty string, Python will not try to create a .pyc file when importing a module.
  • The launcher command line option -B, if given, has the same effect as the above.
  • Guest language code can change the attribute dont_write_bytecode of the sys built-in module at runtime to change the behavior for subsequent imports.
  • The GraalPy launcher reads the PYTHONPYCACHEPREFIX environment variable. If set, it creates the __pycache__ directory at the path specified by the prefix, and creates a mirror of the directory structure of the source tree on-demand to store the .pyc files.
  • A guest language code can change the attribute pycache_prefix of the sys module at runtime to change the location for subsequent imports.

Because the developer cannot use environment variables or CPython options to communicate these options to GraalPy, these options are made available as language options:

  • python.DontWriteBytecodeFlag - equivalent to -B or PYTHONDONTWRITEBYTECODE
  • python.PyCachePrefix - equivalent to PYTHONPYCACHEPREFIX

Note that a Python context will not enable writing .pyc files by default. The GraalPy launcher enables it by default, but if this is desired in the embedding use case, care should be taken to ensure that the __pycache__ location is properly managed and the files in that location are secured against manipulation in the same way as the source code files (.py) from which they were derived.

Note also that to upgrade the application sources to Oracle GraalPy, old .pyc files must be removed by the developer as required.

Security Considerations

GraalPy performs all file operations (obtaining the data, timestamps, and writing .pyc files) via the FileSystem API. Developers can modify all of these operations by means of custom (for example, read-only) FileSystem implementations. The developer can also effectively disable the creation of .pyc files by disabling I/O permissions for GraalPy.

If .pyc files are not readable, their location is not writable. If the .pyc files’ serialization data or magic numbers are corrupted in any way, the deserialization fails and GraalPy parses the .py source code file again. This comes with a minor performance hit only for the parsing of modules, which should not be significant for most applications (provided the application performs actual work in addition to loading Python code).

Modern Python for the JVM

For Python version 2 (now EOL), Jython is the de facto means of interfacing Python and Java. Most existing Jython code that uses Java integration will be based on a stable Jython release—however, these are only available in Python 2.x versions. GraalPy, in contrast, is compatible with Python 3.x and does not provide full compatibility with earlier 2.x versions of Jython.

To migrate code from Python 2 to Python 3, follow the official guide from the Python community. Once your Jython code is compatible with Python 3, follow this guide to iron out other differences between GraalPy and Jython.

GraalPy’s first-class support for Java interoperability makes using Java libraries from Python as easy as possible, with special affordances for Java code beyond the generic interoperability support for other Graal languages (languages implemented on the Truffle framework).

Not all features of Jython are supported on GraalPy. Some are supported, but disabled by default due to their negative impact on runtime performance. During migration, you can enable these features using a command line option: --python.EmulateJython. We recommend to move away from these features, however, to achieve optimal performance.

Migrating Jython Scripts

To move plain Jython scripts from Jython to GraalPy, use a GraalPy JVM-based runtime. (For more information, see available GraalPy Distributions).

Importing a Java Package

There are certain features of Jython’s Java integration that are enabled by default on GraalPy. Here is an example:

>>> import java.awt as awt
>>> win = awt.Frame()
>>> win.setSize(200, 200)
>>> win.setTitle("Hello from Python!")
>>> win.getSize().toString()
'java.awt.Dimension[width=200,height=200]'
>>> win.show()

This example produces the same result when run on both Jython and GraalPy. However, when the example is run on GraalPy, only packages that are in the java namespace can be imported directly. To import classes from packages outside the java namespace, use the --python.EmulateJython option.

Note: When embedding GraalPy in a modularized application, you may have to add exports for the required modules according to JSR 376.

Additionally, it is not possible to import Java packages as Python modules in all circumstances. For example, this will work:

import java.lang as lang

But, this will not work:

import javax.swing as swing
from javax.swing import *

Instead, import one of the classes directly:

import javax.swing.Window as Window

Basic Object Usage

Constructing and working with Java objects and classes is achieved with conventional Python syntax. The methods of a Java object can also be retrieved and referenced as first class objects (bound to their instance), in the same way as Python methods. For example:

>>> from java.util import Random
>>> rg = Random(99)
>>> rg.nextInt()
1491444859
>>> boundNextInt = rg.nextInt
>>> boundNextInt()
1672896916

Java-to-Python Types: Automatic Conversion

Method overloads are resolved by matching the Python arguments in a best-effort manner to the available parameter types. This approach is also taken when converting data. The goal here is to make using Java from Python as smooth as possible. The matching approach taken by GraalPy is similar to Jython, but GraalPy uses a more dynamic approach to matching—Python types emulating int or float are also converted to the appropriate Java types. This enables you, for example, to use a Pandas frame as double[][] or NumPy array elements as int[] when the elements fit into those Java primitive types.

Java type Python type
null None
boolean bool
byte, short, int, long int, any object that has an __int__ method
float float, any object that has a __float__ method
char str of length 1
java.lang.String str
byte[] bytes, bytearray, wrapped Java array, Python list with only the appropriate types
Java arrays Wrapped Java array or Python list with only the appropriate types
Java objects Wrapped Java object of the appropriate type
java.lang.Object Any object

Special Jython Module: jarray

GraalPy implements the jarray module (to create primitive Java arrays) for compatibility. This module is always available, since we have not found its presence to have a negative impact. For example:

>>> import jarray
>>> jarray.array([1,2,3], 'i')

Note that its usage is equivalent to constructing the array type using the java.type function and then populating the array, as follows:

>>> import java
>>> java.type("int[]")(10)

The code that creates a Java array can also use Python types. However, implicitly, this may produce a copy of the array data, which can be deceptive when using a Java array as an output parameter:

>>> i = java.io.ByteArrayInputStream(b"foobar")
>>> buf = [0, 0, 0]
>>> i.read(buf) # buf is automatically converted to a byte[] array
3
>>> buf
[0, 0, 0] # the converted byte[] array is lost
>>> jbuf = java.type("byte[]")(3)
>>> i.read(jbuf)
3
>>> jbuf
[98, 97, 122]

Exceptions from Java

You can catch Java exceptions as you would expect. For example:

>>> import java
>>> v = java.util.Vector()
>>> try:
...    x = v.elementAt(7)
... except java.lang.ArrayIndexOutOfBoundsException as e:
...    print(e.getMessage())
...
7 >= 0

Java Collections

  • Java arrays and collections that implement the java.util.Collection interface can be accessed using the [] syntax. An empty collection is considered false in boolean conversions. The length of a collection is exposed by the len built-in function. For example:

      >>> from java.util import ArrayList
      >>> l = ArrayList()
      >>> l.add("foo")
      True
      >>> l.add("baz")
      True
      >>> l[0]
      'foo'
      >>> l[1] = "bar"
      >>> del l[1]
      >>> len(l)
      1
      >>> bool(l)
      True
      >>> del l[0]
      >>> bool(l)
      False
    
  • Java iterables that implement the java.lang.Iterable interface can be iterated over using a for loop or the iter built-in function and are accepted by all built-ins that expect an iterable. For example:

      >>> [x for x in l]
      ['foo', 'bar']
      >>> i = iter(l)
      >>> next(i)
      'foo'
      >>> next(i)
      'bar'
      >>> next(i)
      Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
      StopIteration
      >>> set(l)
      {'foo', 'bar'}
    
  • An iterator can be iterated as well. For example:

      >>> from java.util import ArrayList
      >>> l = ArrayList()
      >>> l.add("foo")
      True
      >>> i = l.iterator()  # Calls the Java iterator methods
      >>> next(i)
      'foo'
    
  • Mapped collections that implement the java.util.Map interface can be accessed using the [] notation. An empty map is considered false in boolean conversions. Iteration of a map yields its keys, consistent with dict. For example:

      >>> from java.util import HashMap
      >>> m = HashMap()
      >>> m['foo'] = 5
      >>> m['foo']
      5
      >>> m['bar']
      Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
      KeyError: bar
      >>> [k for k in m]
      ['foo']
      >>> bool(m)
      True
      >>> del m['foo']
      >>> bool(m)
      False
    

Inheritance from Java

Inheriting from a Java class (or implementing a Java interface) is supported with some syntactical and significant behavioral differences from Jython. To create a class that inherits from a Java class (or implements a Java interface), use the conventional Python class statement. Declared methods override (implement) superclass (interface) methods when their names match.

It is important to understand that there is actually delegation happening here - when inheriting from Java, two classes are created, one in Java and one in Python. These reference each other and any methods that are declared in Python that override or implement a Java method on the superclass are declared on the Java side as delegating to Python. The created object does not behave like a Python object but instead in the same way as a foreign Java object. The reason for this is that when you create an instance of your new class, you get a reference to the Java object.

To call Python methods that do not override or implement methods that already existed on the superclass, you need to use the special this attribute. Once you are in a Python method, your self refers to the Python object, and to get back from a Python method to Java, use the special attribute __super__. And since we do not expose static members on the instance side, if you need to call a static method from an instance on the Java side, use getClass().static to get to the meta-object holding the static members.

One important consequence of the two-object-schema here is that the __init__ method on the Python object is actually called before the connection to the Java side is established. So you cannot currently override construction of the Java object or run code during initialization that would affect the Java half of the combined structure. You will have to create a factory method if you want to achieve this.

For example:

import atexit
from java.util.logging import Logger, Handler


class MyHandler(Handler):
    def __init__(self):
        self.logged = []

    def publish(self, record):
        self.logged.append(record)


logger = Logger.getLogger("mylog")
logger.setUseParentHandlers(False)
handler = MyHandler()
logger.addHandler(handler)
# Make sure the handler is not used after the Python context has been closed
atexit.register(lambda: logger.removeHandler(handler))

logger.info("Hi")
logger.warning("Bye")

# The python attributes/methods of the object are accessed through 'this' attribute
for record in handler.this.logged:
    print(f'Python captured message "{record.getMessage()}" at level {record.getLevel().getName()}')

For more information about how the generated Java subclass behaves, see the Truffle documentation.

Embedding Python into Java

The other way to use Jython was to embed it into a Java application. There were two options for such an embedding.

  1. Use the PythonInterpreter object that Jython provides. Existing code using Jython in this manner depends directly on the Jython package (for example, in the Maven configuration), because the Java code has references to Jython internal classes. These classes do not exist in GraalVM, and no equivalent classes are exposed. To migrate from this usage, switch to the GraalVM SDK. Using this SDK, no APIs particular to Python are exposed, everything is achieved via the GraalVM API, with maximum configurability of the Python runtime. Refer to the Getting Started documentation for preparing a setup.

  2. Embed Jython in Java via JSR 223 by using the classes of the the javax.script package, and, in particular, via the ScriptEngine class. We do not recommend this approach, because the ScriptEngine APIs are not a clean fit for the options and capabilities of GraalPy. However, to migrate existing code, we provide an example ScriptEngine implementation that you can inline into your project. Refer to the reference manual for embedding for details.

Native Executables with Python

GraalPy supports GraalVM Native Image to generate native binaries of Java applications that use GraalPy.

Quickstart

If you started with the Maven archetype, the generated pom.xml makes it easy to generate a native executable using the Maven plugin for Native Image building.

To build the application, run:

mvn -Pnative package

The command packages the project and creates a native executable.

Take a look at the generated files pom.xml and Main.java. They are documented to explain how Python resources are included in the resulting binary. The generated project should be viewed as a starting point. It includes the entire Python standard library, so the Python code can invoke all of the standard library code. The resources can be manually pruned to reduce the included Python libraries to the necessary amount, reducing both the size of the package and the time to start up. This Java example demonstrates some useful default options for the Python context, but other settings may be desirable to further control what the Python code is allowed to do.

Reducing Binary Size

Python is a large language. “Batteries included” has long been a core tenet of CPython. As a compatible replacement, GraalPy includes most of those “batteries” as well. This can result in significant increases in binary size when including GraalPy in a Java application.

Only you as the developer know your specific embedding scenario. The defaults may include much more than needed for any specific application. An embedded Python-in-Java application usually has more limited use cases for the Python interpreter than the full GraalPy distribution, and often you can know ahead of time whether certain features are needed. Some features (for example, cryptographic algorithms or socket access) may even be undesirable to include in some cases. Thus, when embedding GraalPy in a Java application, the binary size can be reduced and security improved by excluding components of the Python language.

Excluding Python Components

GraalPy defines a few system properties that can be passed on the native-image command line to exclude aspects of the language. The options can, when taken together, reduce the size of the executable by around 20%. These are:

  • python.WithoutSSL=true - This option removes the ssl module. If no secure network access or certificate checking is required, this removes Java’s SSL classes and the BouncyCastle library.
  • python.WithoutDigest=true - This option removes the _md5, _sha1, _sha256, _sha512, _sha3, and _hashlib modules. This removes the direct usages of java.security.MessageDigest and javax.crypto.Mac from GraalPy.
  • python.WithoutPlatformAccess=true - This removes the signal and subprocess modules, removes accesses to process properties such as the Unix UID and GID, or setting the Java default time zone. This has no significant impact on binary size, but if these are unwanted capabilities that are dynamically disabled with context options anyway, they can also be removed ahead of time.
  • python.WithoutCompressionLibraries=true - This removes the zlib, lzma, bzip2, and zipimporter modules and related classes. These modules have both native and pure Java implementations (the former for performance, the latter for better sandboxing); however, if they are not needed, they can be removed entirely.
  • python.WithoutNativePosix=true - The default os module backend is a pure Java implementation when GraalPy is embedded rather than run via its launcher. The native POSIX backend of GraalPy is recommended only for 100% compatibility with CPython’s POSIX interfaces, and if not used, can be removed from the build with this option.
  • python.WithoutJavaInet=true - The Java implementation of Python’s socket module is based on Java’s networking classes. If network access is denied for an embedding scenario, this option can reduce the binary size further.
  • python.AutomaticAsyncActions=false - Signal handling, Python weak reference callbacks, and cleaning up native resources is usually achieved automatically by spawning GraalPy daemon threads that submit safepoint actions to the Python main thread. This uses an ExecutorService with a thread pool. If you want to disallow such extra threads or avoid pulling in ExecutorService and related classes, then set this property to false and retrieve the PollPythonAsyncActions object from the context’s polyglot bindings. This object is executable and can be used to trigger Python asynchronous actions at the locations you desire.
  • python.WithoutJNI=true - This option removes any code that uses JNI. As a consequence, you cannot use the HPy JNI backend and maybe other parts that rely on JNI.

Removing Pre-initialized Python Heap

Another useful option to reduce the size of the native executable is to omit a pre-initialized Python context from the executable. By default, a default Python context is already pre-initialized and ready for immediate execution. This can lead to slightly improved startup, at the cost of including a few thousand Python objects in the binary. In embedded applications that use a custom polyglot engine to allow context sharing, the pre-initialized context cannot be used at all, and including those objects is wasted. The pre-initialized heap can be omitted by passing the following to the native-image command:

-Dimage-build-time.PreinitializeContexts=

Disabling Runtime Compilation of Python Code

If binary size is significantly more important than execution speed (which may be the case if all Python scripts are expected to be short running, perform a lot of I/O, or scripts are rarely executed more than once), it may make sense to disable JIT compilation entirely. Be aware that this may significantly impact your Python performance, so be sure to test the runtime behavior of your actual use cases when choosing to use this option. This can be achieved by passing the following options:

-Dtruffle.TruffleRuntime=com.oracle.truffle.api.impl.DefaultTruffleRuntime \
-Dpolyglot.engine.WarnInterpreterOnly=false

Summary

Combining all of these approaches can halve the size of the GraalPy binary. Every embedded application is different and the code pulled in by the rest of the Java code also matters, so combinations of these options should be tried to determine which effect they have in a specific instance.

Shipping Python Packages

Our Maven archetype by default is set up to include all needed Python files in the native binary itself, so the image is self-contained.

In custom embeddings, the Python standard library is copied next to the native image. When moving the native image, the standard library folder needs to be kept next to it.

Python Standalone Applications

GraalPy enables you to create a Python application or library as a native application or JAR file with no external dependencies. The Truffle framework on which GraalPy is built virtualizes all filesystem accesses, including those to the standard library and installed pure Python packages. Packages that include native code can still circumvent this, however!

GraalPy includes a module named standalone to create a Python binary for Linux, macOS, and Windows. The modules bundles all your application’s resources into a single file.

Prerequisite: GraalPy distribution beginning with version 23.1.0. See GraalPy releases.

For example, if you want to produce a native executable from a Python file named my_script.py along with packages you have installed in a virtual environment named my_venv, run the following command:

graalpy -m standalone native \
      --module my_script.py \
      --output my_binary \
      --venv my_env

It produces a native my_binary file which includes your Python code, the GraalPy runtime, and the Python standard library in a single, self-contained executable. Use graalpy -m standalone native --help for further options.

Security Considerations of Python Standalone Applications

Creating a native executable or a JAR file that includes Python code could be seen as a mild form of obfuscation, but it does not protect your source code. Python source code is not stored verbatim into the executable (only the GraalPy bytecode is stored), but bytecode is easy to convert back into Python source code.

Interoperability

Besides being primarily recommended to use in your Java application, GraalPy can interoperate with other Graal languages (languages implemented on the Truffle framework). This means that you can use the objects and functions provided by those other languages directly from your Python scripts.

Interacting with Java from Python scripts

Java is the host language of the JVM and runs the GraalPy interpreter itself. To interoperate with Java from Python scripts, use the java module:

import java
BigInteger = java.type("java.math.BigInteger")
myBigInt = BigInteger.valueOf(42)
# a public Java methods can just be called
myBigInt.shiftLeft(128) # returns a <JavaObject[java.math.BigInteger] at ...>
# Java method names that are keywords in Python must be accessed using `getattr`
getattr(myBigInt, "not")() # returns a <JavaObject[java.math.BigInteger] at ...>
byteArray = myBigInt.toByteArray()
# Java arrays can act like Python lists
assert len(byteArray) == 1 and byteArray[0] == 42

To import packages from the java namespace, you can also use the conventional Python import syntax:

import java.util.ArrayList
from java.util import ArrayList
assert java.util.ArrayList == ArrayList

al = ArrayList()
al.add(1)
al.add(12)
assert list(al) == [1, 12]

In addition to the type built-in method, the java module exposes the following methods:

Built-in Specification
instanceof(obj, class) returns True if obj is an instance of class (class must be a foreign object class)
is_function(obj) returns True if obj is a Java host language function wrapped using interop
is_object(obj) returns True if obj if the argument is Java host language object wrapped using interop
is_symbol(obj) returns True if obj if the argument is a Java host symbol, representing the constructor and static members of a Java class, as obtained by java.type
ArrayList = java.type('java.util.ArrayList')
my_list = ArrayList()
assert java.is_symbol(ArrayList)
assert not java.is_symbol(my_list)
assert java.is_object(ArrayList)
assert java.is_function(my_list.add)
assert java.instanceof(my_list, ArrayList)

See Polyglot Programming and Embed Languages for more information about interoperability with other programming languages.

Interacting with foreign objects from Python scripts

Foreign objects are given a Python class corresponding to their interop traits:

from java.util import ArrayList, HashMap
type(ArrayList()).mro() # => [<class 'polyglot.ForeignList'>, <class 'list'>, <class 'polyglot.ForeignObject'>, <class 'object'>]
type(HashMap()).mro() # => [<class 'polyglot.ForeignDict'>, <class 'dict'>, <class 'polyglot.ForeignObject'>, <class 'object'>]

This means all Python methods of these types are available on the corresponding foreign objects, which behave as close as possible as if they were Python objects:

from java.util import ArrayList, HashMap
l = ArrayList()
l.append(1) # l: [1]
l.extend([2, 3]) # l: [1, 2, 3]
l.add(4) # l: [1, 2, 3, 4] # we can still call Java methods, this is calling ArrayList#add
l[1:3] # => [2, 3]
l.pop(1) # => 2; l: [1, 3, 4]
l.insert(1, 2) # l: [1, 2, 3, 4]
l == [1, 2, 3, 4] # True

h = HashMap()
h[1] = 2 # h: {1: 2}
h.setdefault(3, 4) # h: {1: 2, 3: 4}
h |= {3: 6} # {1: 2, 3: 6}
h == {1: 2, 3: 6} # True

In case of a method defined both in Python and on the foreign object, the Python method wins. To call the foreign method instead, use super(type_owning_the_python_method, foreign_object).method(*args):

from java.util import ArrayList
l = ArrayList()
l.extend([5, 6, 7])
l.remove(7) # Python list.remove
assert l == [5, 6]

super(list, l).remove(0) # ArrayList#remove(int index)
assert l == [6]

See this section for more interop traits and how they map to Python types.

Interacting with other dynamic languages from Python scripts

More general, non-JVM specific interactions with other languages from Python scripts are achieved via the polyglot API. This includes all interactions with dynamic languages supported via the Truffle framework, including JavaScript and Ruby.

Installing other dynamic languages

Other languages can be included by using their respective Maven dependencies in the same manner as GraalPy. For example, if you have already configured a Maven project with GraalPy, add the following dependency to gain access to JavaScript:

<dependency>
    <groupId>org.graalvm.polyglot</groupId>
    <artifactId>js</artifactId>
    <version>24.1.0</version>
</dependency>

Examples

  1. Import the polyglot module to interact with other languages:
    import polyglot
    
  2. Evaluate inlined code in another language:
    assert polyglot.eval(string="1 + 1", language="js") == 2
    
  3. Evaluate code from a file:
    with open("./my_js_file.js", "w") as f:
        f.write("Polyglot.export('JSMath', Math)")
    polyglot.eval(path="./my_js_file.js", language="js")
    
  4. Import a glocal value from the polyglot scope:
    Math = polyglot.import_value("JSMath")
    

    This global value should then work as expected:

    • Accessing attributes reads from the polyglot members namespace:
      assert Math.E == 2.718281828459045
      
    • Calling a method on the result attempts to do a straight invoke and falls back to reading the member and trying to execute it.
      assert Math.toString() == "[object Math]"
      
    • Accessing items is supported both with strings and numbers.
      assert Math["PI"] == 3.141592653589793
      
  5. Use the JavaScript regular expression engine to match Python strings:
    js_re = polyglot.eval(string="RegExp()", language="js")
    
    pattern = js_re.compile(".*(?:we have (?:a )?matching strings?(?:[!\\?] )?)(.*)")
    
    if pattern.exec("This string does not match"): raise SystemError("that shouldn't happen")
    
    md = pattern.exec("Look, we have matching strings! This string was matched by Graal.js")
    
    assert "Graal.js" in md[1]
    

    This program matches Python strings using the JavaScript regular expression object. Python reads the captured group from the JavaScript result and checks for a substring in it.

Exporting Python Objects to other Languages

The polyglot module can be used to expose Python objects to JVM languages and other Graal languages (languages implemented on the Truffle framework).

  1. You can export some object from Python to other languages so they can import it:
    import ssl
    polyglot.export_value(value=ssl, name="python_ssl")
    

    Then use it in (for example) from JavaScript code:

    Polyglot.import('python_ssl).get_server_certificate(["oracle.com", 443])
    
  2. You can decorate a Python function to export it by name:
    @polyglot.export_value
    def python_method():
        return "Hello from Python!"
    

    Then use it (for example) from Java code:

    import org.graalvm.polyglot.*;
    
    class Main {
        public static void main(String[] args) {
            try (var context = Context.create()) {
                context.eval(Source.newBuilder("python", "file:///python_script.py").build());
    
                String result = context.
                    getPolyglotBindings().
                    getMember("python_method").
                    execute().
                    asString();
                assert result.equals("Hello from Python!");
            }
        }
     }
    

Mapping Types between Python and Other Languages

The interop protocol defines different “types/traits” which can overlap in all kinds of ways and have restrictions on how they can interact with Python.

Interop Types to Python

All foreign objects passed into Python have the Python type polyglot.ForeignObject or a subclass.

Types not listed in the table below have no special interpretation in Python.

Interop Type Inherits from Python Interpretation
null ForeignNone, NoneType null is like None. Important to know: interop null values are all identical to None. JavaScript defines two “null-like” values; undefined and null, which are not identical, but when passed to Python, they are treated so.
boolean ForeignBoolean, ForeignNumber boolean behaves like Python booleans, including the fact that in Python, all booleans are also integers (1 and 0 for true and false, respectively).
number ForeignNumber number behaves like Python numbers. Python only has one integer and one floating point type, but ranges are imported in some places such as typed arrays.
string ForeignString, str Behaves in the same way as a Python string.
buffer ForeignObject Buffers are also a concept in Python’s native API (albeit slightly different). Interop buffers are treated in the same was as Python buffers in some places (such as memoryview) to avoid copies of data.
array ForeignList, list An array behaves like a Python list.
hash ForeignDict, dict A hash behaves like a Python dict, with any “hashable” object as a key. “Hashable” follows Python semantics: generally every interop type with an identity is deemed “hashable”.
members ForeignObject An object of type members can be read using conventional Python . notation or getattr and related functions.
iterable ForeignIterable An iterable is treated in the same way as any Python object with an __iter__ method. That is, it can be used in a loop and other places that accept Python iterables.
iterator ForeignIterator, iterator An iterator is treated in the same way as any Python object with a __next__ method.
exception ForeignException, BaseException An exception can be caught in a generic except clause.
MetaObject ForeignAbstractClass Meta objects can be used in subtype and isinstance checks.
executable ForeignExecutable An executable object can be executed as a function, but never with keyword arguments.
instantiable ForeignInstantiable An instantiable object can be called just like a Python type, but never with keyword arguments.

Foreign numbers inherit from polyglot.ForeignNumber and not int or float because InteropLibrary has currently no way to differentiate integers and floats. However:

  • When foreign numbers are represented as Java primitives byte, short, int, long, they are considered Python int objects.
  • When foreign numbers are represented as Java primitives float, double, they are considered Python float objects.
  • When foreign booleans re represented as Java primitives boolean, they are considered Python bool objects.

Python to Interop Types

Interop Type Python Interpretation
null Only None.
boolean Only subtypes of Python bool. Note that in contrast to Python semantics, Python bool is never also an interop number.
number Only subtypes of int and float.
string Only subtypes of str.
array Any object with __getitem__ and __len__ methods, but not if it also has keys, values, and items methods (in the same way that dict does.)
hash Only subtypes of dict.
members Any Python object. Note that the rules for readable/writable are a bit ad-hoc, since checking that is not part of the Python MOP.
iterable Any Python object that has __iter__ or a __getitem__ methods.
iterator Any Python object with a __next__ method.
exception Any Python BaseException subtype.
MetaObject Any Python type.
executable Any Python object with a __call__ method.
instantiable Any Python type.

The Interoperability Extension API

It is possible to extend the interoperability protocol directly from Python via a simple API defined in the polyglot module. The purpose of this API is to enable custom / user defined types to take part in the interop ecosystem. This is particularly useful for external types which are not compatible by default with the interop protocol. An example in this sense are the numpy numeric types (for example, numpy.int32) which are not supported by default by the interop protocol.

The API

Function Description
register_interop_behavior Takes the receiver type as first argument. The remainder keyword arguments correspond to the respective interop messages. Not All interop messages are supported.
get_registered_interop_behavior Takes the receiver type as first argument. Returns the list of extended interop messages for the given type.
@interop_behavior Class decorator, takes the receiver type as only argument. The interop messages are extended via static methods defined in the decorated class (supplier).
register_interop_type Takes a foreign class and python class as positional arguments and allow_method_overwrites as optional argument (default: False). Every instance of foreign class is then treated as an instance of the given python class.
@interop_type Class decorator, takes the foreign class and optionally allow_method_overwrites as arguments. The instances of foreign class will be treated as an instance of the annotated python class.

Usage Examples

Interop Behavior

A simple register_interop_behavior API is available to register interop behaviors for existing types:

import polyglot
import numpy

polyglot.register_interop_behavior(numpy.int32,
    is_number=True,
    fitsInByte=lambda v: -128 <= v < 128,
    fitsInShort=lambda v: -0x8000 <= v < 0x8000,
    fitsInInt = True,
    fitsInLong = True,
    fitsInBigInteger = True,
    asByte = int,
    asShort = int,
    asInt = int,
    asLong = int,
    asBigInteger = int,
)

The @interop_behavior decorator may be more convenient when declaring more behaviors. Interop message extension is achieved via static methods of the decorated class. The names of the static methods are identical to the keyword names expected by register_interop_behavior.

from polyglot import interop_behavior
import numpy


@interop_behavior(numpy.float64)
class Int8InteropBehaviorSupplier:
    @staticmethod
    def is_number(_):
        return True

    @staticmethod
    def fitsInDouble(_):
        return True

    @staticmethod
    def asDouble(v):
        return float(v)

Both classes can then behave as expected when embedded:

import java.nio.file.Files;
import java.nio.file.Path;

import org.graalvm.polyglot.Context;

class Main {
    public static void main(String[] args) {
        try (var context = Context.create()) {
            context.eval("python", Files.readString(Path.of("path/to/interop/behavior/script.py")));
            assert context.eval("python", "numpy.float64(12)").asDouble() == 12.0;
            assert context.eval("python", "numpy.int32(12)").asByte() == 12;
        }
    }
}
Interop Types

The register_interop_type API allows the usage of python classes for foreign objects. The class of such a foreign object will no longer be polyglot.ForeignObject or polyglot.Foreign*. Instead, it will be a generated class with the registered python classes and polyglot.ForeignObject as super class. This allows custom mapping of foreign methods and attributes to Python’s magic methods or more idiomatic Python code.

package org.example;

class MyJavaClass {
      private int x;
      private int y;
      
      public MyJavaClass(int x, int y) {
         this.x = x;
         this.y = y;
      }

      public int getX() {
         return x;
      }

      public int getY() {
         return y;
      }
   }
import org.example.MyJavaClass;
        
class Main {

   public static void main(String[] args) {
      MyJavaClass myJavaObject = new MyJavaClass(42, 17);
      try (var context = Context.create()) {
         // myJavaObject will be globally available in example.py as my_java_object
         context.getBindings("python").putMember("my_java_object", myJavaObject);
         context.eval(Source.newBuilder("python", "example.py"));
      }
   }
}
# example.py
import java
from polyglot import register_interop_type

print(my_java_object.getX()) # 42
print(type(my_java_object)) # <class 'polyglot.ForeignObject'>

class MyPythonClass:
   def get_tuple(self):
      return (self.getX(), self.getY())

foreign_class = java.type("org.example.MyJavaClass")

register_interop_type(foreign_class, MyPythonClass)

print(my_java_object.get_tuple()) # (42, 17)
print(type(my_java_object)) # <class 'polyglot.Java_org.example.MyJavaClass_generated'>
print(type(my_java_object).mro()) # [polyglot.Java_org.example.MyJavaClass_generated, MyPythonClass, polyglot.ForeignObject, object]

class MyPythonClassTwo:
   def get_tuple(self):
      return (self.getY(), self.getX())
   
   def __str__(self):
      return f"MyJavaInstance(x={self.getX()}, y={self.getY()}"

# If 'allow_method_overwrites=True' is not given, this would lead to an error due to the method conflict of 'get_tuple'  
register_interop_type(foreign_class, MyPythonClassTwo, allow_method_overwrites=True)

# A newly registered class will be before already registered classes in the mro.
# It allows overwriting methods from already registered classes with the flag 'allow_method_overwrites=True'
print(type(my_java_object).mro()) # [generated_class, MyPythonClassTwo, MyPythonClass, polyglot.ForeignObject, object]

print(my_java_object.get_tuple()) # (17, 42)
print(my_java_object) # MyJavaInstance(x=42, y=17)

Registering classes may be more convenient with @interop_type:

import java
from polyglot import interop_type

foreign_class = java.type("org.example.MyJavaClass")

@interop_type(foreign_class)
class MyPythonClass:
   def get_tuple(self):
      return (self.getX(), self.getY())

Supported messages

The majority (with some exceptions) of the interop messages are supported by the interop behavior extension API, as shown in the table below.
The naming convention for the register_interop_behavior keyword arguments follows the snake_case naming convention, i.e. the interop fitsInLong message becomes fits_in_long and so on. Each message can be extended with a pure python function (default keyword arguments, free vars and cell vars are not allowed) or a boolean constant. The table below describes the supported interop messages:

Message Extension argument name Expected return type
isBoolean is_boolean bool
isDate is_date bool
isDuration is_duration bool
isIterator is_iterator bool
isNumber is_number bool
isString is_string bool
isTime is_time bool
isTimeZone is_time_zone bool
isExecutable is_executable bool
fitsInBigInteger fits_in_big_integer bool
fitsInByte fits_in_byte bool
fitsInDouble fits_in_double bool
fitsInFloat fits_in_float bool
fitsInInt fits_in_int bool
fitsInLong fits_in_long bool
fitsInShort fits_in_short bool
asBigInteger as_big_integer int
asBoolean as_boolean bool
asByte as_byte int
asDate as_date 3-tuple with the following elements: (year: int, month: int, day: int)
asDouble as_double float
asDuration as_duration 2-tuple with the following elements: (seconds: long, nano_adjustment: long)
asFloat as_float float
asInt as_int int
asLong as_long int
asShort as_short int
asString as_string str
asTime as_time 4-tuple with the following elements: (hour: int, minute: int, second: int, microsecond: int)
asTimeZone as_time_zone a string (the timezone) or int (utc delta in seconds)
execute execute object
readArrayElement read_array_element object
getArraySize get_array_size int
hasArrayElements has_array_elements bool
isArrayElementReadable is_array_element_readable bool
isArrayElementModifiable is_array_element_modifiable bool
isArrayElementInsertable is_array_element_insertable bool
isArrayElementRemovable is_array_element_removable bool
removeArrayElement remove_array_element NoneType
writeArrayElement write_array_element NoneType
hasIterator has_iterator bool
hasIteratorNextElement has_iterator_next_element bool
getIterator get_iterator a python iterator
getIteratorNextElement get_iterator_next_element object
hasHashEntries has_hash_entries bool
getHashEntriesIterator get_hash_entries_iterator a python iterator
getHashKeysIterator get_hash_keys_iterator a python iterator
getHashSize get_hash_size int
getHashValuesIterator get_hash_values_iterator a python iterator
isHashEntryReadable is_hash_entry_readable bool
isHashEntryModifiable is_hash_entry_modifiable bool
isHashEntryInsertable is_hash_entry_insertable bool
isHashEntryRemovable is_hash_entry_removable bool
readHashValue read_hash_value object
writeHashEntry write_hash_entry NoneType
removeHashEntry remove_hash_entry NoneType

Embedding Build Tools

The GraalPy Maven and Gradle plugins provide functionality to manage Python related resources required for embedding Python code in Java-based applications:

  • Python application files provided by the user, for example, Python sources which are part of the project.
  • Third-party Python packages installed by the plugin during the build according to the plugin configuration.

Apart from physically managing and deploying those files, it is also necessary to make them available in Python at runtime by configuring the GraalPy Context in your Java code accordingly. The GraalPyResources API provides factory methods to create a Context preconfigured for accessing Python, embedding relevant resources with a Virtual Filesystem or from a dedicated external directory.

Deployment

There are two modes how to deploy the resources: as Java resources using the Virtual Filesystem to access them in Python, or as an external directory.

Virtual Filesystem

The Python related resources are embedded in the application file, either in JAR or Native Image generated executable, as standard Java resources. The GraalPy Virtual Filesystem internally accesses the resource files as standard Java resources and makes them available to Python code running in GraalPy. This is transparent to the Python code, which can use standard Python IO to access those files.

Java resource files in a Maven or Gradle project are typically located in dedicated resources directories, such as src/main/resources. Moreover, there can be multiple resources directories and Maven or Gradle usually merges them.

User can choose relative Java resources path that will be made accessible in Python through the virtual filesystem, by default it is org.graalvm.python.vfs. All resources subdirectories with this path are merged during build and mapped to a configurable Virtual Filesystem mount point at the Python side, by default /graalpy_vfs. For example, a Python file with the real filesystem path ${project_resources_directory}/org.graalvm.python.vfs/src/foo/bar.py will be accessible as /graalpy_vfs/src/foo/bar.py in Python.

Use the following GraalPyResources factory methods to create GraalPy Context preconfigured for the use of the Virtual Filesystem:

  • GraalPyResources.createContext()
  • GraalPyResources.contextBuilder()
  • GraalPyResources.contextBuilder(VirtualFileSystem)
Java Resource Path

Particularly when developing reusable libraries, it is recommended to use custom unique Java resources path for your virtual filesystem to avoid conflicts with other libraries on the classpath or module path that may also use the Virtual Filesystem. The recommended path is:

GRAALPY-VFS/${project.groupId}/${project.artifactId}

The Java resources path must be configured in the Maven and Gradle plugins and must be also set to the same value at runtime using the VirtualFileSystem$Builder#resourceDirectory API.

Note regarding Java module system: resources in named modules are subject to the encapsulation rules specified by Module.getResourceAsStream. This is also the case of the default virtual filesystem location. When a resources directory is not a valid Java package name, such as the recommended “GRAALPY-VFS”, the resources are not subject to the encapsulation rules and do not require additional module system configuration.

External Directory

As an alternative to Java resources with the Virtual Filesystem, it is also possible to configure the Maven or Gradle plugin to manage the contents of an external directory, which will not be embedded as a Java resource into the resulting application. A user is then responsible for the deployment of such directory. Python code will access the files directly from the real filesystem.

Use the following GraalPyResources factory methods to create GraalPy Context preconfigured for the use of an external directory:

  • GraalPyResources.createContextBuilder(Path)

Conventions

The factory methods in GraalPyResources rely on the following conventions, where the ${root} is either an external directory, or a Virtual System mount point on the Python side and Java resources directories, such as ${project_resources_directory}/org.graalvm.python.vfs, on the real filesystem:

  • ${root}/src: used for Python application files. This directory will be configured as the default search path for Python module files (equivalent to PYTHONPATH environment variable).
  • ${root}/venv: used for the Python virtual environment holding installed third-party Python packages. The Context will be configured as if it is executed from this virtual environment. Notably packages installed in this virtual environment will be automatically available for importing.

The Maven or Gradle plugin will fully manage the contents of the venv subdirectory. Any manual change will be overridden by the plugin during the build.

  • ${root}/venv: the plugin creates a virtual environment and installs required packages according to the plugin configuration in pom.xml or build.gradle.

The src subdirectory is left to be manually populated by the user with custom Python scripts or modules.

To manage third-party Python packages, a Python virtual environment is used behind the scenes. Whether deployed in a virtual filesystem or an external directory, its contents are managed by the plugin based on the Python packages specified in the plugin configuration.

Python Dependency Management

The list of third-party Python packages to be downloaded and installed can be specified in Maven or Gradle plugin configuration. Unfortunately, Python does not enforce strict versioning of dependencies, which can result in problems if a third-party package or one of its transitive dependencies is unexpectedly updated to a newer version, leading to unforeseen behavior.

It is regarded as good practice to always specify a Python package with its exact version. In simpler scenarios, where only a few packages are required, specifying the exact version of each package in the plugin configuration, along with their transitive dependencies, might be sufficient. However, this method is often impractical, as manually managing the entire dependency tree can quickly become overwhelming.

Locking Dependencies

For these cases, we highly recommend locking all Python dependencies whenever there is a change in the list of required packages for a project. The GraalPy plugins provide an action to do so, and as a result, a GraalPy lock file will be created, listing all required Python packages with their specific versions based on the packages defined in the plugin configuration and their dependencies. Subsequent GraalPy plugin executions will then use this file exclusively to install all packages with guaranteed versions.

The default location of the lock file is in the project root, and since it serves as input for generating resources, it should be stored alongside other project files in a version control system.

For information on the specific Maven or Gradle lock packages actions, please refer to the plugin descriptions below in this document.

GraalPy Maven Plugin

Maven Plugin Configuration

Add the plugin configuration in the configuration block of graalpy-maven-plugin in the pom.xml file:

<plugin>
    <groupId>org.graalvm.python</groupId>
    <artifactId>graalpy-maven-plugin</artifactId>
    ...
    <configuration>
        ...
    </configuration>
    ...
</plugin>
  • The packages element declares a list of third-party Python packages to be downloaded and installed by the plugin. The Python packages and their versions are specified as if used with pip:
    <configuration>
        <packages>
            <package>termcolor==2.2</package>
            ...
        </packages>
        ...
    </configuration>
    
  • The graalPyLockFile element can specify an alternative path to a GraalPy lock file. Default value is ${basedir}/graalpy.lock.
    <configuration>
        <graalPyLockFile>${basedir}/graalpy.lock</graalPyLockFile>
        ...
    </configuration>
    
  • The resourceDirectory element can specify the relative Java resource path. Remember to use VirtualFileSystem$Builder#resourceDirectory when configuring the VirtualFileSystem in Java.
    <resourceDirectory>GRAALPY-VFS/${project.groupId}/${project.artifactId}</resourceDirectory>
    
  • If the externalDirectory element is specified, then the given directory is used as an external directory and no Java resources are embedded. Remember to use the appropriate GraalPyResources API to create the Context. This element and resourceDirectory are mutually exclusive.
    <configuration>
        <externalDirectory>${basedir}/python-resources</externalDirectory>
        ...
    </configuration>
    

Locking Python Packages

To lock the dependency tree of the specified Python packages, execute the GraalPy plugin goal org.graalvm.python:graalpy-maven-plugin:lock-packages.

$ mvn org.graalvm.python:graalpy-maven-plugin:lock-packages

Note that the action will override the existing lock file.

For more information on managing Python packages, please refer to the descriptions of the graalPyLockFile and packages fields in the plugin configuration, as well as the Python Dependency Management section above in this document.

GraalPy Gradle Plugin

Gradle Plugin Configuration

The plugin must be added to the plugins section in the build.gradle file. The version property defines which version of GraalPy to use.

plugins {
    // other plugins ...
    id 'org.graalvm.python' version '24.2.0'
}

The plugin automatically injects these dependencies of the same version as the plugin version:

  • org.graalvm.python:python
  • org.graalvm.python:python-embedding

The plugin can be configured in the graalPy block:

  • The packages element declares a list of third-party Python packages to be downloaded and installed by the plugin. The Python packages and their versions are specified as if used with pip.
    graalPy {
      packages = ["termcolor==2.2"]
      ...
    }
    
  • The graalPyLockFile element can specify an alternative path to a GraalPy lock file. Default value is $rootDir/graalpy.lock.
    graalPy {
      graalPyLockFile = file("$rootDir/graalpy.lock")
      ...
    }
    
  • The resourceDirectory element can specify the relative Java resource path. Remember to use VirtualFileSystem$Builder#resourceDirectory when configuring the VirtualFileSystem in Java.
    resourceDirectory = "GRAALPY-VFS/my.group.id/artifact.id"
    
  • If the externalDirectory element is specified, then the given directory is used as an external directory and no Java resources are embedded. Remember to use the appropriate GraalPyResources API to create the Context.
    graalPy {
      externalDirectory = file("$rootDir/python-resources")
      ...
    }
    
  • Boolean flag community switches the automatically injected dependency org.graalvm.python:python to the community build: org.graalvm.python:python-community.
    graalPy {
      community = true
      ...
    }
    

    Locking Python Packages

    To lock the dependency tree of the specified Python packages, execute the GraalPy plugin task graalPyLockPackages.

    gradle graalPyLockPackages
    

    Note that the action will override the existing lock file.

For more information on managing Python packages, please refer to the descriptions of the graalPyLockFile and packages fields in the plugin configuration, as well as the Python Dependency Management sections in this document.

Permissions for Python Embeddings

Access Control and Security Limits for Python Scripts

Embedding GraalPy into Java works with the GraalVM Polyglot Sandbox.

Python’s POSIX Interface

The way the operating system interface is exposed to Python scripts is GraalPy-specific. By default all access is routed through Java interfaces, but some packages rely on details of POSIX APIs and require direct native access.

Graal languages (those implemented on the Truffle framework) usually implement system-related functions using the Truffle abstraction layer, which is OS independent and provides extension points for the users when embedding GraalPy or other Graal languages into Java applications. See, for example, the Truffle FileSystem service-provider.

The standard Python library also provides an OS abstraction, but exposes lower level interfaces. For example, the os module directly exposes some POSIX functions. On non-POSIX platforms, this interface is emulated to a degree.

GraalPy provides two alternative implementations (each referred to as a “backend”) of system-related functionality offered by built-in Python modules such as os. The PosixModuleBackend option determines which backend is used: valid values are native and java.

Native Backend

This backend directly calls the POSIX API in mostly the same way as CPython (the reference Python implementation).

This approach is the most compatible with CPython and provides bare access to the underlying OS interface without an intermediate emulation layer.

By default, this implementation bypasses the Truffle abstraction layer, and therefore it is not sandboxed and does not support custom implementations of Truffle FileSystem service-provider, and other Polyglot API providers related to system interfaces.

The native backend is chosen by default when GraalPy is started via the graalpy or any other Python related launcher.

Limitations of the Native Backend

Known limitations are:

  • os.fork is not supported
  • _posixsubprocess.fork_exec does not support the preexec_fn parameter

Java Backend

This backend uses the Truffle abstraction layer and therefore supports custom Polyglot API providers related to system interfaces and sandboxing. Because this abstraction is POSIX agnostic, it does not expose all the necessary functionality. Some functionality is emulated, and some functionality is unsupported.

The Java backend is the default when GraalPy is run via the Context API, that is, embedded in Java applications.

Limitations of the Java Backend

GraalPy can log information about known incompatibility of functions executed at runtime, which includes the OS interface-related functions. To turn on this logging, use the command-line option --log.python.compatibility.level=FINE (or other desired logging level).

Known limitations of the Java backend are:

  • Its state is disconnected from the actual OS state, which applies especially to:
    • file descriptors: Python-level file descriptors are not usable in native code.
    • current working directory: is initialized to the current working directory at the startup, but is then maintained separately. So, for example, chdir in Python does not change the actual current working directory of the process.
    • umask: has the same limitation as that of the current working directory, but it is always initialized to 0022 regardless of the actual system value at startup.
  • Resolution of file access/modification times depends on the JDK. The best the Java backend can guarantee is seconds resolution.
  • os.access and any other functionality based on faccessat POSIX function does not support:
    • effective IDs.
    • follow_symlinks=False unless the mode is only F_OK.

Python Native Extensions

Python native extensions run by default as native binaries, with full access to the underlying system. See Embedding limitations

The context permissions needed to run native extensions are:

.allowIO(IOAccess.ALL)
.allowCreateThread(true)
.allowNativeAccess(true)

Tooling Support for Python

Debugging

GraalPy provides the standard Python debugger pdb. Refer to the official PDB documentation for usage. The built-in breakpoint() function uses pdb by default.

GraalPy also comes with built-in support for graphical debugging through Chrome’s developer tools. To enable the debugger, pass the --inspect command-line option. You can inspect variables, set watch expressions, interactively evaluate code snippets, and so on.

  1. Run a Python script using the command-line option --inspect:
     graalpy --inspect my_script.py
    
  2. You should see output similar to:
     Debugger listening on ws://127.0.0.1:9229/VrhCaY7wR5tIqy2zLsdFr3f7ixY3QB6kVQ0S54_SOMo
     For help, see: https://www.graalvm.org/tools/chrome-debugger
     E.g. in Chrome open: devtools://devtools/bundled/js_app.html?ws=127.0.0.1:9229/VrhCaY7wR5tIqy2zLsdFr3f7ixY3QB6kVQ0S54_SOMo
    
  3. Open your Chrome browser and enter the URL provided. Now you can inspect the stack, variables, evaluate variables, and selected expressions in a tooltip, and so on. For example:

    Chrome Inspector

Profiling

GraalPy provides three main profiling capabilities: a CPU sampler, a CPU tracer, and a memory tracer. These are described below. (For details, use the graalpy --help:tools command.)

CPU Sampler

Use the --cpusampler command-line option to take a CPU sample. For example:

graalpy --cpusampler my_script.py

You should see output similar to:

CPU Sampler Output (Click to expand)
--------------------------------------------------------------------------------------------------------------------------------------------------------------
Sampling Histogram. Recorded 564 samples with period 10ms. Missed 235 samples.
  Self Time: Time spent on the top of the stack.
  Total Time: Time spent somewhere on the stack.
--------------------------------------------------------------------------------------------------------------------------------------------------------------
Thread[main,5,main]
 Name                                                                       ||             Total Time    ||              Self Time    || Location            
--------------------------------------------------------------------------------------------------------------------------------------------------------------
 parse_starttag                                                             ||             1090ms  19.3% ||              570ms  10.1% || <install-dir>/lib/python3.10/html/parser.py~300-347:11658-13539
 match                                                                      ||              190ms   3.4% ||              190ms   3.4% || <venv-dir>/lib/python3.10/site-packages/soupsieve/css_parser.py~320-323:9862-10026
 _replace_cdata_list_attribute_values                                       ||              190ms   3.4% ||              190ms   3.4% || <venv-dir>/lib/python3.10/site-packages/bs4/builder/__init__.py~295-331:11245-13031
 goahead                                                                    ||             1430ms  25.4% ||              150ms   2.7% || <install-dir>/lib/python3.10/html/parser.py~133-250:4711-9678
 check_for_whole_start_tag                                                  ||              130ms   2.3% ||              130ms   2.3% || <install-dir>/lib/python3.10/html/parser.py~351-382:13647-14758
 <module>                                                                   ||              800ms  14.2% ||              130ms   2.3% || <venv-dir>/lib/python3.10/site-packages/soupsieve/css_parser.py~1-1296:0-47061
 ...
--------------------------------------------------------------------------------------------------------------------------------------------------------------

CPU Tracer

Use the --cputracer --cputracer.TraceStatements command-line options to trace CPU usage. For example:

graalpy --cputracer --cputracer.TraceStatements my_script.py

You should see output similar to:

CPU Tracer Output (Click to Expand)
--------------------------------------------------------------------------------------------------------------------
Tracing Histogram. Counted a total of 1135 element executions.
  Total Count: Number of times the element was executed and percentage of total executions.
  Interpreted Count: Number of times the element was interpreted and percentage of total executions of this element.
  Compiled Count: Number of times the compiled element was executed and percentage of total executions of this element.
--------------------------------------------------------------------------------------------------------------------
 Name                                |          Total Count |    Interpreted Count |       Compiled Count | Location
--------------------------------------------------------------------------------------------------------------------
 get_newfunc_typeid                  |           110   9.7% |           110 100.0% |             0   0.0% | capi.c~596:0
 PyTruffle_PopulateType              |           110   9.7% |           110 100.0% |             0   0.0% | capi.c~721:0
 PyTruffle_AllocMemory               |            86   7.6% |            86 100.0% |             0   0.0% | obmalloc.c~77:0
 PyTruffle_AllocateType              |            66   5.8% |            66 100.0% |             0   0.0% | capi.c~874:0
 PyMem_RawMalloc                     |            66   5.8% |            66 100.0% |             0   0.0% | obmalloc.c~170:0
 initialize_type_structure           |            50   4.4% |            50 100.0% |             0   0.0% | capi.c~181:0
 _Py_TYPE                            |            45   4.0% |            45 100.0% |             0   0.0% | object_shared.c~55:0
 PyType_GetFlags                     |            41   3.6% |            41 100.0% |             0   0.0% | typeobject_shared.c~44:0
 get_tp_name                         |            37   3.3% |            37 100.0% |             0   0.0% | capi.c~507:0
 ...    
--------------------------------------------------------------------------------------------------------------------
Memory Tracer

Use the --memtracer --memtracer.TraceStatements command-line options to trace memory usage. For example:

graalpy --experimental-options --memtracer --memtracer.TraceStatements my_script.py

You should see output similar to:

Memory Tracer Output (Click to Expand)
----------------------------------------------------------------------------
 Location Histogram with Allocation Counts. Recorded a total of 565 allocations.
   Total Count: Number of allocations during the execution of this element.
   Self Count: Number of allocations in this element alone (excluding sub calls).
----------------------------------------------------------------------------
 Name                         |      Self Count |     Total Count | Location
----------------------------------------------------------------------------
 PyTruffle_PopulateType       |      440  77.9% |      440  77.9% | capi.c~721:0
 PyType_Ready                 |       61  10.8% |       68  12.0% | typeobject.c~463:0
 _PyObject_MakeTpCall         |       20   3.5% |       24   4.2% | object.c~155:0
 PyUnicode_FromString         |       11   1.9% |       11   1.9% | capi.c~2161:0
 PyErr_NewException           |       11   1.9% |       11   1.9% | capi.c~1537:0
 _PyUnicode_AsASCIIString     |        6   1.1% |        6   1.1% | capi.c~2281:0
 PyDict_New                   |        4   0.7% |        4   0.7% | capi.c~1505:0
 PyTuple_New                  |        4   0.7% |        4   0.7% | capi.c~2097:0
 PyUnicode_FromStringAndSize  |        3   0.5% |        3   0.5% | unicodeobject.c~171:0
 ...
----------------------------------------------------------------------------

Coverage

GraalPy provides its own implementation of the Coverage.py tool to measure code coverage of Python programs. Enable it using the --coverage command-line option, as shown below. (For details, use the graalpy --help:tools command.)

graalpy --coverage my_script.py

You should see output similar to:

CPU Sampler Output (Click to expand)
------------------------------------------------------------------------------------------------------------------------------------------------
Code coverage histogram.
  Shows what percent of each element was covered during execution
------------------------------------------------------------------------------------------------------------------------------------------------
 Path                                                                          |  Statements |    Lines |    Roots
------------------------------------------------------------------------------------------------------------------------------------------------
 <venv-dir>/lib/python3.10/site-packages/_distutils_hack/__init__.py           |       0.00% |    0.00% |    0.00%
 <venv-dir>/lib/python3.10/site-packages/bs4/__init__.py                       |      56.10% |   56.14% |   55.26%
 <venv-dir>/lib/python3.10/site-packages/bs4/builder/__init__.py               |      79.12% |   78.84% |   50.00%
 <venv-dir>/lib/python3.10/site-packages/bs4/builder/_html5lib.py              |       2.41% |    2.46% |    2.38%
 <venv-dir>/lib/python3.10/site-packages/bs4/builder/_htmlparser.py            |      69.08% |   68.67% |   83.33%
 <venv-dir>/lib/python3.10/site-packages/bs4/builder/_lxml.py                  |       3.72% |    3.78% |    4.00%
 <venv-dir>/lib/python3.10/site-packages/bs4/css.py                            |      32.73% |   31.48% |   15.38%
 <venv-dir>/lib/python3.10/site-packages/bs4/dammit.py                         |      65.46% |   65.29% |   24.14%
 <venv-dir>/lib/python3.10/site-packages/bs4/element.py                        |      44.15% |   43.13% |   31.08%
 <venv-dir>/lib/python3.10/site-packages/bs4/formatter.py                      |      73.49% |   74.36% |   66.67%
 <venv-dir>/lib/python3.10/site-packages/certifi/__init__.py                   |     100.00% |  100.00% |  100.00%
 <venv-dir>/lib/python3.10/site-packages/certifi/core.py                       |      33.33% |   33.33% |   25.00%
 <venv-dir>/lib/python3.10/site-packages/charset_normalizer/__init__.py        |     100.00% |  100.00% |  100.00%
 <venv-dir>/lib/python3.10/site-packages/charset_normalizer/api.py             |      11.87% |   11.94% |   16.67%
 <venv-dir>/lib/python3.10/site-packages/charset_normalizer/assets/__init__.py |     100.00% |  100.00% |  100.00%
 <venv-dir>/lib/python3.10/site-packages/charset_normalizer/cd.py              |      12.81% |   13.54% |    4.35%
 <venv-dir>/lib/python3.10/site-packages/charset_normalizer/constant.py        |     100.00% |  100.00% |  100.00%
 <venv-dir>/lib/python3.10/site-packages/charset_normalizer/legacy.py          |      25.00% |   25.00% |   50.00%
 <venv-dir>/lib/python3.10/site-packages/charset_normalizer/md.py              |      22.05% |   20.37% |   17.24%
 <venv-dir>/lib/python3.10/site-packages/charset_normalizer/models.py          |      38.46% |   38.50% |    9.30%
 <venv-dir>/lib/python3.10/site-packages/charset_normalizer/utils.py           |      26.79% |   26.89% |    3.33%
 <venv-dir>/lib/python3.10/site-packages/charset_normalizer/version.py         |     100.00% |  100.00% |  100.00%
 <venv-dir>/lib/python3.10/site-packages/idna/__init__.py                      |     100.00% |  100.00% |  100.00%
 <install-dir>/lib/python3.10/collections/abc.py                               |     100.00% |  100.00% |  100.00%
 <install-dir>/lib/python3.10/contextlib.py                                    |      40.80% |   37.99% |   31.71%
 <install-dir>/lib/python3.10/copy.py                                          |      36.36% |   36.41% |   21.43%
 <install-dir>/lib/python3.10/copyreg.py                                       |       3.20% |    3.20% |    7.69%
 <install-dir>/lib/python3.10/csv.py                                           |      25.17% |   23.91% |   25.00%
 <install-dir>/lib/python3.10/datetime.py                                      |      30.32% |   30.01% |   14.74%
 <install-dir>/lib/python3.10/email/__init__.py                                |      42.86% |   42.86% |   20.00%
 <install-dir>/lib/python3.10/email/_encoded_words.py                          |      35.11% |   34.44% |   14.29%
 <install-dir>/lib/python3.10/email/_parseaddr.py                              |      12.64% |   12.15% |   10.71%
 <install-dir>/lib/python3.10/email/_policybase.py                             |      55.22% |   54.69% |   39.29%
 <install-dir>/lib/python3.10/email/base64mime.py                              |      35.00% |   35.00% |   20.00%
 <install-dir>/lib/python3.10/typing.py                                        |      49.86% |   48.93% |   34.60%
 <install-dir>/lib/python3.10/urllib/__init__.py                               |     100.00% |  100.00% |  100.00%
 <install-dir>/lib/python3.10/warnings.py                                      |      21.29% |   20.77% |   25.00%
 <install-dir>/lib/python3.10/weakref.py                                       |      37.93% |   36.78% |   23.68%
 <install-dir>/lib/python3.10/zipfile.py                                       |      17.86% |   17.23% |   11.03%
 <src-dir>/my_script.py                                                        |     100.00% |  100.00% |  100.00%
------------------------------------------------------------------------------------------------------------------------------------------------

Trace

The standard Python trace module is also provided.

Note: This works in the same way as CPython. The programmatic API also works, with some limitations: it does not currently track calls, only line counts and called functions.

For example, running this command:

graalpy -m trace -c -s text_styler.py Welcome to GraalPy!

You should see output similar to:

CPU Tracer Output (Click to Expand)
_       __     __                             __     
| |     / /__  / /________  ____ ___  ___     / /_____
| | /| / / _ \/ / ___/ __ \/ __ `__ \/ _ \   / __/ __ \
| |/ |/ /  __/ / /__/ /_/ / / / / / /  __/  / /_/ /_/ /
|__/|__/\___/_/\___/\____/_/ /_/ /_/\___/   \__/\____/
                                                        
   ______                 ______        __
  / ____/________ _____ _/ / __ \__  __/ /
 / / __/ ___/ __ `/ __ `/ / /_/ / / / / /
/ /_/ / /  / /_/ / /_/ / / ____/ /_/ /_/ 
\____/_/   \__,_/\__,_/_/_/    \__, (_)  
                              /____/     
 
lines   cov%   module   (path)
    9   100%   __about__   (<venv-dir>/lib/python3.10/site-packages/pkg_resources/_vendor/packaging/__about__.py)
   51   100%   __future__   (<install-dir>/lib/python3.10/__future__.py)
    1   100%   __init__   (<venv-dir>/lib/python3.10/site-packages/pyfiglet/fonts/__init__.py)
   27   100%   _adapters   (<install-dir>/lib/python3.10/importlib/_adapters.py)
   25   100%   _common   (<install-dir>/lib/python3.10/importlib/_common.py)
   44   100%   _manylinux   (<venv-dir>/lib/python3.10/site-packages/pkg_resources/_vendor/packaging/_manylinux.py)
   20   100%   _musllinux   (<venv-dir>/lib/python3.10/site-packages/pkg_resources/_vendor/packaging/_musllinux.py)
   66   100%   _osx_support   (<install-dir>/lib/python3.10/_osx_support.py)
   43   100%   _parseaddr   (<install-dir>/lib/python3.10/email/_parseaddr.py)
   62   100%   _policybase   (<install-dir>/lib/python3.10/email/_policybase.py)
   20   100%   _structures   (<venv-dir>/lib/python3.10/site-packages/pkg_resources/_vendor/packaging/_structures.py)
  105   100%   abc   (<install-dir>/lib/python3.10/importlib/abc.py)
   18   100%   actions   (<venv-dir>/lib/python3.10/site-packages/pkg_resources/_vendor/pyparsing/actions.py)
   41   100%   appdirs   (<venv-dir>/lib/python3.10/site-packages/pkg_resources/_vendor/appdirs.py)
   59   100%   base64   (<install-dir>/lib/python3.10/base64.py)
   14   100%   base64mime   (<install-dir>/lib/python3.10/email/base64mime.py)
   11   100%   bisect   (<install-dir>/lib/python3.10/bisect.py)
  124   100%   calendar   (<install-dir>/lib/python3.10/calendar.py)
   94   100%   charset   (<install-dir>/lib/python3.10/email/charset.py)
  122   100%   common   (<venv-dir>/lib/python3.10/site-packages/pkg_resources/_vendor/pyparsing/common.py)
   40   100%   context   (<venv-dir>/lib/python3.10/site-packages/pkg_resources/_vendor/jaraco/context.py)
    3   100%   contextlib   (<install-dir>/lib/python3.10/contextlib.py)
   91   100%   copy   (<install-dir>/lib/python3.10/copy.py)
 1497   100%   core   (<venv-dir>/lib/python3.10/site-packages/pkg_resources/_vendor/pyparsing/core.py)
  108   100%   dataclasses   (<install-dir>/lib/python3.10/dataclasses.py)
   31   100%   datetime   (<install-dir>/lib/python3.10/datetime.py)
    9   100%   encoders   (<install-dir>/lib/python3.10/email/encoders.py)
 2493   100%   entities   (<install-dir>/lib/python3.10/html/entities.py)
   58   100%   errors   (<install-dir>/lib/python3.10/email/errors.py)
   49   100%   exceptions   (<venv-dir>/lib/python3.10/site-packages/pkg_resources/_vendor/pyparsing/exceptions.py)
    5   100%   expat   (<install-dir>/lib/python3.10/xml/parsers/expat.py)
   41   100%   feedparser   (<install-dir>/lib/python3.10/email/feedparser.py)
   45   100%   functools   (<venv-dir>/lib/python3.10/site-packages/pkg_resources/_vendor/jaraco/functools.py)
   69   100%   gettext   (<install-dir>/lib/python3.10/gettext.py)
   56   100%   header   (<install-dir>/lib/python3.10/email/header.py)
  162   100%   helpers   (<venv-dir>/lib/python3.10/site-packages/pkg_resources/_vendor/pyparsing/helpers.py)
    1   100%   inspect   (<install-dir>/lib/python3.10/inspect.py)
   47   100%   linecache   (<install-dir>/lib/python3.10/linecache.py)
   95   100%   markers   (<venv-dir>/lib/python3.10/site-packages/pkg_resources/_vendor/packaging/markers.py)
  192   100%   more   (<venv-dir>/lib/python3.10/site-packages/pkg_resources/_vendor/more_itertools/more.py)
  204   100%   optparse   (<install-dir>/lib/python3.10/optparse.py)
   14   100%   os   (<install-dir>/lib/python3.10/os.py)
  167   100%   parse   (<install-dir>/lib/python3.10/urllib/parse.py)
   19   100%   parser   (<install-dir>/lib/python3.10/email/parser.py)
  242   100%   pathlib   (<install-dir>/lib/python3.10/pathlib.py)
   66   100%   pkgutil   (<install-dir>/lib/python3.10/pkgutil.py)
  137   100%   platform   (<install-dir>/lib/python3.10/platform.py)
  102   100%   plistlib   (<install-dir>/lib/python3.10/plistlib.py)
   79   100%   pprint   (<install-dir>/lib/python3.10/pprint.py)
   54   100%   queue   (<install-dir>/lib/python3.10/queue.py)
   21   100%   quopri   (<install-dir>/lib/python3.10/quopri.py)
   32   100%   quoprimime   (<install-dir>/lib/python3.10/email/quoprimime.py)
  101   100%   random   (<install-dir>/lib/python3.10/random.py)
   43   100%   recipes   (<venv-dir>/lib/python3.10/site-packages/pkg_resources/_vendor/more_itertools/recipes.py)
   51   100%   requirements   (<venv-dir>/lib/python3.10/site-packages/pkg_resources/_vendor/packaging/requirements.py)
   46   100%   resources   (<install-dir>/lib/python3.10/importlib/resources.py)
  155   100%   results   (<venv-dir>/lib/python3.10/site-packages/pkg_resources/_vendor/pyparsing/results.py)
   79   100%   selectors   (<install-dir>/lib/python3.10/selectors.py)
   30   100%   signal   (<install-dir>/lib/python3.10/signal.py)
   94   100%   socket   (<install-dir>/lib/python3.10/socket.py)
  143   100%   specifiers   (<venv-dir>/lib/python3.10/site-packages/pkg_resources/_vendor/packaging/specifiers.py)
   50   100%   string   (<install-dir>/lib/python3.10/string.py)
  118   100%   subprocess   (<install-dir>/lib/python3.10/subprocess.py)
   96   100%   sysconfig   (<install-dir>/lib/python3.10/sysconfig.py)
   67   100%   tags   (<venv-dir>/lib/python3.10/site-packages/pkg_resources/_vendor/packaging/tags.py)
  119   100%   tempfile   (<install-dir>/lib/python3.10/tempfile.py)
   35   100%   testing   (<venv-dir>/lib/python3.10/site-packages/pkg_resources/_vendor/pyparsing/testing.py)
    7   100%   text_styler   (<src-dir>/text_styler.py)
   51   100%   textwrap   (<install-dir>/lib/python3.10/textwrap.py)
    2   100%   threading   (<install-dir>/lib/python3.10/threading.py)
   32   100%   tokenize   (<install-dir>/lib/python3.10/tokenize.py)
   43   100%   traceback   (<install-dir>/lib/python3.10/traceback.py)
  703   100%   typing   (<install-dir>/lib/python3.10/typing.py)
  238   100%   unicode   (<venv-dir>/lib/python3.10/site-packages/pkg_resources/_vendor/pyparsing/unicode.py)
   76   100%   util   (<venv-dir>/lib/python3.10/site-packages/pkg_resources/_vendor/pyparsing/util.py)
   20   100%   utils   (<venv-dir>/lib/python3.10/site-packages/pkg_resources/_vendor/packaging/utils.py)
    1   100%   version   (<venv-dir>/lib/python3.10/site-packages/pyfiglet/version.py)
   16   100%   warnings   (<install-dir>/lib/python3.10/warnings.py)
  127   100%   weakref   (<install-dir>/lib/python3.10/weakref.py)
  432   100%   zipfile   (<install-dir>/lib/python3.10/zipfile.py)

Using PyCharm with GraalPy

You can use GraalPy in PyCharm to create a virtual environment, install packages, and develop and run your Python application.

  1. Install graalpy. (For more information, see Installing GraalPy.)

  2. Install PyCharm. (For more information, see Install PyCharm.)

  3. Create, or open, a Python project. (For more information, see Create a Python project, or Open, reopen, and close projects, respectively.)

  4. Create a new venv virtual environment for your Python project. (For more information, see Create a virtualenv environment.)

  5. Install packages by following the PyCharm instructions. (For more information, see Install, uninstall, and upgrade packages.)

  6. Use the PyCharm menu items to run your Python application. Alternatively, use the terminal emulator to run the graalpy command.

Using Visual Studio Code with GraalPy

You can use GraalPy in Visual Studio (VS) Code to create a virtual environment, install packages, and develop and run your Python application.

  1. Install graalpy. (For more information, see Installing GraalPy.)

  2. Install VS Code and the Python Extension, following the instructions here: Install Visual Studio Code and the Python Extension.

  3. Create, or open, a Python file.

  4. Create a new venv virtual environment for your Python project. (For more information, see Creating environments.)

  5. Install packages by following the VS Code instructions. (For more information, see Install and use packages.)

  6. Use the VS Code menu items to run your Python application. (For more information, see Run Hello World.) Alternatively, use a VS Code terminal emulator to run the graalpy command.

  7. You cannot use VS Code to debug your Python application. Instead, open a VS Code terminal emulator and follow these instructions: Debugging a Python Application.