2025, Nov 28 15:00
Run Python in Flutter with Chaquopy: fix AttributeError by using the required script.py bootstrap
Learn how to run Python in Flutter with Chaquopy by using the correct script.py bootstrap. Fix AttributeError and import issues with a step-by-step guide.
Running Python from Flutter via Chaquopy is straightforward in principle, but small integration details can trip you up. A common stumbling block is using the wrong bootstrap file or naming it incorrectly, which leads to runtime errors like AttributeError on module attributes. Below is a minimal walkthrough of the problem and a precise fix based on the plugin’s instructions.
Problem recap
The app calls a Python script to generate a random number. Initially, importing the module failed until the file was moved under the Python directory. After that, the run produced AttributeError: module 'script' has no attribute 'mainTextCode'. The Dart code loads a Python file from assets and passes it to Chaquopy for execution; the Python file simply returns a number.
Problematic code example
Future<void> _runProbe() async {
showDialog(
context: context,
barrierDismissible: false,
builder: (ctx) => const AlertDialog(
content: Row(
children: [
CircularProgressIndicator(),
SizedBox(width: 20),
Text("Running analysis..."),
],
),
),
);
try {
final pySource = await rootBundle.loadString('assets/python/placeholder.py');
final pyResult = await Chaquopy.executeCode(pySource);
Navigator.pop(context);
String displayText = '';
if (pyResult is Map<String, dynamic>) {
displayText = pyResult['textOutput']?.toString() ?? 'No output';
} else {
displayText = pyResult?.toString() ?? 'No result';
}
showDialog(
context: context,
builder: (ctx) => AlertDialog(
title: Row(
children: [
Icon(Icons.check_circle, color: Colors.green),
SizedBox(width: 8),
Text('Analysis Result'),
],
),
content: Text(
displayText,
style: const TextStyle(fontSize: 16),
),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('OK'),
),
],
),
);
} catch (err) {
if (Navigator.canPop(context)) {
Navigator.pop(context);
}
showDialog(
context: context,
builder: (ctx) => AlertDialog(
title: Row(
children: [
Icon(Icons.error, color: Colors.red),
SizedBox(width: 8),
Text('Error'),
],
),
content: Text('Failed to run analysis: ${err.toString()}'),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('OK'),
),
],
),
);
}
}
import random
def rnd_val():
return random.randint(1, 100)
What’s going on
The plugin you’re using is the unofficial Flutter integration for Chaquopy. Its setup explicitly requires a particular bootstrap file named script.py. That file must be placed in the plugin’s Python directory and must keep the exact name script.py. The plugin’s documentation also points out that indentations in that file matter. When the expected file or its contents aren’t in place, you’ll see errors such as AttributeError on attributes the plugin tries to access on the script module.
Download script.py and put it in python directory. (Kindly note that this python file should not be renamed other than script.py and also if your code doesn’t work, check the intendations of the downloaded file.)
There’s also a copy of that script.py available in the plugin’s repository.
Fix
Follow the plugin’s instructions precisely: use the provided script.py, place it in the python directory the plugin expects, and do not rename it. If execution still fails, check the file’s indentation. Once the correct script.py is in place, your app can call into Python as intended.
If your current code loads a differently named asset, switch it to script.py so that you’re executing the expected bootstrap file.
Corrected code example
Future<void> _runProbe() async {
showDialog(
context: context,
barrierDismissible: false,
builder: (ctx) => const AlertDialog(
content: Row(
children: [
CircularProgressIndicator(),
SizedBox(width: 20),
Text("Running analysis..."),
],
),
),
);
try {
// Use the required bootstrap file name
final pySource = await rootBundle.loadString('assets/python/script.py');
final pyResult = await Chaquopy.executeCode(pySource);
Navigator.pop(context);
String displayText = '';
if (pyResult is Map<String, dynamic>) {
displayText = pyResult['textOutput']?.toString() ?? 'No output';
} else {
displayText = pyResult?.toString() ?? 'No result';
}
showDialog(
context: context,
builder: (ctx) => AlertDialog(
title: Row(
children: [
Icon(Icons.check_circle, color: Colors.green),
SizedBox(width: 8),
Text('Analysis Result'),
],
),
content: Text(
displayText,
style: const TextStyle(fontSize: 16),
),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('OK'),
),
],
),
);
} catch (err) {
if (Navigator.canPop(context)) {
Navigator.pop(context);
}
showDialog(
context: context,
builder: (ctx) => AlertDialog(
title: Row(
children: [
Icon(Icons.error, color: Colors.red),
SizedBox(width: 8),
Text('Error'),
],
),
content: Text('Failed to run analysis: ${err.toString()}'),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('OK'),
),
],
),
);
}
}
After applying the setup from the plugin’s docs, the integration works and the Python code runs. If you later see a message like “the asset does not exist or has empty data”, that’s a different, asset-loading problem related to where files are placed.
Why this matters
Some integrations rely on a convention-over-configuration entry point. The filename, location, and even the exact structure of the bootstrap file can be part of the contract. Deviating from those requirements, including renaming the file, can surface as attribute errors or missing-module errors. Sticking to the documented setup eliminates entire classes of issues before you start debugging app logic.
Takeaways
Use the script.py provided by the plugin, keep the name unchanged, and place it in the correct python directory. If something still fails, validate the indentation of that file as advised. For unresolved issues or unclear behavior, share your current code and full error message on the Chaquopy GitHub page—this makes it easier to pinpoint the exact cause.