This example shows how to import a Python module which defines a custom class.
The console app creates a Person object in Dart (defined as a Python dataclass) and passes it to
the Python module. It can then be moved around, by calling a method on the class instance. The
current position is printed to the console in Dart, but the String is generated by the Python
module.
- Usage (the final product)
- Prerequisites
- Including the Python module source
- Adding the Python module to the Dart project
- Using the Python module in Dart
- Testing the Python module
- Next step
$ dart bin/basic_dataclass.dart MyPlayerName
<use W,A,S,D to move, press q to quit>
MyPlayerName @ (0, 0)
> w
MyPlayerName @ (0, -1)
> d
MyPlayerName @ (1, -1)
> q- The dartpip cli should be installed (either globally or as dev-dependency in this Dart project).
The Python module source is a single file basic_dataclass.py in the python-modules directory. It
could be located in any other directory, even outside the project root. This will result in a module
named basic_dataclass that we will be importing from Dart.
# python-modules/basic_dataclass.py
from dataclasses import dataclass
@dataclass
class Person:
name: str
x: int = 0
y: int = 0
def move(self, dx: int, dy: int):
self.x += dx
self.y += dy
def __str__(self):
return f"{self.name} @ ({self.x}, {self.y})"# pubspec.yaml
python_ffi:
modules:
basic_dataclass:
path: python-modules/basic_dataclass.pyThe following command should be run from the root of your Dart project. It will bundle the Python module source into the Dart project.
$ dartpip installEach Python module needs its corresponding Dart Module-definition.
The install command will automatically generate a Module-definition in Dart for the Python module.
The generated file will be located at lib/python_modules/basic_dataclass.g.dart.
Click to see the generated file
// lib/python_modules/basic_dataclass.g.dart
// ignore_for_file: camel_case_types, non_constant_identifier_names, prefer_void_to_null
library basic_dataclass;
import "package:python_ffi_dart/python_ffi_dart.dart";
/// ## Person
///
/// ### python docstring
///
/// Person(name: str, x: int = 0, y: int = 0)
///
/// ### python source
/// ```py
/// @dataclass
/// class Person:
/// name: str
/// x: int = 0
/// y: int = 0
///
/// def move(self, dx: int, dy: int) -> None:
/// self.x += dx
/// self.y += dy
///
/// def __str__(self) -> str:
/// return f"{self.name} @ ({self.x}, {self.y})"
/// ```
final class Person extends PythonClass {
factory Person({
required String name,
int x = 0,
int y = 0,
}) =>
PythonFfiDart.instance.importClass(
"basic_dataclass",
"Person",
Person.from,
<Object?>[
name,
x,
y,
],
<String, Object?>{},
);
Person.from(super.pythonClass) : super.from();
/// ## move
///
/// ### python source
/// ```py
/// def move(self, dx: int, dy: int) -> None:
/// self.x += dx
/// self.y += dy
/// ```
Null move({
required int dx,
required int dy,
}) =>
getFunction("move").call(
<Object?>[
dx,
dy,
],
kwargs: <String, Object?>{},
);
/// ## x (getter)
///
/// ### python docstring
///
/// Person(name: str, x: int = 0, y: int = 0)
Object? get x => getAttribute("x");
/// ## x (setter)
///
/// ### python docstring
///
/// Person(name: str, x: int = 0, y: int = 0)
set x(Object? x) => setAttribute("x", x);
/// ## y (getter)
///
/// ### python docstring
///
/// Person(name: str, x: int = 0, y: int = 0)
Object? get y => getAttribute("y");
/// ## y (setter)
///
/// ### python docstring
///
/// Person(name: str, x: int = 0, y: int = 0)
set y(Object? y) => setAttribute("y", y);
/// ## name (getter)
///
/// ### python docstring
///
/// Person(name: str, x: int = 0, y: int = 0)
Object? get name => getAttribute("name");
/// ## name (setter)
///
/// ### python docstring
///
/// Person(name: str, x: int = 0, y: int = 0)
set name(Object? name) => setAttribute("name", name);
}
/// ## basic_dataclass
///
/// ### python source
/// ```py
/// from dataclasses import dataclass
///
/// @dataclass
/// class Person:
/// name: str
/// x: int = 0
/// y: int = 0
///
/// def move(self, dx: int, dy: int) -> None:
/// self.x += dx
/// self.y += dy
///
/// def __str__(self) -> str:
/// return f"{self.name} @ ({self.x}, {self.y})"
/// ```
final class basic_dataclass extends PythonModule {
basic_dataclass.from(super.pythonModule) : super.from();
static basic_dataclass import() =>
PythonFfiDart.instance.importModule(
"basic_dataclass",
basic_dataclass.from,
);
}Note: The Dart toString method is not mapped to the Python __str__ method. This is handled
automatically during runtime. Simply printing the Dart instance will invoke the Python __str__
method.
To make the Python module available to other Dart files, we need to export the generated files.
To do this, we modify lib/basic_dataclass.dart:
// lib/basic_dataclass.dart
export "python_modules/basic_dataclass.g.dart";
export "python_modules/src/python_modules.g.dart";First we need to initialize the Python runtime once. This is done by calling the initialize method
on the PythonFfiDart.instance singleton. The initialize method takes the encoded Python modules
added via dartpip install.
We can then instantiate a Person object in Dart. It will be automatically backed by Python memory.
Then we can call the move method on the Dart instance, which will call the corresponding Python
method. Printing the Dart instance will invoke the Python __str__ method.
// bin/basic_dataclass.dart
import "dart:io";
import "package:basic_dataclass/basic_dataclass.dart";
import "package:python_ffi_dart/python_ffi_dart.dart";
void main(List<String> arguments) async {
await PythonFfiDart.instance.initialize(pythonModules: kPythonModules);
final Person person = Person(name: arguments[0]);
print("<use W,A,S,D to move, press q to quit>");
String? input;
do {
print(person);
stdout.write("> ");
input = stdin.readLineSync();
switch (input) {
case "w":
person.move(dx: 0, dy: -1);
case "a":
person.move(dx: -1, dy: 0);
case "s":
person.move(dx: 0, dy: 1);
case "d":
person.move(dx: 1, dy: 0);
}
} while (input != "q");
}Note: When you look at the file bin/basic_dataclass.dart as it is in this repository, you will
notice that it is quite different. This is because the complete example project allows for passing
arguments via the command line. This is not relevant for this tutorial, so we have removed it here.
We can write Dart tests to test our Python module. Again we need to make sure to initialize the Python runtime once first. Then we can import the Python module and test its function:
// test/basic_dataclass_test.dart
import "package:basic_dataclass/basic_dataclass.dart";
import "package:python_ffi_dart/python_ffi_dart.dart";
import "package:test/test.dart";
void main() async {
setUpAll(() async {
await PythonFfiDart.instance.initialize(pythonModules: kPythonModules);
});
test("move W", () {
expect(
(Person(name: "John")
..move(dx: 0, dy: -1)).toString(),
"John @ (0, -1)",
);
});
test("move A", () {
expect(
(Person(name: "John")
..move(dx: -1, dy: 0)).toString(),
"John @ (-1, 0)",
);
});
test("move S", () {
expect(
(Person(name: "John")
..move(dx: 0, dy: 1)).toString(),
"John @ (0, 1)",
);
});
test("move D", () {
expect(
(Person(name: "John")
..move(dx: 1, dy: 0)).toString(),
"John @ (1, 0)",
);
});
}Importing a Python module from PyPI. See pytimeparse_dart.