Skip to content

Commit dbca624

Browse files
committed
Initial
0 parents  commit dbca624

File tree

8 files changed

+1807
-0
lines changed

8 files changed

+1807
-0
lines changed

.github/workflows/ci.yml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
name: CI
2+
on: [push]
3+
jobs:
4+
test:
5+
runs-on: ubuntu-latest
6+
steps:
7+
- uses: actions/checkout@v3
8+
- name: Install dependencies
9+
run: |
10+
sudo snap install --beta --classic zig
11+
pip install pytest
12+
zig version
13+
- name: Build example
14+
run: pip install ./example
15+
- name: Test example
16+
run: pytest tests

.gitignore

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
dist/
2+
build/
3+
__pycache__
4+
*.so
5+
*.pyc
6+
*.egg-info
7+

README.md

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
# Lightweight Python ffi for zig
2+
3+
py.zig provides a _lightweight_ interface for creating python extensions in zig.
4+
5+
It is intended to be used with [setuptools-zig](https://pypi.org/project/setuptools-zig/) by
6+
simply cloning or copying `py.zig` into your python project and
7+
adding an `Extension` entry in your `setup.py`.
8+
9+
10+
11+
```py
12+
from setuptools import Extension, setup
13+
14+
setup(
15+
name='pyzigtest',
16+
version='0.1.0',
17+
python_requires='>=3.10',
18+
build_zig=True,
19+
ext_modules=[
20+
Extension(
21+
'pyzigtest',
22+
sources=[
23+
'src/pyzigtest.zig'
24+
],
25+
extra_compile_args=["-DOptimize=ReleaseSafe"]
26+
)
27+
],
28+
setup_requires=['setuptools-zig'],
29+
)
30+
```
31+
32+
Then in your extension import `py.zig` and define the python module.
33+
34+
See the `/examples` for a complete example.
35+
36+
## Why
37+
38+
py.zig is designed but a much lighter alternative to something like [ziggy-pydust](https://github.com/spiraldb/ziggy-pydust).
39+

example/py.zig

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../py.zig

example/pyzigtest.zig

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
const py = @import("py.zig");
2+
const std = @import("std");
3+
// Import basic objects
4+
const Object = py.Object;
5+
const Int = py.Int;
6+
const Str = py.Str;
7+
const Module = py.Module;
8+
9+
pub fn add(mod: *Module, args: [*]*Object, n: isize) ?*Object {
10+
_ = mod;
11+
if (n != 2) {
12+
return py.typeError("sum requires 2 arguments");
13+
}
14+
// We can now safely access indexes 0 and 1
15+
if (!Int.check(args[0]) or !Int.check(args[1])) {
16+
return py.typeError("both arguments must be ints!");
17+
}
18+
19+
// We can now safely cast to Int objects and access their methods
20+
const a_obj: *Int = @ptrCast(args[0]);
21+
const a = a_obj.as(isize) catch return null;
22+
23+
// Or use the method statically and do it in one step
24+
const b = Int.as(@ptrCast(args[1]), isize) catch return null;
25+
26+
// Add them and create a new Int object
27+
return @ptrCast(Int.fromNumber(a + b) catch return null);
28+
}
29+
30+
31+
fn modexec(mod: *py.Module) !c_int {
32+
const stdout = std.io.getStdOut().writer();
33+
const s = mod.str() catch return -1;
34+
defer s.decref();
35+
try stdout.print("modexec on {s}!\n", .{s.asString()});
36+
37+
// Add a str
38+
const test_str = try Str.fromSlice("test!");
39+
defer test_str.decref();
40+
try mod.addObjectRef("TEST_STR", @ptrCast(test_str));
41+
42+
return 0;
43+
}
44+
45+
pub export fn py_mod_exec(mod: *py.Module) c_int {
46+
return modexec(mod) catch |err| switch (err) {
47+
// py.zig uses error.PyError for any error caught from the python c-api
48+
error.PyError => -1, // Python error
49+
// Any other errors need to set an error in python
50+
else => blk: {
51+
_ = py.systemError("module init failed");
52+
break :blk -1;
53+
},
54+
};
55+
}
56+
57+
var module_methods = [_]py.MethodDef{
58+
.{
59+
.ml_name="add",
60+
.ml_meth=@constCast(@ptrCast(&add)),
61+
.ml_flags=py.c.METH_FASTCALL,
62+
.ml_doc="Add two numbers"
63+
},
64+
.{} // sentinel
65+
};
66+
67+
var module_slots = [_]py.SlotDef{
68+
.{.slot = py.c.Py_mod_exec, .value = @constCast(@ptrCast(&py_mod_exec))},
69+
.{} // sentinel
70+
};
71+
72+
var moduledef = py.ModuleDef.new(.{
73+
.m_name = "pyzigtest",
74+
.m_doc = "pyzigtest module",
75+
.m_methods = &module_methods,
76+
.m_slots = &module_slots,
77+
});
78+
79+
pub export fn PyInit_pyzigtest( _:*anyopaque ) [*c]Object {
80+
return moduledef.init();
81+
}

example/setup.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
from setuptools import Extension, setup
2+
3+
setup(
4+
name='pyzigtest',
5+
version='0.1.0',
6+
python_requires='>=3.10',
7+
build_zig=True,
8+
ext_modules=[
9+
Extension(
10+
'pyzigtest',
11+
sources=[
12+
'pyzigtest.zig'
13+
],
14+
extra_compile_args=["-DOptimize=Debug"]
15+
)
16+
],
17+
setup_requires=['setuptools-zig'],
18+
)

0 commit comments

Comments
 (0)