Quick Start¶
This guide walks you through creating your first PyOZ module using the pyoz CLI.
Prerequisites¶
Option 1: Download Prebuilt Binaries¶
Download the latest pyoz binary for your platform from the GitHub Releases page and add it to your PATH.
Option 2: Build from Source¶
The pyoz binary will be in zig-out/bin/. Add it to your PATH or use the full path.
Your First Module¶
Step 1: Initialize a Project¶
This creates a project with:
mymodule/
├── build.zig
├── build.zig.zon
├── pyproject.toml
├── README.md
├── .gitignore
└── src/
└── lib.zig # Your module code
Step 2: Edit Your Module¶
Open src/lib.zig - it already contains a starter template:
const pyoz = @import("PyOZ");
// ============================================================================
// Define your functions here
// ============================================================================
/// Add two integers
fn add(a: i64, b: i64) i64 {
return a + b;
}
/// Multiply two floats
fn multiply(a: f64, b: f64) f64 {
return a * b;
}
/// Greet someone by name
fn greet(name: []const u8) ![]const u8 {
_ = name;
return "Hello from mymodule!";
}
// ============================================================================
// Module definition
// ============================================================================
pub const Module = pyoz.module(.{
.name = "mymodule",
.doc = "mymodule - A Python extension module built with PyOZ",
.funcs = &.{
pyoz.func("add", add, "Add two integers"),
pyoz.func("multiply", multiply, "Multiply two floats"),
pyoz.func("greet", greet, "Return a greeting"),
},
.classes = &.{},
});
// Module initialization function
pub export fn PyInit_mymodule() ?*pyoz.PyObject {
return Module.init();
}
Step 3: Build the Wheel¶
This compiles your module and creates a wheel in dist/:
Step 4: Install and Test¶
Development Workflow¶
PyOZ provides several commands to streamline your workflow:
| Command | Description |
|---|---|
pyoz build |
Build a debug wheel |
pyoz build --release |
Build an optimized release wheel |
pyoz develop |
Build and install in development mode (symlinked) |
pyoz publish |
Publish wheel(s) to PyPI |
For detailed CLI options, see the CLI Reference.
Adding a Class¶
Edit src/lib.zig to add a class:
const pyoz = @import("PyOZ");
const std = @import("std");
// Define a struct - it becomes a Python class
const Point = struct {
x: f64,
y: f64,
// Methods take *const Self or *Self as first parameter
pub fn magnitude(self: *const Point) f64 {
return @sqrt(self.x * self.x + self.y * self.y);
}
pub fn scale(self: *Point, factor: f64) void {
self.x *= factor;
self.y *= factor;
}
// Static methods don't take self
pub fn origin() Point {
return .{ .x = 0.0, .y = 0.0 };
}
};
pub const Module = pyoz.module(.{
.name = "mymodule",
.doc = "Module with a Point class",
.funcs = &.{},
.classes = &.{
pyoz.class("Point", Point),
},
});
pub export fn PyInit_mymodule() ?*pyoz.PyObject {
return Module.init();
}
Use it from Python:
import mymodule
# Create instances (fields become __init__ parameters)
p = mymodule.Point(3.0, 4.0)
print(p.x, p.y) # 3.0 4.0
print(p.magnitude()) # 5.0
# Mutating methods work
p.scale(2.0)
print(p.x, p.y) # 6.0 8.0
# Static methods
origin = mymodule.Point.origin()
print(origin.x, origin.y) # 0.0 0.0
Error Handling¶
Zig errors automatically become Python exceptions:
fn divide(a: f64, b: f64) !f64 {
if (b == 0.0) {
return error.DivisionByZero;
}
return a / b;
}
const MyModule = pyoz.module(.{
.name = "mymodule",
.funcs = &.{
pyoz.func("divide", divide, "Divide two numbers"),
},
// Map Zig errors to Python exceptions
.error_mappings = &.{
pyoz.mapError("DivisionByZero", .RuntimeError),
},
});
import mymodule
try:
mymodule.divide(10, 0)
except RuntimeError as e:
print(f"Error: {e}") # Error: DivisionByZero
Module Constants¶
Add compile-time constants to your module:
const MyModule = pyoz.module(.{
.name = "mymodule",
.funcs = &.{},
.consts = &.{
pyoz.constant("VERSION", "1.0.0"),
pyoz.constant("PI", 3.14159265358979),
pyoz.constant("MAX_SIZE", @as(i64, 1000)),
pyoz.constant("DEBUG", false),
},
});
import mymodule
print(mymodule.VERSION) # "1.0.0"
print(mymodule.PI) # 3.14159265358979
print(mymodule.MAX_SIZE) # 1000
print(mymodule.DEBUG) # False
Enums¶
Define Python enums from Zig enums:
// Integer enum (becomes IntEnum)
const Color = enum(i32) {
Red = 1,
Green = 2,
Blue = 3,
};
// String enum (becomes StrEnum) - no explicit integer type
const Status = enum {
pending,
active,
completed,
};
const MyModule = pyoz.module(.{
.name = "mymodule",
.funcs = &.{},
// Auto-detects IntEnum vs StrEnum based on tag type
.enums = &.{
pyoz.enumDef("Color", Color),
pyoz.enumDef("Status", Status),
},
});
import mymodule
# Color is an IntEnum
print(mymodule.Color.Red) # Color.Red
print(mymodule.Color.Red.value) # 1
# Status is a StrEnum
print(mymodule.Status.pending) # Status.pending
print(mymodule.Status.pending.value) # "pending"
Next Steps¶
- Functions Guide - Optional parameters, keyword arguments
- Classes Guide - Magic methods, operators, inheritance
- Properties Guide - Computed properties, getters/setters
- Type Mappings - Complete type conversion reference
- Error Handling - Custom exceptions, error mapping
- CLI Reference - Detailed CLI options