Gradle plugin#
Chaquopy is distributed as a plugin for Android’s Gradle-based build system. It can be
used in an Android appplication module (com.android.application
), or an Android
library module
(com.android.library
).
The Chaquopy plugin can only be used in one module per app: either in the app module, or in exactly one library module. If you use it in multiple modules, the build may fail, and even if it succeeds, only one module’s Python code will be included in the app.
Project setup#
Gradle plugin#
The following instructions use the plugins
syntax. If your project uses the
previous buildscript
and apply
syntax, follow the instructions here, but replace the Chaquopy version number with
the current one shown below.
In your project’s settings.gradle
file, find the pluginManagement
repositories list,
and make sure it includes mavenCentral()
. If your project was generated by a recent
version of Android Studio, this line should already be there.
In your top-level build.gradle
file, set the Chaquopy version:
plugins { id("com.chaquo.python") version "15.0.1" apply false }
Also check the Android Gradle plugin version (com.android.application
or
com.android.library
): it should be between 7.0.x and 8.5.x. Older versions as far back
as 2.2 are supported by older versions of Chaquopy.
Then apply the Chaquopy plugin in the module-level build.gradle
file (usually in the
app
directory):
plugins {
id("com.chaquo.python")
}
minSdk
#
Your project’s minSdk must be at least 21. Older versions as far back as 15 are supported by older versions of Chaquopy.
abiFilters
#
The Python interpreter is a native component, so you must use the abiFilters setting to specify which ABIs you want the app to support. The currently available ABIs are:
armeabi-v7a
for older Android devices (Python 3.11 and older only)arm64-v8a
for current Android devices, and emulators on Apple siliconx86
for older emulators (Python 3.11 and older only)x86_64
for current emulators
The following setting will work for most projects:
android {
defaultConfig {
ndk {
// On Apple silicon, you can omit x86_64.
abiFilters += listOf("arm64-v8a", "x86_64")
}
}
}
android {
defaultConfig {
ndk {
// On Apple silicon, you can omit x86_64.
abiFilters "arm64-v8a", "x86_64"
}
}
}
There’s no need to actually install the NDK, as all of Chaquopy’s native libraries are already pre-compiled and stripped.
Each ABI will add several MB to the size of the app, plus the size of any native requirements. If you find this makes your app too large, see the FAQ.
chaquopy
block#
Note
There is also a deprecated DSL which is available in Groovy build.gradle files only. For details, see the Chaquopy 14 documentation.
All of the Chaquopy plugin’s settings are configured with the chaquopy
block in
your module-level build.gradle file. Its structure is similar to the android
block:
chaquopy {
defaultConfig { }
productFlavors { }
sourceSets { }
}
The examples below will use defaultConfig
, but every setting can also appear within a
product flavor. For
example, here’s how to create flavors for different Python versions:
android {
flavorDimensions += "pyVersion"
productFlavors {
create("py310") { dimension = "pyVersion" }
create("py311") { dimension = "pyVersion" }
}
}
chaquopy {
productFlavors {
getByName("py310") { version = "3.10" }
getByName("py311") { version = "3.11" }
}
}
android {
flavorDimensions "pyVersion"
productFlavors {
create("py310") { dimension = "pyVersion" }
create("py311") { dimension = "pyVersion" }
}
}
chaquopy {
productFlavors {
getByName("py310") { version = "3.10" }
getByName("py311") { version = "3.11" }
}
}
buildPython#
Some features require Python 3.7 or later to be available on the build machine. These features are indicated by a note in their documentation sections.
By default, Chaquopy will try to find Python on the PATH with the standard command for your operating system, first with a matching minor version, and then with a matching major version. For example, if your app’s Python version is 3.8, then:
On Linux and Mac it will try
python3.8
, thenpython3
.On Windows, it will try
py -3.8
, thenpy -3
.On all platforms, it will finally try
python
.
If this doesn’t work for you, set your Python command using the buildPython
setting.
For example, on Windows you might use one of the following:
chaquopy {
defaultConfig {
buildPython("C:/path/to/python.exe")
buildPython("C:/path/to/py.exe", "-3.8")
}
}
Development#
Startup#
Before your app can run any Python code, you must call Python.start(). There are two basic ways to achieve this:
If the app always uses Python, then call Python.start() from a location which is guaranteed
to run exactly once per process, such as Application.onCreate(). The
easiest way to do this is to use PyApplication, or your own subclass of it. Simply
add the following attribute to the <application>
element in AndroidManifest.xml
:
android:name="com.chaquo.python.android.PyApplication"
Alternatively, if the app only sometimes uses Python, then call Python.start() after first checking whether it’s already been started:
// "context" must be an Activity, Service or Application object from your app.
if (! Python.isStarted()) {
Python.start(new AndroidPlatform(context));
}
Python version#
You can set your app’s Python version like this:
chaquopy {
defaultConfig {
version = "3.8"
}
}
In this version of Chaquopy, the default Python version is 3.8. The other available versions are 3.9, 3.10, 3.11 and 3.12, but these may have fewer packages available.
Source code#
By default, Chaquopy will look for Python source code in the python
subdirectory of
each source set.
This means the default Python source directory is src/main/python
.
To include Python code from other directories, use the chaquopy.sourceSets
block. For
example:
chaquopy {
sourceSets {
getByName("main") {
srcDir("some/other/dir")
}
}
}
The setRoot method only takes effect on the standard Android directories. If you want to set the Python root to the same place, you must do so explicitly, e.g.:
android {
sourceSets.getByName("main") {
setRoot("some/other/main")
}
}
chaquopy {
sourceSets.getByName("main") {
setSrcDirs(listOf("some/other/main/python"))
}
}
android {
sourceSets.getByName("main") {
setRoot("some/other/main")
}
}
chaquopy {
sourceSets.getByName("main") {
srcDirs = ["some/other/main/python"]
}
}
As with Java, it
is usually an error if the source directories for a given build variant include multiple
copies of the same filename. This is only permitted if the duplicate files are all
empty, such as may happen with __init__.py
.
Requirements#
Note
This feature requires Python on the build machine, which can be configured with the buildPython setting.
External Python packages may be built into the app using the pip
block.
Within this block, add install
lines, which can take any of the forms accepted by pip
install. For example:
chaquopy {
defaultConfig {
pip {
// A requirement specifier, with or without a version number:
install("scipy")
install("requests==2.24.0")
// An sdist or wheel filename, relative to the project directory:
install("MyPackage-1.2.3-py2.py3-none-any.whl")
// A directory containing a setup.py, relative to the project
// directory (must contain at least one slash):
install("./MyPackage")
// "-r"` followed by a requirements filename, relative to the
// project directory:
install("-r", "requirements.txt")
}
}
}
In our most recent tests, Chaquopy could install over 90% of the top 1000 packages on PyPI. This includes almost all pure-Python packages, plus a constantly-growing selection of packages with native components. To see which native packages are currently available, you can browse the repository here. To request a package to be added or updated, or for any other problem with installing requirements, please visit our issue tracker.
To pass options to pip install
, give them as a comma-separated list to the options
method. For example:
chaquopy {
defaultConfig {
pip {
options("--extra-index-url", "https://example.com/private/repository")
install("MyPackage==1.2.3")
}
}
}
Any options in the pip documentation may be
used, except for those which relate to the target environment, such as --target
, --user
or
-e
. If there are multiple options
lines, they will be combined in the order given.
Static proxy generator#
Note
This feature requires Python on the build machine, which can be configured with the buildPython setting.
The static proxy feature allows a Python class to extend a Java class, or to be referenced
directly in Java code or the AndroidManifest.xml
file without going through the Java API.
To use this feature, write your Python classes using the syntax described in the “Static proxy” section, then declare the containing modules as follows:
chaquopy {
defaultConfig {
staticProxy("module.one", "module.two")
}
}
Packaging#
extractPackages#
At runtime, Python modules are usually loaded directly from the APK, and don’t exist as separate files. If there are certain packages in your source code or requirements which need to exist as separate files, you can declare them like this:
chaquopy {
defaultConfig {
extractPackages("package1", "package2.subpkg")
}
}
Each extracted file will slightly slow down your app’s startup, so this setting should be used on the deepest possible package.
Data files#
Any data files in your source code and requirements will be automatically built into your app. You can read them at runtime
using a path relative to __file__
.
For example, if the data file is in the same directory as the Python file:
from os.path import dirname, join
filename = join(dirname(__file__), "filename.txt")
You can then pass this filename to open
, or any other function which reads a file.
If the data file and the Python file are in different directories, then change the path
accordingly. For example, if the Python file is alpha/hello.py
, and the data file is
bravo/filename.txt
, then replace filename.txt
above with ../bravo/filename.txt
.
Unlike Python modules, data files are always extracted onto the
filesystem at runtime. However, files stored within a top-level directory containing an
__init__.py
file will not be extracted until the corresponding Python package is
imported. All other files will be extracted when Python starts.
Do not write any files to these directories at runtime, as they may be deleted when the app is
upgraded. Instead, write files to os.environ["HOME"]
, as described in the “os”
section.
Bytecode compilation#
Note
This feature requires Python on the build machine, which can be configured with the buildPython setting.
Your app will start up faster if its Python code is compiled to .pyc format, so this is enabled by default.
If bytecode compilation succeeds, the original .py files will not be included in the APK, unless they’re covered by the extractPackages setting. However, this prevents source code text from appearing in stack traces, so during development you may wish to disable it. There are individual settings for:
src
: local source codepip
: requirementsstdlib
: the Python standard library
For example, to disable compilation of your local source code:
chaquopy {
defaultConfig {
pyc {
src = false
}
}
}
In the case of src
and pip
, your buildPython must use the same
bytecode format as Chaquopy itself. Usually this means it must have the same minor version,
e.g. if your app’s Python version is 3.8, then buildPython
can be
any version of Python 3.8.
If bytecode compilation fails, the build will continue with a warning, unless you’ve
explicitly set one of the pyc
settings to true
. Your app will still work, but its code will
have to be compiled on the target device, which means it will start up slower and use more
storage space.
Python standard library#
Chaquopy supports the entire Python standard library, except as documented below. If you discover a problem with any other module, please let us know.
Unsupported modules#
The following modules are unsupported because they require OS features which aren’t available on Android:
The following modules are unsupported because they require libraries which we don’t currently include:
multiprocessing#
Because Android doesn’t support POSIX semaphores, most of the multiprocessing
APIs will
fail with the error “This platform lacks a functioning sem_open implementation”. The simplest
solution is to use multiprocessing.dummy
instead.
os#
Don’t pass a simple filename to functions which write a file, as this will try to write to the
current directory, which is usually read-only on Android. Instead, use a path relative to
os.environ["HOME"]
, like this:
import os
from os.path import join
filename = join(os.environ["HOME"], "filename.txt")
You can then pass this filename to open
, or any other function which writes a file.
os.environ["HOME"]
is set to your app’s internal storage directory. Any files or
subdirectories created in this location will persist until the app is uninstalled.
If your app is debuggable, you can
read and write this directory from Android Studio using the Device File Explorer. Its path will be something
like /data/data/your.application.id/files
.
socket#
The following functions are unavailable because they’re not supported by our minimum Android version:
ssl#
The ssl
module is configured to use a copy of the CA bundle from certifi version 2023.11.17. The system CA store is
not used.
sys#
sys.stdout
and sys.stderr
are redirected to the Logcat with the tags
python.stdout
and python.stderr
respectively. These streams are line-buffered by
default: if you want to disable that, use io.TextIOWrapper.reconfigure
to set
write_through=True
.
This redirection does not affect the native stdout and stderr streams, which may be used by non-Python libraries. If you want to redirect them as well, see AndroidPlatform.redirectStdioToLogcat.
By default, sys.stdin
always returns EOF. If you want to run some code which takes
interactive text input, have a look at the console app template.
Android Studio plugin#
To add Python editing suppport to the Android Studio user interface, you may optionally install the “Python Community Edition” plugin. However, Chaquopy isn’t integrated with this plugin, so you’ll see the warning “No Python interpreter configured for the module”, and your code will probably display many error indicators such as “Unresolved reference” and “No module named”. These are harmless: just go ahead and run your app, and if there really is an error, the details will be displayed in the Logcat.