Skip to content
This repository was archived by the owner on Nov 26, 2025. It is now read-only.
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions examples/build.zig.zon
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,6 @@
.compile_c = .{ .path = "compile_c" },
.import_header = .{ .path = "import_header" },
.use_static_lib = .{ .path = "use_static_lib" },
.python_extension = .{ .path = "python_extension" },
},
}
65 changes: 65 additions & 0 deletions examples/python_extension/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# Python Extension Example

This example demonstrates how to use translate-c to create a minimal Python extension in Zig.

## What it does

This example shows how to:
1. Use translate-c to parse Python.h header file
2. Create a Python extension module in Zig
3. Export functions that can be called from Python
4. Handle Python C API calls using the translated bindings

## Building

```bash
zig build install
```

This creates a shared library `zig-out/lib/libzig_ext.so` which can be imported as a Python extension.

## Testing

```bash
# Test the build
zig build install

# Test importing in Python (from the project directory)
python3 -c "import sys; sys.path.insert(0, 'zig-out/lib'); import zig_ext; print('Successfully imported zig_ext!'); print('zig_ext.get_greeting():', zig_ext.get_greeting()); print('zig_ext.add_numbers(5, 3):', zig_ext.add_numbers(5, 3))"
```

## Usage

After building, you can import the extension in Python:

```python
import sys
sys.path.insert(0, 'zig-out/lib') # Add the library path to Python path
import zig_ext

# Add two numbers
result = zig_ext.add_numbers(5, 3)
print(f"zig_ext.add_numbers(5, 3) = {result}") # Output: 8

# Get a greeting
greeting = zig_ext.get_greeting()
print(greeting) # Output: Hello from Zig-based Python extension!
```

The extension is named `zig_ext` and provides two functions:
- `zig_ext.get_greeting()` - Returns a greeting string
- `zig_ext.add_numbers(a, b)` - Adds two integers and returns the result

## Files

- `build.zig` - Build script that sets up translate-c for Python.h
- `extension.zig` - Zig implementation of the Python extension
- `README.md` - This file

## How it works

1. The build script uses `translate-c` to translate Python.h directly into Zig bindings
2. The `extension.zig` file imports these bindings as a module named "python"
3. It then implements Python C API functions using the translated bindings
4. The module exports two functions: `add_numbers` and `get_greeting`
5. These can be called from Python just like any other Python module
87 changes: 87 additions & 0 deletions examples/python_extension/build.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
const std = @import("std");

/// Import the `Translator` helper from the `translate_c` dependency.
const Translator = @import("translate_c").Translator;

pub fn build(b: *std.Build) !void {
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});

// Prepare the `translate-c` dependency.
const translate_c = b.dependency("translate_c", .{});

// Create a step to translate Python.h directly. This also creates a Zig module from the output.
const python_h: Translator = .init(translate_c, .{
.c_source_file = .{ .cwd_relative = "/home/jacobz/miniforge3/include/python3.12/Python.h" },
.target = target,
.optimize = optimize,
});

// Add Python include paths for resolving dependencies
python_h.addIncludePath(.{ .cwd_relative = "/home/jacobz/miniforge3/include/python3.12" });

// Create a module for our Python extension
const extension_module = b.createModule(.{
.root_source_file = b.path("extension.zig"),
.target = target,
.optimize = optimize,
.link_libc = true,
.imports = &.{
// Add the translated header module as an import for the extension module.
.{
.name = "python",
.module = python_h.mod,
},
},
});

// Build the shared library (Python extension) - use b.addLibrary with dynamic linkage
const extension_lib = b.addLibrary(.{
.name = "zig_ext",
.root_module = extension_module,
.linkage = .dynamic, // This creates a shared library (.so)
});

// Allow undefined symbols for Python dynamic linking
extension_lib.linker_allow_shlib_undefined = true;

// Install the shared library
b.installArtifact(extension_lib);

// Create build step
const build_step = b.step("build", "Build the Python extension");
build_step.dependOn(&extension_lib.step);

// Create test step
const test_step = b.step("test", "Test the Python extension by importing and using it");
test_step.dependOn(&extension_lib.step);

// Add a Python script to test the extension
const test_python = b.addSystemCommand(&.{
"python3", "-c",
\\import sys;
\\import os;
\\# Add the zig-out/lib directory to Python path
\\sys.path.insert(0, 'zig-out/lib');
\\try:
\\ import zig_ext;
\\ print("Successfully imported Python extension 'zig_ext'!")
\\ print("Testing zig_ext.get_greeting():");
\\ greeting = zig_ext.get_greeting();
\\ print(greeting)
\\ print("Testing zig_ext.add_numbers(10, 20):");
\\ result = zig_ext.add_numbers(10, 20);
\\ print(f"zig_ext.add_numbers(10, 20) = {result}")
\\ print("✅ All tests passed!")
\\except Exception as e:
\\ print(f"❌ Error: {e}");
\\ import traceback;
\\ traceback.print_exc();
\\ sys.exit(1)
});

test_step.dependOn(&test_python.step);

// Set default step to build
b.default_step = build_step;
}
11 changes: 11 additions & 0 deletions examples/python_extension/build.zig.zon
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
.{
.name = .python_extension,
.version = "0.0.0",
.fingerprint = 0x8d05bab9cad09ad3,
.paths = .{""},

.dependencies = .{
// Install by running `zig fetch --save git+https://github.com/ziglang/translate-c`.
.translate_c = .{ .path = "../../" },
},
}
60 changes: 60 additions & 0 deletions examples/python_extension/extension.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
// Python extension written in Zig using translate-c
const python = @import("python"); // This is the translated Python.h
const std = @import("std");

// Python method definitions
const methods = [_]python.PyMethodDef{
.{
.ml_name = "get_greeting",
.ml_meth = py_get_greeting,
.ml_flags = python.METH_NOARGS,
.ml_doc = "Get a greeting from Zig",
},
.{
.ml_name = "add_numbers",
.ml_meth = py_add_numbers,
.ml_flags = python.METH_VARARGS,
.ml_doc = "Add two integers together",
},
.{ .ml_name = null, .ml_meth = null, .ml_flags = 0, .ml_doc = null },
};

// Python wrapper functions
fn py_get_greeting(self: [*c]python.PyObject, args: [*c]python.PyObject) callconv(.c) [*c]python.PyObject {
_ = self;
_ = args;
const greeting = "Hello from Zig-based Python extension!";
return python.PyUnicode_FromString(greeting);
}

fn py_add_numbers(self: [*c]python.PyObject, args: [*c]python.PyObject) callconv(.c) [*c]python.PyObject {
_ = self;

var a: c_int = undefined;
var b: c_int = undefined;

if (python.PyArg_ParseTuple(args, "ii", &a, &b) == 0) {
return null;
}

const result = a + b;
return python.PyLong_FromLong(result);
}

// Module definition
var module_def = python.PyModuleDef{
.m_base = .{ .ob_base = std.mem.zeroes(python.PyObject) },
.m_name = "zig_ext",
.m_doc = "Python extension written in Zig using translate-c",
.m_size = -1,
.m_methods = @constCast(&methods),
.m_slots = null,
.m_traverse = null,
.m_clear = null,
.m_free = null,
};

// Module initialization function - this is called when Python imports the module
export fn PyInit_zig_ext() *python.PyObject {
return python.PyModule_Create(&module_def);
}