Documentation for Python Developers
You want to use GraalPy instead of the standard Python from python.org.
Install GraalPy on your machine and use it like any Python interpreter. You get better performance, the ability to compile to native binaries, and access to the GraalVM ecosystem.
Version Compatibility
The following table shows which Python versions are supported by each GraalPy release:
| GraalPy Version | Python Version | GraalVM Platform |
|---|---|---|
| 25.x | Python 3.12.8 | Oracle GraalVM 25.x, GraalVM Community Edition 25.x |
| 23.x | Python 3.10.8 | Oracle GraalVM for JDK 21.x, Oracle GraalVM for JDK 17.x |
| 22.x | Python 3.8.5 | GraalVM Enterprise Edition 21.3.x |
Platform Support
GraalPy is mostly written in Java and Python, but the Python package ecosystem is rich in native packages that need platform specific support via native libraries that expose platform-specific APIs. The main operating system is Oracle Linux, the CPU architectures are AMD64 and ARM, and the primary JDK is Oracle GraalVM. Linux is recommended for getting started with GraalPy. Windows and macOS with GraalVM JDK are less well tested, and outside of those combinations only basic test coverage is provided. As macOS and other platforms are not prioritized, some GraalPy features may not work on these platforms. See Test Tiers for a detailed breakdown.
These guides cover everything you need to know:
Using GraalPy as a Standalone Python Runtime
GraalPy can be used as a standalone Python runtime, providing a drop-in replacement for CPython. This guide covers choosing a distribution, installation, package management, basic usage, and deployment options for standalone GraalPy applications.
Choosing a GraalPy Distribution
GraalPy is available in multiple distributions:
Distribution Options
- GraalPy built on Oracle GraalVM provides the best experience with additional optimizations, significantly faster performance, and better memory efficiency. It is licensed under the GraalVM Free Terms and Conditions (GFTC), 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.
Runtime Options
Two language runtime options are available for both distributions:
- Native (recommended for standalone use)
- GraalPy is compiled ahead-of-time to a native executable
- You do not need a JVM to run GraalPy and it is compact in size
- Faster startup time
- Faster time to reach peak performance
- JVM
- You can easily exploit Java interoperability
- Peak performance may be higher than the native option
- Slower startup time
Distribution Identification
The GraalPy runtimes are identified using the pattern graalpy(-community)(-jvm)-<version>-<os>-<arch>:
| Distribution | Native | JVM |
|---|---|---|
| Oracle | graalpy-<version>-<os>-<arch> |
graalpy-jvm-<version>-<os>-<arch> |
| Community | graalpy-community-<version>-<os>-<arch> |
graalpy-community-jvm-<version>-<os>-<arch> |
Runtime 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 |
GraalPy Capabilities
GraalPy provides a Python 3.12 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 provides the following capabilities:
- CPython-compatible distribution for testing Python code on GraalPy.
- A single native binary packaging mode for Python applications.
- Access to GraalVM language ecosystems and tools.
Installation
Note: There may be a delay between GraalPy release and its availability on Pyenv.
Linux (Recommended Platform)
The easiest way to install GraalPy on Linux is to use Pyenv (the Python version manager):
# Update pyenv to include latest GraalPy versions (if needed)
pyenv update
# Install GraalPy 25.0.2
pyenv install graalpy-25.0.2
# Use GraalPy for the current shell session
pyenv shell graalpy-25.0.2
Manual Installation (Linux)
-
Download the appropriate binary from GitHub releases:
- AMD64:
graalpy-XX.Y.Z-linux-amd64.tar.gz - ARM64:
graalpy-XX.Y.Z-linux-aarch64.tar.gz
- AMD64:
-
Extract and add it to your
PATHenvironment variable:tar -xzf graalpy-25.0.2-linux-amd64.tar.gz export PATH="$PWD/graalpy-25.0.2-linux-amd64/bin:$PATH"
macOS
Using Pyenv (recommended):
# Install GraalPy 25.0.2
pyenv install graalpy-25.0.2
# Use GraalPy for the current shell session
pyenv shell graalpy-25.0.2
Manual Installation (macOS)
-
Download the binary from GitHub releases.
-
Remove quarantine attribute:
sudo xattr -r -d com.apple.quarantine /path/to/graalpy # For example: sudo xattr -r -d com.apple.quarantine ~/.pyenv/versions/graalpy-25.0.2 -
Extract and add it to your
PATHenvironment variable:tar -xzf graalpy-25.0.2-macos-aarch64.tar.gz export PATH="$PWD/graalpy-25.0.2-macos-aarch64/bin:$PATH"
Windows
Warning: The Windows distribution has more limitations than Linux or macOS. Not all features and packages may be available.
-
Download the binary from GitHub releases.
-
Extract and add it to your
PATHenvironment variable:# Extract the file and update your PATH environment variable # to include the graalpy-XX.Y.Z-windows-amd64/bin directory tar -xzf graalpy-25.0.2-windows-amd64.tar.gz $env:PATH = "$PWD\graalpy-25.0.2-windows-amd64\bin;$env:PATH"
Known Windows Limitations
- JLine treats Windows as a dumb terminal (no autocomplete, limited REPL editing)
- Interactive
help()in REPL doesn’t work - Virtual environment issues:
graalpy.cmdandgraalpy.exeare broken insidevenvpip.execannot be used directly- Use
myvenv/Scripts/python.exe -m pip --no-cache-dir install <pkg> - Only pure Python binary wheels supported
- PowerShell works better than CMD
Using Virtual Environments
The recommended way to use GraalPy is with venv virtual environments:
Creating a Virtual Environment
# Create a virtual environment
graalpy -m venv ~/.virtualenvs/graalpy-25.0.2
# Activate the environment
source ~/.virtualenvs/graalpy-25.0.2/bin/activate
Installing Packages
Once in a virtual environment, you can use pip to install packages:
# Install a package
pip install requests
# Install with requirements file
pip install -r requirements.txt
Note: GraalPy’s
pipimplementation may choose different package versions to ensure better compatibility.
To deactivate the virtual environment:
# Return to your normal shell environment
deactivate
Running Python Code
Once installed, you can use GraalPy like any other Python interpreter:
# Interactive REPL
graalpy
# Run a Python script
graalpy myscript.py
# Run a module
graalpy -m mymodule
# Execute inline code
graalpy -c "print('Hello from GraalPy!')"
Platform Support
Linux is the recommended platform for GraalPy. The main testing focus is:
- Operating System: Oracle Linux
- CPU Architectures: AMD64 and ARM64
- Primary JDK: Oracle GraalVM
Windows and macOS with GraalVM JDK have less comprehensive testing. Some GraalPy features may not work optimally on these platforms.
See Test Tiers for a detailed breakdown of platform support.
Python Standalone Applications
GraalPy enables you to package your Python applications or libraries into native executables or JAR files with no external dependencies. This means users can run your application without installing Python or any packages.
GraalPy uses the Truffle framework to bundle your Python code, dependencies, and the Python runtime into standalone executables. Truffle’s filesystem virtualization allows everything to work from a single file, though packages with native C extensions may have limitations.
GraalPy includes a module named standalone to create a Python binary for Linux, macOS, and Windows.
The module bundles all your application’s resources into a single file.
Prerequisite: GraalPy 23.1.0 or later. Download here or verify your version with
graalpy --version.
Running Python Standalone Applications
To create an native executable from a Python file with its dependencies, use this command:
graalpy -m standalone native \
--module my_script.py \
--output my_binary \
--venv my_env
Where:
--module my_script.pystates the main Python file that contains your application’s entry point--output my_binarystates the name for your standalone executable (no file extension needed)--venv my_envstates the path to virtual environment with installed packages (you can omit this if there are no dependencies)
This 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
Standalone executables do not protect your source code. Your Python code becomes bytecode, and bytecode can be easily decompiled back to readable Python code.
Native Extensions Support
CPython provides a native extensions API for writing Python extensions in C/C++.
GraalPy provides experimental support for this API, which allows many packages like NumPy and PyTorch to work well for many use cases.
The support extends only to the API, not the binary interface (ABI), so extensions built for CPython are not binary compatible with GraalPy.
Packages that use the native API must be built and installed with GraalPy, and the prebuilt wheels for CPython from pypi.org cannot be used.
For best results, it is crucial that you only use the pip command that comes preinstalled in GraalPy virtual environments to install packages.
The version of pip shipped with GraalPy applies additional patches to packages upon installation to fix known compatibility issues and it is preconfigured to use an additional repository from graalvm.org where we publish a selection of prebuilt wheels for GraalPy.
Please do not update pip or use alternative tools such as uv.
Interoperability
GraalPy can interoperate with Java and other Graal languages that are implemented on the Truffle framework. This means that you can use other languages’ objects and functions directly from your Python scripts. This interoperability works in both directions. Python can call other languages, and other languages can call Python code.
Note for Python users: The Java interoperability sections below require a JVM-based GraalPy runtime with the relevant Java classes on the classpath. The later
polyglotsections describe the more general cross-language APIs for working with other Graal languages.
Call Java from Python
Java is the host language of the JVM and runs the GraalPy interpreter itself. This means you can seamlessly access any Java class available in your classpath directly from Python.
Basic Java access
Import the java module to access Java classes and methods:
import java
BigInteger = java.type("java.math.BigInteger")
myBigInt = BigInteger.valueOf(42)
# Call Java methods directly
myBigInt.shiftLeft(128) # returns a <JavaObject[java.math.BigInteger] at ...>
# Java method names that are Python keywords 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
Importing Java packages
You can import packages from the java namespace using 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]
Java module methods
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 is a Java host language object wrapped using interop. |
is_symbol(obj) |
Returns True if obj is a Java host symbol, representing the constructor and static members of a Java class, as obtained by java.type. |
Here’s how to use these methods in practice:
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 the Polyglot Programming and Embed Languages documentation for more information about interoperability with other programming languages.
Call Foreign Objects from Python
When you use foreign objects in Python, GraalPy automatically makes them behave like their Python equivalents.
For example, a Java ArrayList acts like a Python list, and a Java HashMap acts like a Python dict:
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 you can use Python methods on foreign objects:
from java.util import ArrayList, HashMap
# ArrayList behaves like a Python list so you can use Python methods
l = ArrayList()
l.append(1) # Python list method - l: [1]
l.extend([2, 3]) # Python list method - l: [1, 2, 3]
l.add(4) # Java ArrayList method still works - l: [1, 2, 3, 4]
l[1:3] # Python slicing works - returns [2, 3]
l.pop(1) # Python list method - returns 2, l: [1, 3, 4]
l.insert(1, 2) # Python list method - l: [1, 2, 3, 4]
l == [1, 2, 3, 4] # Python comparison works - True
# HashMap behaves like a Python dict so you can use Python methods
h = HashMap()
h[1] = 2 # Python dict syntax - h: {1: 2}
h.setdefault(3, 4) # Python dict method - h: {1: 2, 3: 4}
h |= {3: 6} # Python dict operator - h: {1: 2, 3: 6}
h == {1: 2, 3: 6} # Python comparison works - True
When a method is defined both in Python and on the foreign object, the Python’s method takes precedence.
To call the foreign method explicitly, 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) # Calls Python list.remove()
assert l == [5, 6]
super(list, l).remove(0) # Calls Java's ArrayList.remove()
assert l == [6]
See the Interop Types to Python section for more interop traits and how they map to Python types.
Passing Binary Data Between Java and Python
Passing binary data between Java and Python deserves attention:
- Java code typically uses
byte[]orjava.nio.ByteBuffer - Python code typically uses
bytes,bytearray,memoryview, or file-like APIs such asio.BytesIO
Java to Python
Raw Java byte[] are accessible as list-like objects in Python.
Only integral values that fit into a signed byte can be read from or written to such objects.
Python, on the other hand, usually exposes binary data as unsigned byte values.
To achieve the equivalent of a “re-interpreting cast”, Java byte arrays should be passed to Python using ByteBuffer.wrap(byte[]):
import java.nio.ByteBuffer;
byte[] data = ...;
ByteBuffer buffer = ByteBuffer.wrap(data); // does not copy
context.getBindings("python").putMember("java_buffer", buffer);
Python can then use the object through buffer-oriented binary data APIs:
memoryview(java_buffer) # does not copy
bytes(java_buffer) # copies into an immutable Python-owned buffer
bytearray(java_buffer) # copies into a mutable Python-owned buffer
io.BytesIO(java_buffer) # copies into BytesIO's internal storage
Python to Java
Python bytes and other bytes-like objects can be interpreted like any java.lang.List.
Because Python bytes are usually unsigned, however, they cannot simply be converted via Value#as(byte[].class) if any values are larger than 127.
The Graal polyglot sdk provides org.graalvm.polyglot.io.ByteSequence as a target type to deal with this issue explicitly.
import org.graalvm.polyglot.Value;
import org.graalvm.polyglot.io.ByteSequence;
Value result = context.eval("python", "b'hello'");
ByteSequence seq = result.as(ByteSequence.class); // does not copy
ByteSequence keeps the data as a Python-owned byte sequence without immediately copying.
It provides a toByteArray() method that deals with re-interpreting unsigned Python bytes as signed Java bytes.
import java.nio.charset.StandardCharsets;
import org.graalvm.polyglot.io.ByteSequence;
ByteSequence seq = result.as(ByteSequence.class);
byte[] bytes = seq.toByteArray(); // copies into Java byte[]
String s = new String(bytes, StandardCharsets.UTF_8);
Call Other Languages from Python
The polyglot API allows non-JVM specific interactions with other languages from Python scripts. This includes all interactions with dynamic languages supported via the Truffle framework, including JavaScript and Ruby.
Multithreading
GraalPy implements the Python global interpreter lock (GIL), which prevents any two threads from executing Python code at the same instant. When methods in other languages are called from Python, no Python code is running while the other language executes. To give other Python threads a chance to run at this point in time, GraalPy releases the GIL around such foreign method calls by default. This (un)locking of the GIL can impact performance negatively if the foreign code runs only for a very short while, however, so this behavior can be controlled per dynamic scope using Python context managers.
class JavaFile(io.FileIO):
def write(self, obj):
# Unlock the GIL when doing IO in Java
with polyglot.gil_locked_during_interop(False):
self.java_file.write(obj)
with polyglot.gil_locked_during_interop(True):
# Keep the GIL locked when accessing Java maps, because those method calls will return very quickly
some_file.write(java_map.get(key1) + java_map.get(key2))
Beware of always keeping the GIL locked. That may lead to deadlocks if the foreign language attempts to wait on another thread of execution, and that thread tries to call back into Python.
Installing other dynamic languages
To use other languages, like JavaScript, you need to add their Maven dependencies to your project.
If you’re using Maven with GraalPy, add the JavaScript dependency to your pom.xml file:
<dependency>
<groupId>org.graalvm.polyglot</groupId>
<artifactId>js</artifactId>
<version>25.0.2</version>
</dependency>
Examples
Here are practical examples of using the polyglot API to work with JavaScript from Python:
- Import the
polyglotmodule to interact with other languages:import polyglot - Evaluate inlined code in another language:
assert polyglot.eval(string="1 + 1", language="js") == 2 - 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") - Import a global 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
invokeand 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
- Accessing attributes reads from the polyglot members namespace:
- 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.
Export Python Objects
Use the polyglot module to expose Python objects to JVM languages and other Graal languages (languages implemented on the Truffle framework).
This allows other languages to call your Python code directly.
- You can export a Python object so other languages can access it:
import ssl polyglot.export_value(value=ssl, name="python_ssl")Then use it, for example, from JavaScript code:
Polyglot.import('python_ssl').get_server_certificate(["oracle.com", 443]) - 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.*; import org.graalvm.python.embedding.GraalPyResources; class Main { public static void main(String[] args) { try (var context = GraalPyResources.createContext()) { 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!"); } } }
Types Mapping
The interop protocol defines different types and traits that determine foreign objects behavior and restrictions when used in 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 |
|---|---|---|
array |
ForeignList, list |
An array behaves like a Python list. |
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). |
buffer |
ForeignObject | Buffers work like Python buffer objects (such as those used with memoryview) to avoid copying data. |
exception |
ForeignException, BaseException |
An exception can be caught in a generic except clause. |
executable |
ForeignExecutable | An executable object can be executed as a function, but never with keyword arguments. |
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”. |
instantiable |
ForeignInstantiable | An instantiable object can be called just like a Python type, but never with keyword arguments. |
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. |
members |
ForeignObject | Objects with members can be accessed using Python dot notation (.) or getattr(). |
MetaObject |
ForeignAbstractClass | Meta objects can be used in subtype and isinstance checks. |
null |
ForeignNone, NoneType |
null behaves like Python None. All interop null values (including JavaScript undefined and null) are treated as None in Python. |
number |
ForeignNumber | number behaves like Python numbers (int and float). Foreign ranges are imported in some places such as typed arrays. |
string |
ForeignString, str |
Behaves in the same way as a Python string. |
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 Pythonintobjects. - When foreign numbers are represented as Java primitives
float,double, they are considered Pythonfloatobjects. - When foreign booleans are represented as Java primitives
boolean, they are considered Pythonboolobjects.
Python to Interop Types
The following table shows how Python objects are converted to interop types when passed to other languages:
| Interop Type | Python Interpretation |
|---|---|
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.) |
boolean |
Only subtypes of Python bool. Note that in contrast to Python semantics, Python bool is never also an interop number. |
exception |
Any Python BaseException subtype. |
executable |
Any Python object with a __call__ method. |
hash |
Only subtypes of dict. |
instantiable |
Any Python type. |
iterable |
Any Python object that has __iter__ or __getitem__ methods. |
iterator |
Any Python object with a __next__ method. |
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. |
MetaObject |
Any Python type. |
null |
Only None. |
number |
Only subtypes of int and float. |
string |
Only subtypes of str. |
Interoperability Extension API
You can extend the interoperability protocol directly from Python through a simple API defined in the polyglot module.
This API lets you define interoperability behavior for custom or user-defined types that are not automatically supported.
This is particularly useful for external types which are not compatible by default with the interop protocol.
For example, numpy numeric types (for example, numpy.int32) which are not supported by default by the interop protocol need special handling to work properly with other languages.
The polyglot module provides these functions for customizing interop behavior:
| Function | Description |
|---|---|
register_interop_behavior |
Takes the receiver type as the first argument. The remaining keyword arguments correspond to the respective interop messages. Not all interop messages are supported. |
get_registered_interop_behavior |
Takes the receiver type as the first argument. Returns the list of extended interop messages for the given type. |
@interop_behavior |
Class decorator that takes the receiver type as the 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 an optional argument (default: False). Every instance of the foreign class is then treated as an instance of the given python class. |
@interop_type |
Class decorator that takes the foreign class and optionally allow_method_overwrites as arguments. The instances of the foreign class will be treated as an instance of the annotated python class. |
Interop behavior usage example
You can use the register_interop_behavior API to add custom interop behavior to existing types:
For example, to make numpy.int32 work properly with other languages:
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,
)
Alternatively, you can use the @interop_behavior decorator when you need to define multiple behaviors for a type.
With this decorator, you define interop behaviors using static methods in a decorated class.
The static method names must match the keyword argument names used by register_interop_behavior.
The following example uses the decorator approach for numpy.float64:
from polyglot import interop_behavior
import numpy
@interop_behavior(numpy.float64)
class Float64InteropBehaviorSupplier:
@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;
import org.graalvm.python.embedding.GraalPyResources;
class Main {
public static void main(String[] args) {
try (var context = GraalPyResources.createContext()) {
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 usage example
The register_interop_type API allows the usage of python classes for foreign objects.
When you register a Python class for a foreign type, instances of that foreign object will no longer have the default polyglot.ForeignObject or polyglot.Foreign* class.
Instead, GraalPy creates a new generated class that inherits from both your Python class and polyglot.ForeignObject.
This lets you add Python methods to foreign objects, and map foreign functionality to Python’s magic methods or more idiomatic Python patterns.
This is a simple Java class to customize:
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;
}
}
The following snippet sets up the Java environment and makes the object available to Python:
import org.example.MyJavaClass;
import org.graalvm.python.embedding.GraalPyResources;
class Main {
public static void main(String[] args) {
MyJavaClass myJavaObject = new MyJavaClass(42, 17);
try (var context = GraalPyResources.createContext()) {
// 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"));
}
}
}
This snippet states how to customize the Java object’s behavior using Python classes:
# 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)
For simpler cases, you can use the @interop_type decorator:
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
Most interop messages are supported by the interop behavior extension API.
The naming convention for register_interop_behavior keyword arguments uses snake_case, so the interop fitsInLong message becomes fits_in_long.
Each message can be extended with either a pure Python function (no default keyword arguments, free vars, or cell vars allowed) or a boolean constant.
The following table describes the supported interop messages:
| Message | Extension argument name | Expected return type |
|---|---|---|
isBoolean |
is_boolean |
bool |
isDate |
is_date |
bool |
isDuration |
is_duration |
bool |
isExecutable |
is_executable |
bool |
isIterator |
is_iterator |
bool |
isNumber |
is_number |
bool |
isString |
is_string |
bool |
isTime |
is_time |
bool |
isTimeZone |
is_time_zone |
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 |
tuple: (year: int, month: int, day: int) |
asDouble |
as_double |
float |
asDuration |
as_duration |
tuple: (seconds: int, nano_adjustment: int) |
asFloat |
as_float |
float |
asInt |
as_int |
int |
asLong |
as_long |
int |
asShort |
as_short |
int |
asString |
as_string |
str |
asTime |
as_time |
tuple: (hour: int, minute: int, second: int, microsecond: int) |
asTimeZone |
as_time_zone |
str (timezone name) 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 |
None |
writeArrayElement |
write_array_element |
None |
hasIterator |
has_iterator |
bool |
hasIteratorNextElement |
has_iterator_next_element |
bool |
getIterator |
get_iterator |
Python iterator |
getIteratorNextElement |
get_iterator_next_element |
object |
hasHashEntries |
has_hash_entries |
bool |
getHashEntriesIterator |
get_hash_entries_iterator |
Python iterator |
getHashKeysIterator |
get_hash_keys_iterator |
Python iterator |
getHashSize |
get_hash_size |
int |
getHashValuesIterator |
get_hash_values_iterator |
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 |
None |
removeHashEntry |
remove_hash_entry |
None |
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 thePYTHONDONTWRITEBYTECODEenvironment 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_bytecodeof thesysbuilt-in module at runtime to change the behavior for subsequent imports. - The GraalPy launcher reads the
PYTHONPYCACHEPREFIXenvironment 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_prefixof thesysmodule 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-BorPYTHONDONTWRITEBYTECODEpython.PyCachePrefix- equivalent toPYTHONPYCACHEPREFIX
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).
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.
-
Run a Python script using the command-line option
--inspect:graalpy --inspect my_script.py -
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 -
Open Chrome browser and enter the provided URL.
You can now:
- Inspect the call stack and variables
- Set breakpoints and step through code
- Evaluate expressions in the console
- View variable values in tooltips
For example:

