Skip to content

Latest commit

 

History

History
356 lines (290 loc) · 8.93 KB

File metadata and controls

356 lines (290 loc) · 8.93 KB

basic_dataclass

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.

Table of contents

  1. Usage (the final product)
  2. Prerequisites
  3. Including the Python module source
  4. Adding the Python module to the Dart project
    1. List the Python module as a dependency in pubspec.yaml
    2. Run dartpip install
    3. Export the generated files
  5. Using the Python module in Dart
  6. Testing the Python module
  7. Next step

Usage (the final product)

$ 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

Prerequisites

  • The dartpip cli should be installed (either globally or as dev-dependency in this Dart project).

Including the Python module source

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})"

Adding the Python module to the Dart project

1. List the Python module as a dependency in pubspec.yaml

# pubspec.yaml

python_ffi:
  modules:
    basic_dataclass:
      path: python-modules/basic_dataclass.py

2. Run dartpip install

The following command should be run from the root of your Dart project. It will bundle the Python module source into the Dart project.

$ dartpip install

Each 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.

3. Export the generated files

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";

Using the Python module in 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.

Testing the Python module

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)",
    );
  });
}

Next step

Importing a Python module from PyPI. See pytimeparse_dart.