Skip to content

Python Modules

Python modules represent the entrypoint into your Zig code. You can create a new module by adding an entry into your pyproject.toml:

pyproject.toml
1
2
3
[[tool.pydust.ext_module]]
name = "example.modules"   # A fully-qualified python module name
root = "src/modules.zig"   # Path to a Zig file that exports this module.

Note

Poetry doesn't support building exclusively native modules without a containing python package. In this example, you would need to create an empty example/__init__.py.

In Pydust, all Python declarations start life as a struct. When a struct is registered with Pydust as a module, a PyObject *PyInit_<modulename>(void) function is created automatically and exported from the compiled shared library. This allows the module to be imported by Python.

Example Module

Please refer to the annotations in this example module for an explanation of the Pydust features.

src/modules.zig
pub const __doc__ =
    \\Zig multi-line strings make it easy to define a docstring...
    \\
    \\..with lots of lines!
    \\
    \\P.S. I'm sure one day we'll hook into Zig's AST and read the Zig doc comments ;)
;

const std = @import("std");
const py = @import("pydust");

const Self = @This(); // (1)!

count: u32 = 0, // (2)!
name: py.PyString,

pub fn __new__() !Self { // (3)!
    return .{ .name = try py.PyString.create("Ziggy") };
}

pub fn increment(self: *Self) void { // (4)!
    self.count += 1;
}

pub fn count(self: *const Self) u32 {
    return self.count;
}

pub fn whoami(self: *const Self) !py.PyString {
    return self.name;
}

pub fn hello(
    self: *const Self,
    args: struct { name: py.PyString }, // (5)!
) !py.PyString {
    var str = try py.PyString.create("Hello, ");
    str = try str.append(args.name);
    str = try str.appendSlice(". It's ");
    str = try str.append(self.name);
    return str;
}

pub const submod = py.module(struct { // (6)!
    pub fn world() !py.PyString {
        return try py.PyString.create("Hello, World!");
    }
});

comptime {
    py.rootmodule(@This()); // (7)!
}
  1. In Zig, every file is itself a struct. So assigning Self = @This(); allows you to get a reference to your own type.

  2. Unlike regular Python modules, native Python modules are able to carry private internal state.

  3. Any fields that cannot be defaulted at comptime (i.e. if they require calling into Python) must be initialized in the module's __new__ function.

  4. Module functions taking a *Self or *const Self argument are passed a pointer to their internal state.

  5. Arguments in Pydust are accepted as a pointer to a const struct. This allows Pydust to generate function docstrings using the struct field names.

  6. Submodules can also be arbitrarily nested. Note however that submodules are not Python packages. That means from example.modules import submodule will work, but from example.modules.submodule import world will not.

  7. All modules must be registered with Pydust such that a PyInit_<modulename> function is generated and exported from the object file.