Profiling
GraalPy provides three main profiling capabilities: CPU sampling, CPU tracing, and memory tracing. Each tool is described below.
For complete options, use: graalpy --help:tools
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:
Coverage 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 but with limitations: it currently tracks only line counts and called functions, not calls themselves.
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.
-
Install
graalpy. (For more information, see Installing GraalPy.) -
Install PyCharm. (For more information, see Install PyCharm.)
-
Create, or open, a Python project. (For more information, see Create a Python project, or Open, reopen, and close projects, respectively.)
- Create a new virtual environment using GraalPy:
- In PyCharm settings, set the interpreter to your GraalPy installation
- Create a new
venvvirtual environment (For more information, see Create a virtualenv environment.)
-
Install packages using pip or PyCharm’s package manager. (For more information, see Install, uninstall, and upgrade packages.)
- Use the PyCharm menu items to run your Python application.
Alternatively, use the terminal emulator to run the
graalpycommand.
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.
-
Install
graalpy. (For more information, see Installing GraalPy.) -
Install VS Code and the Python Extension, following the instructions here: Install Visual Studio Code and the Python Extension.
-
Create, or open, a Python file.
-
Create a new venv virtual environment for your Python project. (For more information, see Creating environments.)
-
Install packages by following the VS Code instructions. (For more information, see Install and use packages.)
-
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
graalpycommand. -
You can use VS Code’s built-in Python debugger to set breakpoints, inspect variables, and step through your code just like with standard Python.
Detailed Test Tier Breakdown
GraalPy organizes platform testing into tiers that indicate the level of testing rigor and support you can expect for different platform configurations. This tiering system helps you understand:
- How thoroughly your platform is tested
- What level of stability to expect
- Which features are fully supported vs. experimental
Platforms are identified using the target tuple format: [CPU architecture]-[Operating System]-[libc]-[JDK]-[Java version]. JDK names follow SDKMAN! conventions, and “graal” refers to both Oracle GraalVM and GraalVM Community Edition (including Native Image).
Important: GraalPy test tiers are similar to CPython Platform Support Tiers, but do not constitute or imply any commitment to support.
Pure Python code runs reliably on GraalPy with recent JDKs when JIT compilation is disabled. However, advanced features like native extensions, platform-specific APIs, and JIT compilation have varying support depending on your platform tier.
Tier 1
- Stability: CI failures block releases. Changes which would break the main or release branches are not allowed to be merged; any breakage should be fixed or reverted immediately.
- Responsibility: All core developers are responsible to keep main, and thus these platforms, working.
- Coverage: Platform-specific Python APIs and Python C extensions are fully tested.
| Platform | Notes |
|---|---|
| amd64-linux-glibc-graal-latest | Oracle Linux 8 or similar. |
| aarch64-linux-glibc-graal-latest | Oracle Linux 8 or similar. |
Tier 2
- Stability: CI failures block releases. Changes which would break the main or release branches are not allowed to be merged; any breakage should be fixed or tests marked as skipped.
- Test Coverage: Circa 10% of tests running on Tier 1 platforms may be skipped on Tier 2 platforms.
- Feature Support: Platform-specific Python APIs are fully tested; Python C extensions may have more issues than on Tier 1 platforms.
| Platform | Notes |
|---|---|
| aarch64-macos-darwin-graal-latest | macOS on M-series CPUs. |
Tier 3
- Stability: CI failures block releases. Changes which would break the main or release branches are not allowed to be merged; any breakage should be fixed or tests marked as skipped.
- Test Coverage: Circa 25% of tests running on Tier 1 platforms may be skipped on Tier 3.
- Feature Support: Tests for platform-specific Python APIs and Python C extension are run, but not prioritized.
| Platform | Notes |
|---|---|
| amd64-windows-msvc-graal-latest | Windows 11, Windows Server 2025, or newer. |
| amd64-linux-glibc-oracle-21 | JDK 21 is tested without JIT compilation. |
| aarch64-linux-glibc-oracle-21 | JDK 21 is tested without JIT compilation. |
| aarch64-macos-darwin-oracle-21 | JDK 21 is tested without JIT compilation. |
| amd64-macos-darwin-oracle-21 | JDK 21 is tested without JIT compilation. |
| amd64-windows-msvc-oracle-21 | JDK 21 is tested without JIT compilation. |
Tier 4
- Stability: CI failures do not block releases. Tests may be broken on the main and release branches.
- Test Coverage: Smoke tests with platform-agnostic pure Python workloads are run on a regular schedule.
- Feature Support: Only basic pure Python functionality is tested; platform-specific features and extensions are not prioritized.
| Platform | Notes |
|---|---|
| amd64-linux-musl-graal-latest | Ensures GraalPy can be built for and used on musl libc platforms such as Alpine Linux. |
| amd64-linux-glibc-j9-17 | Ensures that non-Oracle JVMs work for pure Python code without JIT. |
| ppc64le-linux-glibc-oracle-17 | Ensures that other architectures (ppc64le, s390x, risc-v) work for pure Python code without JIT. |