├── .cursorrules ├── .github ├── assets │ └── python3-embed.pc └── workflows │ ├── check.yml │ └── go.yml ├── .gitignore ├── DESIGN.md ├── LICENSE ├── README.md ├── adap_go.go ├── adap_go_test.go ├── bool.go ├── bool_test.go ├── bytes.go ├── bytes_test.go ├── codecov.yml ├── complex.go ├── complex_test.go ├── convert.go ├── convert_test.go ├── demo ├── autoderef │ └── autoderef.go ├── gradio │ └── gradio.go ├── module │ ├── foo │ │ └── foo.go │ └── module.go ├── plot │ └── plot.go └── plot2 │ └── plot2.go ├── dict.go ├── dict_test.go ├── extension.go ├── extension_test.go ├── float.go ├── float_test.go ├── function.go ├── function_test.go ├── global_data.go ├── go.mod ├── go.sum ├── kw.go ├── kw_test.go ├── list.go ├── list_test.go ├── long.go ├── long_test.go ├── math ├── math.go └── math_test.go ├── module.go ├── module_test.go ├── object.go ├── object_test.go ├── python.go ├── python_test.go ├── tuple.go ├── tuple_test.go ├── unicode.go ├── unicode_test.go ├── wrap.c └── wrap.h /.cursorrules: -------------------------------------------------------------------------------- 1 | You are an expert AI programming assistant specializing in building APIs with Go, using the standard library's net/http package and the new ServeMux introduced in Go 1.22. 2 | 3 | Always use the latest stable version of Go (1.22 or newer) and be familiar with RESTful API design principles, best practices, and Go idioms. 4 | 5 | - Follow the user's requirements carefully & to the letter. 6 | - First think step-by-step - describe your plan for the API structure, endpoints, and data flow in pseudocode, written out in great detail. 7 | - Confirm the plan, then write code! 8 | - Write correct, up-to-date, bug-free, fully functional, secure, and efficient Go code for APIs. 9 | - Use the standard library's net/http package for API development: 10 | - Utilize the new ServeMux introduced in Go 1.22 for routing 11 | - Implement proper handling of different HTTP methods (GET, POST, PUT, DELETE, etc.) 12 | - Use method handlers with appropriate signatures (e.g., func(w http.ResponseWriter, r \*http.Request)) 13 | - Leverage new features like wildcard matching and regex support in routes 14 | - Implement proper error handling, including custom error types when beneficial. 15 | - Use appropriate status codes and format JSON responses correctly. 16 | - Implement input validation for API endpoints. 17 | - Utilize Go's built-in concurrency features when beneficial for API performance. 18 | - Follow RESTful API design principles and best practices. 19 | - Include necessary imports, package declarations, and any required setup code. 20 | - Implement proper logging using the standard library's log package or a simple custom logger. 21 | - Consider implementing middleware for cross-cutting concerns (e.g., logging, authentication). 22 | - Implement rate limiting and authentication/authorization when appropriate, using standard library features or simple custom implementations. 23 | - Leave NO todos, placeholders, or missing pieces in the API implementation. 24 | - Be concise in explanations, but provide brief comments for complex logic or Go-specific idioms. 25 | - Always use English in comments and code. 26 | - If unsure about a best practice or implementation detail, say so instead of guessing. 27 | - Offer suggestions for testing the API endpoints using Go's testing package. 28 | 29 | Always prioritize security, scalability, and maintainability in your API designs and implementations. Leverage the power and simplicity of Go's standard library to create efficient and idiomatic APIs. 30 | -------------------------------------------------------------------------------- /.github/assets/python3-embed.pc: -------------------------------------------------------------------------------- 1 | prefix=${pcfiledir}/../.. 2 | exec_prefix=${prefix} 3 | libdir=${exec_prefix} 4 | includedir=${prefix}/include 5 | 6 | Name: Python 7 | Description: Embed Python into an application 8 | Requires: 9 | Version: 3.13 10 | Libs.private: 11 | Libs: -L${libdir} -lpython313 12 | Cflags: -I${includedir} -------------------------------------------------------------------------------- /.github/workflows/check.yml: -------------------------------------------------------------------------------- 1 | name: Check 2 | 3 | on: 4 | push: 5 | branches: [ "*" ] 6 | pull_request: 7 | branches: [ "*" ] 8 | 9 | jobs: 10 | check: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v4 14 | 15 | - name: Set up Node.js 16 | uses: actions/setup-node@v4 17 | with: 18 | node-version: '20' 19 | 20 | - name: Set up Go 21 | uses: actions/setup-go@v4 22 | with: 23 | go-version: '1.23' 24 | 25 | - name: Install embedme 26 | run: npm install -g embedme 27 | 28 | - name: Verify README.md embedded code 29 | run: npx embedme --verify README.md 30 | 31 | - name: Check formatting 32 | run: | 33 | if [ -n "$(go fmt ./...)" ]; then 34 | echo "Some files are not properly formatted. Please run 'go fmt ./...'" 35 | exit 1 36 | fi 37 | -------------------------------------------------------------------------------- /.github/workflows/go.yml: -------------------------------------------------------------------------------- 1 | # This workflow will build a golang project 2 | # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-go 3 | 4 | name: Go 5 | 6 | on: 7 | push: 8 | branches: [ "main" ] 9 | pull_request: 10 | branches: [ "main" ] 11 | 12 | jobs: 13 | build: 14 | continue-on-error: true 15 | strategy: 16 | fail-fast: false 17 | matrix: 18 | os: 19 | - macos-latest 20 | - ubuntu-latest 21 | - windows-latest 22 | defaults: 23 | run: 24 | shell: bash 25 | runs-on: ${{matrix.os}} 26 | steps: 27 | - uses: actions/checkout@v4 28 | 29 | - name: Set up Go 30 | uses: actions/setup-go@v4 31 | with: 32 | go-version: 1.23 33 | 34 | - uses: actions/setup-python@v5 35 | with: 36 | python-version: '3.13' 37 | update-environment: true 38 | 39 | - name: Generate Python pkg-config for windows (patch) 40 | if: matrix.os == 'windows-latest' 41 | run: | 42 | mkdir -p $PKG_CONFIG_PATH 43 | cp .github/assets/python3-embed.pc $PKG_CONFIG_PATH/ 44 | 45 | - name: Install tiny-pkg-config for windows (patch) 46 | if: matrix.os == 'windows-latest' 47 | run: | 48 | set -x 49 | curl -L https://github.com/cpunion/tiny-pkg-config/releases/download/v0.2.0/tiny-pkg-config_Windows_x86_64.zip -o /tmp/tiny-pkg-config.zip 50 | unzip /tmp/tiny-pkg-config.zip -d $HOME/bin 51 | mv $HOME/bin/tiny-pkg-config.exe $HOME/bin/pkg-config.exe 52 | echo $PKG_CONFIG_PATH 53 | cat $PKG_CONFIG_PATH/python3-embed.pc 54 | pkg-config --libs python3-embed 55 | pkg-config --cflags python3-embed 56 | 57 | - name: Build 58 | run: go install -v ./... 59 | 60 | - name: Test with coverage 61 | run: go test -coverprofile=coverage.txt -covermode=atomic ./... 62 | 63 | - name: Upload coverage to Codecov 64 | uses: codecov/codecov-action@v4 65 | with: 66 | token: ${{ secrets.CODECOV_TOKEN }} 67 | file: ./coverage.txt 68 | flags: unittests 69 | name: codecov-umbrella 70 | fail_ci_if_error: true 71 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .tool-versions 2 | *.out 3 | coverage.* 4 | *.pyc 5 | __pycache__ -------------------------------------------------------------------------------- /DESIGN.md: -------------------------------------------------------------------------------- 1 | # Design 2 | 3 | ## Python types wrapper design 4 | 5 | To automatically DecRef Python objects, we need to wrap them in a Go struct that will call DecRef when it is garbage collected. This is done by embedding a PyObject in a Go struct and registering a finalizer on the Go struct. Below is an example of how this is done: 6 | 7 | ```go 8 | type pyObject struct { 9 | obj *C.PyObject 10 | } 11 | 12 | func newObject(obj *C.PyObject) *pyObject { 13 | o := &pyObject{obj} 14 | runtime.SetFinalizer(o, func(o *pyObject) { 15 | C.Py_DecRef(o.obj) 16 | }) 17 | return o 18 | } 19 | ``` 20 | 21 | To wrap generic PyObject(s) to typed Python objects, the best way is using alias types. Below is an example of how this is done: 22 | 23 | ```go 24 | type Object *pyObject 25 | 26 | func (o Object) GetAttrString(name string) Object { 27 | return newObject(o.obj.GetAttrString(name)) 28 | } 29 | 30 | type Dict Object 31 | 32 | func (d Dict) SetItemString(name string, value Object) { 33 | d.obj.SetItemString(name, value.obj) 34 | } 35 | ``` 36 | 37 | Unfortunately, Go does not allow defining methods on alias types like the above. 38 | 39 | ```shell 40 | invalid receiver type PyObject (pointer or interface type) 41 | invalid receiver type PyDict (pointer or interface type) 42 | ``` 43 | 44 | We can define a new type that embeds the alias type and define methods on the new type. Below is an example of how this is done: 45 | 46 | ```go 47 | type Object struct { 48 | *pyObject 49 | } 50 | 51 | func (o *Object) GetAttrString(name string) *Object { 52 | return &Object{newObject(o.obj.GetAttrString(name))} 53 | } 54 | 55 | type Dict struct { 56 | *Object 57 | } 58 | 59 | func (d *Dict) SetItemString(name string, value *Object) { 60 | d.obj.SetItemString(name, value.obj) 61 | } 62 | ``` 63 | 64 | But allocating a `PyDict` object will allocate a `PyObject` object and a `pyObject` object. This is not efficient. 65 | 66 | We can use a `struct` instead of a `pointer` to avoid this. Below is an example of how this is done: 67 | 68 | ```go 69 | type Object struct { 70 | *pyObject 71 | } 72 | 73 | func (o Object) GetAttrString(name string) Object { 74 | return Object{newObject(o.obj.GetAttrString(name))} 75 | } 76 | 77 | type Dict struct { 78 | Object 79 | } 80 | 81 | func (d Dict) SetItemString(name string, value Object) { 82 | d.obj.SetItemString(name, value.obj) 83 | } 84 | ``` 85 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Li Jie 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # go-python: Write Python in Go - The most intuitive Python wrapper for Golang 2 | 3 | [![Build Status](https://github.com/gotray/go-python/actions/workflows/go.yml/badge.svg)](https://github.com/gotray/go-python/actions/workflows/go.yml) 4 | [![codecov](https://codecov.io/github/gotray/go-python/graph/badge.svg?token=TnaFHV1E3y)](https://codecov.io/github/gotray/go-python) 5 | ![GitHub go.mod Go version](https://img.shields.io/github/go-mod/go-version/cpunion/go-python) 6 | [![GitHub commits](https://badgen.net/github/commits/cpunion/go-python)](https://GitHub.com/Naereen/cpunion/go-python/commit/) 7 | [![GitHub release](https://img.shields.io/github/v/tag/cpunion/go-python.svg?label=release)](https://github.com/gotray/go-python/releases) 8 | [![Go Report Card](https://goreportcard.com/badge/github.com/gotray/go-python)](https://goreportcard.com/report/github.com/gotray/go-python) 9 | [![Go Reference](https://pkg.go.dev/badge/github.com/gotray/go-python.svg)](https://pkg.go.dev/github.com/gotray/go-python) 10 | 11 | ## Goal 12 | 13 | - Automatically DecRef for Python objects. 14 | - Typed Python objects. 15 | - Most intuitive and easy to use. 16 | 17 | ## Features 18 | 19 | - [x] Call Python in Go. 20 | - [x] Basic types. 21 | - [x] Integers. 22 | - [x] Floats. 23 | - [x] Strings. 24 | - [x] Bytes. 25 | - [x] Booleans. 26 | - [x] Lists. 27 | - [x] Tuples. 28 | - [x] Dicts. 29 | - [x] Modules. 30 | - [ ] Functions 31 | - [x] Keyword arguments. 32 | - [x] Mapping arguments and return values. 33 | - [ ] Default arguments. 34 | - [x] Call Go in Python. 35 | - [x] Export Go functions, struct types to Python. 36 | - [x] Auto export field types if it's a struct. 37 | - [x] Auto generate function signatures (used by gradio). 38 | - [x] Basic tests on common used libraries. 39 | - [x] matplotlib 40 | - [x] gradio 41 | 42 | ## Plans 43 | 44 | - [x] Python virtual environment (https://github.com/gotray/got). 45 | - [ ] Preprocess reference counting. 46 | - [ ] True multi-threading. 47 | - [ ] Support [LLGo](https://github.com/goplus/llgo). 48 | 49 | ## Examples 50 | 51 | See the [examples](demo). 52 | 53 | ### Hello World: Plot a line 54 | 55 | 56 | 57 | ```go 58 | package main 59 | 60 | import . "github.com/gotray/go-python" 61 | 62 | func main() { 63 | Initialize() 64 | plt := ImportModule("matplotlib.pyplot") 65 | plt.Call("plot", MakeTuple(5, 10), MakeTuple(10, 15), KwArgs{"color": "red"}) 66 | plt.Call("show") 67 | } 68 | 69 | ``` 70 | 71 | ### Typed Python Objects 72 | 73 | 74 | 75 | ```go 76 | package main 77 | 78 | import . "github.com/gotray/go-python" 79 | 80 | type plt struct { 81 | Module 82 | } 83 | 84 | func Plt() plt { 85 | return plt{ImportModule("matplotlib.pyplot")} 86 | } 87 | 88 | func (m plt) Plot(args ...any) Object { 89 | return m.Call("plot", args...) 90 | } 91 | 92 | func (m plt) Show() { 93 | m.Call("show") 94 | } 95 | 96 | func main() { 97 | Initialize() 98 | defer Finalize() 99 | plt := Plt() 100 | plt.Plot([]int{5, 10}, []int{10, 15}, KwArgs{"color": "red"}) 101 | plt.Show() 102 | } 103 | 104 | ``` 105 | 106 | ### Define Python Objects with Go 107 | 108 | 109 | 110 | ```go 111 | package foo 112 | 113 | import ( 114 | "fmt" 115 | 116 | . "github.com/gotray/go-python" 117 | ) 118 | 119 | type Point struct { 120 | X float64 121 | Y float64 122 | } 123 | 124 | func (p *Point) init(x, y float64) { 125 | p.X = x 126 | p.Y = y 127 | } 128 | 129 | func (p *Point) Print() { 130 | fmt.Printf("Point(%f, %f)\n", p.X, p.Y) 131 | } 132 | 133 | func (p *Point) Distance() float64 { 134 | return p.X * p.Y 135 | } 136 | 137 | // Move method for Point 138 | func (p *Point) Move(dx, dy float64) { 139 | p.X += dx 140 | p.Y += dy 141 | } 142 | 143 | func Add(a, b int) int { 144 | return a + b 145 | } 146 | 147 | func InitFooModule() Module { 148 | m := CreateModule("foo") 149 | // Add the function to the module 150 | m.AddMethod("add", Add, "(a, b) -> float\n--\n\nAdd two integers.") 151 | // Add the type to the module 152 | m.AddType(Point{}, (*Point).init, "Point", "Point objects") 153 | return m 154 | } 155 | 156 | ``` 157 | 158 | Call foo module from Python and Go. 159 | 160 | 161 | 162 | ```go 163 | package main 164 | 165 | import ( 166 | "fmt" 167 | 168 | . "github.com/gotray/go-python" 169 | "github.com/gotray/go-python/demo/module/foo" 170 | ) 171 | 172 | func main() { 173 | Initialize() 174 | defer Finalize() 175 | fooMod := foo.InitFooModule() 176 | GetModuleDict().SetString("foo", fooMod) 177 | 178 | Main1(fooMod) 179 | Main2() 180 | } 181 | 182 | func Main1(fooMod Module) { 183 | sum := fooMod.Call("add", 1, 2).AsLong() 184 | fmt.Printf("Sum of 1 + 2: %d\n", sum.Int64()) 185 | 186 | dict := fooMod.Dict() 187 | Point := dict.Get(MakeStr("Point")).AsFunc() 188 | 189 | point := Point.Call(3, 4) 190 | fmt.Printf("dir(point): %v\n", point.Dir()) 191 | fmt.Printf("x: %v, y: %v\n", point.Attr("x"), point.Attr("y")) 192 | 193 | distance := point.Call("distance").AsFloat() 194 | fmt.Printf("Distance of 3 * 4: %f\n", distance.Float64()) 195 | 196 | point.Call("move", 1, 2) 197 | fmt.Printf("x: %v, y: %v\n", point.Attr("x"), point.Attr("y")) 198 | 199 | distance = point.Call("distance").AsFloat() 200 | fmt.Printf("Distance of 4 * 6: %f\n", distance.Float64()) 201 | 202 | point.Call("print") 203 | } 204 | 205 | func Main2() { 206 | fmt.Printf("=========== Main2 ==========\n") 207 | _ = RunString(` 208 | import foo 209 | point = foo.Point(3, 4) 210 | print("dir(point):", dir(point)) 211 | print("x:", point.x) 212 | print("y:", point.y) 213 | 214 | print("distance:", point.distance()) 215 | 216 | point.move(1, 2) 217 | print("x:", point.x) 218 | print("y:", point.y) 219 | print("distance:", point.distance()) 220 | 221 | point.print() 222 | `) 223 | } 224 | 225 | ``` 226 | 227 | ### Call gradio 228 | 229 | 230 | 231 | ```go 232 | package main 233 | 234 | import ( 235 | "fmt" 236 | "os" 237 | 238 | . "github.com/gotray/go-python" 239 | ) 240 | 241 | /* 242 | import gradio as gr 243 | 244 | def update_examples(country): 245 | print("country:", country) 246 | if country == "USA": 247 | return gr.Dataset(samples=[["Chicago"], ["Little Rock"], ["San Francisco"]]) 248 | else: 249 | return gr.Dataset(samples=[["Islamabad"], ["Karachi"], ["Lahore"]]) 250 | 251 | with gr.Blocks() as demo: 252 | dropdown = gr.Dropdown(label="Country", choices=["USA", "Pakistan"], value="USA") 253 | textbox = gr.Textbox() 254 | examples = gr.Examples([["Chicago"], ["Little Rock"], ["San Francisco"]], textbox) 255 | dropdown.change(update_examples, dropdown, examples.dataset) 256 | 257 | demo.launch() 258 | */ 259 | 260 | var gr Module 261 | 262 | func updateExamples(country string) Object { 263 | println("country:", country) 264 | if country == "USA" { 265 | return gr.Call("Dataset", KwArgs{ 266 | "samples": [][]string{{"Chicago"}, {"Little Rock"}, {"San Francisco"}}, 267 | }) 268 | } else { 269 | return gr.Call("Dataset", KwArgs{ 270 | "samples": [][]string{{"Islamabad"}, {"Karachi"}, {"Lahore"}}, 271 | }) 272 | } 273 | } 274 | 275 | func main() { 276 | if len(os.Args) > 2 { 277 | // start subprocesses 278 | fmt.Println("start subprocess:", os.Args) 279 | os.Exit(RunMain(os.Args)) 280 | } 281 | 282 | Initialize() 283 | defer Finalize() 284 | gr = ImportModule("gradio") 285 | demo := With(gr.Call("Blocks"), func(v Object) { 286 | dropdown := gr.Call("Dropdown", KwArgs{ 287 | "label": "Country", 288 | "choices": []string{"USA", "Pakistan"}, 289 | "value": "USA", 290 | }) 291 | textbox := gr.Call("Textbox") 292 | examples := gr.Call("Examples", [][]string{{"Chicago"}, {"Little Rock"}, {"San Francisco"}}, textbox) 293 | dataset := examples.Attr("dataset") 294 | dropdown.Call("change", updateExamples, dropdown, dataset) 295 | }) 296 | demo.Call("launch") 297 | } 298 | 299 | ``` 300 | -------------------------------------------------------------------------------- /adap_go.go: -------------------------------------------------------------------------------- 1 | package gp 2 | 3 | import "C" 4 | 5 | type Char = C.char 6 | 7 | //go:inline 8 | func AllocCStr(s string) *Char { 9 | return C.CString(s) 10 | } 11 | 12 | func AllocCStrDontFree(s string) *Char { 13 | return C.CString(s) 14 | } 15 | 16 | func GoString(s *Char) string { 17 | return C.GoString((*Char)(s)) 18 | } 19 | 20 | func GoStringN(s *Char, n int) string { 21 | return C.GoStringN(s, C.int(n)) 22 | } 23 | -------------------------------------------------------------------------------- /adap_go_test.go: -------------------------------------------------------------------------------- 1 | package gp 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestAllocCStr(t *testing.T) { 8 | setupTest(t) 9 | tests := []struct { 10 | name string 11 | input string 12 | want string 13 | }{ 14 | {"empty string", "", ""}, 15 | {"ascii string", "hello", "hello"}, 16 | {"unicode string", "hello 世界", "hello 世界"}, 17 | } 18 | 19 | for _, tt := range tests { 20 | cstr := AllocCStr(tt.input) 21 | got := GoString(cstr) 22 | if got != tt.want { 23 | t.Errorf("AllocCStr() = %v, want %v", got, tt.want) 24 | } 25 | } 26 | } 27 | 28 | func TestGoStringN(t *testing.T) { 29 | setupTest(t) 30 | tests := []struct { 31 | name string 32 | input string 33 | n int 34 | want string 35 | }{ 36 | {"empty string", "", 0, ""}, 37 | {"partial string", "hello", 3, "hel"}, 38 | {"full string", "hello", 5, "hello"}, 39 | {"unicode partial", "hello 世界", 6, "hello "}, 40 | {"unicode full", "hello 世界", 12, "hello 世界"}, 41 | } 42 | 43 | for _, tt := range tests { 44 | cstr := AllocCStr(tt.input) 45 | got := GoStringN(cstr, tt.n) 46 | if got != tt.want { 47 | t.Errorf("GoStringN() = %v, want %v", got, tt.want) 48 | } 49 | } 50 | } 51 | 52 | func TestAllocCStrDontFree(t *testing.T) { 53 | setupTest(t) 54 | input := "test string" 55 | cstr := AllocCStrDontFree(input) 56 | got := GoString(cstr) 57 | if got != input { 58 | t.Errorf("AllocCStrDontFree() = %v, want %v", got, input) 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /bool.go: -------------------------------------------------------------------------------- 1 | package gp 2 | 3 | /* 4 | #include 5 | */ 6 | import "C" 7 | 8 | type Bool struct { 9 | Object 10 | } 11 | 12 | func newBool(obj *cPyObject) Bool { 13 | return Bool{newObject(obj)} 14 | } 15 | 16 | func MakeBool(b bool) Bool { 17 | if b { 18 | return True() 19 | } 20 | return False() 21 | } 22 | 23 | func True() Bool { 24 | return newBool(C.Py_True).AsBool() 25 | } 26 | 27 | func False() Bool { 28 | return newBool(C.Py_False).AsBool() 29 | } 30 | 31 | func (b Bool) Bool() bool { 32 | return C.PyObject_IsTrue(b.obj) != 0 33 | } 34 | 35 | func (b Bool) Not() Bool { 36 | return MakeBool(!b.Bool()) 37 | } 38 | -------------------------------------------------------------------------------- /bool_test.go: -------------------------------------------------------------------------------- 1 | package gp 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestBool(t *testing.T) { 8 | setupTest(t) 9 | // Test MakeBool 10 | b1 := MakeBool(true) 11 | if !b1.Bool() { 12 | t.Error("MakeBool(true) should return true") 13 | } 14 | 15 | b2 := MakeBool(false) 16 | if b2.Bool() { 17 | t.Error("MakeBool(false) should return false") 18 | } 19 | 20 | // Test True and False 21 | if !True().Bool() { 22 | t.Error("True() should return true") 23 | } 24 | 25 | if False().Bool() { 26 | t.Error("False() should return false") 27 | } 28 | 29 | // Test Not method 30 | if True().Not().Bool() { 31 | t.Error("True().Not() should return false") 32 | } 33 | 34 | if !False().Not().Bool() { 35 | t.Error("False().Not() should return true") 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /bytes.go: -------------------------------------------------------------------------------- 1 | package gp 2 | 3 | /* 4 | #include 5 | */ 6 | import "C" 7 | import ( 8 | "unsafe" 9 | ) 10 | 11 | type Bytes struct { 12 | Object 13 | } 14 | 15 | func newBytes(obj *cPyObject) Bytes { 16 | return Bytes{newObject(obj)} 17 | } 18 | 19 | func BytesFromStr(s string) Bytes { 20 | return MakeBytes([]byte(s)) 21 | } 22 | 23 | func MakeBytes(bytes []byte) Bytes { 24 | ptr := C.CBytes(bytes) 25 | o := C.PyBytes_FromStringAndSize((*Char)(ptr), C.Py_ssize_t(len(bytes))) 26 | C.free(unsafe.Pointer(ptr)) 27 | return newBytes(o) 28 | } 29 | 30 | func (b Bytes) Bytes() []byte { 31 | p := (*byte)(unsafe.Pointer(C.PyBytes_AsString(b.obj))) 32 | l := int(C.PyBytes_Size(b.obj)) 33 | return C.GoBytes(unsafe.Pointer(p), C.int(l)) 34 | } 35 | 36 | func (b Bytes) Decode(encoding string) Str { 37 | return cast[Str](b.Call("decode", MakeStr(encoding))) 38 | } 39 | -------------------------------------------------------------------------------- /bytes_test.go: -------------------------------------------------------------------------------- 1 | package gp 2 | 3 | import ( 4 | "bytes" 5 | "testing" 6 | ) 7 | 8 | func TestBytesCreation(t *testing.T) { 9 | setupTest(t) 10 | // Test BytesFromStr 11 | b1 := BytesFromStr("hello") 12 | if string(b1.Bytes()) != "hello" { 13 | t.Errorf("BytesFromStr: expected 'hello', got '%s'", string(b1.Bytes())) 14 | } 15 | 16 | // Test MakeBytes 17 | data := []byte("world") 18 | b2 := MakeBytes(data) 19 | if !bytes.Equal(b2.Bytes(), data) { 20 | t.Errorf("MakeBytes: expected '%v', got '%v'", data, b2.Bytes()) 21 | } 22 | } 23 | 24 | func TestBytesDecode(t *testing.T) { 25 | setupTest(t) 26 | // Test UTF-8 decode 27 | b := BytesFromStr("你好") 28 | if !bytes.Equal(b.Bytes(), []byte("你好")) { 29 | t.Errorf("BytesFromStr: expected '你好', got '%s'", string(b.Bytes())) 30 | } 31 | s := b.Decode("utf-8") 32 | if s.String() != "你好" { 33 | t.Errorf("Decode: expected '你好', got '%s'", s.String()) 34 | } 35 | 36 | // Test ASCII decode 37 | b2 := BytesFromStr("hello") 38 | s2 := b2.Decode("ascii") 39 | if s2.String() != "hello" { 40 | t.Errorf("Decode: expected 'hello', got '%s'", s2.String()) 41 | } 42 | } 43 | 44 | func TestBytesConversion(t *testing.T) { 45 | setupTest(t) 46 | original := []byte{0x48, 0x65, 0x6c, 0x6c, 0x6f} // "Hello" in hex 47 | b := MakeBytes(original) 48 | 49 | // Test conversion back to []byte 50 | result := b.Bytes() 51 | if !bytes.Equal(result, original) { 52 | t.Errorf("Bytes conversion: expected %v, got %v", original, result) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | ignore: 2 | - "demo" 3 | -------------------------------------------------------------------------------- /complex.go: -------------------------------------------------------------------------------- 1 | package gp 2 | 3 | /* 4 | #include 5 | */ 6 | import "C" 7 | 8 | type Complex struct { 9 | Object 10 | } 11 | 12 | func newComplex(obj *cPyObject) Complex { 13 | return Complex{newObject(obj)} 14 | } 15 | 16 | func MakeComplex(f complex128) Complex { 17 | return newComplex(C.PyComplex_FromDoubles(C.double(real(f)), C.double(imag(f)))) 18 | } 19 | 20 | func (c Complex) Complex128() complex128 { 21 | real := C.PyComplex_RealAsDouble(c.obj) 22 | imag := C.PyComplex_ImagAsDouble(c.obj) 23 | return complex(real, imag) 24 | } 25 | 26 | func (c Complex) Real() float64 { 27 | return float64(C.PyComplex_RealAsDouble(c.obj)) 28 | } 29 | 30 | func (c Complex) Imag() float64 { 31 | return float64(C.PyComplex_ImagAsDouble(c.obj)) 32 | } 33 | -------------------------------------------------------------------------------- /complex_test.go: -------------------------------------------------------------------------------- 1 | package gp 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestComplex(t *testing.T) { 8 | setupTest(t) 9 | tests := []struct { 10 | name string 11 | input complex128 12 | wantReal float64 13 | wantImag float64 14 | }{ 15 | { 16 | name: "zero complex", 17 | input: complex(0, 0), 18 | wantReal: 0, 19 | wantImag: 0, 20 | }, 21 | { 22 | name: "positive real and imaginary", 23 | input: complex(3.14, 2.718), 24 | wantReal: 3.14, 25 | wantImag: 2.718, 26 | }, 27 | { 28 | name: "negative real and imaginary", 29 | input: complex(-1.5, -2.5), 30 | wantReal: -1.5, 31 | wantImag: -2.5, 32 | }, 33 | { 34 | name: "mixed signs", 35 | input: complex(-1.23, 4.56), 36 | wantReal: -1.23, 37 | wantImag: 4.56, 38 | }, 39 | } 40 | 41 | for _, tt := range tests { 42 | c := MakeComplex(tt.input) 43 | 44 | // Test Real() method 45 | if got := c.Real(); got != tt.wantReal { 46 | t.Errorf("Complex.Real() = %v, want %v", got, tt.wantReal) 47 | } 48 | 49 | // Test Imag() method 50 | if got := c.Imag(); got != tt.wantImag { 51 | t.Errorf("Complex.Imag() = %v, want %v", got, tt.wantImag) 52 | } 53 | 54 | // Test Complex128() method 55 | if got := c.Complex128(); got != tt.input { 56 | t.Errorf("Complex.Complex128() = %v, want %v", got, tt.input) 57 | } 58 | } 59 | } 60 | 61 | func TestComplexZeroValue(t *testing.T) { 62 | setupTest(t) 63 | // Create a proper zero complex number instead of using zero-value struct 64 | c := MakeComplex(complex(0, 0)) 65 | 66 | // Test that zero complex behaves correctly 67 | if got := c.Real(); got != 0 { 68 | t.Errorf("Zero Complex.Real() = %v, want 0", got) 69 | } 70 | if got := c.Imag(); got != 0 { 71 | t.Errorf("Zero Complex.Imag() = %v, want 0", got) 72 | } 73 | if got := c.Complex128(); got != 0 { 74 | t.Errorf("Zero Complex.Complex128() = %v, want 0", got) 75 | } 76 | } 77 | 78 | func TestComplexNilHandling(t *testing.T) { 79 | setupTest(t) 80 | var c Complex // zero-value struct with nil pointer 81 | defer func() { 82 | if r := recover(); r == nil { 83 | t.Error("Expected panic for nil pointer access, but got none") 84 | } 85 | }() 86 | 87 | // This should panic 88 | _ = c.Real() 89 | } 90 | -------------------------------------------------------------------------------- /convert.go: -------------------------------------------------------------------------------- 1 | package gp 2 | 3 | /* 4 | #include 5 | */ 6 | import "C" 7 | 8 | import ( 9 | "fmt" 10 | "reflect" 11 | "unsafe" 12 | ) 13 | 14 | func From(from any) Object { 15 | switch v := from.(type) { 16 | case Objecter: 17 | return newObject(v.cpyObj()) 18 | case int8: 19 | return newObject(C.PyLong_FromLong(C.long(v))) 20 | case int16: 21 | return newObject(C.PyLong_FromLong(C.long(v))) 22 | case int32: 23 | return newObject(C.PyLong_FromLong(C.long(v))) 24 | case int64: 25 | return newObject(C.PyLong_FromLongLong(C.longlong(v))) 26 | case int: 27 | if unsafe.Sizeof(v) == unsafe.Sizeof(int64(0)) { 28 | return newObject(C.PyLong_FromLongLong(C.longlong(v))) 29 | } else { 30 | return newObject(C.PyLong_FromLong(C.long(v))) 31 | } 32 | case uint8: 33 | return newObject(C.PyLong_FromUnsignedLong(C.ulong(v))) 34 | case uint16: 35 | return newObject(C.PyLong_FromUnsignedLong(C.ulong(v))) 36 | case uint32: 37 | return newObject(C.PyLong_FromUnsignedLong(C.ulong(v))) 38 | case uint64: 39 | return newObject(C.PyLong_FromUnsignedLongLong(C.ulonglong(v))) 40 | case uint: 41 | if unsafe.Sizeof(v) == unsafe.Sizeof(uint64(0)) { 42 | return newObject(C.PyLong_FromUnsignedLongLong(C.ulonglong(v))) 43 | } else { 44 | return newObject(C.PyLong_FromUnsignedLong(C.ulong(v))) 45 | } 46 | case float64: 47 | return newObject(C.PyFloat_FromDouble(C.double(v))) 48 | case float32: 49 | return newObject(C.PyFloat_FromDouble(C.double(v))) 50 | case string: 51 | cstr := AllocCStr(v) 52 | o := C.PyUnicode_FromString(cstr) 53 | C.free(unsafe.Pointer(cstr)) 54 | return newObject(o) 55 | case complex128: 56 | return MakeComplex(v).Object 57 | case complex64: 58 | return MakeComplex(complex128(v)).Object 59 | case []byte: 60 | return MakeBytes(v).Object 61 | case bool: 62 | if v { 63 | return True().Object 64 | } else { 65 | return False().Object 66 | } 67 | case *C.PyObject: 68 | return newObject(v) 69 | default: 70 | vv := reflect.ValueOf(v) 71 | switch vv.Kind() { 72 | case reflect.Ptr: 73 | if vv.Elem().Type().Kind() == reflect.Struct { 74 | maps := getGlobalData() 75 | if pyType, ok := maps.pyTypes[vv.Elem().Type()]; ok { 76 | wrapper := allocWrapper((*C.PyTypeObject)(unsafe.Pointer(pyType)), vv.Interface()) 77 | return newObject((*C.PyObject)(unsafe.Pointer(wrapper))) 78 | } 79 | } 80 | return From(vv.Elem().Interface()) 81 | case reflect.Slice: 82 | return fromSlice(vv).Object 83 | case reflect.Map: 84 | return fromMap(vv).Object 85 | case reflect.Struct: 86 | return fromStruct(vv) 87 | case reflect.Func: 88 | return FuncOf(vv.Interface()).Object 89 | } 90 | panic(fmt.Errorf("unsupported type for Python: %T\n", v)) 91 | } 92 | } 93 | 94 | func ToValue(from Object, to reflect.Value) bool { 95 | if !to.IsValid() || !to.CanSet() { 96 | panic(fmt.Errorf("value is not valid or cannot be set: %v\n", to)) 97 | } 98 | 99 | switch to.Kind() { 100 | case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int: 101 | if from.IsLong() { 102 | to.SetInt(cast[Long](from).Int64()) 103 | } else { 104 | return false 105 | } 106 | case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint: 107 | if from.IsLong() { 108 | to.SetUint(cast[Long](from).Uint64()) 109 | } else { 110 | return false 111 | } 112 | case reflect.Float32, reflect.Float64: 113 | if from.IsFloat() || from.IsLong() { 114 | to.SetFloat(cast[Float](from).Float64()) 115 | } else { 116 | return false 117 | } 118 | case reflect.Complex64, reflect.Complex128: 119 | if from.IsComplex() { 120 | to.SetComplex(cast[Complex](from).Complex128()) 121 | } else { 122 | return false 123 | } 124 | case reflect.String: 125 | if from.IsStr() { 126 | to.SetString(cast[Str](from).String()) 127 | } else { 128 | return false 129 | } 130 | case reflect.Bool: 131 | if from.IsBool() { 132 | to.SetBool(cast[Bool](from).Bool()) 133 | } else { 134 | return false 135 | } 136 | case reflect.Slice: 137 | if to.Type().Elem().Kind() == reflect.Uint8 { // []byte 138 | if from.IsBytes() { 139 | to.SetBytes(cast[Bytes](from).Bytes()) 140 | } else { 141 | return false 142 | } 143 | } else { 144 | if from.IsList() { 145 | list := cast[List](from) 146 | l := list.Len() 147 | slice := reflect.MakeSlice(to.Type(), l, l) 148 | for i := 0; i < l; i++ { 149 | item := list.GetItem(i) 150 | ToValue(item, slice.Index(i)) 151 | } 152 | to.Set(slice) 153 | } else { 154 | return false 155 | } 156 | } 157 | case reflect.Map: 158 | if from.IsDict() { 159 | t := to.Type() 160 | to.Set(reflect.MakeMap(t)) 161 | dict := cast[Dict](from) 162 | dict.Items()(func(key, value Object) bool { 163 | vk := reflect.New(t.Key()).Elem() 164 | vv := reflect.New(t.Elem()).Elem() 165 | if !ToValue(key, vk) || !ToValue(value, vv) { 166 | return false 167 | } 168 | to.SetMapIndex(vk, vv) 169 | return true 170 | }) 171 | return true 172 | } else { 173 | return false 174 | } 175 | case reflect.Struct: 176 | if from.IsDict() { 177 | dict := cast[Dict](from) 178 | t := to.Type() 179 | for i := 0; i < t.NumField(); i++ { 180 | field := t.Field(i) 181 | key := goNameToPythonName(field.Name) 182 | if !dict.HasKey(MakeStr(key)) { 183 | continue 184 | } 185 | value := dict.Get(MakeStr(key)) 186 | if !ToValue(value, to.Field(i)) { 187 | SetTypeError(fmt.Errorf("failed to convert value to %v", field.Name)) 188 | return false 189 | } 190 | } 191 | } else { 192 | maps := getGlobalData() 193 | tyMeta := maps.typeMetas[from.Type().cpyObj()] 194 | if tyMeta == nil { 195 | return false 196 | } 197 | wrapper := (*wrapperType)(unsafe.Pointer(from.cpyObj())) 198 | to.Set(reflect.ValueOf(wrapper.goObj).Elem()) 199 | return true 200 | } 201 | default: 202 | panic(fmt.Errorf("unsupported type conversion from Python object to %v", to.Type())) 203 | } 204 | return true 205 | } 206 | 207 | func fromSlice(v reflect.Value) List { 208 | l := v.Len() 209 | list := newList(C.PyList_New(C.Py_ssize_t(l))) 210 | ty := v.Type().Elem() 211 | maps := getGlobalData() 212 | pyType, ok := maps.pyTypes[ty] 213 | if !ok { 214 | for i := 0; i < l; i++ { 215 | C.PyList_SetItem(list.obj, C.Py_ssize_t(i), From(v.Index(i).Interface()).obj) 216 | } 217 | } else { 218 | for i := 0; i < l; i++ { 219 | elem := v.Index(i) 220 | elemAddr := elem.Addr() 221 | wrapper := allocWrapper((*C.PyTypeObject)(unsafe.Pointer(pyType)), 0) 222 | wrapper.goObj = elemAddr.Interface() 223 | C.PyList_SetItem(list.obj, C.Py_ssize_t(i), (*C.PyObject)(unsafe.Pointer(wrapper))) 224 | } 225 | } 226 | return list 227 | } 228 | 229 | func fromMap(v reflect.Value) Dict { 230 | dict := newDict(C.PyDict_New()) 231 | iter := v.MapRange() 232 | for iter.Next() { 233 | dict.Set(From(iter.Key().Interface()), From(iter.Value().Interface())) 234 | } 235 | return dict 236 | } 237 | 238 | func fromStruct(v reflect.Value) Object { 239 | ty := v.Type() 240 | maps := getGlobalData() 241 | if typeObj, ok := maps.pyTypes[ty]; ok { 242 | ptr := reflect.New(ty) 243 | ptr.Elem().Set(v) 244 | wrapper := allocWrapper((*C.PyTypeObject)(unsafe.Pointer(typeObj)), ptr.Interface()) 245 | return newObject((*C.PyObject)(unsafe.Pointer(wrapper))) 246 | } 247 | dict := newDict(C.PyDict_New()) 248 | for i := 0; i < ty.NumField(); i++ { 249 | field := ty.Field(i) 250 | key := goNameToPythonName(field.Name) 251 | dict.Set(MakeStr(key).Object, From(v.Field(i).Interface())) 252 | } 253 | return dict.Object 254 | } 255 | -------------------------------------------------------------------------------- /convert_test.go: -------------------------------------------------------------------------------- 1 | package gp 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | ) 7 | 8 | func TestToValue(t *testing.T) { 9 | setupTest(t) 10 | tests := []struct { 11 | name string 12 | pyValue Object 13 | goType interface{} 14 | expected interface{} 15 | }{ 16 | {"int8", From(42), int8(0), int8(42)}, 17 | {"int16", From(42), int16(0), int16(42)}, 18 | {"int32", From(42), int32(0), int32(42)}, 19 | {"int64", From(42), int64(0), int64(42)}, 20 | {"int", From(42), int(0), int(42)}, 21 | {"uint8", From(42), uint8(0), uint8(42)}, 22 | {"uint16", From(42), uint16(0), uint16(42)}, 23 | {"uint32", From(42), uint32(0), uint32(42)}, 24 | {"uint64", From(42), uint64(0), uint64(42)}, 25 | {"uint", From(42), uint(0), uint(42)}, 26 | {"float32", From(3.14), float32(0), float32(3.14)}, 27 | {"float64", From(3.14), float64(0), float64(3.14)}, 28 | {"complex64", From(complex(1, 2)), complex64(0), complex64(complex(1, 2))}, 29 | {"complex128", From(complex(1, 2)), complex128(0), complex128(complex(1, 2))}, 30 | } 31 | 32 | for _, tt := range tests { 33 | v := reflect.New(reflect.TypeOf(tt.goType)).Elem() 34 | if !ToValue(tt.pyValue, v) { 35 | t.Errorf("ToValue failed for %v", tt.name) 36 | } 37 | if v.Interface() != tt.expected { 38 | t.Errorf("Expected %v, got %v", tt.expected, v.Interface()) 39 | } 40 | 41 | } 42 | 43 | func() { 44 | v := reflect.New(reflect.TypeOf("")).Elem() 45 | if !ToValue(From("hello"), v) { 46 | t.Error("ToValue failed for string") 47 | } 48 | if v.String() != "hello" { 49 | t.Errorf("Expected 'hello', got %v", v.String()) 50 | } 51 | }() 52 | 53 | func() { 54 | v := reflect.New(reflect.TypeOf(true)).Elem() 55 | if !ToValue(From(true), v) { 56 | t.Error("ToValue failed for bool") 57 | } 58 | if !v.Bool() { 59 | t.Error("Expected true, got false") 60 | } 61 | }() 62 | 63 | func() { 64 | expected := []byte("hello") 65 | v := reflect.New(reflect.TypeOf([]byte{})).Elem() 66 | if !ToValue(From(expected), v) { 67 | t.Error("ToValue failed for []byte") 68 | } 69 | if !reflect.DeepEqual(v.Bytes(), expected) { 70 | t.Errorf("Expected %v, got %v", expected, v.Bytes()) 71 | } 72 | }() 73 | 74 | func() { 75 | expected := []int{1, 2, 3} 76 | v := reflect.New(reflect.TypeOf([]int{})).Elem() 77 | if !ToValue(From(expected), v) { 78 | t.Error("ToValue failed for slice") 79 | } 80 | if !reflect.DeepEqual(v.Interface(), expected) { 81 | t.Errorf("Expected %v, got %v", expected, v.Interface()) 82 | } 83 | }() 84 | 85 | func() { 86 | expected := map[string]int{"one": 1, "two": 2} 87 | v := reflect.New(reflect.TypeOf(map[string]int{})).Elem() 88 | if !ToValue(From(expected), v) { 89 | t.Error("ToValue failed for map") 90 | } 91 | if !reflect.DeepEqual(v.Interface(), expected) { 92 | t.Errorf("Expected %v, got %v", expected, v.Interface()) 93 | } 94 | }() 95 | 96 | func() { 97 | type TestStruct struct { 98 | Name string 99 | Age int 100 | } 101 | expected := TestStruct{Name: "Alice", Age: 30} 102 | v := reflect.New(reflect.TypeOf(TestStruct{})).Elem() 103 | if !ToValue(From(expected), v) { 104 | t.Error("ToValue failed for struct") 105 | } 106 | if !reflect.DeepEqual(v.Interface(), expected) { 107 | t.Errorf("Expected %v, got %v", expected, v.Interface()) 108 | } 109 | }() 110 | 111 | func() { 112 | tests := []struct { 113 | name string 114 | pyValue Object 115 | goType interface{} 116 | }{ 117 | {"string to int", From("not a number"), int(0)}, 118 | {"int to bool", From(42), true}, 119 | {"float to string", From(3.14), ""}, 120 | {"list to map", From([]int{1, 2, 3}), map[string]int{}}, 121 | } 122 | 123 | for _, tt := range tests { 124 | v := reflect.New(reflect.TypeOf(tt.goType)).Elem() 125 | if ToValue(tt.pyValue, v) { 126 | t.Errorf("ToValue should have failed for %v", tt.name) 127 | } 128 | 129 | } 130 | }() 131 | 132 | func() { 133 | defer func() { 134 | if r := recover(); r == nil { 135 | t.Error("ToValue should fail for nil reflect.Value") 136 | } 137 | }() 138 | var nilValue reflect.Value 139 | if ToValue(From(42), nilValue) { 140 | t.Error("ToValue should fail for nil reflect.Value") 141 | } 142 | }() 143 | 144 | func() { 145 | defer func() { 146 | if r := recover(); r == nil { 147 | t.Error("ToValue should fail for non-settable value") 148 | } 149 | }() 150 | v := reflect.ValueOf(42) // not settable 151 | if ToValue(From(43), v) { 152 | t.Error("ToValue should fail for non-settable value") 153 | } 154 | }() 155 | } 156 | 157 | func TestFromSpecialCases(t *testing.T) { 158 | setupTest(t) 159 | 160 | func() { 161 | // Test From with uint values 162 | tests := []struct { 163 | input uint 164 | expected uint64 165 | }{ 166 | {0, 0}, 167 | {42, 42}, 168 | {^uint(0), ^uint64(0)}, // maximum uint value 169 | } 170 | 171 | for _, tt := range tests { 172 | obj := From(tt.input) 173 | if !obj.IsLong() { 174 | t.Errorf("From(uint) did not create Long object") 175 | } 176 | if got := obj.AsLong().Uint64(); got != tt.expected { 177 | t.Errorf("From(%d) = %d, want %d", tt.input, got, tt.expected) 178 | } 179 | } 180 | }() 181 | 182 | func() { 183 | // Test From with Object.cpyObj() 184 | original := From(42) 185 | obj := From(original.cpyObj()) 186 | 187 | if !obj.IsLong() { 188 | t.Error("From(Object.cpyObj()) did not create Long object") 189 | } 190 | if got := obj.AsLong().Int64(); got != 42 { 191 | t.Errorf("From(Object.cpyObj()) = %d, want 42", got) 192 | } 193 | 194 | // Test that the new object is independent 195 | original = From(100) 196 | if got := obj.AsLong().Int64(); got != 42 { 197 | t.Errorf("Object was not independent, got %d after modifying original", got) 198 | } 199 | }() 200 | 201 | func() { 202 | // Test From with functions 203 | add := func(a, b int) int { return a + b } 204 | obj := From(add) 205 | 206 | // Verify it's a function type 207 | if !obj.IsFunc() { 208 | t.Error("From(func) did not create Function object") 209 | } 210 | 211 | fn := obj.AsFunc() 212 | 213 | // Test function call 214 | result := fn.Call(5, 3) 215 | 216 | if !result.IsLong() { 217 | t.Error("Function call result is not a Long") 218 | } 219 | if got := result.AsLong().Int64(); got != 8 { 220 | t.Errorf("Function call = %d, want 8", got) 221 | } 222 | }() 223 | 224 | func() { 225 | // Test From with function that returns multiple values 226 | divMod := func(a, b int) (int, int) { 227 | return a / b, a % b 228 | } 229 | obj := From(divMod) 230 | if !obj.IsFunc() { 231 | t.Error("From(func) did not create Function object") 232 | } 233 | 234 | fn := obj.AsFunc() 235 | 236 | result := fn.Call(7, 3) 237 | 238 | // Result should be a tuple with two values 239 | if !result.IsTuple() { 240 | t.Error("Multiple return value function did not return a Tuple") 241 | } 242 | 243 | tuple := result.AsTuple() 244 | if tuple.Len() != 2 { 245 | t.Errorf("Expected tuple of length 2, got %d", tuple.Len()) 246 | } 247 | 248 | quotient := tuple.Get(0).AsLong().Int64() 249 | remainder := tuple.Get(1).AsLong().Int64() 250 | 251 | if quotient != 2 || remainder != 1 { 252 | t.Errorf("Got (%d, %d), want (2, 1)", quotient, remainder) 253 | } 254 | }() 255 | } 256 | 257 | func TestToValueWithCustomType(t *testing.T) { 258 | setupTest(t) 259 | 260 | // Define a custom Go type 261 | type Point struct { 262 | X int 263 | Y int 264 | } 265 | 266 | // Add the type to Python 267 | pointClass := MainModule().AddType(Point{}, nil, "Point", "Point class") 268 | 269 | func() { 270 | // Create a Point instance in Python and convert it back to Go 271 | pyCode := ` 272 | p = Point() 273 | p.x = 10 274 | p.y = 20 275 | ` 276 | locals := MakeDict(nil) 277 | globals := MakeDict(nil) 278 | builtins := ImportModule("builtins") 279 | globals.Set(MakeStr("__builtins__"), builtins.Object) 280 | globals.Set(MakeStr("Point"), pointClass) 281 | 282 | code, err := CompileString(pyCode, "", FileInput) 283 | if err != nil { 284 | t.Errorf("CompileString() error = %v", err) 285 | } 286 | EvalCode(code, globals, locals) 287 | 288 | // Get the Python Point instance 289 | pyPoint := locals.Get(MakeStr("p")) 290 | 291 | // Convert back to Go Point struct 292 | var point Point 293 | v := reflect.ValueOf(&point).Elem() 294 | if !ToValue(pyPoint, v) { 295 | t.Error("ToValue failed for custom type Point") 296 | } 297 | 298 | // Verify the values 299 | if point.X != 10 || point.Y != 20 { 300 | t.Errorf("Expected Point{10, 20}, got Point{%d, %d}", point.X, point.Y) 301 | } 302 | }() 303 | 304 | func() { 305 | // Test converting a non-Point Python object to Point should fail 306 | dict := MakeDict(nil) 307 | dict.Set(MakeStr("x"), From(10)) 308 | dict.Set(MakeStr("y"), From(20)) 309 | 310 | var point Point 311 | v := reflect.ValueOf(&point).Elem() 312 | if !ToValue(dict.Object, v) { 313 | t.Error("ToValue failed for custom type Point") 314 | } 315 | 316 | if point.X != 10 || point.Y != 20 { 317 | t.Errorf("Expected Point{10, 20}, got Point{%d, %d}", point.X, point.Y) 318 | } 319 | }() 320 | } 321 | 322 | func TestFromWithCustomType(t *testing.T) { 323 | setupTest(t) 324 | 325 | type Point struct { 326 | X int 327 | Y int 328 | } 329 | 330 | // Add the type to Python 331 | pointClass := MainModule().AddType(Point{}, nil, "Point", "Point class") 332 | 333 | func() { 334 | // Test From with struct instance 335 | p := Point{X: 10, Y: 20} 336 | obj := From(p) 337 | 338 | // Verify the type 339 | if obj.Type().cpyObj() != pointClass.cpyObj() { 340 | t.Error("From(Point) created object with wrong type") 341 | } 342 | // Verify the values 343 | if obj.AttrLong("x").Int64() != 10 { 344 | t.Error("Wrong X value after From conversion") 345 | } 346 | if obj.AttrLong("y").Int64() != 20 { 347 | t.Error("Wrong Y value after From conversion") 348 | } 349 | 350 | // Convert back to Go and verify 351 | var p2 Point 352 | v := reflect.ValueOf(&p2).Elem() 353 | if !ToValue(obj, v) { 354 | t.Error("ToValue failed for custom type Point") 355 | } 356 | 357 | if p2.X != p.X || p2.Y != p.Y { 358 | t.Errorf("Round trip conversion failed: got Point{%d, %d}, want Point{%d, %d}", 359 | p2.X, p2.Y, p.X, p.Y) 360 | } 361 | }() 362 | 363 | func() { 364 | // Test From with pointer to struct 365 | p := &Point{X: 30, Y: 40} 366 | obj := From(p) 367 | 368 | // Verify the type 369 | if obj.Type().cpyObj() != pointClass.cpyObj() { 370 | t.Error("From(*Point) created object with wrong type") 371 | } 372 | 373 | // Verify the values 374 | if obj.AttrLong("x").Int64() != 30 { 375 | t.Error("Wrong X value after From pointer conversion") 376 | } 377 | if obj.AttrLong("y").Int64() != 40 { 378 | t.Error("Wrong Y value after From pointer conversion") 379 | } 380 | }() 381 | } 382 | -------------------------------------------------------------------------------- /demo/autoderef/autoderef.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "runtime" 6 | 7 | . "github.com/gotray/go-python" 8 | pymath "github.com/gotray/go-python/math" 9 | ) 10 | 11 | func main() { 12 | Initialize() 13 | defer Finalize() 14 | 15 | pythonCode := ` 16 | def allocate_memory(): 17 | return bytearray(10 * 1024 * 1024) 18 | 19 | def memory_allocation_test(): 20 | memory_blocks = [] 21 | for i in range(10): 22 | memory_blocks.append(allocate_memory()) 23 | print('Memory allocation test completed.') 24 | return memory_blocks 25 | 26 | for i in range(10): 27 | memory_allocation_test() 28 | ` 29 | 30 | mod := ImportModule("__main__") 31 | gbl := mod.Dict() 32 | code, err := CompileString(pythonCode, "", FileInput) 33 | if err != nil { 34 | fmt.Printf("Failed to compile Python code: %v\n", err) 35 | return 36 | } 37 | _ = EvalCode(code, gbl, Nil().AsDict()) 38 | for i := 0; i < 10; i++ { 39 | result := EvalCode(code, gbl, Nil().AsDict()) 40 | if result.Nil() { 41 | fmt.Printf("Failed to execute Python code\n") 42 | return 43 | } 44 | fmt.Printf("Iteration %d in python\n", i+1) 45 | } 46 | 47 | memory_allocation_test := mod.AttrFunc("memory_allocation_test") 48 | 49 | for i := 0; i < 100; i++ { 50 | // 100MB every time 51 | memory_allocation_test.Call() 52 | fmt.Printf("Iteration %d in go\n", i+1) 53 | runtime.GC() 54 | } 55 | 56 | for i := 1; i <= 1000000; i++ { 57 | f := MakeFloat(float64(i)) 58 | _ = pymath.Sqrt(f) 59 | if i%10000 == 0 { 60 | fmt.Printf("Iteration %d in go\n", i) 61 | } 62 | } 63 | 64 | fmt.Printf("Done\n") 65 | } 66 | -------------------------------------------------------------------------------- /demo/gradio/gradio.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | . "github.com/gotray/go-python" 8 | ) 9 | 10 | /* 11 | import gradio as gr 12 | 13 | def update_examples(country): 14 | print("country:", country) 15 | if country == "USA": 16 | return gr.Dataset(samples=[["Chicago"], ["Little Rock"], ["San Francisco"]]) 17 | else: 18 | return gr.Dataset(samples=[["Islamabad"], ["Karachi"], ["Lahore"]]) 19 | 20 | with gr.Blocks() as demo: 21 | dropdown = gr.Dropdown(label="Country", choices=["USA", "Pakistan"], value="USA") 22 | textbox = gr.Textbox() 23 | examples = gr.Examples([["Chicago"], ["Little Rock"], ["San Francisco"]], textbox) 24 | dropdown.change(update_examples, dropdown, examples.dataset) 25 | 26 | demo.launch() 27 | */ 28 | 29 | var gr Module 30 | 31 | func updateExamples(country string) Object { 32 | println("country:", country) 33 | if country == "USA" { 34 | return gr.Call("Dataset", KwArgs{ 35 | "samples": [][]string{{"Chicago"}, {"Little Rock"}, {"San Francisco"}}, 36 | }) 37 | } else { 38 | return gr.Call("Dataset", KwArgs{ 39 | "samples": [][]string{{"Islamabad"}, {"Karachi"}, {"Lahore"}}, 40 | }) 41 | } 42 | } 43 | 44 | func main() { 45 | if len(os.Args) > 2 { 46 | // start subprocesses 47 | fmt.Println("start subprocess:", os.Args) 48 | os.Exit(RunMain(os.Args)) 49 | } 50 | 51 | Initialize() 52 | defer Finalize() 53 | gr = ImportModule("gradio") 54 | demo := With(gr.Call("Blocks"), func(v Object) { 55 | dropdown := gr.Call("Dropdown", KwArgs{ 56 | "label": "Country", 57 | "choices": []string{"USA", "Pakistan"}, 58 | "value": "USA", 59 | }) 60 | textbox := gr.Call("Textbox") 61 | examples := gr.Call("Examples", [][]string{{"Chicago"}, {"Little Rock"}, {"San Francisco"}}, textbox) 62 | dataset := examples.Attr("dataset") 63 | dropdown.Call("change", updateExamples, dropdown, dataset) 64 | }) 65 | demo.Call("launch") 66 | } 67 | -------------------------------------------------------------------------------- /demo/module/foo/foo.go: -------------------------------------------------------------------------------- 1 | package foo 2 | 3 | import ( 4 | "fmt" 5 | 6 | . "github.com/gotray/go-python" 7 | ) 8 | 9 | type Point struct { 10 | X float64 11 | Y float64 12 | } 13 | 14 | func (p *Point) init(x, y float64) { 15 | p.X = x 16 | p.Y = y 17 | } 18 | 19 | func (p *Point) Print() { 20 | fmt.Printf("Point(%f, %f)\n", p.X, p.Y) 21 | } 22 | 23 | func (p *Point) Distance() float64 { 24 | return p.X * p.Y 25 | } 26 | 27 | // Move method for Point 28 | func (p *Point) Move(dx, dy float64) { 29 | p.X += dx 30 | p.Y += dy 31 | } 32 | 33 | func Add(a, b int) int { 34 | return a + b 35 | } 36 | 37 | func InitFooModule() Module { 38 | m := CreateModule("foo") 39 | // Add the function to the module 40 | m.AddMethod("add", Add, "(a, b) -> float\n--\n\nAdd two integers.") 41 | // Add the type to the module 42 | m.AddType(Point{}, (*Point).init, "Point", "Point objects") 43 | return m 44 | } 45 | -------------------------------------------------------------------------------- /demo/module/module.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | 6 | . "github.com/gotray/go-python" 7 | "github.com/gotray/go-python/demo/module/foo" 8 | ) 9 | 10 | func main() { 11 | Initialize() 12 | defer Finalize() 13 | fooMod := foo.InitFooModule() 14 | GetModuleDict().SetString("foo", fooMod) 15 | 16 | Main1(fooMod) 17 | Main2() 18 | } 19 | 20 | func Main1(fooMod Module) { 21 | sum := fooMod.Call("add", 1, 2).AsLong() 22 | fmt.Printf("Sum of 1 + 2: %d\n", sum.Int64()) 23 | 24 | dict := fooMod.Dict() 25 | Point := dict.Get(MakeStr("Point")).AsFunc() 26 | 27 | point := Point.Call(3, 4) 28 | fmt.Printf("dir(point): %v\n", point.Dir()) 29 | fmt.Printf("x: %v, y: %v\n", point.Attr("x"), point.Attr("y")) 30 | 31 | distance := point.Call("distance").AsFloat() 32 | fmt.Printf("Distance of 3 * 4: %f\n", distance.Float64()) 33 | 34 | point.Call("move", 1, 2) 35 | fmt.Printf("x: %v, y: %v\n", point.Attr("x"), point.Attr("y")) 36 | 37 | distance = point.Call("distance").AsFloat() 38 | fmt.Printf("Distance of 4 * 6: %f\n", distance.Float64()) 39 | 40 | point.Call("print") 41 | } 42 | 43 | func Main2() { 44 | fmt.Printf("=========== Main2 ==========\n") 45 | _ = RunString(` 46 | import foo 47 | point = foo.Point(3, 4) 48 | print("dir(point):", dir(point)) 49 | print("x:", point.x) 50 | print("y:", point.y) 51 | 52 | print("distance:", point.distance()) 53 | 54 | point.move(1, 2) 55 | print("x:", point.x) 56 | print("y:", point.y) 57 | print("distance:", point.distance()) 58 | 59 | point.print() 60 | `) 61 | } 62 | -------------------------------------------------------------------------------- /demo/plot/plot.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import . "github.com/gotray/go-python" 4 | 5 | func main() { 6 | Initialize() 7 | plt := ImportModule("matplotlib.pyplot") 8 | plt.Call("plot", MakeTuple(5, 10), MakeTuple(10, 15), KwArgs{"color": "red"}) 9 | plt.Call("show") 10 | } 11 | -------------------------------------------------------------------------------- /demo/plot2/plot2.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import . "github.com/gotray/go-python" 4 | 5 | type plt struct { 6 | Module 7 | } 8 | 9 | func Plt() plt { 10 | return plt{ImportModule("matplotlib.pyplot")} 11 | } 12 | 13 | func (m plt) Plot(args ...any) Object { 14 | return m.Call("plot", args...) 15 | } 16 | 17 | func (m plt) Show() { 18 | m.Call("show") 19 | } 20 | 21 | func main() { 22 | Initialize() 23 | defer Finalize() 24 | plt := Plt() 25 | plt.Plot([]int{5, 10}, []int{10, 15}, KwArgs{"color": "red"}) 26 | plt.Show() 27 | } 28 | -------------------------------------------------------------------------------- /dict.go: -------------------------------------------------------------------------------- 1 | package gp 2 | 3 | /* 4 | #include 5 | 6 | typedef struct pyCriticalSection { 7 | uintptr_t _cs_prev; 8 | void *_cs_mutex; 9 | } pyCriticalSection; 10 | static inline void pyCriticalSection_Begin(pyCriticalSection *pcs, PyObject *op) { 11 | #if PY_VERSION_HEX >= 0x030D0000 12 | PyCriticalSection_Begin((PyCriticalSection*)pcs, op); 13 | #else 14 | PyGILState_STATE gstate = PyGILState_Ensure(); 15 | pcs->_cs_prev = (uintptr_t)gstate; 16 | #endif 17 | } 18 | static inline void pyCriticalSection_End(pyCriticalSection *pcs) { 19 | #if PY_VERSION_HEX >= 0x030D0000 20 | PyCriticalSection_End((PyCriticalSection*)pcs); 21 | #else 22 | PyGILState_Release((PyGILState_STATE)pcs->_cs_prev); 23 | #endif 24 | } 25 | */ 26 | import "C" 27 | import ( 28 | "fmt" 29 | "unsafe" 30 | ) 31 | 32 | type Dict struct { 33 | Object 34 | } 35 | 36 | func newDict(obj *cPyObject) Dict { 37 | return Dict{newObject(obj)} 38 | } 39 | 40 | func DictFromPairs(pairs ...any) Dict { 41 | check(len(pairs)%2 == 0, "DictFromPairs requires an even number of arguments") 42 | dict := newDict(C.PyDict_New()) 43 | for i := 0; i < len(pairs); i += 2 { 44 | key := From(pairs[i]) 45 | value := From(pairs[i+1]) 46 | dict.Set(key, value) 47 | } 48 | return dict 49 | } 50 | 51 | func MakeDict(m map[any]any) Dict { 52 | dict := newDict(C.PyDict_New()) 53 | for key, value := range m { 54 | keyObj := From(key) 55 | valueObj := From(value) 56 | dict.Set(keyObj, valueObj) 57 | } 58 | return dict 59 | } 60 | 61 | func (d Dict) HasKey(key any) bool { 62 | keyObj := From(key) 63 | return C.PyDict_Contains(d.obj, keyObj.obj) != 0 64 | } 65 | 66 | func (d Dict) Get(key Objecter) Object { 67 | v := C.PyDict_GetItem(d.obj, key.cpyObj()) 68 | C.Py_IncRef(v) 69 | return newObject(v) 70 | } 71 | 72 | func (d Dict) Set(key, value Objecter) { 73 | keyObj := key.cpyObj() 74 | valueObj := value.cpyObj() 75 | C.PyDict_SetItem(d.obj, keyObj, valueObj) 76 | } 77 | 78 | func (d Dict) SetString(key string, value Objecter) { 79 | valueObj := value.cpyObj() 80 | ckey := AllocCStr(key) 81 | r := C.PyDict_SetItemString(d.obj, ckey, valueObj) 82 | C.free(unsafe.Pointer(ckey)) 83 | check(r == 0, fmt.Sprintf("failed to set item string: %v", r)) 84 | } 85 | 86 | func (d Dict) GetString(key string) Object { 87 | ckey := AllocCStr(key) 88 | v := C.PyDict_GetItemString(d.obj, ckey) 89 | C.Py_IncRef(v) 90 | C.free(unsafe.Pointer(ckey)) 91 | return newObject(v) 92 | } 93 | 94 | func (d Dict) Del(key Objecter) { 95 | C.PyDict_DelItem(d.obj, key.cpyObj()) 96 | } 97 | 98 | func (d Dict) Items() func(func(Object, Object) bool) { 99 | obj := d.cpyObj() 100 | var cs C.pyCriticalSection 101 | C.pyCriticalSection_Begin(&cs, obj) 102 | return func(fn func(Object, Object) bool) { 103 | defer C.pyCriticalSection_End(&cs) 104 | var pos C.Py_ssize_t 105 | var key, value *C.PyObject 106 | for C.PyDict_Next(obj, &pos, &key, &value) == 1 { 107 | if !fn(newObject(key), newObject(value)) { 108 | return 109 | } 110 | } 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /dict_test.go: -------------------------------------------------------------------------------- 1 | package gp 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestDictFromPairs(t *testing.T) { 8 | setupTest(t) 9 | // Add panic test case 10 | func() { 11 | defer func() { 12 | if r := recover(); r == nil { 13 | t.Errorf("DictFromPairs() with odd number of arguments should panic") 14 | } else if r != "DictFromPairs requires an even number of arguments" { 15 | t.Errorf("Expected panic message 'DictFromPairs requires an even number of arguments', got '%v'", r) 16 | } 17 | }() 18 | 19 | DictFromPairs("key1", "value1", "key2") // Should panic 20 | }() 21 | 22 | tests := []struct { 23 | name string 24 | pairs []any 25 | wantKeys []any 26 | wantVals []any 27 | }{ 28 | { 29 | name: "string keys and values", 30 | pairs: []any{"key1", "value1", "key2", "value2"}, 31 | wantKeys: []any{"key1", "key2"}, 32 | wantVals: []any{"value1", "value2"}, 33 | }, 34 | { 35 | name: "mixed types", 36 | pairs: []any{"key1", 42, "key2", 3.14}, 37 | wantKeys: []any{"key1", "key2"}, 38 | wantVals: []any{42, 3.14}, 39 | }, 40 | } 41 | 42 | for _, tt := range tests { 43 | dict := DictFromPairs(tt.pairs...) 44 | 45 | // Verify each key-value pair 46 | for i := 0; i < len(tt.wantKeys); i++ { 47 | key := From(tt.wantKeys[i]) 48 | val := dict.Get(key) 49 | if !ObjectsAreEqual(val, From(tt.wantVals[i])) { 50 | t.Errorf("DictFromPairs() got value %v for key %v, want %v", 51 | val, tt.wantKeys[i], tt.wantVals[i]) 52 | } 53 | } 54 | } 55 | } 56 | 57 | func TestMakeDict(t *testing.T) { 58 | setupTest(t) 59 | tests := []struct { 60 | name string 61 | m map[any]any 62 | }{ 63 | { 64 | name: "string map", 65 | m: map[any]any{ 66 | "key1": "value1", 67 | "key2": "value2", 68 | }, 69 | }, 70 | { 71 | name: "mixed types map", 72 | m: map[any]any{ 73 | "int": 42, 74 | "float": 3.14, 75 | "string": "hello", 76 | }, 77 | }, 78 | } 79 | 80 | for _, tt := range tests { 81 | dict := MakeDict(tt.m) 82 | 83 | // Verify each key-value pair 84 | for k, v := range tt.m { 85 | key := From(k) 86 | got := dict.Get(key) 87 | if !ObjectsAreEqual(got, From(v)) { 88 | t.Errorf("MakeDict() got value %v for key %v, want %v", got, k, v) 89 | } 90 | } 91 | } 92 | } 93 | 94 | func TestDictSetGet(t *testing.T) { 95 | setupTest(t) 96 | dict := DictFromPairs() 97 | 98 | // Test Set and Get 99 | key := From("test_key") 100 | value := From("test_value") 101 | dict.Set(key, value) 102 | 103 | got := dict.Get(key) 104 | if !ObjectsAreEqual(got, value) { 105 | t.Errorf("Dict.Get() got %v, want %v", got, value) 106 | } 107 | } 108 | 109 | func TestDictSetGetString(t *testing.T) { 110 | setupTest(t) 111 | dict := DictFromPairs() 112 | 113 | // Test SetString and GetString 114 | value := From("test_value") 115 | dict.SetString("test_key", value) 116 | 117 | got := dict.GetString("test_key") 118 | if !ObjectsAreEqual(got, value) { 119 | t.Errorf("Dict.GetString() got %v, want %v", got, value) 120 | } 121 | } 122 | 123 | func TestDictDel(t *testing.T) { 124 | setupTest(t) 125 | dict := DictFromPairs("test_key", "test_value") 126 | key := From("test_key") 127 | 128 | // Verify key exists 129 | got := dict.Get(key) 130 | if !ObjectsAreEqual(got, From("test_value")) { 131 | t.Errorf("Before deletion, got %v, want %v", got, "test_value") 132 | } 133 | 134 | // Delete the key 135 | dict.Del(key) 136 | 137 | // After deletion, the key should not exist 138 | if dict.HasKey(key) { 139 | t.Errorf("After deletion, key %v should not exist", key) 140 | } 141 | } 142 | 143 | func TestDictForEach(t *testing.T) { 144 | setupTest(t) 145 | dict := DictFromPairs( 146 | "key1", "value1", 147 | "key2", "value2", 148 | "key3", "value3", 149 | ) 150 | 151 | count := 0 152 | expectedPairs := map[string]string{ 153 | "key1": "value1", 154 | "key2": "value2", 155 | "key3": "value3", 156 | } 157 | 158 | dict.Items()(func(key, value Object) bool { 159 | count++ 160 | k := key.String() 161 | v := value.String() 162 | if expectedVal, ok := expectedPairs[k]; !ok || expectedVal != v { 163 | t.Errorf("ForEach() unexpected pair: %v: %v", k, v) 164 | } 165 | return true 166 | }) 167 | 168 | if count != len(expectedPairs) { 169 | t.Errorf("ForEach() visited %d pairs, want %d", count, len(expectedPairs)) 170 | } 171 | } 172 | 173 | // Helper function to compare Python objects 174 | func ObjectsAreEqual(obj1, obj2 Object) bool { 175 | return obj1.String() == obj2.String() 176 | } 177 | -------------------------------------------------------------------------------- /extension.go: -------------------------------------------------------------------------------- 1 | package gp 2 | 3 | /* 4 | #include 5 | #include 6 | #include 7 | 8 | #include "wrap.h" 9 | extern PyObject* wrapperAlloc(PyTypeObject* type, Py_ssize_t size); 10 | extern void wrapperDealloc(PyObject* self); 11 | extern int wrapperInit(PyObject* self, PyObject* args); 12 | static int isModule(PyObject* ob) 13 | { 14 | return PyObject_TypeCheck(ob, &PyModule_Type); 15 | } 16 | */ 17 | import "C" 18 | 19 | import ( 20 | "fmt" 21 | "reflect" 22 | "runtime" 23 | "strings" 24 | "unicode" 25 | "unsafe" 26 | ) 27 | 28 | func FuncOf(fn any) Func { 29 | return CreateFunc("", fn, "") 30 | } 31 | 32 | func CreateFunc(name string, fn any, doc string) Func { 33 | m := MainModule() 34 | return m.AddMethod(name, fn, doc) 35 | } 36 | 37 | type wrapperType struct { 38 | cPyObject 39 | goObj any 40 | holder *objectHolder 41 | } 42 | 43 | type objectHolder struct { 44 | obj any 45 | prev *objectHolder 46 | next *objectHolder 47 | } 48 | 49 | type slotMeta struct { 50 | name string 51 | methodName string 52 | fn any 53 | doc string 54 | hasRecv bool // whether it has a receiver 55 | index int // used for member type 56 | typ reflect.Type // member/method type 57 | def *C.PyMethodDef 58 | } 59 | 60 | type typeMeta struct { 61 | typ reflect.Type 62 | init *slotMeta 63 | methods map[uint]*slotMeta 64 | } 65 | 66 | func allocWrapper(typ *C.PyTypeObject, obj any) *wrapperType { 67 | self := C.PyType_GenericAlloc(typ, 0) 68 | check(self != nil, "failed to allocate wrapper") 69 | wrapper := (*wrapperType)(unsafe.Pointer(self)) 70 | holder := new(objectHolder) 71 | holder.obj = obj 72 | maps := getGlobalData() 73 | maps.holders.PushFront(holder) 74 | wrapper.goObj = holder.obj 75 | wrapper.holder = holder 76 | return wrapper 77 | } 78 | 79 | func freeWrapper(wrapper *wrapperType) { 80 | maps := getGlobalData() 81 | maps.holders.Remove(wrapper.holder) 82 | } 83 | 84 | //export wrapperAlloc 85 | func wrapperAlloc(typ *C.PyTypeObject, size C.Py_ssize_t) *C.PyObject { 86 | maps := getGlobalData() 87 | meta := maps.typeMetas[(*C.PyObject)(unsafe.Pointer(typ))] 88 | wrapper := allocWrapper(typ, reflect.New(meta.typ).Interface()) 89 | check(wrapper != nil, "failed to allocate wrapper") 90 | return (*C.PyObject)(unsafe.Pointer(wrapper)) 91 | } 92 | 93 | //export wrapperDealloc 94 | func wrapperDealloc(self *C.PyObject) { 95 | wrapper := (*wrapperType)(unsafe.Pointer(self)) 96 | freeWrapper(wrapper) 97 | C.PyObject_Free(unsafe.Pointer(self)) 98 | } 99 | 100 | //export wrapperInit 101 | func wrapperInit(self, args *C.PyObject) C.int { 102 | typ := (*C.PyObject)(self).ob_type 103 | maps := getGlobalData() 104 | typeMeta := maps.typeMetas[(*C.PyObject)(unsafe.Pointer(typ))] 105 | check(typeMeta != nil, "type not registered") 106 | check(typeMeta.init != nil, "init method not found") 107 | if wrapperMethod_(typeMeta, typeMeta.init, self, args, 0) == nil { 108 | return -1 109 | } 110 | return 0 111 | } 112 | 113 | //export getterMethod 114 | func getterMethod(self *C.PyObject, _closure unsafe.Pointer, methodId C.int) *C.PyObject { 115 | maps := getGlobalData() 116 | typeMeta := maps.typeMetas[(*C.PyObject)(unsafe.Pointer(self.ob_type))] 117 | check(typeMeta != nil, fmt.Sprintf("type %v not registered", FromPy(self))) 118 | methodMeta := typeMeta.methods[uint(methodId)] 119 | check(methodMeta != nil, fmt.Sprintf("getter method %d not found", methodId)) 120 | 121 | wrapper := (*wrapperType)(unsafe.Pointer(self)) 122 | goPtr := reflect.ValueOf(wrapper.goObj) 123 | goValue := goPtr.Elem() 124 | field := goValue.Field(methodMeta.index) 125 | 126 | fieldType := field.Type() 127 | if fieldType.Kind() == reflect.Ptr && fieldType.Elem().Kind() == reflect.Struct { 128 | if field.IsNil() { 129 | return C.Py_None 130 | } 131 | if pyType, ok := maps.pyTypes[fieldType.Elem()]; ok { 132 | newWrapper := allocWrapper((*C.PyTypeObject)(unsafe.Pointer(pyType)), field.Interface()) 133 | check(newWrapper != nil, "failed to allocate wrapper for nested struct pointer") 134 | return (*C.PyObject)(unsafe.Pointer(newWrapper)) 135 | } 136 | } else if field.Kind() == reflect.Struct { 137 | if pyType, ok := maps.pyTypes[field.Type()]; ok { 138 | baseAddr := goPtr.UnsafePointer() 139 | fieldAddr := unsafe.Add(baseAddr, typeMeta.typ.Field(methodMeta.index).Offset) 140 | fieldPtr := reflect.NewAt(fieldType, fieldAddr).Interface() 141 | newWrapper := allocWrapper((*C.PyTypeObject)(unsafe.Pointer(pyType)), fieldPtr) 142 | check(newWrapper != nil, "failed to allocate wrapper for nested struct") 143 | return (*C.PyObject)(unsafe.Pointer(newWrapper)) 144 | } 145 | } 146 | return From(field.Interface()).cpyObj() 147 | } 148 | 149 | //export setterMethod 150 | func setterMethod(self, value *C.PyObject, _closure unsafe.Pointer, methodId C.int) C.int { 151 | maps := getGlobalData() 152 | typeMeta := maps.typeMetas[(*C.PyObject)(unsafe.Pointer(self.ob_type))] 153 | check(typeMeta != nil, fmt.Sprintf("type %v not registered", FromPy(self))) 154 | methodMeta := typeMeta.methods[uint(methodId)] 155 | check(methodMeta != nil, fmt.Sprintf("setter method %d not found", methodId)) 156 | 157 | wrapper := (*wrapperType)(unsafe.Pointer(self)) 158 | goPtr := reflect.ValueOf(wrapper.goObj) 159 | goValue := goPtr.Elem() 160 | 161 | structValue := goValue 162 | if !structValue.CanSet() { 163 | SetTypeError(fmt.Errorf("struct value cannot be set")) 164 | return -1 165 | } 166 | 167 | field := structValue.Field(methodMeta.index) 168 | if !field.CanSet() { 169 | SetTypeError(fmt.Errorf("field %s cannot be set", methodMeta.name)) 170 | return -1 171 | } 172 | 173 | fieldType := field.Type() 174 | if fieldType.Kind() == reflect.Ptr && fieldType.Elem().Kind() == reflect.Struct { 175 | if C.Py_Is(value, C.Py_None) != 0 { 176 | field.Set(reflect.Zero(fieldType)) 177 | return 0 178 | } 179 | if C.Py_IS_TYPE(value, &C.PyDict_Type) != 0 { 180 | if field.IsNil() { 181 | field.Set(reflect.New(fieldType.Elem())) 182 | } 183 | if !ToValue(FromPy(value), field.Elem()) { 184 | SetTypeError(fmt.Errorf("failed to convert dict to %s", fieldType.Elem())) 185 | return -1 186 | } 187 | } else { 188 | pyType := C.Py_TYPE(value) 189 | if _, ok := maps.typeMetas[(*C.PyObject)(unsafe.Pointer(pyType))]; !ok { 190 | SetTypeError(fmt.Errorf("invalid value of type %v for struct pointer field", FromPy((*C.PyObject)(unsafe.Pointer(pyType))))) 191 | return -1 192 | } 193 | valueWrapper := (*wrapperType)(unsafe.Pointer(value)) 194 | if valueWrapper == nil { 195 | SetTypeError(fmt.Errorf("invalid value for struct pointer field")) 196 | return -1 197 | } 198 | field.Set(reflect.ValueOf(valueWrapper.goObj)) 199 | } 200 | return 0 201 | } else if field.Kind() == reflect.Struct { 202 | if C.Py_IS_TYPE(value, &C.PyDict_Type) != 0 { 203 | if !ToValue(FromPy(value), field) { 204 | SetTypeError(fmt.Errorf("failed to convert dict to %s", field.Type())) 205 | return -1 206 | } 207 | } else { 208 | pyType := (*C.PyTypeObject)(unsafe.Pointer(value.ob_type)) 209 | if _, ok := maps.typeMetas[(*C.PyObject)(unsafe.Pointer(pyType))]; !ok { 210 | SetTypeError(fmt.Errorf("invalid value of type %v for struct field", FromPy((*C.PyObject)(unsafe.Pointer(pyType))))) 211 | return -1 212 | } 213 | valueWrapper := (*wrapperType)(unsafe.Pointer(value)) 214 | baseAddr := goPtr.UnsafePointer() 215 | fieldAddr := unsafe.Add(baseAddr, typeMeta.typ.Field(methodMeta.index).Offset) 216 | fieldPtr := reflect.NewAt(fieldType, fieldAddr) 217 | fieldPtr.Elem().Set(reflect.ValueOf(valueWrapper.goObj).Elem()) 218 | } 219 | return 0 220 | } 221 | 222 | if !ToValue(FromPy(value), field) { 223 | SetTypeError(fmt.Errorf("failed to convert value to %s", methodMeta.typ)) 224 | return -1 225 | } 226 | return 0 227 | } 228 | 229 | //export wrapperMethod 230 | func wrapperMethod(self, args *C.PyObject, methodId C.int) *C.PyObject { 231 | key := self 232 | if C.isModule(self) == 0 { 233 | key = (*C.PyObject)(unsafe.Pointer(self.ob_type)) 234 | } 235 | 236 | maps := getGlobalData() 237 | typeMeta, ok := maps.typeMetas[key] 238 | check(ok, fmt.Sprintf("type %v not registered", FromPy(key))) 239 | 240 | methodMeta := typeMeta.methods[uint(methodId)] 241 | return wrapperMethod_(typeMeta, methodMeta, self, args, methodId) 242 | } 243 | 244 | func wrapperMethod_(typeMeta *typeMeta, methodMeta *slotMeta, self, args *C.PyObject, methodId C.int) *C.PyObject { 245 | methodType := methodMeta.typ 246 | argc := C.PyTuple_Size(args) 247 | expectedArgs := methodType.NumIn() 248 | hasReceiver := methodMeta.hasRecv 249 | isInit := typeMeta.init == methodMeta 250 | 251 | if hasReceiver { 252 | expectedArgs-- // decrease expected number if it has a receiver 253 | } 254 | 255 | if int(argc) != expectedArgs { 256 | SetTypeError(fmt.Errorf("method %s expects %d arguments, got %d", methodMeta.name, expectedArgs, argc)) 257 | return nil 258 | } 259 | 260 | goArgs := make([]reflect.Value, methodType.NumIn()) 261 | argIndex := 0 262 | 263 | if hasReceiver { 264 | wrapper := (*wrapperType)(unsafe.Pointer(self)) 265 | receiverType := methodType.In(0) 266 | var recv reflect.Value 267 | 268 | if receiverType.Kind() == reflect.Ptr { 269 | recv = reflect.ValueOf(wrapper.goObj) 270 | } else { 271 | recv = reflect.ValueOf(wrapper.goObj).Elem() 272 | } 273 | 274 | goArgs[0] = recv 275 | argIndex = 1 276 | } 277 | 278 | for i := 0; i < int(argc); i++ { 279 | arg := C.PySequence_GetItem(args, C.Py_ssize_t(i)) 280 | argType := methodType.In(i + argIndex) 281 | argPy := FromPy(arg) 282 | goValue := reflect.New(argType).Elem() 283 | if !ToValue(argPy, goValue) { 284 | SetTypeError(fmt.Errorf("failed to convert argument %v to %v", argPy, argType)) 285 | return nil 286 | } 287 | goArgs[i+argIndex] = goValue 288 | } 289 | 290 | results := reflect.ValueOf(methodMeta.fn).Call(goArgs) 291 | 292 | // Handle init function return value 293 | if isInit && !hasReceiver { 294 | if len(results) == 1 { 295 | wrapper := (*wrapperType)(unsafe.Pointer(self)) 296 | goObj := reflect.ValueOf(wrapper.goObj).Elem() 297 | 298 | // Handle both pointer and value returns 299 | result := results[0] 300 | if result.Type() == reflect.PointerTo(typeMeta.typ) { 301 | // For pointer constructor, dereference the pointer 302 | goObj.Set(result.Elem()) 303 | } else { 304 | // For value constructor 305 | goObj.Set(result) 306 | } 307 | return (*C.PyObject)(unsafe.Pointer(wrapper)) 308 | } else { 309 | panic("init function without receiver must return the type being created") 310 | } 311 | } 312 | 313 | if len(results) == 0 { 314 | return None().cpyObj() 315 | } 316 | if len(results) == 1 { 317 | return From(results[0].Interface()).cpyObj() 318 | } 319 | 320 | tuple := MakeTupleWithLen(len(results)) 321 | for i := range results { 322 | tuple.Set(i, From(results[i].Interface())) 323 | } 324 | return tuple.cpyObj() 325 | } 326 | 327 | func goNameToPythonName(name string) string { 328 | var result strings.Builder 329 | for i, r := range name { 330 | if i > 0 && unicode.IsUpper(r) { 331 | result.WriteRune('_') 332 | } 333 | result.WriteRune(unicode.ToLower(r)) 334 | } 335 | return result.String() 336 | } 337 | 338 | func getMethods_(t reflect.Type, methods map[uint]*slotMeta) (ret []C.PyMethodDef) { 339 | for i := 0; i < t.NumMethod(); i++ { 340 | method := t.Method(i) 341 | if method.IsExported() { 342 | methodId := uint(len(methods)) 343 | 344 | pythonName := goNameToPythonName(method.Name) 345 | methods[methodId] = &slotMeta{ 346 | name: method.Name, 347 | methodName: pythonName, 348 | fn: method.Func.Interface(), 349 | typ: method.Type, 350 | hasRecv: true, 351 | } 352 | 353 | methodPtr := C.wrapperMethods[methodId] 354 | 355 | ret = append(ret, C.PyMethodDef{ 356 | ml_name: AllocCStrDontFree(pythonName), 357 | ml_meth: (C.PyCFunction)(unsafe.Pointer(methodPtr)), 358 | ml_flags: C.METH_VARARGS, 359 | ml_doc: nil, 360 | }) 361 | } 362 | } 363 | return 364 | } 365 | 366 | func getMethods(t reflect.Type, methods map[uint]*slotMeta) *C.PyMethodDef { 367 | methodsDef := getMethods_(t, methods) 368 | methodsDef = append(methodsDef, getMethods_(reflect.PointerTo(t), methods)...) 369 | methodsDef = append(methodsDef, C.PyMethodDef{}) 370 | methodSize := C.size_t(C.sizeof_PyMethodDef * len(methodsDef)) 371 | methodsPtr := (*C.PyMethodDef)(C.malloc(methodSize)) 372 | C.memset(unsafe.Pointer(methodsPtr), 0, methodSize) 373 | 374 | methodArrayPtr := unsafe.Pointer(methodsPtr) 375 | for i, method := range methodsDef { 376 | currentMethod := (*C.PyMethodDef)(unsafe.Pointer(uintptr(methodArrayPtr) + uintptr(i)*unsafe.Sizeof(C.PyMethodDef{}))) 377 | *currentMethod = method 378 | } 379 | return methodsPtr 380 | } 381 | 382 | func getGetsets(t reflect.Type, methods map[uint]*slotMeta) (getsets *C.PyGetSetDef) { 383 | getsetsList := make([]C.PyGetSetDef, 0) 384 | 385 | for i := 0; i < t.NumField(); i++ { 386 | field := t.Field(i) 387 | if !field.IsExported() { 388 | continue 389 | } 390 | 391 | pythonName := goNameToPythonName(field.Name) 392 | 393 | // Use getter/setter for all fields 394 | getId := uint(len(methods)) 395 | methods[getId] = &slotMeta{ 396 | name: field.Name, 397 | methodName: pythonName, 398 | typ: field.Type, 399 | hasRecv: false, 400 | index: i, 401 | } 402 | setId := uint(len(methods)) 403 | methods[setId] = &slotMeta{ 404 | name: field.Name, 405 | methodName: pythonName, 406 | typ: field.Type, 407 | hasRecv: false, 408 | index: i, 409 | } 410 | getsetsList = append(getsetsList, C.PyGetSetDef{ 411 | name: AllocCStrDontFree(pythonName), 412 | get: C.getterMethods[getId], 413 | set: C.setterMethods[setId], 414 | doc: nil, 415 | closure: nil, 416 | }) 417 | } 418 | 419 | // Add null terminators 420 | getsetsList = append(getsetsList, C.PyGetSetDef{}) 421 | 422 | // Allocate and copy getsets array 423 | getsetSize := C.size_t(C.sizeof_PyGetSetDef * len(getsetsList)) 424 | getsetsPtr := (*C.PyGetSetDef)(C.malloc(getsetSize)) 425 | C.memset(unsafe.Pointer(getsetsPtr), 0, getsetSize) 426 | 427 | getsetArrayPtr := unsafe.Pointer(getsetsPtr) 428 | for i, getset := range getsetsList { 429 | currentGetSet := (*C.PyGetSetDef)(unsafe.Pointer(uintptr(getsetArrayPtr) + uintptr(i)*unsafe.Sizeof(C.PyGetSetDef{}))) 430 | *currentGetSet = getset 431 | } 432 | 433 | return getsetsPtr 434 | } 435 | 436 | func (m Module) AddType(obj, init any, name, doc string) Object { 437 | ty := reflect.TypeOf(obj) 438 | if ty.Kind() == reflect.Ptr { 439 | ty = ty.Elem() 440 | } 441 | if ty.Kind() != reflect.Struct { 442 | panic("AddType: obj must be a struct or pointer to struct") 443 | } 444 | 445 | // Check if type already registered 446 | maps := getGlobalData() 447 | if pyType, ok := maps.pyTypes[ty]; ok { 448 | return newObjectRef(pyType) 449 | } 450 | 451 | meta := &typeMeta{ 452 | typ: ty, 453 | methods: make(map[uint]*slotMeta), 454 | } 455 | 456 | slots := make([]C.PyType_Slot, 0) 457 | 458 | slots = append(slots, C.PyType_Slot{ 459 | slot: C.Py_tp_alloc, 460 | pfunc: unsafe.Pointer(C.wrapperAlloc), 461 | }) 462 | 463 | slots = append(slots, C.PyType_Slot{ 464 | slot: C.Py_tp_dealloc, 465 | pfunc: unsafe.Pointer(C.wrapperDealloc), 466 | }) 467 | 468 | if init != nil { 469 | slots = append(slots, C.PyType_Slot{ 470 | slot: C.Py_tp_init, 471 | pfunc: unsafe.Pointer(C.wrapperInit), 472 | }) 473 | 474 | initVal := reflect.ValueOf(init) 475 | initType := initVal.Type() 476 | 477 | if initType.Kind() != reflect.Func { 478 | panic("Init must be a function") 479 | } 480 | 481 | // Check if it's a method with receiver 482 | if initType.NumIn() > 0 && 483 | initType.In(0).Kind() == reflect.Ptr && 484 | initType.In(0).Elem() == ty { 485 | // (*T).Init form - pointer receiver 486 | meta.init = &slotMeta{ 487 | name: runtime.FuncForPC(initVal.Pointer()).Name(), 488 | methodName: "__init__", 489 | fn: init, 490 | typ: initType, 491 | hasRecv: true, 492 | } 493 | } else if initType.NumOut() == 1 && 494 | (initType.Out(0) == ty || 495 | (initType.Out(0).Kind() == reflect.Ptr && initType.Out(0).Elem() == ty)) { 496 | // Constructor function returning T or *T 497 | meta.init = &slotMeta{ 498 | name: runtime.FuncForPC(initVal.Pointer()).Name(), 499 | methodName: "__init__", 500 | fn: init, 501 | typ: initType, 502 | hasRecv: false, 503 | } 504 | } else { 505 | panic("Init function must either have a pointer receiver (*T) or return T/*T") 506 | } 507 | } 508 | getsets := getGetsets(ty, meta.methods) 509 | slots = append(slots, C.PyType_Slot{slot: C.Py_tp_getset, pfunc: unsafe.Pointer(getsets)}) 510 | slots = append(slots, C.PyType_Slot{slot: C.Py_tp_methods, pfunc: unsafe.Pointer(getMethods(ty, meta.methods))}) 511 | 512 | slotCount := len(slots) + 1 513 | slotSize := C.size_t(C.sizeof_PyType_Slot * slotCount) 514 | slotsPtr := (*C.PyType_Slot)(C.malloc(slotSize)) 515 | C.memset(unsafe.Pointer(slotsPtr), 0, slotSize) 516 | 517 | slotArrayPtr := unsafe.Pointer(slotsPtr) 518 | for i, slot := range slots { 519 | currentSlot := (*C.PyType_Slot)(unsafe.Pointer(uintptr(slotArrayPtr) + uintptr(i)*unsafe.Sizeof(C.PyType_Slot{}))) 520 | *currentSlot = slot 521 | } 522 | 523 | typeName := fmt.Sprintf("%s.%s", m.Name(), name) 524 | 525 | totalSize := unsafe.Sizeof(wrapperType{}) 526 | spec := &C.PyType_Spec{ 527 | name: C.CString(typeName), 528 | basicsize: C.int(totalSize), 529 | flags: C.Py_TPFLAGS_DEFAULT, 530 | slots: slotsPtr, 531 | } 532 | 533 | typeObj := C.PyType_FromSpec(spec) 534 | if typeObj == nil { 535 | C.free(unsafe.Pointer(spec.name)) 536 | C.free(unsafe.Pointer(slotsPtr)) 537 | panic(fmt.Sprintf("Failed to create type %s", name)) 538 | } 539 | 540 | maps.typeMetas[typeObj] = meta 541 | maps.pyTypes[ty] = typeObj 542 | 543 | if C.PyModule_AddObjectRef(m.obj, C.CString(name), typeObj) < 0 { 544 | panic(fmt.Sprintf("Failed to add type %s to module", name)) 545 | } 546 | 547 | // First register any struct field types 548 | for i := 0; i < ty.NumField(); i++ { 549 | field := ty.Field(i) 550 | if !field.IsExported() { 551 | continue 552 | } 553 | 554 | fieldType := field.Type 555 | // Handle pointer types 556 | if fieldType.Kind() == reflect.Ptr { 557 | fieldType = fieldType.Elem() 558 | } 559 | // Recursively register struct types 560 | if fieldType.Kind() == reflect.Struct { 561 | maps := getGlobalData() 562 | if _, ok := maps.pyTypes[fieldType]; !ok { 563 | // Generate a unique type name based on package path and type name 564 | nestedName := fieldType.Name() 565 | m.AddType(reflect.New(fieldType).Elem().Interface(), nil, nestedName, "") 566 | } 567 | } 568 | } 569 | 570 | return newObjectRef(typeObj) 571 | } 572 | 573 | func (m Module) AddMethod(name string, fn any, doc string) Func { 574 | v := reflect.ValueOf(fn) 575 | t := v.Type() 576 | if t.Kind() != reflect.Func { 577 | panic("AddFunction: fn must be a function") 578 | } 579 | if name == "" { 580 | name = runtime.FuncForPC(v.Pointer()).Name() 581 | } 582 | if name == "" { 583 | name = fmt.Sprintf("anonymous_func_%p", fn) 584 | } else { 585 | if idx := strings.LastIndex(name, "."); idx >= 0 { 586 | name = name[idx+1:] 587 | } 588 | } 589 | name = goNameToPythonName(name) 590 | 591 | hasRecv := false 592 | if t.NumIn() > 0 { 593 | firstParam := t.In(0) 594 | if firstParam.Kind() == reflect.Ptr || firstParam.Kind() == reflect.Interface { 595 | hasRecv = true 596 | } 597 | } 598 | 599 | kwargsType := reflect.TypeOf(KwArgs{}) 600 | hasKwArgs := false 601 | if t.NumIn() > 0 && t.In(t.NumIn()-1) == kwargsType { 602 | hasKwArgs = true 603 | } 604 | 605 | sig := genSig(fn, hasRecv) 606 | fullDoc := name + sig + "\n--\n\n" + doc 607 | cDoc := C.CString(fullDoc) 608 | 609 | maps := getGlobalData() 610 | meta, ok := maps.typeMetas[m.obj] 611 | if !ok { 612 | meta = &typeMeta{ 613 | methods: make(map[uint]*slotMeta), 614 | } 615 | maps.typeMetas[m.obj] = meta 616 | } 617 | 618 | methodId := uint(len(meta.methods)) 619 | 620 | cName := C.CString(name) 621 | 622 | def := (*C.PyMethodDef)(C.malloc(C.size_t(unsafe.Sizeof(C.PyMethodDef{})))) 623 | def.ml_name = cName 624 | def.ml_meth = C.PyCFunction(C.wrapperMethods[methodId]) 625 | def.ml_flags = C.METH_VARARGS 626 | if hasKwArgs { 627 | def.ml_flags |= C.METH_KEYWORDS 628 | def.ml_meth = C.PyCFunction(C.wrapperMethodsWithKwargs[methodId]) 629 | } 630 | def.ml_doc = cDoc 631 | 632 | methodMeta := &slotMeta{ 633 | name: name, 634 | methodName: name, 635 | fn: fn, 636 | typ: t, 637 | doc: fullDoc, 638 | hasRecv: hasRecv, 639 | def: def, 640 | } 641 | meta.methods[methodId] = methodMeta 642 | 643 | pyFunc := C.PyCFunction_NewEx(def, m.obj, m.obj) 644 | check(pyFunc != nil, fmt.Sprintf("Failed to create function %s", name)) 645 | 646 | if C.PyModule_AddObjectRef(m.obj, cName, pyFunc) < 0 { 647 | C.Py_DecRef(pyFunc) 648 | panic(fmt.Sprintf("Failed to add function %s to module", name)) 649 | } 650 | 651 | return newFunc(pyFunc) 652 | } 653 | 654 | func SetTypeError(err error) { 655 | errStr := C.CString(err.Error()) 656 | C.PyErr_SetString(C.PyExc_TypeError, errStr) 657 | C.free(unsafe.Pointer(errStr)) 658 | } 659 | 660 | // FetchError returns the current Python error as a Go error 661 | func FetchError() error { 662 | var ptype, pvalue, ptraceback *C.PyObject 663 | C.PyErr_Fetch(&ptype, &pvalue, &ptraceback) 664 | if ptype == nil { 665 | return nil 666 | } 667 | defer C.Py_DecRef(ptype) 668 | if pvalue == nil { 669 | return fmt.Errorf("python error") 670 | } 671 | defer C.Py_DecRef(pvalue) 672 | if ptraceback != nil { 673 | defer C.Py_DecRef(ptraceback) 674 | } 675 | 676 | // Convert error to string 677 | pyStr := C.PyObject_Str(pvalue) 678 | if pyStr == nil { 679 | return fmt.Errorf("python error (failed to get error message)") 680 | } 681 | defer C.Py_DecRef(pyStr) 682 | 683 | // Get error message as Go string 684 | cstr := C.PyUnicode_AsUTF8(pyStr) 685 | if cstr == nil { 686 | return fmt.Errorf("python error (failed to decode error message)") 687 | } 688 | 689 | return fmt.Errorf("python error: %s", C.GoString(cstr)) 690 | } 691 | 692 | func genSig(fn any, hasRecv bool) string { 693 | t := reflect.TypeOf(fn) 694 | if t.Kind() != reflect.Func { 695 | panic("genSig: fn must be a function") 696 | } 697 | 698 | var args []string 699 | startIdx := 0 700 | if hasRecv { 701 | startIdx = 1 // skip receiver 702 | } 703 | 704 | kwargsType := reflect.TypeOf(KwArgs{}) 705 | hasKwArgs := false 706 | lastParamIdx := t.NumIn() - 1 707 | if lastParamIdx >= startIdx && t.In(lastParamIdx) == kwargsType { 708 | hasKwArgs = true 709 | lastParamIdx-- // don't include KwArgs in regular parameters 710 | } 711 | 712 | for i := startIdx; i <= lastParamIdx; i++ { 713 | paramName := fmt.Sprintf("arg%d", i-startIdx) 714 | args = append(args, paramName) 715 | } 716 | 717 | // add "/" separator only if there are parameters 718 | if len(args) > 0 { 719 | args = append(args, "/") 720 | } 721 | 722 | // add "**kwargs" if there are keyword arguments 723 | if hasKwArgs { 724 | args = append(args, "**kwargs") 725 | } 726 | 727 | return fmt.Sprintf("(%s)", strings.Join(args, ", ")) 728 | } 729 | 730 | //export wrapperMethodWithKwargs 731 | func wrapperMethodWithKwargs(self, args, kwargs *C.PyObject, methodId C.int) *C.PyObject { 732 | key := self 733 | if C.isModule(self) == 0 { 734 | key = (*C.PyObject)(unsafe.Pointer(self.ob_type)) 735 | } 736 | 737 | maps := getGlobalData() 738 | typeMeta, ok := maps.typeMetas[key] 739 | check(ok, fmt.Sprintf("type %v not registered", FromPy(key))) 740 | 741 | methodMeta := typeMeta.methods[uint(methodId)] 742 | methodType := methodMeta.typ 743 | hasReceiver := methodMeta.hasRecv 744 | 745 | expectedArgs := methodType.NumIn() 746 | if hasReceiver { 747 | expectedArgs-- // skip receiver 748 | } 749 | expectedArgs-- // skip KwArgs 750 | 751 | argc := C.PyTuple_Size(args) 752 | if int(argc) != expectedArgs { 753 | SetTypeError(fmt.Errorf("method %s expects %d arguments, got %d", methodMeta.name, expectedArgs, argc)) 754 | return nil 755 | } 756 | 757 | goArgs := make([]reflect.Value, methodType.NumIn()) 758 | argIndex := 0 759 | 760 | if hasReceiver { 761 | wrapper := (*wrapperType)(unsafe.Pointer(self)) 762 | receiverType := methodType.In(0) 763 | var recv reflect.Value 764 | 765 | if receiverType.Kind() == reflect.Ptr { 766 | recv = reflect.ValueOf(wrapper.goObj) 767 | } else { 768 | recv = reflect.ValueOf(wrapper.goObj).Elem() 769 | } 770 | 771 | goArgs[0] = recv 772 | argIndex = 1 773 | } 774 | 775 | for i := 0; i < int(argc); i++ { 776 | arg := C.PySequence_GetItem(args, C.Py_ssize_t(i)) 777 | argType := methodType.In(i + argIndex) 778 | argPy := FromPy(arg) 779 | goValue := reflect.New(argType).Elem() 780 | if !ToValue(argPy, goValue) { 781 | SetTypeError(fmt.Errorf("failed to convert argument %v to %v", argPy, argType)) 782 | return nil 783 | } 784 | goArgs[i+argIndex] = goValue 785 | } 786 | 787 | kwargsValue := make(KwArgs) 788 | if kwargs != nil { 789 | dict := newDict(kwargs) 790 | dict.Items()(func(key, value Object) bool { 791 | kwargsValue[key.String()] = value 792 | return true 793 | }) 794 | } 795 | goArgs[len(goArgs)-1] = reflect.ValueOf(kwargsValue) 796 | 797 | results := reflect.ValueOf(methodMeta.fn).Call(goArgs) 798 | 799 | if len(results) == 0 { 800 | return None().cpyObj() 801 | } 802 | if len(results) == 1 { 803 | return From(results[0].Interface()).cpyObj() 804 | } 805 | 806 | tuple := MakeTupleWithLen(len(results)) 807 | for i := range results { 808 | tuple.Set(i, From(results[i].Interface())) 809 | } 810 | return tuple.cpyObj() 811 | } 812 | -------------------------------------------------------------------------------- /extension_test.go: -------------------------------------------------------------------------------- 1 | package gp 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | "testing" 7 | ) 8 | 9 | // TestStruct contains various types of fields for testing 10 | type TestStruct struct { 11 | // C-compatible basic types 12 | BoolField bool 13 | Int8Field int8 14 | Int16Field int16 15 | Int32Field int32 16 | Int64Field int64 17 | IntField int 18 | Uint8Field uint8 19 | Uint16Field uint16 20 | Uint32Field uint32 21 | Uint64Field uint64 22 | UintField uint 23 | Float32Field float32 24 | Float64Field float64 25 | Complex64Field complex64 26 | Complex128Field complex128 27 | 28 | // Non-C-compatible types 29 | StringField string 30 | SliceField []int 31 | MapField map[string]int 32 | StructField struct{ X int } 33 | } 34 | 35 | func (t *TestStruct) TestMethod() int { 36 | return 42 37 | } 38 | 39 | func TestAddType(t *testing.T) { 40 | setupTest(t) 41 | m := MainModule() 42 | 43 | // test add type 44 | typ := m.AddType(TestStruct{}, nil, "TestStruct", "Test struct documentation") 45 | if typ.Nil() { 46 | t.Fatal("Failed to create type") 47 | } 48 | 49 | // test type by Python code 50 | code := ` 51 | # create instance 52 | obj = TestStruct() 53 | 54 | # test C-compatible types 55 | obj.bool_field = True 56 | obj.int8_field = 127 57 | obj.int16_field = 32767 58 | obj.int32_field = 2147483647 59 | obj.int64_field = 9223372036854775807 60 | obj.int_field = 1234567890 61 | obj.uint8_field = 255 62 | obj.uint16_field = 65535 63 | obj.uint32_field = 4294967295 64 | obj.uint64_field = 18446744073709551615 65 | obj.uint_field = 4294967295 66 | obj.float32_field = 3.14 67 | obj.float64_field = 3.14159265359 68 | obj.complex64_field = 1.5 + 2.5j 69 | obj.complex128_field = 3.14 + 2.718j 70 | 71 | # test non-C-compatible types 72 | obj.string_field = "test string" 73 | assert obj.string_field == "test string" 74 | 75 | obj.slice_field = [1, 2, 3] 76 | assert obj.slice_field == [1, 2, 3] 77 | 78 | obj.map_field = {"key": 42} 79 | assert obj.map_field["key"] == 42 80 | 81 | obj.struct_field = {"x": 100} 82 | assert obj.struct_field.x == 100 83 | 84 | # test method call 85 | result = obj.test_method() 86 | assert result == 42 87 | 88 | # verify C-compatible types 89 | assert obj.bool_field == True 90 | assert obj.int8_field == 127 91 | assert obj.int16_field == 32767 92 | assert obj.int32_field == 2147483647 93 | assert obj.int64_field == 9223372036854775807 94 | assert obj.int_field == 1234567890 95 | assert obj.uint8_field == 255 96 | assert obj.uint16_field == 65535, f"Expected 65535, got {obj.uint16_field}" 97 | assert obj.uint32_field == 4294967295, f"Expected 4294967295, got {obj.uint32_field}" 98 | assert obj.uint64_field == 18446744073709551615, f"Expected 18446744073709551615, got {obj.uint64_field}" 99 | assert obj.uint_field == 4294967295, f"Expected 4294967295, got {obj.uint_field}" 100 | assert abs(obj.float32_field - 3.14) < 0.0001, f"Expected 3.14, got {obj.float32_field}" 101 | assert abs(obj.float64_field - 3.14159265359) < 0.0000001, f"Expected 3.14159265359, got {obj.float64_field}" 102 | assert abs(obj.complex64_field - (1.5 + 2.5j)) < 0.0001, f"Expected (1.5 + 2.5j), got {obj.complex64_field}" 103 | assert abs(obj.complex128_field - (3.14 + 2.718j)) < 0.0000001, f"Expected (3.14 + 2.718j), got {obj.complex128_field}" 104 | 105 | # verify non-C-compatible types 106 | assert obj.string_field == "test string", f"Expected 'test string', got {obj.string_field}" 107 | assert obj.slice_field == [1, 2, 3], f"Expected [1, 2, 3], got {obj.slice_field}" 108 | assert obj.map_field["key"] == 42, f"Expected 42, got {obj.map_field['key']}" 109 | assert obj.struct_field.x == 100, f"Expected 100, got {obj.struct_field.x}" 110 | ` 111 | 112 | err := RunString(code) 113 | if err != nil { 114 | t.Fatalf("Test failed: %v", err) 115 | } 116 | } 117 | 118 | type InitTestStruct struct { 119 | Value int 120 | } 121 | 122 | func (i *InitTestStruct) Init(val int) { 123 | i.Value = val 124 | } 125 | 126 | func TestAddTypeWithInit(t *testing.T) { 127 | setupTest(t) 128 | m := MainModule() 129 | 130 | typ := m.AddType(InitTestStruct{}, (*InitTestStruct).Init, "InitTestStruct", "Test init struct") 131 | if typ.Nil() { 132 | t.Fatal("Failed to create type with init") 133 | } 134 | 135 | // test init function 136 | code := ` 137 | # test init function 138 | obj = InitTestStruct(42) 139 | assert obj.value == 42 140 | 141 | # test error handling without arguments 142 | try: 143 | obj2 = InitTestStruct() 144 | assert False, "Should fail without arguments" 145 | except TypeError as e: 146 | pass 147 | 148 | # test error handling with wrong argument type 149 | try: 150 | obj3 = InitTestStruct("wrong type") 151 | assert False, "Should fail with wrong argument type" 152 | except TypeError: 153 | pass 154 | ` 155 | 156 | err := RunString(code) 157 | if err != nil { 158 | t.Fatalf("Test failed: %v", err) 159 | } 160 | } 161 | 162 | func TestCreateFunc(t *testing.T) { 163 | setupTest(t) 164 | 165 | tests := []struct { 166 | name string 167 | fn any 168 | doc string 169 | testCode string 170 | }{ 171 | { 172 | name: "simple", 173 | fn: func(x int) int { 174 | return x * 2 175 | }, 176 | doc: "Doubles the input value", 177 | testCode: ` 178 | result = simple_func(21) 179 | assert result == 42, f"Expected 42, got {result}" 180 | assert str(inspect.signature(simple_func)) == "(arg0, /)" 181 | `, 182 | }, 183 | { 184 | name: "multi_args", 185 | fn: func(x int, y string) (int, string) { 186 | return x * 2, y + y 187 | }, 188 | doc: "Returns doubled number and duplicated string", 189 | testCode: ` 190 | num, text = multi_args_func(5, "hello") 191 | assert num == 10, f"Expected 10, got {num}" 192 | assert text == "hellohello", f"Expected 'hellohello', got {text}" 193 | assert str(inspect.signature(multi_args_func)) == "(arg0, arg1, /)" 194 | `, 195 | }, 196 | { 197 | name: "with_kwargs", 198 | fn: func(name string, kwargs KwArgs) Object { 199 | return None() 200 | }, 201 | doc: "Function with kwargs", 202 | testCode: ` 203 | result = with_kwargs_func("test", extra="value") 204 | assert result is None 205 | assert str(inspect.signature(with_kwargs_func)) == "(arg0, /, **kwargs)" 206 | `, 207 | }, 208 | { 209 | name: "no_args", 210 | fn: func() string { 211 | return "hello" 212 | }, 213 | doc: "Function with no arguments", 214 | testCode: ` 215 | result = no_args_func() 216 | assert result == "hello" 217 | assert str(inspect.signature(no_args_func)) == "()" 218 | `, 219 | }, 220 | { 221 | name: "only_kwargs", 222 | fn: func(kwargs KwArgs) Object { 223 | return None() 224 | }, 225 | doc: "Function with only kwargs", 226 | testCode: ` 227 | result = only_kwargs_func(x=1, y=2) 228 | assert result is None 229 | assert str(inspect.signature(only_kwargs_func)) == "(**kwargs)" 230 | `, 231 | }, 232 | } 233 | 234 | code := ` 235 | import inspect 236 | 237 | ` 238 | for _, tt := range tests { 239 | funcName := tt.name + "_func" 240 | f := CreateFunc(funcName, tt.fn, tt.doc) 241 | if f.Nil() { 242 | t.Fatalf("Failed to create function for test case: %s", tt.name) 243 | } 244 | code += tt.testCode + "\n" 245 | } 246 | t.Log(code) 247 | err := RunString(code) 248 | if err != nil { 249 | t.Fatalf("Test failed: %v", err) 250 | } 251 | 252 | // Test failure cases 253 | func() { 254 | defer func() { 255 | if r := recover(); r == nil { 256 | t.Error("CreateFunc should panic with non-function argument") 257 | } 258 | }() 259 | CreateFunc("invalid", 42, "should panic") 260 | }() 261 | 262 | func() { 263 | defer func() { 264 | if r := recover(); r == nil { 265 | t.Error("CreateFunc should panic with nil function") 266 | } 267 | }() 268 | CreateFunc("nil_func", nil, "should panic") 269 | }() 270 | } 271 | 272 | func TestCreateFuncInvalid(t *testing.T) { 273 | setupTest(t) 274 | // Test invalid function type 275 | defer func() { 276 | if r := recover(); r == nil { 277 | t.Error("CreateFunc should panic with non-function argument") 278 | } 279 | }() 280 | CreateFunc("non_func", 42, "This should panic") 281 | } 282 | 283 | func explicitFunc(x int) int { 284 | return x + 1 285 | } 286 | 287 | func TestModuleAddMethod(t *testing.T) { 288 | setupTest(t) 289 | m := MainModule() 290 | 291 | tests := []struct { 292 | name string 293 | fn any 294 | doc string 295 | testCode string 296 | }{ 297 | { 298 | name: "explicit", 299 | fn: explicitFunc, 300 | doc: "adds one to input", 301 | testCode: ` 302 | result = explicit_func(41) 303 | assert result == 42, f"Expected 42, got {result}" 304 | assert str(inspect.signature(explicit_func)) == "(arg0, /)" 305 | `, 306 | }, 307 | { 308 | name: "with_kwargs", 309 | fn: func(x int, kwargs KwArgs) int { 310 | return x 311 | }, 312 | doc: "function with kwargs", 313 | testCode: ` 314 | result = with_kwargs_func(42, extra="value") 315 | assert result == 42 316 | assert str(inspect.signature(with_kwargs_func)) == "(arg0, /, **kwargs)" 317 | `, 318 | }, 319 | { 320 | name: "multi_args", 321 | fn: func(x, y int) int { 322 | return x * y 323 | }, 324 | doc: "multiplies two numbers", 325 | testCode: ` 326 | result = multi_args_func(6, 7) 327 | assert result == 42 328 | assert str(inspect.signature(multi_args_func)) == "(arg0, arg1, /)" 329 | `, 330 | }, 331 | { 332 | name: "no_args", 333 | fn: func() string { 334 | return "hello" 335 | }, 336 | doc: "returns hello", 337 | testCode: ` 338 | result = no_args_func() 339 | assert result == "hello" 340 | assert str(inspect.signature(no_args_func)) == "()" 341 | `, 342 | }, 343 | { 344 | name: "only_kwargs", 345 | fn: func(kwargs KwArgs) Object { 346 | return None() 347 | }, 348 | doc: "function with only kwargs", 349 | testCode: ` 350 | result = only_kwargs_func(x=1, y=2) 351 | assert result is None 352 | assert str(inspect.signature(only_kwargs_func)) == "(**kwargs)" 353 | `, 354 | }, 355 | } 356 | 357 | code := ` 358 | import inspect 359 | 360 | ` 361 | for _, tt := range tests { 362 | funcName := tt.name + "_func" 363 | f := m.AddMethod(funcName, tt.fn, tt.doc) 364 | if f.Nil() { 365 | t.Fatalf("Failed to create function for test case: %s", tt.name) 366 | } 367 | code += tt.testCode + "\n" 368 | } 369 | 370 | t.Log(code) 371 | err := RunString(code) 372 | if err != nil { 373 | t.Fatalf("Test failed: %v", err) 374 | } 375 | 376 | func() { 377 | defer func() { 378 | if r := recover(); r == nil { 379 | t.Error("AddMethod should panic with non-function argument") 380 | } 381 | }() 382 | m.AddMethod("invalid", 42, "should panic") 383 | }() 384 | 385 | func() { 386 | defer func() { 387 | if r := recover(); r == nil { 388 | t.Error("AddMethod should panic with nil function") 389 | } 390 | }() 391 | m.AddMethod("nil_func", nil, "should panic") 392 | }() 393 | 394 | func() { 395 | defer func() { 396 | if r := recover(); r == nil { 397 | t.Error("AddMethod should panic with empty module") 398 | } 399 | }() 400 | var emptyModule Module 401 | emptyModule.AddMethod("empty", func() {}, "should panic") 402 | }() 403 | } 404 | 405 | func TestAddTypeWithPtrReceiverInit(t *testing.T) { 406 | setupTest(t) 407 | m := MainModule() 408 | 409 | type InitTestType struct { 410 | Value int 411 | Name string 412 | Active bool 413 | } 414 | 415 | ptrInit := func(t *InitTestType, val int, name string, active bool) { 416 | t.Value = val 417 | t.Name = name 418 | t.Active = active 419 | } 420 | typ := m.AddType(InitTestType{}, ptrInit, "InitTestType", "") 421 | if typ.Nil() { 422 | t.Fatal("Failed to create type with pointer receiver init") 423 | } 424 | 425 | code := ` 426 | # Test pointer receiver init with multiple args 427 | obj = InitTestType(42, "hello", True) 428 | assert obj.value == 42 429 | assert obj.name == "hello" 430 | assert obj.active == True 431 | 432 | # Test error handling 433 | try: 434 | obj2 = InitTestType(42) # Missing arguments 435 | assert False, "Should fail with wrong number of arguments" 436 | except TypeError: 437 | pass 438 | 439 | try: 440 | obj3 = InitTestType("wrong", "type", True) # Wrong argument type 441 | assert False, "Should fail with wrong argument type" 442 | except TypeError: 443 | pass 444 | ` 445 | err := RunString(code) 446 | if err != nil { 447 | t.Fatalf("Test failed: %v", err) 448 | } 449 | } 450 | 451 | func TestAddTypeWithValueConstructor(t *testing.T) { 452 | setupTest(t) 453 | m := MainModule() 454 | 455 | type InitTestType struct { 456 | Value int 457 | Name string 458 | Active bool 459 | } 460 | 461 | constructorInit := func(val int, name string, active bool) InitTestType { 462 | return InitTestType{ 463 | Value: val, 464 | Name: name, 465 | Active: active, 466 | } 467 | } 468 | typ := m.AddType(InitTestType{}, constructorInit, "InitTestType", "") 469 | if typ.Nil() { 470 | t.Fatal("Failed to create type with value constructor") 471 | } 472 | 473 | code := ` 474 | # Test value constructor with multiple args 475 | obj = InitTestType(43, "world", False) 476 | assert obj.value == 43 477 | assert obj.name == "world" 478 | assert obj.active == False 479 | ` 480 | err := RunString(code) 481 | if err != nil { 482 | t.Fatalf("Test failed: %v", err) 483 | } 484 | } 485 | 486 | func TestAddTypeWithPtrConstructor(t *testing.T) { 487 | setupTest(t) 488 | m := MainModule() 489 | 490 | type InitTestType struct { 491 | Value int 492 | Name string 493 | Active bool 494 | } 495 | 496 | ptrConstructorInit := func(val int, name string) *InitTestType { 497 | return &InitTestType{ 498 | Value: val, 499 | Name: name, 500 | } 501 | } 502 | typ := m.AddType(InitTestType{}, ptrConstructorInit, "InitTestType", "") 503 | if typ.Nil() { 504 | t.Fatal("Failed to create type with pointer constructor") 505 | } 506 | 507 | code := ` 508 | # Test pointer constructor with multiple args 509 | obj = InitTestType(44, "python") 510 | assert obj.value == 44 511 | assert obj.name == "python" 512 | assert obj.active == False # default value 513 | ` 514 | err := RunString(code) 515 | if err != nil { 516 | t.Fatalf("Test failed: %v", err) 517 | } 518 | } 519 | 520 | type Inner struct { 521 | X int 522 | Y string 523 | } 524 | 525 | func TestAddTypeRecursive(t *testing.T) { 526 | setupTest(t) 527 | m := MainModule() 528 | 529 | type Outer struct { 530 | Inner Inner 531 | InnerPtr *Inner 532 | Value int 533 | InnerList []Inner 534 | } 535 | 536 | // Register Outer type - should automatically register Inner 537 | obj := m.AddType(Outer{}, nil, "Outer", "") 538 | if obj.Nil() { 539 | t.Fatal("Failed to create Outer type") 540 | } 541 | 542 | code := ` 543 | # Test nested struct access 544 | o = Outer() 545 | o.inner.x = 42 546 | o.inner.y = "hello" 547 | assert o.inner.x == 42 548 | assert o.inner.y == "hello" 549 | 550 | # Test pointer to struct 551 | o.inner_ptr = Inner() 552 | o.inner_ptr.x = 43 553 | o.inner_ptr.y = "world" 554 | assert o.inner_ptr.x == 43 555 | assert o.inner_ptr.y == "world" 556 | 557 | # Test basic field 558 | o.value = 100 559 | assert o.value == 100 560 | 561 | # Test slice of structs 562 | o.inner_list = [Inner()] 563 | o.inner_list[0].x = 44 564 | o.inner_list[0].y = "python" 565 | assert o.inner_list[0].x == 44 566 | assert o.inner_list[0].y == "python" 567 | ` 568 | 569 | err := RunString(code) 570 | if err != nil { 571 | t.Fatalf("Test failed: %v", err) 572 | } 573 | } 574 | 575 | func TestSetterMethodEdgeCases(t *testing.T) { 576 | setupTest(t) 577 | 578 | type ChildStruct struct { 579 | Value int 580 | } 581 | 582 | type ParentStruct struct { 583 | unexported int 584 | Value int 585 | Child *ChildStruct 586 | Nested ChildStruct 587 | } 588 | 589 | m := MainModule() 590 | m.AddType(ChildStruct{}, nil, "ChildStruct", "") 591 | m.AddType(ParentStruct{}, nil, "ParentStruct", "") 592 | 593 | code := ` 594 | obj = ParentStruct() 595 | try: 596 | obj.value = "invalid" # Try to set int with string 597 | assert False, "Should have raised TypeError" 598 | except TypeError: 599 | pass 600 | 601 | try: 602 | obj.child = 123 # Try to set struct pointer with int 603 | assert False, "Should have raised TypeError" 604 | except TypeError: 605 | pass 606 | 607 | try: 608 | obj.nested = 123 # Try to set struct with int 609 | assert False, "Should have raised TypeError" 610 | except TypeError: 611 | pass 612 | ` 613 | err := RunString(code) 614 | if err != nil { 615 | t.Fatal(err) 616 | } 617 | } 618 | 619 | func TestGetterMethodEdgeCases(t *testing.T) { 620 | setupTest(t) 621 | 622 | type ChildStruct struct { 623 | Value int 624 | } 625 | 626 | type ParentStruct struct { 627 | Value int 628 | Child *ChildStruct 629 | Nested ChildStruct 630 | } 631 | 632 | m := MainModule() 633 | m.AddType(ChildStruct{}, nil, "ChildStruct", "") 634 | m.AddType(ParentStruct{}, nil, "ParentStruct", "") 635 | 636 | code := ` 637 | obj = ParentStruct() 638 | obj.child = None # Set pointer to nil 639 | val = obj.child # Should return None for nil pointer 640 | assert val is None 641 | 642 | obj.nested = ChildStruct() # Set nested struct 643 | val = obj.nested # Should return wrapper for nested struct 644 | assert isinstance(val, ChildStruct) 645 | 646 | # Test accessing nested struct fields 647 | obj.nested.value = 42 648 | assert obj.nested.value == 42 649 | ` 650 | err := RunString(code) 651 | if err != nil { 652 | t.Fatal(err) 653 | } 654 | } 655 | 656 | func TestWrapperMethodEdgeCases(t *testing.T) { 657 | setupTest(t) 658 | 659 | type TestStruct struct { 660 | Value int 661 | } 662 | 663 | m := MainModule() 664 | 665 | // Test method with wrong number of arguments 666 | m.AddMethod("test_func", func(x int, y int) int { return x + y }, "") 667 | 668 | code := ` 669 | try: 670 | test_func(1) # Missing argument 671 | assert False, "Should have raised TypeError" 672 | except TypeError: 673 | pass 674 | 675 | try: 676 | test_func(1, 2, 3) # Too many arguments 677 | assert False, "Should have raised TypeError" 678 | except TypeError: 679 | pass 680 | 681 | try: 682 | test_func("invalid", 2) # Invalid argument type 683 | assert False, "Should have raised TypeError" 684 | except TypeError: 685 | pass 686 | ` 687 | err := RunString(code) 688 | if err != nil { 689 | t.Fatal(err) 690 | } 691 | } 692 | 693 | func TestAddTypeEdgeCases(t *testing.T) { 694 | setupTest(t) 695 | 696 | // Test adding non-struct type 697 | defer func() { 698 | if r := recover(); r == nil { 699 | t.Error("Expected panic when adding non-struct type") 700 | } 701 | }() 702 | 703 | m := MainModule() 704 | m.AddType(123, nil, "NotAStruct", "") 705 | } 706 | 707 | func TestInitFunctionEdgeCases(t *testing.T) { 708 | setupTest(t) 709 | 710 | type TestStruct struct { 711 | Value int 712 | } 713 | 714 | // Test init function with invalid signature 715 | defer func() { 716 | if r := recover(); r == nil { 717 | t.Error("Expected panic when using invalid init function") 718 | } 719 | }() 720 | 721 | m := MainModule() 722 | invalidInit := func(x string) string { return x } // Wrong signature 723 | m.AddType(TestStruct{}, invalidInit, "TestStruct", "") 724 | } 725 | 726 | func TestNestedStructRegistration(t *testing.T) { 727 | setupTest(t) 728 | 729 | type NestedStruct struct { 730 | Value int 731 | } 732 | 733 | type ParentStruct struct { 734 | Nested NestedStruct 735 | NestedPtr *NestedStruct 736 | } 737 | 738 | m := MainModule() 739 | m.AddType(ParentStruct{}, nil, "ParentStruct", "") 740 | 741 | code := ` 742 | parent = ParentStruct() 743 | assert hasattr(parent, "nested") 744 | assert hasattr(parent, "nested_ptr") 745 | 746 | # Test nested struct manipulation 747 | parent.nested.value = 42 748 | assert parent.nested.value == 42 749 | 750 | parent.nested_ptr = None 751 | assert parent.nested_ptr is None 752 | 753 | # Create and assign new nested struct 754 | parent.nested_ptr = NestedStruct() 755 | parent.nested_ptr.value = 100 756 | assert parent.nested_ptr.value == 100 757 | ` 758 | err := RunString(code) 759 | if err != nil { 760 | t.Fatal(err) 761 | } 762 | } 763 | 764 | func TestAddTypeWithPointerArg(t *testing.T) { 765 | setupTest(t) 766 | m := MainModule() 767 | 768 | type TestStruct struct { 769 | Value int 770 | } 771 | 772 | // Test adding type with pointer argument 773 | typ1 := m.AddType(&TestStruct{}, nil, "TestStruct", "") 774 | if typ1.Nil() { 775 | t.Fatal("Failed to create type with pointer argument") 776 | } 777 | 778 | code := ` 779 | obj = TestStruct() 780 | obj.value = 42 781 | assert obj.value == 42 782 | ` 783 | err := RunString(code) 784 | if err != nil { 785 | t.Fatal(err) 786 | } 787 | } 788 | 789 | func TestAddTypeDuplicate(t *testing.T) { 790 | setupTest(t) 791 | m := MainModule() 792 | 793 | type TestStruct struct { 794 | Value int 795 | } 796 | 797 | // First registration 798 | typ1 := m.AddType(TestStruct{}, nil, "TestStruct", "") 799 | if typ1.Nil() { 800 | t.Fatal("Failed to create type on first registration") 801 | } 802 | 803 | // Second registration should return the same type object 804 | typ2 := m.AddType(TestStruct{}, nil, "TestStruct", "") 805 | if typ2.Nil() { 806 | t.Fatal("Failed to get type on second registration") 807 | } 808 | 809 | if typ1.cpyObj() != typ2.cpyObj() { 810 | t.Fatal("Expected same type object on second registration") 811 | } 812 | 813 | // Both types should work with the same underlying Go type 814 | code := ` 815 | obj1 = TestStruct() 816 | obj1.value = 42 817 | assert obj1.value == 42 818 | ` 819 | err := RunString(code) 820 | if err != nil { 821 | t.Fatal(err) 822 | } 823 | 824 | // Also test with pointer argument 825 | typ3 := m.AddType(&TestStruct{}, nil, "TestStruct3", "") 826 | if typ3.Nil() { 827 | t.Fatal("Failed to get type on registration with pointer") 828 | } 829 | 830 | if typ1.cpyObj() != typ3.cpyObj() { 831 | t.Fatal("Expected same type object on second registration") 832 | } 833 | } 834 | 835 | func TestStructPointerFieldDictAssignment(t *testing.T) { 836 | setupTest(t) 837 | m := MainModule() 838 | 839 | type NestedStruct struct { 840 | IntVal int 841 | StringVal string 842 | } 843 | 844 | type ParentStruct struct { 845 | PtrField *NestedStruct 846 | } 847 | 848 | m.AddType(ParentStruct{}, nil, "ParentStruct", "") 849 | 850 | // Test assigning dict to nil pointer field 851 | code := ` 852 | obj = ParentStruct() 853 | # Initially the pointer should be nil 854 | assert obj.ptr_field is None 855 | 856 | # Assign dict to nil pointer field 857 | obj.ptr_field = {"int_val": 42, "string_val": "hello"} 858 | assert obj.ptr_field.int_val == 42 859 | assert obj.ptr_field.string_val == "hello" 860 | 861 | # Test invalid dict value type 862 | try: 863 | obj.ptr_field = {"int_val": "not an int", "string_val": "hello"} 864 | assert False, "Should have raised TypeError for invalid int_val" 865 | except TypeError: 866 | pass 867 | 868 | # Test completely wrong type 869 | try: 870 | obj.ptr_field = ["not", "a", "dict"] 871 | assert False, "Should have raised TypeError for list" 872 | except TypeError: 873 | pass 874 | 875 | # Test nested dict with wrong type 876 | try: 877 | obj.ptr_field = {"int_val": {"nested": "dict"}, "string_val": "hello"} 878 | assert False, "Should have raised TypeError for nested dict" 879 | except TypeError: 880 | pass 881 | ` 882 | err := RunString(code) 883 | if err != nil { 884 | t.Fatal(err) 885 | } 886 | } 887 | 888 | func TestStructFieldDictAssignment(t *testing.T) { 889 | setupTest(t) 890 | m := MainModule() 891 | 892 | type NestedStruct struct { 893 | IntVal int 894 | StringVal string 895 | } 896 | 897 | type ParentStruct struct { 898 | Field NestedStruct 899 | } 900 | 901 | m.AddType(ParentStruct{}, nil, "ParentStruct", "") 902 | 903 | // Test assigning dict to struct field 904 | code := ` 905 | obj = ParentStruct() 906 | 907 | # Assign valid dict 908 | obj.field = {"int_val": 42, "string_val": "hello"} 909 | assert obj.field.int_val == 42 910 | assert obj.field.string_val == "hello" 911 | 912 | # Test invalid value type 913 | try: 914 | obj.field = {"int_val": "not an int", "string_val": "hello"} 915 | assert False, "Should have raised TypeError for invalid int_val" 916 | except TypeError: 917 | pass 918 | 919 | # Test completely wrong type 920 | try: 921 | obj.field = ["not", "a", "dict"] 922 | assert False, "Should have raised TypeError for list" 923 | except TypeError: 924 | pass 925 | 926 | # Test nested dict with wrong type 927 | try: 928 | obj.field = {"int_val": {"nested": "dict"}, "string_val": "hello"} 929 | assert False, "Should have raised TypeError for nested dict" 930 | except TypeError: 931 | pass 932 | 933 | # Test with complex nested structure 934 | obj.field = { 935 | "int_val": 100, 936 | "string_val": "test" 937 | } 938 | assert obj.field.int_val == 100 939 | assert obj.field.string_val == "test" 940 | ` 941 | err := RunString(code) 942 | if err != nil { 943 | t.Fatal(err) 944 | } 945 | } 946 | 947 | func TestGenSig(t *testing.T) { 948 | type CustomStruct struct { 949 | Value int 950 | } 951 | 952 | tests := []struct { 953 | name string 954 | fn any 955 | hasRecv bool 956 | expectedSig string 957 | }{ 958 | { 959 | name: "function with return", 960 | fn: func(x int) string { return "" }, 961 | hasRecv: false, 962 | expectedSig: "(arg0, /)", 963 | }, 964 | { 965 | name: "multiple arguments", 966 | fn: func(x string, y int) (int, string) { return 0, "" }, 967 | hasRecv: false, 968 | expectedSig: "(arg0, arg1, /)", 969 | }, 970 | { 971 | name: "with kwargs", 972 | fn: func(name string, kwargs KwArgs) Object { return None() }, 973 | hasRecv: false, 974 | expectedSig: "(arg0, /, **kwargs)", 975 | }, 976 | { 977 | name: "method with receiver", 978 | fn: func(r *CustomStruct, x int) float64 { return 0 }, 979 | hasRecv: true, 980 | expectedSig: "(arg0, /)", 981 | }, 982 | { 983 | name: "no arguments", 984 | fn: func() {}, 985 | hasRecv: false, 986 | expectedSig: "()", 987 | }, 988 | { 989 | name: "only kwargs", 990 | fn: func(kwargs KwArgs) {}, 991 | hasRecv: false, 992 | expectedSig: "(**kwargs)", 993 | }, 994 | } 995 | 996 | for _, tt := range tests { 997 | t.Run(tt.name, func(t *testing.T) { 998 | got := genSig(tt.fn, tt.hasRecv) 999 | if got != tt.expectedSig { 1000 | t.Errorf("genSig() = %q, want %q", got, tt.expectedSig) 1001 | } 1002 | }) 1003 | } 1004 | } 1005 | 1006 | func TestCreateFuncNaming(t *testing.T) { 1007 | setupTest(t) 1008 | 1009 | tests := []struct { 1010 | name string 1011 | givenName string 1012 | fn any 1013 | doc string 1014 | expectedName string 1015 | }{ 1016 | { 1017 | name: "explicit name", 1018 | givenName: "my_func", 1019 | fn: func(x int) int { return x }, 1020 | doc: "test function", 1021 | expectedName: "my_func", 1022 | }, 1023 | { 1024 | name: "empty name uses function name", 1025 | givenName: "", 1026 | fn: explicitFunc, 1027 | doc: "test function", 1028 | expectedName: "explicit_func", 1029 | }, 1030 | { 1031 | name: "camelCase to snake_case", 1032 | givenName: "camelCaseName", 1033 | fn: func() {}, 1034 | doc: "test function", 1035 | expectedName: "camel_case_name", 1036 | }, 1037 | { 1038 | name: "package path stripped", 1039 | givenName: "github.com/user/pkg.MyFunc", 1040 | fn: func() {}, 1041 | doc: "test function", 1042 | expectedName: "my_func", 1043 | }, 1044 | } 1045 | 1046 | for _, tt := range tests { 1047 | func() { 1048 | f := CreateFunc(tt.givenName, tt.fn, tt.doc) 1049 | if f.Nil() { 1050 | t.Fatal("Failed to create function") 1051 | } 1052 | 1053 | code := fmt.Sprintf(` 1054 | assert "%s" in globals(), "Function %s not found in globals" 1055 | assert callable(globals()["%s"]), "Function %s is not callable" 1056 | `, tt.expectedName, tt.expectedName, tt.expectedName, tt.expectedName) 1057 | 1058 | err := RunString(code) 1059 | if err != nil { 1060 | t.Fatalf("Test failed: %v", err) 1061 | } 1062 | }() 1063 | } 1064 | } 1065 | 1066 | func TestAddMethodNaming(t *testing.T) { 1067 | setupTest(t) 1068 | m := MainModule() 1069 | 1070 | tests := []struct { 1071 | name string 1072 | givenName string 1073 | fn any 1074 | doc string 1075 | expectedName string 1076 | }{ 1077 | { 1078 | name: "explicit name", 1079 | givenName: "my_method", 1080 | fn: func(x int) int { return x }, 1081 | doc: "test method", 1082 | expectedName: "my_method", 1083 | }, 1084 | { 1085 | name: "empty name uses function name", 1086 | givenName: "", 1087 | fn: explicitFunc, 1088 | doc: "test method", 1089 | expectedName: "explicit_func", 1090 | }, 1091 | { 1092 | name: "anonymous function gets generated name", 1093 | givenName: "", 1094 | fn: func() {}, 1095 | doc: "test method", 1096 | expectedName: "func", 1097 | }, 1098 | { 1099 | name: "camelCase to snake_case", 1100 | givenName: "myMethodName", 1101 | fn: func() {}, 1102 | doc: "test method", 1103 | expectedName: "my_method_name", 1104 | }, 1105 | } 1106 | 1107 | for _, tt := range tests { 1108 | func() { 1109 | f := m.AddMethod(tt.givenName, tt.fn, tt.doc) 1110 | if f.Nil() { 1111 | t.Fatal("Failed to create method") 1112 | } 1113 | if !strings.HasPrefix(f.Name(), tt.expectedName) { 1114 | t.Errorf("Expected method name to start with %s, got %s", tt.expectedName, f.Name()) 1115 | } 1116 | }() 1117 | } 1118 | } 1119 | -------------------------------------------------------------------------------- /float.go: -------------------------------------------------------------------------------- 1 | package gp 2 | 3 | /* 4 | #include 5 | */ 6 | import "C" 7 | 8 | // Float represents a Python float object. It provides methods to convert between 9 | // Go float types and Python float objects, as well as checking numeric properties. 10 | type Float struct { 11 | Object 12 | } 13 | 14 | func newFloat(obj *cPyObject) Float { 15 | return Float{newObject(obj)} 16 | } 17 | 18 | func MakeFloat(f float64) Float { 19 | return newFloat(C.PyFloat_FromDouble(C.double(f))) 20 | } 21 | 22 | func (f Float) Float64() float64 { 23 | return float64(C.PyFloat_AsDouble(f.obj)) 24 | } 25 | 26 | func (f Float) Float32() float32 { 27 | return float32(C.PyFloat_AsDouble(f.obj)) 28 | } 29 | 30 | func (f Float) IsInteger() Bool { 31 | fn := cast[Func](f.Attr("is_integer")) 32 | return cast[Bool](fn.callNoArgs()) 33 | } 34 | -------------------------------------------------------------------------------- /float_test.go: -------------------------------------------------------------------------------- 1 | package gp 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestFloat(t *testing.T) { 8 | setupTest(t) 9 | func() { 10 | // Test creating float and converting back 11 | f := MakeFloat(3.14159) 12 | 13 | // Test Float64 conversion 14 | if got := f.Float64(); got != 3.14159 { 15 | t.Errorf("Float64() = %v, want %v", got, 3.14159) 16 | } 17 | 18 | // Test Float32 conversion 19 | if got := f.Float32(); float64(got) != float64(float32(3.14159)) { 20 | t.Errorf("Float32() = %v, want %v", got, float32(3.14159)) 21 | } 22 | }() 23 | 24 | func() { 25 | // Test integer float 26 | intFloat := MakeFloat(5.0) 27 | 28 | if !intFloat.IsInteger().Bool() { 29 | t.Errorf("IsInteger() for 5.0 = false, want true") 30 | } 31 | 32 | // Test non-integer float 33 | fracFloat := MakeFloat(5.5) 34 | 35 | if fracFloat.IsInteger().Bool() { 36 | t.Errorf("IsInteger() for 5.5 = true, want false") 37 | } 38 | }() 39 | 40 | func() { 41 | // Test zero 42 | zero := MakeFloat(0.0) 43 | 44 | if got := zero.Float64(); got != 0.0 { 45 | t.Errorf("Float64() = %v, want 0.0", got) 46 | } 47 | 48 | // Test very large number 49 | large := MakeFloat(1e308) 50 | 51 | if got := large.Float64(); got != 1e308 { 52 | t.Errorf("Float64() = %v, want 1e308", got) 53 | } 54 | }() 55 | } 56 | -------------------------------------------------------------------------------- /function.go: -------------------------------------------------------------------------------- 1 | package gp 2 | 3 | /* 4 | #include 5 | */ 6 | import "C" 7 | 8 | type Objecter interface { 9 | cpyObj() *cPyObject 10 | object() Object 11 | Ensure() 12 | } 13 | 14 | type Func struct { 15 | Object 16 | } 17 | 18 | func newFunc(obj *cPyObject) Func { 19 | return Func{newObject(obj)} 20 | } 21 | 22 | func (f Func) Ensure() { 23 | f.pyObject.Ensure() 24 | } 25 | 26 | func (f Func) Name() string { 27 | return f.AttrString("__name__").String() 28 | } 29 | 30 | func (f Func) call(args Tuple, kwargs Dict) Object { 31 | return newObject(C.PyObject_Call(f.obj, args.obj, kwargs.obj)) 32 | } 33 | 34 | func (f Func) callNoArgs() Object { 35 | return newObject(C.PyObject_CallNoArgs(f.obj)) 36 | } 37 | 38 | func (f Func) callOneArg(arg Objecter) Object { 39 | return newObject(C.PyObject_CallOneArg(f.obj, arg.cpyObj())) 40 | } 41 | 42 | func (f Func) CallObject(args Tuple) Object { 43 | defer getGlobalData().decRefObjectsIfNeeded() 44 | return newObject(C.PyObject_CallObject(f.obj, args.obj)) 45 | } 46 | 47 | func (f Func) CallObjectKw(args Tuple, kw KwArgs) Object { 48 | defer getGlobalData().decRefObjectsIfNeeded() 49 | // Convert keyword arguments to Python dict 50 | kwDict := MakeDict(nil) 51 | for k, v := range kw { 52 | kwDict.Set(MakeStr(k), From(v)) 53 | } 54 | return f.call(args, kwDict) 55 | } 56 | 57 | func (f Func) Call(args ...any) Object { 58 | argsTuple, kwArgs := splitArgs(args...) 59 | if kwArgs == nil { 60 | defer getGlobalData().decRefObjectsIfNeeded() 61 | switch len(args) { 62 | case 0: 63 | return f.callNoArgs() 64 | case 1: 65 | return f.callOneArg(From(args[0])) 66 | default: 67 | return f.CallObject(argsTuple) 68 | } 69 | } else { 70 | return f.CallObjectKw(argsTuple, kwArgs) 71 | } 72 | } 73 | 74 | // ---------------------------------------------------------------------------- 75 | -------------------------------------------------------------------------------- /function_test.go: -------------------------------------------------------------------------------- 1 | package gp 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestFuncCall(t *testing.T) { 8 | Initialize() 9 | defer Finalize() 10 | 11 | // Get the Python built-in len function 12 | builtins := ImportModule("builtins") 13 | lenFunc := builtins.AttrFunc("len") 14 | 15 | func() { 16 | // Test calling with no args (should fail, len requires an argument) 17 | defer func() { 18 | if r := recover(); r == nil { 19 | t.Error("Expected panic when calling len() with no arguments") 20 | } 21 | }() 22 | lenFunc.Call() 23 | t.Error("Should not reach this point") 24 | }() 25 | 26 | // Test calling with one arg 27 | list := MakeList(1, 2, 3) 28 | result := lenFunc.Call(list) 29 | if result.AsLong().Int64() != 3 { 30 | t.Errorf("Expected len([1,2,3]) to be 3, got %d", result.AsLong().Int64()) 31 | } 32 | 33 | // Test str.format with keyword args 34 | str := MakeStr("Hello {name}!") 35 | formatFunc := str.AttrFunc("format") 36 | result = formatFunc.Call(KwArgs{"name": "World"}) 37 | if result.AsStr().String() != "Hello World!" { 38 | t.Errorf("Expected 'Hello World!', got '%s'", result.AsStr().String()) 39 | } 40 | 41 | // Test calling with multiple keyword args 42 | str = MakeStr("{greeting} {name}!") 43 | formatFunc = str.AttrFunc("format") 44 | result = formatFunc.Call(KwArgs{ 45 | "greeting": "Hi", 46 | "name": "Python", 47 | }) 48 | if result.AsStr().String() != "Hi Python!" { 49 | t.Errorf("Expected 'Hi Python!', got '%s'", result.AsStr().String()) 50 | } 51 | 52 | // Test pow function with positional args only 53 | math := ImportModule("math") 54 | powFunc := math.AttrFunc("pow") 55 | result = powFunc.Call(2, 3) 56 | if result.AsFloat().Float64() != 8 { // 2^3 = 8 57 | t.Errorf("Expected pow(2, 3) to be 8, got %v", result) 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /global_data.go: -------------------------------------------------------------------------------- 1 | package gp 2 | 3 | /* 4 | #include 5 | */ 6 | import "C" 7 | 8 | import ( 9 | "reflect" 10 | "sync" 11 | "sync/atomic" 12 | "unsafe" 13 | ) 14 | 15 | // ---------------------------------------------------------------------------- 16 | 17 | type holderList struct { 18 | head *objectHolder 19 | } 20 | 21 | func (l *holderList) PushFront(holder *objectHolder) { 22 | if l.head != nil { 23 | l.head.prev = holder 24 | holder.next = l.head 25 | } 26 | l.head = holder 27 | } 28 | 29 | func (l *holderList) Remove(holder *objectHolder) { 30 | if holder.prev != nil { 31 | holder.prev.next = holder.next 32 | } else { 33 | l.head = holder.next 34 | } 35 | if holder.next != nil { 36 | holder.next.prev = holder.prev 37 | } 38 | } 39 | 40 | // ---------------------------------------------------------------------------- 41 | 42 | const maxPyObjects = 128 43 | 44 | type decRefList struct { 45 | objects []*C.PyObject 46 | mu sync.Mutex 47 | } 48 | 49 | func (l *decRefList) add(obj *C.PyObject) { 50 | l.mu.Lock() 51 | l.objects = append(l.objects, obj) 52 | l.mu.Unlock() 53 | } 54 | 55 | func (l *decRefList) len() int { 56 | l.mu.Lock() 57 | defer l.mu.Unlock() 58 | return len(l.objects) 59 | } 60 | 61 | func (l *decRefList) decRefAll() { 62 | l.mu.Lock() 63 | list := l.objects 64 | l.objects = make([]*C.PyObject, 0, maxPyObjects*2) 65 | l.mu.Unlock() 66 | 67 | for _, obj := range list { 68 | C.Py_DecRef(obj) 69 | } 70 | } 71 | 72 | // ---------------------------------------------------------------------------- 73 | 74 | type globalData struct { 75 | typeMetas map[*C.PyObject]*typeMeta 76 | pyTypes map[reflect.Type]*C.PyObject 77 | holders holderList 78 | decRefList decRefList 79 | finished int32 80 | alwaysDecRef bool 81 | } 82 | 83 | var ( 84 | global *globalData 85 | ) 86 | 87 | func getGlobalData() *globalData { 88 | return global 89 | } 90 | 91 | func (gd *globalData) addDecRef(obj *C.PyObject) { 92 | if atomic.LoadInt32(&gd.finished) != 0 { 93 | return 94 | } 95 | gd.decRefList.add(obj) 96 | } 97 | 98 | func (gd *globalData) decRefObjectsIfNeeded() { 99 | if gd.alwaysDecRef || gd.decRefList.len() >= maxPyObjects { 100 | gd.decRefList.decRefAll() 101 | } 102 | } 103 | 104 | // ---------------------------------------------------------------------------- 105 | 106 | func initGlobal() { 107 | global = &globalData{ 108 | typeMetas: make(map[*C.PyObject]*typeMeta), 109 | pyTypes: make(map[reflect.Type]*C.PyObject), 110 | } 111 | } 112 | 113 | func markFinished() { 114 | atomic.StoreInt32(&global.finished, 1) 115 | } 116 | 117 | func cleanupGlobal() { 118 | for _, meta := range global.typeMetas { 119 | for _, method := range meta.methods { 120 | def := method.def 121 | if def != nil { 122 | C.free(unsafe.Pointer(def.ml_name)) 123 | C.free(unsafe.Pointer(def.ml_doc)) 124 | C.free(unsafe.Pointer(def)) 125 | } 126 | } 127 | } 128 | global = nil 129 | } 130 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/gotray/go-python 2 | 3 | go 1.20 4 | 5 | require ( 6 | github.com/fatih/color v1.18.0 7 | github.com/klauspost/compress v1.17.11 8 | github.com/spf13/cobra v1.8.1 9 | go.uber.org/zap v1.27.0 10 | ) 11 | 12 | require ( 13 | github.com/inconshreveable/mousetrap v1.1.0 // indirect 14 | github.com/mattn/go-colorable v0.1.13 // indirect 15 | github.com/mattn/go-isatty v0.0.20 // indirect 16 | github.com/spf13/pflag v1.0.5 // indirect 17 | go.uber.org/multierr v1.10.0 // indirect 18 | golang.org/x/sys v0.25.0 // indirect 19 | ) 20 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= 2 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 3 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 4 | github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= 5 | github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= 6 | github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= 7 | github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= 8 | github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc= 9 | github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0= 10 | github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= 11 | github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= 12 | github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= 13 | github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= 14 | github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 15 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 16 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 17 | github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 18 | github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= 19 | github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= 20 | github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= 21 | github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 22 | github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= 23 | github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 24 | go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= 25 | go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= 26 | go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ= 27 | go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= 28 | go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= 29 | go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= 30 | golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 31 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 32 | golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= 33 | golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 34 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 35 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 36 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 37 | -------------------------------------------------------------------------------- /kw.go: -------------------------------------------------------------------------------- 1 | package gp 2 | 3 | type KwArgs map[string]any 4 | 5 | func splitArgs(args ...any) (Tuple, KwArgs) { 6 | if len(args) > 0 { 7 | last := args[len(args)-1] 8 | if kw, ok := last.(KwArgs); ok { 9 | return MakeTuple(args[:len(args)-1]...), kw 10 | } 11 | } 12 | return MakeTuple(args...), nil 13 | } 14 | -------------------------------------------------------------------------------- /kw_test.go: -------------------------------------------------------------------------------- 1 | package gp 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | ) 7 | 8 | func TestSplitArgs(t *testing.T) { 9 | setupTest(t) 10 | tests := []struct { 11 | name string 12 | args []any 13 | wantTup Tuple 14 | wantKw KwArgs 15 | }{ 16 | { 17 | name: "empty args", 18 | args: []any{}, 19 | wantTup: MakeTuple(), 20 | wantKw: nil, 21 | }, 22 | { 23 | name: "only positional args", 24 | args: []any{1, "two", 3.0}, 25 | wantTup: MakeTuple(1, "two", 3.0), 26 | wantKw: nil, 27 | }, 28 | { 29 | name: "with kwargs", 30 | args: []any{1, "two", KwArgs{"a": 1, "b": "test"}}, 31 | wantTup: MakeTuple(1, "two"), 32 | wantKw: KwArgs{"a": 1, "b": "test"}, 33 | }, 34 | { 35 | name: "only kwargs", 36 | args: []any{KwArgs{"x": 10, "y": 20}}, 37 | wantTup: MakeTuple(), 38 | wantKw: KwArgs{"x": 10, "y": 20}, 39 | }, 40 | } 41 | 42 | for _, tt := range tests { 43 | gotTup, gotKw := splitArgs(tt.args...) 44 | 45 | if !reflect.DeepEqual(gotTup, tt.wantTup) { 46 | t.Errorf("splitArgs() tuple = %v, want %v", gotTup, tt.wantTup) 47 | } 48 | 49 | if !reflect.DeepEqual(gotKw, tt.wantKw) { 50 | t.Errorf("splitArgs() kwargs = %v, want %v", gotKw, tt.wantKw) 51 | } 52 | } 53 | } 54 | 55 | func TestKwArgs(t *testing.T) { 56 | setupTest(t) 57 | kw := KwArgs{ 58 | "name": "test", 59 | "age": 42, 60 | } 61 | 62 | // Test type assertion 63 | if _, ok := interface{}(kw).(KwArgs); !ok { 64 | t.Error("KwArgs failed type assertion") 65 | } 66 | 67 | // Test map operations 68 | kw["new"] = "value" 69 | if v, ok := kw["new"]; !ok || v != "value" { 70 | t.Error("KwArgs map operations failed") 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /list.go: -------------------------------------------------------------------------------- 1 | package gp 2 | 3 | /* 4 | #include 5 | */ 6 | import "C" 7 | import "fmt" 8 | 9 | type List struct { 10 | Object 11 | } 12 | 13 | func newList(obj *cPyObject) List { 14 | return List{newObject(obj)} 15 | } 16 | 17 | func MakeList(args ...any) List { 18 | list := newList(C.PyList_New(C.Py_ssize_t(len(args)))) 19 | for i, arg := range args { 20 | obj := From(arg) 21 | list.SetItem(i, obj) 22 | } 23 | return list 24 | } 25 | 26 | func (l List) GetItem(index int) Object { 27 | return newObject(C.PySequence_GetItem(l.obj, C.Py_ssize_t(index))) 28 | } 29 | 30 | func (l List) SetItem(index int, item Objecter) { 31 | itemObj := item.cpyObj() 32 | C.Py_IncRef(itemObj) 33 | r := C.PyList_SetItem(l.obj, C.Py_ssize_t(index), itemObj) 34 | check(r == 0, fmt.Sprintf("failed to set item %d in list", index)) 35 | } 36 | 37 | func (l List) Len() int { 38 | return int(C.PyList_Size(l.obj)) 39 | } 40 | 41 | func (l List) Append(obj Objecter) { 42 | r := C.PyList_Append(l.obj, obj.cpyObj()) 43 | check(r == 0, "failed to append item to list") 44 | } 45 | -------------------------------------------------------------------------------- /list_test.go: -------------------------------------------------------------------------------- 1 | package gp 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestMakeList(t *testing.T) { 8 | setupTest(t) 9 | tests := []struct { 10 | name string 11 | args []any 12 | wantLen int 13 | wantVals []any 14 | }{ 15 | { 16 | name: "empty list", 17 | args: []any{}, 18 | wantLen: 0, 19 | wantVals: []any{}, 20 | }, 21 | { 22 | name: "integers", 23 | args: []any{1, 2, 3}, 24 | wantLen: 3, 25 | wantVals: []any{1, 2, 3}, 26 | }, 27 | { 28 | name: "mixed types", 29 | args: []any{1, "hello", 3.14}, 30 | wantLen: 3, 31 | wantVals: []any{1, "hello", 3.14}, 32 | }, 33 | } 34 | 35 | for _, tt := range tests { 36 | list := MakeList(tt.args...) 37 | 38 | if got := list.Len(); got != tt.wantLen { 39 | t.Errorf("MakeList() len = %v, want %v", got, tt.wantLen) 40 | } 41 | 42 | for i, want := range tt.wantVals { 43 | got := list.GetItem(i).String() 44 | if got != From(want).String() { 45 | t.Errorf("MakeList() item[%d] = %v, want %v", i, got, want) 46 | } 47 | } 48 | } 49 | } 50 | 51 | func TestList_SetItem(t *testing.T) { 52 | setupTest(t) 53 | list := MakeList(1, 2, 3) 54 | list.SetItem(1, From("test")) 55 | 56 | // Get the raw value without quotes for comparison 57 | got := list.GetItem(1).String() 58 | 59 | if got != "test" { 60 | t.Errorf("List.SetItem() = %v, want %v", got, "test") 61 | } 62 | } 63 | 64 | func TestList_Append(t *testing.T) { 65 | setupTest(t) 66 | list := MakeList(1, 2) 67 | initialLen := list.Len() 68 | 69 | list.Append(From(3)) 70 | 71 | if got := list.Len(); got != initialLen+1 { 72 | t.Errorf("List.Append() length = %v, want %v", got, initialLen+1) 73 | } 74 | 75 | if got := list.GetItem(2).String(); got != From(3).String() { 76 | t.Errorf("List.Append() last item = %v, want %v", got, From(3).String()) 77 | } 78 | } 79 | 80 | func TestList_Len(t *testing.T) { 81 | setupTest(t) 82 | tests := []struct { 83 | name string 84 | args []any 85 | want int 86 | }{ 87 | {"empty list", []any{}, 0}, 88 | {"single item", []any{1}, 1}, 89 | {"multiple items", []any{1, 2, 3}, 3}, 90 | } 91 | 92 | for _, tt := range tests { 93 | list := MakeList(tt.args...) 94 | if got := list.Len(); got != tt.want { 95 | t.Errorf("List.Len() = %v, want %v", got, tt.want) 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /long.go: -------------------------------------------------------------------------------- 1 | package gp 2 | 3 | /* 4 | #include 5 | */ 6 | import "C" 7 | import "unsafe" 8 | 9 | type Long struct { 10 | Object 11 | } 12 | 13 | func newLong(obj *cPyObject) Long { 14 | return Long{newObject(obj)} 15 | } 16 | 17 | func MakeLong(i int64) Long { 18 | return newLong(C.PyLong_FromLongLong(C.longlong(i))) 19 | } 20 | 21 | func LongFromFloat64(v float64) Long { 22 | return newLong(C.PyLong_FromDouble(C.double(v))) 23 | } 24 | 25 | func LongFromString(s string, base int) Long { 26 | cstr := AllocCStr(s) 27 | o := C.PyLong_FromString(cstr, nil, C.int(base)) 28 | C.free(unsafe.Pointer(cstr)) 29 | return newLong(o) 30 | } 31 | 32 | func LongFromUnicode(u Object, base int) Long { 33 | return newLong(C.PyLong_FromUnicodeObject(u.cpyObj(), C.int(base))) 34 | } 35 | 36 | func (l Long) Int() int { 37 | return int(l.Int64()) 38 | } 39 | 40 | func (l Long) Int64() int64 { 41 | return int64(C.PyLong_AsLongLong(l.obj)) 42 | } 43 | 44 | func (l Long) Uint() uint { 45 | return uint(l.Uint64()) 46 | } 47 | 48 | func (l Long) Uint64() uint64 { 49 | return uint64(C.PyLong_AsUnsignedLongLong(l.obj)) 50 | } 51 | 52 | func (l Long) Uintptr() uintptr { 53 | return uintptr(l.Int64()) 54 | } 55 | func (l Long) Float64() float64 { 56 | return float64(C.PyLong_AsDouble(l.obj)) 57 | } 58 | 59 | func LongFromUintptr(v uintptr) Long { 60 | return newLong(C.PyLong_FromLong(C.long(v))) 61 | } 62 | -------------------------------------------------------------------------------- /long_test.go: -------------------------------------------------------------------------------- 1 | package gp 2 | 3 | import ( 4 | "math" 5 | "testing" 6 | ) 7 | 8 | func TestLongCreation(t *testing.T) { 9 | setupTest(t) 10 | tests := []struct { 11 | name string 12 | input int64 13 | expected int64 14 | }{ 15 | {"zero", 0, 0}, 16 | {"positive", 42, 42}, 17 | {"negative", -42, -42}, 18 | {"max_int64", math.MaxInt64, math.MaxInt64}, 19 | {"min_int64", math.MinInt64, math.MinInt64}, 20 | } 21 | 22 | for _, tt := range tests { 23 | l := MakeLong(tt.input) 24 | if got := l.Int64(); got != tt.expected { 25 | t.Errorf("MakeLong(%d) = %d; want %d", tt.input, got, tt.expected) 26 | } 27 | } 28 | } 29 | 30 | func TestLongFromFloat64(t *testing.T) { 31 | setupTest(t) 32 | tests := []struct { 33 | name string 34 | input float64 35 | expected int64 36 | }{ 37 | {"integer_float", 42.0, 42}, 38 | {"truncated_float", 42.9, 42}, 39 | {"negative_float", -42.9, -42}, 40 | } 41 | 42 | for _, tt := range tests { 43 | l := LongFromFloat64(tt.input) 44 | if got := l.Int64(); got != tt.expected { 45 | t.Errorf("LongFromFloat64(%f) = %d; want %d", tt.input, got, tt.expected) 46 | } 47 | } 48 | } 49 | 50 | func TestLongFromString(t *testing.T) { 51 | setupTest(t) 52 | tests := []struct { 53 | name string 54 | input string 55 | base int 56 | expected int64 57 | }{ 58 | {"decimal", "42", 10, 42}, 59 | {"hex", "2A", 16, 42}, 60 | {"binary", "101010", 2, 42}, 61 | {"octal", "52", 8, 42}, 62 | {"negative", "-42", 10, -42}, 63 | } 64 | 65 | for _, tt := range tests { 66 | l := LongFromString(tt.input, tt.base) 67 | if got := l.Int64(); got != tt.expected { 68 | t.Errorf("LongFromString(%q, %d) = %d; want %d", tt.input, tt.base, got, tt.expected) 69 | } 70 | } 71 | } 72 | 73 | func TestLongConversions(t *testing.T) { 74 | setupTest(t) 75 | l := MakeLong(42) 76 | 77 | if got := l.Int(); got != 42 { 78 | t.Errorf("Int() = %d; want 42", got) 79 | } 80 | 81 | if got := l.Uint(); got != 42 { 82 | t.Errorf("Uint() = %d; want 42", got) 83 | } 84 | 85 | if got := l.Uint64(); got != 42 { 86 | t.Errorf("Uint64() = %d; want 42", got) 87 | } 88 | 89 | if got := l.Float64(); got != 42.0 { 90 | t.Errorf("Float64() = %f; want 42.0", got) 91 | } 92 | 93 | if got := l.Uintptr(); got != 42 { 94 | t.Errorf("Uintptr() = %d; want 42", got) 95 | } 96 | } 97 | 98 | func TestLongFromUintptr(t *testing.T) { 99 | setupTest(t) 100 | tests := []struct { 101 | name string 102 | input uintptr 103 | expected int64 104 | }{ 105 | {"zero", 0, 0}, 106 | {"positive", 42, 42}, 107 | {"large_number", 1 << 30, 1 << 30}, 108 | } 109 | 110 | for _, tt := range tests { 111 | l := LongFromUintptr(tt.input) 112 | if got := l.Int64(); got != tt.expected { 113 | t.Errorf("LongFromUintptr(%d) = %d; want %d", tt.input, got, tt.expected) 114 | } 115 | } 116 | } 117 | 118 | func TestLongFromUnicode(t *testing.T) { 119 | setupTest(t) 120 | tests := []struct { 121 | name string 122 | input string 123 | base int 124 | expected int64 125 | }{ 126 | {"unicode_decimal", "42", 10, 42}, 127 | {"unicode_hex", "2A", 16, 42}, 128 | } 129 | 130 | for _, tt := range tests { 131 | // Create Unicode object from string 132 | u := MakeStr(tt.input) 133 | l := LongFromUnicode(u.Object, tt.base) 134 | if got := l.Int64(); got != tt.expected { 135 | t.Errorf("LongFromUnicode(%q, %d) = %d; want %d", tt.input, tt.base, got, tt.expected) 136 | } 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /math/math.go: -------------------------------------------------------------------------------- 1 | package math 2 | 3 | import ( 4 | gp "github.com/gotray/go-python" 5 | ) 6 | 7 | var math_ gp.Module 8 | 9 | func math() gp.Module { 10 | if math_.Nil() { 11 | math_ = gp.ImportModule("math") 12 | } 13 | return math_ 14 | } 15 | 16 | func Sqrt(x gp.Float) gp.Float { 17 | return math().Call("sqrt", x).AsFloat() 18 | } 19 | -------------------------------------------------------------------------------- /math/math_test.go: -------------------------------------------------------------------------------- 1 | package math 2 | 3 | import ( 4 | "testing" 5 | 6 | gp "github.com/gotray/go-python" 7 | ) 8 | 9 | func TestSqrt(t *testing.T) { 10 | // Initialize Python 11 | gp.Initialize() 12 | defer gp.Finalize() 13 | 14 | tests := []struct { 15 | input float64 16 | expected float64 17 | }{ 18 | {16.0, 4.0}, 19 | {25.0, 5.0}, 20 | {0.0, 0.0}, 21 | {100.0, 10.0}, 22 | } 23 | 24 | for _, test := range tests { 25 | input := gp.MakeFloat(test.input) 26 | result := Sqrt(input) 27 | 28 | if result.Float64() != test.expected { 29 | t.Errorf("Sqrt(%f) = %f; want %f", test.input, result.Float64(), test.expected) 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /module.go: -------------------------------------------------------------------------------- 1 | package gp 2 | 3 | /* 4 | #include 5 | */ 6 | import "C" 7 | import "unsafe" 8 | 9 | type Module struct { 10 | Object 11 | } 12 | 13 | func newModule(obj *cPyObject) Module { 14 | return Module{newObject(obj)} 15 | } 16 | 17 | func ImportModule(name string) Module { 18 | cname := AllocCStr(name) 19 | mod := C.PyImport_ImportModule(cname) 20 | C.free(unsafe.Pointer(cname)) 21 | return newModule(mod) 22 | } 23 | 24 | func GetModule(name string) Module { 25 | return newModule(C.PyImport_GetModule(MakeStr(name).obj)) 26 | } 27 | 28 | func (m Module) Dict() Dict { 29 | return newDict(C.PyModule_GetDict(m.obj)) 30 | } 31 | 32 | func (m Module) AddObject(name string, obj Object) int { 33 | cname := AllocCStr(name) 34 | r := int(C.PyModule_AddObjectRef(m.obj, cname, obj.obj)) 35 | C.free(unsafe.Pointer(cname)) 36 | return r 37 | } 38 | 39 | func (m Module) Name() string { 40 | return C.GoString(C.PyModule_GetName(m.obj)) 41 | } 42 | 43 | func CreateModule(name string) Module { 44 | mod := C.PyModule_New(AllocCStrDontFree(name)) 45 | return newModule(mod) 46 | } 47 | 48 | func GetModuleDict() Dict { 49 | return newDict(C.PyImport_GetModuleDict()) 50 | } 51 | -------------------------------------------------------------------------------- /module_test.go: -------------------------------------------------------------------------------- 1 | package gp 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestModuleImport(t *testing.T) { 8 | setupTest(t) 9 | // Test importing a built-in module 10 | mathMod := ImportModule("math") 11 | if mathMod.Nil() { 12 | t.Fatal("Failed to import math module") 13 | } 14 | 15 | // Test getting module dictionary 16 | modDict := mathMod.Dict() 17 | if modDict.Nil() { 18 | t.Fatal("Failed to get module dictionary") 19 | } 20 | 21 | // Verify math module has expected attributes 22 | if !modDict.HasKey("pi") { 23 | t.Error("Math module doesn't contain 'pi' constant") 24 | } 25 | } 26 | 27 | func TestGetModule(t *testing.T) { 28 | setupTest(t) 29 | // First import the module 30 | sysModule := ImportModule("sys") 31 | if sysModule.Nil() { 32 | t.Fatal("Failed to import sys module") 33 | } 34 | 35 | // Then try to get it 36 | gotModule := GetModule("sys") 37 | if gotModule.Nil() { 38 | t.Fatal("Failed to get sys module") 39 | } 40 | 41 | // Both should refer to the same module 42 | if !sysModule.Equals(gotModule) { 43 | t.Error("GetModule returned different module instance than ImportModule") 44 | } 45 | } 46 | 47 | func TestCreateModule(t *testing.T) { 48 | setupTest(t) 49 | 50 | // Create a new module 51 | modName := "test_module" 52 | mod := CreateModule(modName) 53 | if mod.Nil() { 54 | t.Fatal("Failed to create new module") 55 | } 56 | 57 | // Add an object to the module 58 | value := From(42) 59 | err := mod.AddObject("test_value", value) 60 | if err != 0 { 61 | t.Fatal("Failed to add object to module") 62 | } 63 | 64 | // Verify the object was added 65 | modDict := mod.Dict() 66 | if !modDict.HasKey("test_value") { 67 | t.Error("Module doesn't contain added value") 68 | } 69 | 70 | // Verify the value is correct 71 | gotValue := modDict.Get(From("test_value")) 72 | if !gotValue.Equals(value) { 73 | t.Error("Retrieved value doesn't match added value") 74 | } 75 | } 76 | 77 | func TestGetModuleDict(t *testing.T) { 78 | setupTest(t) 79 | 80 | // Get the module dictionary 81 | moduleDict := GetModuleDict() 82 | if moduleDict.Nil() { 83 | t.Fatal("Failed to get module dictionary") 84 | } 85 | 86 | // Import a module 87 | mathMod := ImportModule("math") 88 | if mathMod.Nil() { 89 | t.Fatal("Failed to import math module") 90 | } 91 | 92 | // Verify the module is in the module dictionary 93 | if !moduleDict.HasKey("math") { 94 | t.Error("Module dictionary doesn't contain imported module") 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /object.go: -------------------------------------------------------------------------------- 1 | package gp 2 | 3 | /* 4 | #include 5 | */ 6 | import "C" 7 | 8 | import ( 9 | "fmt" 10 | "runtime" 11 | "unsafe" 12 | ) 13 | 14 | // pyObject is a wrapper type that holds a Python Object and automatically calls 15 | // the Python Object's DecRef method during garbage collection. 16 | type pyObject struct { 17 | obj *C.PyObject 18 | g *globalData 19 | } 20 | 21 | func (o *pyObject) cpyObj() *cPyObject { 22 | if o == nil { 23 | return nil 24 | } 25 | return o.obj 26 | } 27 | 28 | func (o *pyObject) Nil() bool { 29 | return o == nil 30 | } 31 | 32 | func (o *pyObject) RefCount() int { 33 | return int(C.Py_REFCNT(o.obj)) 34 | } 35 | 36 | func (o *pyObject) Ensure() { 37 | if o == nil { 38 | C.PyErr_Print() 39 | panic("nil Python object") 40 | } 41 | } 42 | 43 | // ---------------------------------------------------------------------------- 44 | 45 | type Object struct { 46 | *pyObject 47 | } 48 | 49 | func FromPy(obj *cPyObject) Object { 50 | return newObject(obj) 51 | } 52 | 53 | func (o Object) object() Object { 54 | return o 55 | } 56 | 57 | func newObjectRef(obj *cPyObject) Object { 58 | C.Py_IncRef(obj) 59 | return newObject(obj) 60 | } 61 | 62 | func newObject(obj *cPyObject) Object { 63 | if obj == nil { 64 | C.PyErr_Print() 65 | panic("nil Python object") 66 | } 67 | o := &pyObject{obj: obj, g: getGlobalData()} 68 | runtime.SetFinalizer(o, func(o *pyObject) { 69 | o.g.addDecRef(o.obj) 70 | runtime.SetFinalizer(o, nil) 71 | }) 72 | return Object{o} 73 | } 74 | 75 | func (o Object) Dir() List { 76 | return o.Call("__dir__").AsList() 77 | } 78 | 79 | func (o Object) Equals(other Objecter) bool { 80 | return C.PyObject_RichCompareBool(o.obj, other.cpyObj(), C.Py_EQ) != 0 81 | } 82 | 83 | func (o Object) Attr(name string) Object { 84 | cname := AllocCStr(name) 85 | attr := C.PyObject_GetAttrString(o.obj, cname) 86 | C.free(unsafe.Pointer(cname)) 87 | return newObject(attr) 88 | } 89 | 90 | func (o Object) AttrFloat(name string) Float { 91 | return o.Attr(name).AsFloat() 92 | } 93 | 94 | func (o Object) AttrLong(name string) Long { 95 | return o.Attr(name).AsLong() 96 | } 97 | 98 | func (o Object) AttrString(name string) Str { 99 | return o.Attr(name).AsStr() 100 | } 101 | 102 | func (o Object) AttrBytes(name string) Bytes { 103 | return o.Attr(name).AsBytes() 104 | } 105 | 106 | func (o Object) AttrBool(name string) Bool { 107 | return o.Attr(name).AsBool() 108 | } 109 | 110 | func (o Object) AttrDict(name string) Dict { 111 | return o.Attr(name).AsDict() 112 | } 113 | 114 | func (o Object) AttrList(name string) List { 115 | return o.Attr(name).AsList() 116 | } 117 | 118 | func (o Object) AttrTuple(name string) Tuple { 119 | return o.Attr(name).AsTuple() 120 | } 121 | 122 | func (o Object) AttrFunc(name string) Func { 123 | return o.Attr(name).AsFunc() 124 | } 125 | 126 | func (o Object) SetAttr(name string, value any) { 127 | cname := AllocCStr(name) 128 | r := C.PyObject_SetAttrString(o.obj, cname, From(value).obj) 129 | C.PyErr_Print() 130 | check(r == 0, fmt.Sprintf("failed to set attribute %s", name)) 131 | C.free(unsafe.Pointer(cname)) 132 | } 133 | 134 | func (o Object) IsLong() bool { 135 | return C.Py_IS_TYPE(o.obj, &C.PyLong_Type) != 0 136 | } 137 | 138 | func (o Object) IsFloat() bool { 139 | return C.Py_IS_TYPE(o.obj, &C.PyFloat_Type) != 0 140 | } 141 | 142 | func (o Object) IsComplex() bool { 143 | return C.Py_IS_TYPE(o.obj, &C.PyComplex_Type) != 0 144 | } 145 | 146 | func (o Object) IsStr() bool { 147 | return C.Py_IS_TYPE(o.obj, &C.PyUnicode_Type) != 0 148 | } 149 | 150 | func (o Object) IsBytes() bool { 151 | return C.Py_IS_TYPE(o.obj, &C.PyBytes_Type) != 0 152 | } 153 | 154 | func (o Object) IsBool() bool { 155 | return C.Py_IS_TYPE(o.obj, &C.PyBool_Type) != 0 156 | } 157 | 158 | func (o Object) IsList() bool { 159 | return C.Py_IS_TYPE(o.obj, &C.PyList_Type) != 0 160 | } 161 | 162 | func (o Object) IsTuple() bool { 163 | return C.Py_IS_TYPE(o.obj, &C.PyTuple_Type) != 0 164 | } 165 | 166 | func (o Object) IsDict() bool { 167 | return C.Py_IS_TYPE(o.obj, &C.PyDict_Type) != 0 168 | } 169 | 170 | func (o Object) IsFunc() bool { 171 | return C.Py_IS_TYPE(o.obj, &C.PyFunction_Type) != 0 || 172 | C.Py_IS_TYPE(o.obj, &C.PyMethod_Type) != 0 || 173 | C.Py_IS_TYPE(o.obj, &C.PyCFunction_Type) != 0 174 | } 175 | 176 | func (o Object) AsFloat() Float { 177 | return cast[Float](o) 178 | } 179 | 180 | func (o Object) AsLong() Long { 181 | return cast[Long](o) 182 | } 183 | 184 | func (o Object) AsComplex() Complex { 185 | return cast[Complex](o) 186 | } 187 | 188 | func (o Object) AsStr() Str { 189 | return cast[Str](o) 190 | } 191 | 192 | func (o Object) AsBytes() Bytes { 193 | return cast[Bytes](o) 194 | } 195 | 196 | func (o Object) AsBool() Bool { 197 | return cast[Bool](o) 198 | } 199 | 200 | func (o Object) AsDict() Dict { 201 | return cast[Dict](o) 202 | } 203 | 204 | func (o Object) AsList() List { 205 | return cast[List](o) 206 | } 207 | 208 | func (o Object) AsTuple() Tuple { 209 | return cast[Tuple](o) 210 | } 211 | 212 | func (o Object) AsFunc() Func { 213 | return cast[Func](o) 214 | } 215 | 216 | func (o Object) AsModule() Module { 217 | return cast[Module](o) 218 | } 219 | 220 | func (o Object) Call(name string, args ...any) Object { 221 | fn := cast[Func](o.Attr(name)) 222 | argsTuple, kwArgs := splitArgs(args...) 223 | if kwArgs == nil { 224 | return fn.CallObject(argsTuple) 225 | } else { 226 | return fn.CallObjectKw(argsTuple, kwArgs) 227 | } 228 | } 229 | 230 | func (o Object) Repr() string { 231 | return newStr(C.PyObject_Repr(o.obj)).String() 232 | } 233 | 234 | func (o Object) Type() Object { 235 | return newObject(C.PyObject_Type(o.cpyObj())) 236 | } 237 | 238 | func (o Object) String() string { 239 | return newStr(C.PyObject_Str(o.obj)).String() 240 | } 241 | 242 | func (o Object) cpyObj() *cPyObject { 243 | if o.Nil() { 244 | return nil 245 | } 246 | return o.obj 247 | } 248 | -------------------------------------------------------------------------------- /object_test.go: -------------------------------------------------------------------------------- 1 | package gp 2 | 3 | import ( 4 | "bytes" 5 | "reflect" 6 | "testing" 7 | ) 8 | 9 | func TestObjectCreation(t *testing.T) { 10 | setupTest(t) 11 | // Test From() with different Go types 12 | tests := []struct { 13 | name string 14 | input interface{} 15 | checkFn func(Object) bool 16 | expected interface{} 17 | }{ 18 | {"int", 42, func(o Object) bool { return o.IsLong() }, 42}, 19 | {"float64", 3.14, func(o Object) bool { return o.IsFloat() }, 3.14}, 20 | {"string", "hello", func(o Object) bool { return o.IsStr() }, "hello"}, 21 | {"bool", true, func(o Object) bool { return o.IsBool() }, true}, 22 | {"[]byte", []byte("bytes"), func(o Object) bool { return o.IsBytes() }, []byte("bytes")}, 23 | {"slice", []int{1, 2, 3}, func(o Object) bool { return o.IsList() }, []int{1, 2, 3}}, 24 | {"map", map[string]int{"a": 1}, func(o Object) bool { return o.IsDict() }, map[string]int{"a": 1}}, 25 | } 26 | 27 | for _, tt := range tests { 28 | obj := From(tt.input) 29 | if !tt.checkFn(obj) { 30 | t.Errorf("From(%v) created wrong type", tt.input) 31 | } 32 | 33 | // Test conversion back to Go value 34 | switch expected := tt.expected.(type) { 35 | case int: 36 | if got := obj.AsLong().Int64(); got != int64(expected) { 37 | t.Errorf("Expected %v, got %v", expected, got) 38 | } 39 | case float64: 40 | if got := obj.AsFloat().Float64(); got != expected { 41 | t.Errorf("Expected %v, got %v", expected, got) 42 | } 43 | case string: 44 | if got := obj.AsStr().String(); got != expected { 45 | t.Errorf("Expected %v, got %v", expected, got) 46 | } 47 | case bool: 48 | if got := obj.AsBool().Bool(); got != expected { 49 | t.Errorf("Expected %v, got %v", expected, got) 50 | } 51 | case []byte: 52 | if got := obj.AsBytes().Bytes(); !reflect.DeepEqual(got, expected) { 53 | t.Errorf("Expected %v, got %v", expected, got) 54 | } 55 | } 56 | } 57 | } 58 | 59 | func TestObjectAttributes(t *testing.T) { 60 | setupTest(t) 61 | // Test attributes using Python's built-in object type 62 | builtins := ImportModule("builtins") 63 | obj := builtins.AttrFunc("object").Call() 64 | 65 | // Get built-in attribute 66 | cls := obj.Attr("__class__") 67 | if cls.Nil() { 68 | t.Error("Failed to get __class__ attribute") 69 | } 70 | 71 | // Test Dir() method 72 | dir := obj.Dir() 73 | if dir.Len() == 0 { 74 | t.Error("Dir() returned empty list") 75 | } 76 | 77 | // Create a custom class to test attribute setting 78 | pyCode := ` 79 | class TestClass: 80 | pass 81 | ` 82 | locals := MakeDict(nil) 83 | globals := MakeDict(nil) 84 | globals.Set(MakeStr("__builtins__"), builtins.Object) 85 | 86 | code, err := CompileString(pyCode, "", FileInput) 87 | if err != nil { 88 | t.Errorf("CompileString() error = %v", err) 89 | } 90 | 91 | EvalCode(code, globals, locals).AsModule() 92 | testClass := locals.Get(MakeStr("TestClass")).AsFunc() 93 | instance := testClass.Call() 94 | 95 | // Now we can set attributes 96 | instance.SetAttr("new_attr", "test_value") 97 | value := instance.Attr("new_attr") 98 | if value.AsStr().String() != "test_value" { 99 | t.Error("SetAttr failed to set new attribute") 100 | } 101 | } 102 | 103 | func TestDictOperations(t *testing.T) { 104 | setupTest(t) 105 | // Test dictionary operations 106 | pyDict := MakeDict(nil) 107 | pyDict.Set(MakeStr("key1"), From(42)) 108 | pyDict.Set(MakeStr("key2"), From("value")) 109 | 110 | value := pyDict.Get(MakeStr("key1")) 111 | if value.AsLong().Int64() != 42 { 112 | t.Error("Failed to get dictionary item") 113 | } 114 | 115 | func() { 116 | pyDict.Set(MakeStr("key3"), From("new_value")) 117 | value := pyDict.Get(MakeStr("key3")) 118 | if value.AsStr().String() != "new_value" { 119 | t.Error("Failed to set dictionary item") 120 | } 121 | }() 122 | } 123 | 124 | func TestObjectConversion(t *testing.T) { 125 | setupTest(t) 126 | type Person struct { 127 | Name string 128 | Age int 129 | } 130 | 131 | person := Person{Name: "Alice", Age: 30} 132 | obj := From(person) 133 | 134 | if !obj.IsDict() { 135 | t.Error("Struct should be converted to Python dict") 136 | } 137 | 138 | dict := obj.AsDict() 139 | if dict.Get(From("name")).AsStr().String() != "Alice" { 140 | t.Error("Failed to convert struct field 'Name'") 141 | } 142 | if dict.Get(From("age")).AsLong().Int64() != 30 { 143 | t.Error("Failed to convert struct field 'Age'") 144 | } 145 | 146 | func() { 147 | slice := []int{1, 2, 3} 148 | obj := From(slice) 149 | 150 | if !obj.IsList() { 151 | t.Error("Slice should be converted to Python list") 152 | } 153 | 154 | list := obj.AsList() 155 | if list.Len() != 3 { 156 | t.Error("Wrong length after conversion") 157 | } 158 | if list.GetItem(0).AsLong().Int64() != 1 { 159 | t.Error("Wrong value at index 0") 160 | } 161 | }() 162 | } 163 | 164 | func TestObjectString(t *testing.T) { 165 | setupTest(t) 166 | tests := []struct { 167 | name string 168 | input interface{} 169 | expected string 170 | }{ 171 | {"int", 42, "42"}, 172 | {"string", "hello", "hello"}, 173 | {"bool", true, "True"}, 174 | } 175 | 176 | for _, tt := range tests { 177 | obj := From(tt.input) 178 | str := obj.String() 179 | if str != tt.expected { 180 | t.Errorf("String() = %v, want %v", str, tt.expected) 181 | } 182 | 183 | } 184 | } 185 | 186 | func TestPyObjectMethods(t *testing.T) { 187 | setupTest(t) 188 | // Test pyObject.cpyObj() 189 | obj := From(42) 190 | if obj.pyObject.cpyObj() == nil { 191 | t.Error("pyObject.cpyObj() returned nil for valid object") 192 | } 193 | 194 | func() { 195 | var nilObj *pyObject 196 | if nilObj.cpyObj() != nil { 197 | t.Error("pyObject.cpyObj() should return nil for nil object") 198 | } 199 | }() 200 | 201 | func() { 202 | // Test pyObject.Ensure() 203 | obj := From(42) 204 | obj.Ensure() // Should not panic 205 | }() 206 | 207 | func() { 208 | var nilObj Object 209 | defer func() { 210 | if r := recover(); r == nil { 211 | t.Error("Ensure() should panic for nil object") 212 | } 213 | }() 214 | nilObj.Ensure() 215 | }() 216 | } 217 | 218 | func TestObjectMethods(t *testing.T) { 219 | setupTest(t) 220 | // Test Object.object() 221 | obj := From(42) 222 | if obj.object() != obj { 223 | t.Error("object() should return the same object") 224 | } 225 | 226 | // Test Object.Attr* methods 227 | // Create a test class with various attribute types 228 | pyCode := ` 229 | class TestClass: 230 | def __init__(self): 231 | self.int_val = 42 232 | self.float_val = 3.14 233 | self.str_val = "test" 234 | self.bool_val = True 235 | self.list_val = [1, 2, 3] 236 | self.dict_val = {"key": "value"} 237 | self.tuple_val = (1, 2, 3) 238 | ` 239 | locals := MakeDict(nil) 240 | globals := MakeDict(nil) 241 | builtins := ImportModule("builtins") 242 | globals.Set(MakeStr("__builtins__"), builtins.Object) 243 | 244 | code, err := CompileString(pyCode, "", FileInput) 245 | if err != nil { 246 | t.Errorf("CompileString() error = %v", err) 247 | } 248 | EvalCode(code, globals, locals) 249 | 250 | testClass := locals.Get(MakeStr("TestClass")).AsFunc() 251 | instance := testClass.Call() 252 | 253 | // Test each Attr* method 254 | if instance.AttrLong("int_val").Int64() != 42 { 255 | t.Error("AttrLong failed") 256 | } 257 | if instance.AttrFloat("float_val").Float64() != 3.14 { 258 | t.Error("AttrFloat failed") 259 | } 260 | if instance.AttrString("str_val").String() != "test" { 261 | t.Error("AttrString failed") 262 | } 263 | if !instance.AttrBool("bool_val").Bool() { 264 | t.Error("AttrBool failed") 265 | } 266 | if instance.AttrList("list_val").Len() != 3 { 267 | t.Error("AttrList failed") 268 | } 269 | if instance.AttrDict("dict_val").Get(MakeStr("key")).AsStr().String() != "value" { 270 | t.Error("AttrDict failed") 271 | } 272 | if instance.AttrTuple("tuple_val").Len() != 3 { 273 | t.Error("AttrTuple failed") 274 | } 275 | 276 | func() { 277 | // Test Object.IsTuple and AsTuple 278 | // Create a Python tuple using Python code to ensure proper tuple creation 279 | pyCode := ` 280 | def make_tuple(): 281 | return (1, 2, 3) 282 | ` 283 | locals := MakeDict(nil) 284 | globals := MakeDict(nil) 285 | builtins := ImportModule("builtins") 286 | globals.Set(MakeStr("__builtins__"), builtins.Object) 287 | 288 | code, err := CompileString(pyCode, "", FileInput) 289 | if err != nil { 290 | t.Errorf("CompileString() error = %v", err) 291 | } 292 | EvalCode(code, globals, locals) 293 | 294 | makeTuple := locals.Get(MakeStr("make_tuple")).AsFunc() 295 | tuple := makeTuple.Call() 296 | 297 | // Test IsTuple 298 | if !tuple.IsTuple() { 299 | t.Error("IsTuple failed to identify tuple") 300 | } 301 | 302 | // Test AsTuple 303 | pythonTuple := tuple.AsTuple() 304 | if pythonTuple.Len() != 3 { 305 | t.Error("AsTuple conversion failed") 306 | } 307 | 308 | // Verify tuple contents 309 | if pythonTuple.Get(0).AsLong().Int64() != 1 { 310 | t.Error("Incorrect value at index 0") 311 | } 312 | if pythonTuple.Get(1).AsLong().Int64() != 2 { 313 | t.Error("Incorrect value at index 1") 314 | } 315 | if pythonTuple.Get(2).AsLong().Int64() != 3 { 316 | t.Error("Incorrect value at index 2") 317 | } 318 | }() 319 | 320 | func() { 321 | // Test Object.Repr and Type 322 | obj := From(42) 323 | if obj.Repr() != "42" { 324 | t.Error("Repr failed") 325 | } 326 | }() 327 | 328 | func() { 329 | typeObj := obj.Type() 330 | if typeObj.Repr() != "" { 331 | t.Error("Type failed") 332 | } 333 | }() 334 | 335 | func() { 336 | // Test From with various numeric types 337 | tests := []struct { 338 | input interface{} 339 | expected int64 340 | }{ 341 | {int8(42), 42}, 342 | {int16(42), 42}, 343 | {int32(42), 42}, 344 | {int64(42), 42}, 345 | {uint8(42), 42}, 346 | {uint16(42), 42}, 347 | {uint32(42), 42}, 348 | {uint64(42), 42}, 349 | } 350 | 351 | for _, tt := range tests { 352 | obj := From(tt.input) 353 | if obj.AsLong().Int64() != tt.expected { 354 | t.Errorf("From(%T) = %v, want %v", tt.input, obj.AsLong().Int64(), tt.expected) 355 | } 356 | } 357 | }() 358 | 359 | func() { 360 | // Test From with false boolean 361 | obj := From(false) 362 | if obj.AsBool().Bool() != false { 363 | t.Error("From(false) failed") 364 | } 365 | }() 366 | 367 | func() { 368 | // Test Object.cpyObj() 369 | obj := From(42) 370 | if obj.cpyObj() == nil { 371 | t.Error("Object.cpyObj() returned nil for valid object") 372 | } 373 | }() 374 | 375 | func() { 376 | var nilObj Object 377 | if nilObj.cpyObj() != nil { 378 | t.Error("Object.cpyObj() should return nil for nil object") 379 | } 380 | }() 381 | 382 | func() { 383 | // Test AttrBytes 384 | builtins := ImportModule("types") 385 | objType := builtins.AttrFunc("SimpleNamespace") 386 | obj := objType.Call() 387 | 388 | // Create a simple object with bytes attribute 389 | obj.SetAttr("bytes_val", From([]byte("hello"))) 390 | 391 | if !bytes.Equal(obj.AttrBytes("bytes_val").Bytes(), []byte("hello")) { 392 | t.Error("AttrBytes failed") 393 | } 394 | }() 395 | 396 | func() { 397 | // Test Object.Call with kwargs 398 | pyCode := ` 399 | def test_func(a, b=10, c="default"): 400 | return (a, b, c) 401 | ` 402 | locals := MakeDict(nil) 403 | globals := MakeDict(nil) 404 | globals.Set(MakeStr("__builtins__"), builtins.Object) 405 | 406 | code, err := CompileString(pyCode, "", FileInput) 407 | if err != nil { 408 | t.Errorf("CompileString() error = %v", err) 409 | } 410 | EvalCode(code, globals, locals) 411 | 412 | testFunc := locals.Get(MakeStr("test_func")) 413 | 414 | // Call with positional and keyword arguments 415 | result := testFunc.Call("__call__", 1, KwArgs{ 416 | "b": 20, 417 | "c": "custom", 418 | }) 419 | 420 | tuple := result.AsTuple() 421 | if tuple.Get(0).AsLong().Int64() != 1 { 422 | t.Error("Wrong value for first argument") 423 | } 424 | if tuple.Get(1).AsLong().Int64() != 20 { 425 | t.Error("Wrong value for keyword argument b") 426 | } 427 | if tuple.Get(2).AsStr().String() != "custom" { 428 | t.Error("Wrong value for keyword argument c") 429 | } 430 | }() 431 | } 432 | -------------------------------------------------------------------------------- /python.go: -------------------------------------------------------------------------------- 1 | package gp 2 | 3 | /* 4 | #cgo pkg-config: python3-embed 5 | #include 6 | */ 7 | import "C" 8 | import ( 9 | "fmt" 10 | "runtime" 11 | "unsafe" 12 | ) 13 | 14 | type cPyObject = C.PyObject 15 | 16 | func Initialize() { 17 | runtime.LockOSThread() 18 | C.Py_Initialize() 19 | initGlobal() 20 | } 21 | 22 | func Finalize() { 23 | markFinished() 24 | r := C.Py_FinalizeEx() 25 | cleanupGlobal() 26 | check(r == 0, "failed to finalize Python") 27 | } 28 | 29 | // ---------------------------------------------------------------------------- 30 | 31 | type InputType = C.int 32 | 33 | const ( 34 | SingleInput InputType = C.Py_single_input 35 | FileInput InputType = C.Py_file_input 36 | EvalInput InputType = C.Py_eval_input 37 | ) 38 | 39 | func CompileString(code, filename string, start InputType) (Object, error) { 40 | ccode := AllocCStr(code) 41 | cfilename := AllocCStr(filename) 42 | o := C.Py_CompileString(ccode, cfilename, C.int(start)) 43 | // TODO: check why double free 44 | C.free(unsafe.Pointer(ccode)) 45 | C.free(unsafe.Pointer(cfilename)) 46 | if o == nil { 47 | err := FetchError() 48 | if err != nil { 49 | return Object{}, err 50 | } 51 | return Object{}, fmt.Errorf("failed to compile code") 52 | } 53 | return newObject(o), nil 54 | } 55 | 56 | func EvalCode(code Object, globals, locals Dict) Object { 57 | return newObject(C.PyEval_EvalCode(code.cpyObj(), globals.cpyObj(), locals.cpyObj())) 58 | } 59 | 60 | // ---------------------------------------------------------------------------- 61 | 62 | func cast[U, T Objecter](from T) (to U) { 63 | *(*T)(unsafe.Pointer(&to)) = from 64 | return 65 | } 66 | 67 | // ---------------------------------------------------------------------------- 68 | 69 | func With[T Objecter](obj T, fn func(v T)) T { 70 | obj.object().Call("__enter__") 71 | defer obj.object().Call("__exit__") 72 | fn(obj) 73 | return obj 74 | } 75 | 76 | // ---------------------------------------------------------------------------- 77 | 78 | func MainModule() Module { 79 | return ImportModule("__main__") 80 | } 81 | 82 | func None() Object { 83 | return newObject(C.Py_None) 84 | } 85 | 86 | func Nil() Object { 87 | return Object{} 88 | } 89 | 90 | // RunString executes Python code string and returns error if any 91 | func RunString(code string) error { 92 | // Get __main__ module dict for executing code 93 | main := MainModule() 94 | check(!main.Nil(), "failed to get __main__ module") 95 | dict := main.Dict() 96 | 97 | // Run the code string 98 | codeObj, err := CompileString(code, "", FileInput) 99 | if err != nil { 100 | return err 101 | } 102 | 103 | ret := EvalCode(codeObj, dict, dict) 104 | if ret.Nil() { 105 | if err := FetchError(); err != nil { 106 | return err 107 | } 108 | return fmt.Errorf("failed to execute code") 109 | } 110 | return nil 111 | } 112 | 113 | func RunMain(args []string) int { 114 | argc := len(args) 115 | argv := make([]*C.char, argc+1) 116 | for i, arg := range args { 117 | argv[i] = AllocCStr(arg) 118 | } 119 | return int(C.Py_BytesMain(C.int(argc), &argv[0])) 120 | } 121 | 122 | // ---------------------------------------------------------------------------- 123 | 124 | func check(b bool, msg string) { 125 | if !b { 126 | panic(msg) 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /python_test.go: -------------------------------------------------------------------------------- 1 | package gp 2 | 3 | import ( 4 | "runtime" 5 | "sync" 6 | "testing" 7 | ) 8 | 9 | var ( 10 | testMutex sync.Mutex 11 | ) 12 | 13 | func setupTest(t *testing.T) { 14 | testMutex.Lock() 15 | Initialize() 16 | getGlobalData().alwaysDecRef = true 17 | t.Cleanup(func() { 18 | runtime.GC() 19 | Finalize() 20 | testMutex.Unlock() 21 | }) 22 | } 23 | 24 | func TestRunString(t *testing.T) { 25 | setupTest(t) 26 | tests := []struct { 27 | name string 28 | code string 29 | wantErr bool 30 | }{ 31 | { 32 | name: "valid python code", 33 | code: "x = 1 + 1", 34 | wantErr: false, 35 | }, 36 | { 37 | name: "invalid python code", 38 | code: "x = ", 39 | wantErr: true, 40 | }, 41 | { 42 | name: "syntax error", 43 | code: "for i in range(10) print(i)", // missing : 44 | wantErr: true, 45 | }, 46 | } 47 | 48 | for _, tt := range tests { 49 | err := RunString(tt.code) 50 | if (err != nil) != tt.wantErr { 51 | t.Errorf("RunString() error = %v, wantErr %v", err, tt.wantErr) 52 | } 53 | } 54 | } 55 | 56 | func TestCompileString(t *testing.T) { 57 | setupTest(t) 58 | tests := []struct { 59 | name string 60 | code string 61 | filename string 62 | start InputType 63 | wantNil bool 64 | }{ 65 | { 66 | name: "compile expression", 67 | code: "1 + 1", 68 | filename: "", 69 | start: EvalInput, 70 | wantNil: false, 71 | }, 72 | { 73 | name: "compile invalid code", 74 | code: "x =", 75 | filename: "", 76 | start: EvalInput, 77 | wantNil: true, 78 | }, 79 | } 80 | 81 | for _, tt := range tests { 82 | obj, _ := CompileString(tt.code, tt.filename, tt.start) 83 | if obj.Nil() != tt.wantNil { 84 | t.Errorf("CompileString() returned nil = %v, want %v", obj.Nil(), tt.wantNil) 85 | } 86 | } 87 | } 88 | 89 | func TestNone(t *testing.T) { 90 | setupTest(t) 91 | none := None() 92 | if none.Nil() { 93 | t.Error("None() returned nil object") 94 | } 95 | } 96 | 97 | func TestNil(t *testing.T) { 98 | setupTest(t) 99 | nil_ := Nil() 100 | if !nil_.Nil() { 101 | t.Error("Nil() did not return nil object") 102 | } 103 | } 104 | 105 | func TestMainModule(t *testing.T) { 106 | setupTest(t) 107 | main := MainModule() 108 | if main.Nil() { 109 | t.Error("MainModule() returned nil") 110 | } 111 | } 112 | 113 | func TestWith(t *testing.T) { 114 | setupTest(t) 115 | // First create a simple Python context manager class 116 | code := ` 117 | class TestContextManager: 118 | def __init__(self): 119 | self.entered = False 120 | self.exited = False 121 | 122 | def __enter__(self): 123 | self.entered = True 124 | return self 125 | 126 | def __exit__(self, *args): 127 | self.exited = True 128 | return None 129 | ` 130 | if err := RunString(code); err != nil { 131 | t.Fatalf("Failed to create test context manager: %v", err) 132 | } 133 | 134 | // Get the context manager class and create an instance 135 | main := MainModule() 136 | cmClass := main.AttrFunc("TestContextManager") 137 | if cmClass.Nil() { 138 | t.Fatal("Failed to get TestContextManager class") 139 | } 140 | 141 | cm := cmClass.Call() 142 | if cm.Nil() { 143 | t.Fatal("Failed to create context manager instance") 144 | } 145 | 146 | // Test the With function 147 | called := false 148 | With(cm, func(obj Object) { 149 | called = true 150 | 151 | // Check that __enter__ was called 152 | entered := obj.AttrBool("entered") 153 | if entered.Nil() { 154 | t.Error("Could not get entered attribute") 155 | } 156 | if !entered.Bool() { 157 | t.Error("__enter__ was not called") 158 | } 159 | }) 160 | 161 | // Verify the callback was called 162 | if !called { 163 | t.Error("With callback was not called") 164 | } 165 | 166 | // Check that __exit__ was called 167 | exited := cm.AttrBool("exited") 168 | if exited.Nil() { 169 | t.Error("Could not get exited attribute") 170 | } 171 | if !exited.Bool() { 172 | t.Error("__exit__ was not called") 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /tuple.go: -------------------------------------------------------------------------------- 1 | package gp 2 | 3 | /* 4 | #include 5 | */ 6 | import "C" 7 | import "fmt" 8 | 9 | type Tuple struct { 10 | Object 11 | } 12 | 13 | func newTuple(obj *cPyObject) Tuple { 14 | return Tuple{newObject(obj)} 15 | } 16 | 17 | func MakeTupleWithLen(len int) Tuple { 18 | return newTuple(C.PyTuple_New(C.Py_ssize_t(len))) 19 | } 20 | 21 | func MakeTuple(args ...any) Tuple { 22 | tuple := newTuple(C.PyTuple_New(C.Py_ssize_t(len(args)))) 23 | for i, arg := range args { 24 | obj := From(arg) 25 | tuple.Set(i, obj) 26 | } 27 | return tuple 28 | } 29 | 30 | func (t Tuple) Get(index int) Object { 31 | return newObject(C.PySequence_GetItem(t.obj, C.Py_ssize_t(index))) 32 | } 33 | 34 | func (t Tuple) Set(index int, obj Objecter) { 35 | objObj := obj.cpyObj() 36 | C.Py_IncRef(objObj) 37 | r := C.PyTuple_SetItem(t.obj, C.Py_ssize_t(index), objObj) 38 | check(r == 0, fmt.Sprintf("failed to set item %d in tuple", index)) 39 | } 40 | 41 | func (t Tuple) Len() int { 42 | return int(C.PyTuple_Size(t.obj)) 43 | } 44 | 45 | func (t Tuple) Slice(low, high int) Tuple { 46 | return newTuple(C.PyTuple_GetSlice(t.obj, C.Py_ssize_t(low), C.Py_ssize_t(high))) 47 | } 48 | 49 | func (t Tuple) ParseArgs(addrs ...any) bool { 50 | if len(addrs) > t.Len() { 51 | return false 52 | } 53 | 54 | for i, addr := range addrs { 55 | obj := t.Get(i) 56 | 57 | switch v := addr.(type) { 58 | // Integer types 59 | case *int: 60 | *v = int(obj.AsLong().Int64()) 61 | case *int8: 62 | *v = int8(obj.AsLong().Int64()) 63 | case *int16: 64 | *v = int16(obj.AsLong().Int64()) 65 | case *int32: 66 | *v = int32(obj.AsLong().Int64()) 67 | case *int64: 68 | *v = obj.AsLong().Int64() 69 | case *uint: 70 | *v = uint(obj.AsLong().Int64()) 71 | case *uint8: 72 | *v = uint8(obj.AsLong().Int64()) 73 | case *uint16: 74 | *v = uint16(obj.AsLong().Int64()) 75 | case *uint32: 76 | *v = uint32(obj.AsLong().Int64()) 77 | case *uint64: 78 | *v = uint64(obj.AsLong().Int64()) 79 | 80 | // Floating point types 81 | case *float32: 82 | *v = float32(obj.AsFloat().Float64()) 83 | case *float64: 84 | *v = obj.AsFloat().Float64() 85 | 86 | // Complex number types 87 | case *complex64: 88 | *v = complex64(obj.AsComplex().Complex128()) 89 | case *complex128: 90 | *v = obj.AsComplex().Complex128() 91 | 92 | // String types 93 | case *string: 94 | *v = obj.AsStr().String() 95 | case *[]byte: 96 | *v = []byte(obj.AsStr().String()) 97 | 98 | // Boolean type 99 | case *bool: 100 | *v = obj.AsBool().Bool() 101 | 102 | case **cPyObject: 103 | *v = obj.cpyObj() 104 | 105 | // Python object 106 | case *Object: 107 | *v = obj 108 | 109 | default: 110 | return false 111 | } 112 | } 113 | 114 | return true 115 | } 116 | -------------------------------------------------------------------------------- /tuple_test.go: -------------------------------------------------------------------------------- 1 | package gp 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestTupleCreation(t *testing.T) { 8 | setupTest(t) 9 | // Test empty tuple 10 | empty := MakeTupleWithLen(0) 11 | if empty.Len() != 0 { 12 | t.Errorf("Expected empty tuple length 0, got %d", empty.Len()) 13 | } 14 | 15 | // Test tuple with values 16 | tuple := MakeTuple(42, "hello", 3.14) 17 | if tuple.Len() != 3 { 18 | t.Errorf("Expected tuple length 3, got %d", tuple.Len()) 19 | } 20 | } 21 | 22 | func TestTupleGetSet(t *testing.T) { 23 | setupTest(t) 24 | tuple := MakeTupleWithLen(2) 25 | 26 | // Test setting and getting values 27 | tuple.Set(0, From(123)) 28 | tuple.Set(1, From("test")) 29 | 30 | if val := tuple.Get(0).AsLong().Int64(); val != 123 { 31 | t.Errorf("Expected 123, got %d", val) 32 | } 33 | if val := tuple.Get(1).AsStr().String(); val != "test" { 34 | t.Errorf("Expected 'test', got %s", val) 35 | } 36 | } 37 | 38 | func TestTupleSlice(t *testing.T) { 39 | setupTest(t) 40 | tuple := MakeTuple(1, 2, 3, 4, 5) 41 | 42 | // Test slicing 43 | slice := tuple.Slice(1, 4) 44 | if slice.Len() != 3 { 45 | t.Errorf("Expected slice length 3, got %d", slice.Len()) 46 | } 47 | 48 | expected := []int64{2, 3, 4} 49 | for i := 0; i < slice.Len(); i++ { 50 | if val := slice.Get(i).AsLong().Int64(); val != expected[i] { 51 | t.Errorf("At index %d: expected %d, got %d", i, expected[i], val) 52 | } 53 | } 54 | } 55 | 56 | func TestTupleParseArgs(t *testing.T) { 57 | setupTest(t) 58 | tuple := MakeTuple(42, "hello", 3.14, true) 59 | 60 | var ( 61 | intVal int 62 | strVal string 63 | floatVal float64 64 | boolVal bool 65 | extraVal int // This shouldn't get set 66 | ) 67 | 68 | // Test successful parsing 69 | success := tuple.ParseArgs(&intVal, &strVal, &floatVal, &boolVal) 70 | if !success { 71 | t.Error("ParseArgs failed unexpectedly") 72 | } 73 | 74 | if intVal != 42 { 75 | t.Errorf("Expected int 42, got %d", intVal) 76 | } 77 | if strVal != "hello" { 78 | t.Errorf("Expected string 'hello', got %s", strVal) 79 | } 80 | if floatVal != 3.14 { 81 | t.Errorf("Expected float 3.14, got %f", floatVal) 82 | } 83 | if !boolVal { 84 | t.Errorf("Expected bool true, got false") 85 | } 86 | 87 | // Test parsing with too many arguments 88 | success = tuple.ParseArgs(&intVal, &strVal, &floatVal, &boolVal, &extraVal) 89 | if success { 90 | t.Error("ParseArgs should have failed with too many arguments") 91 | } 92 | 93 | // Test parsing with invalid type 94 | var invalidPtr *testing.T 95 | success = tuple.ParseArgs(&invalidPtr) 96 | if success { 97 | t.Error("ParseArgs should have failed with invalid type") 98 | } 99 | } 100 | 101 | func TestTupleParseArgsTypes(t *testing.T) { 102 | setupTest(t) 103 | // Test all supported numeric types 104 | tuple := MakeTuple(42, 42, 42, 42, 42, 42, 42, 42, 42, 42) 105 | 106 | var ( 107 | intVal int 108 | int8Val int8 109 | int16Val int16 110 | int32Val int32 111 | int64Val int64 112 | uintVal uint 113 | uint8Val uint8 114 | uint16Val uint16 115 | uint32Val uint32 116 | uint64Val uint64 117 | ) 118 | 119 | success := tuple.ParseArgs( 120 | &intVal, &int8Val, &int16Val, &int32Val, &int64Val, 121 | &uintVal, &uint8Val, &uint16Val, &uint32Val, &uint64Val, 122 | ) 123 | 124 | if !success { 125 | t.Error("ParseArgs failed for numeric types") 126 | } 127 | 128 | // Test floating point types 129 | floatTuple := MakeTuple(3.14, 3.14) 130 | var float32Val float32 131 | var float64Val float64 132 | 133 | success = floatTuple.ParseArgs(&float32Val, &float64Val) 134 | if !success { 135 | t.Error("ParseArgs failed for floating point types") 136 | } 137 | 138 | // Test complex types 139 | complexTuple := MakeTuple(complex(1, 2), complex(3, 4)) 140 | var complex64Val complex64 141 | var complex128Val complex128 142 | 143 | success = complexTuple.ParseArgs(&complex64Val, &complex128Val) 144 | if !success { 145 | t.Error("ParseArgs failed for complex types") 146 | } 147 | 148 | // Test string and bytes 149 | strTuple := MakeTuple("hello") 150 | var strVal string 151 | var bytesVal []byte 152 | var objVal Object 153 | var pyObj *cPyObject 154 | 155 | success = strTuple.ParseArgs(&strVal) 156 | if !success || strVal != "hello" { 157 | t.Error("ParseArgs failed for string type") 158 | } 159 | 160 | success = strTuple.ParseArgs(&bytesVal) 161 | if !success || string(bytesVal) != "hello" { 162 | t.Error("ParseArgs failed for bytes type") 163 | } 164 | 165 | success = strTuple.ParseArgs(&objVal) 166 | if !success || !objVal.IsStr() { 167 | t.Error("ParseArgs failed for object type") 168 | } 169 | 170 | success = strTuple.ParseArgs(&pyObj) 171 | if !success || pyObj == nil { 172 | t.Error("ParseArgs failed for PyObject type") 173 | } 174 | str := FromPy(pyObj) 175 | if !str.IsStr() || str.String() != "hello" { 176 | t.Error("FromPy returned non-string object") 177 | } 178 | } 179 | -------------------------------------------------------------------------------- /unicode.go: -------------------------------------------------------------------------------- 1 | package gp 2 | 3 | /* 4 | #include 5 | */ 6 | import "C" 7 | import "unsafe" 8 | 9 | type Str struct { 10 | Object 11 | } 12 | 13 | func newStr(obj *cPyObject) Str { 14 | return Str{newObject(obj)} 15 | } 16 | 17 | func MakeStr(s string) Str { 18 | ptr := (*Char)(unsafe.Pointer(unsafe.StringData(s))) 19 | length := C.Py_ssize_t(len(s)) 20 | return newStr(C.PyUnicode_FromStringAndSize(ptr, length)) 21 | } 22 | 23 | func (s Str) String() string { 24 | var l C.Py_ssize_t 25 | buf := C.PyUnicode_AsUTF8AndSize(s.obj, &l) 26 | return GoStringN((*Char)(buf), int(l)) 27 | } 28 | 29 | func (s Str) Len() int { 30 | return int(C.PyUnicode_GetLength(s.obj)) 31 | } 32 | 33 | func (s Str) ByteLen() int { 34 | var l C.Py_ssize_t 35 | _ = C.PyUnicode_AsUTF8AndSize(s.obj, &l) 36 | return int(l) 37 | } 38 | 39 | func (s Str) Encode(encoding string) Bytes { 40 | return cast[Bytes](s.Call("encode", MakeStr(encoding))) 41 | } 42 | -------------------------------------------------------------------------------- /unicode_test.go: -------------------------------------------------------------------------------- 1 | package gp 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestMakeStr(t *testing.T) { 8 | setupTest(t) 9 | tests := []struct { 10 | name string 11 | input string 12 | expected string 13 | length int 14 | byteCount int 15 | }{ 16 | { 17 | name: "empty string", 18 | input: "", 19 | expected: "", 20 | length: 0, 21 | byteCount: 0, 22 | }, 23 | { 24 | name: "ascii string", 25 | input: "hello", 26 | expected: "hello", 27 | length: 5, 28 | byteCount: 5, // ASCII character each takes 1 byte 29 | }, 30 | { 31 | name: "unicode string", 32 | input: "你好世界", 33 | expected: "你好世界", 34 | length: 4, 35 | byteCount: 12, // Chinese character each takes 3 bytes 36 | }, 37 | { 38 | name: "mixed string", 39 | input: "hello世界", 40 | expected: "hello世界", 41 | length: 7, 42 | byteCount: 11, // 5 ASCII characters (5 bytes) + 2 Chinese characters (6 bytes) 43 | }, 44 | { 45 | name: "special unicode", 46 | input: "π∑€", 47 | expected: "π∑€", 48 | length: 3, 49 | byteCount: 8, // π(2bytes) + ∑(3bytes) + €(3bytes) = 8bytes 50 | }, 51 | } 52 | 53 | for _, tt := range tests { 54 | pyStr := MakeStr(tt.input) 55 | 56 | // Test String() method 57 | if got := pyStr.String(); got != tt.expected { 58 | t.Errorf("MakeStr(%q).String() = %q, want %q", tt.input, got, tt.expected) 59 | } 60 | 61 | // Test Len() method 62 | if got := pyStr.Len(); got != tt.length { 63 | t.Errorf("MakeStr(%q).Len() = %d, want %d", tt.input, got, tt.length) 64 | } 65 | 66 | // Test ByteLen() method 67 | if got := pyStr.ByteLen(); got != tt.byteCount { 68 | t.Errorf("MakeStr(%q).ByteLen() = %d, want %d", tt.input, got, tt.byteCount) 69 | } 70 | } 71 | } 72 | 73 | func TestStrEncode(t *testing.T) { 74 | setupTest(t) 75 | tests := []struct { 76 | name string 77 | input string 78 | encoding string 79 | }{ 80 | { 81 | name: "utf-8 encoding", 82 | input: "hello世界", 83 | encoding: "utf-8", 84 | }, 85 | { 86 | name: "ascii encoding", 87 | input: "hello", 88 | encoding: "ascii", 89 | }, 90 | } 91 | 92 | for _, tt := range tests { 93 | pyStr := MakeStr(tt.input) 94 | encoded := pyStr.Encode(tt.encoding) 95 | decoded := encoded.Decode(tt.encoding) 96 | 97 | if got := decoded.String(); got != tt.input { 98 | t.Errorf("String encode/decode roundtrip failed: got %q, want %q", got, tt.input) 99 | } 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /wrap.c: -------------------------------------------------------------------------------- 1 | #include "wrap.h" 2 | 3 | 4 | #define WRAP_METHOD(ida, idb) \ 5 | PyObject *wrapperMethod##ida##idb(PyObject *self, PyObject *args) { \ 6 | return wrapperMethod(self, args, 0x##ida * 16 + 0x##idb); \ 7 | } 8 | 9 | #define WRAP_METHOD_WITH_KWARGS(ida, idb) \ 10 | PyObject *wrapperMethodWithKwargs##ida##idb(PyObject *self, PyObject *args, PyObject *kwargs) { \ 11 | return wrapperMethodWithKwargs(self, args, kwargs, 0x##ida * 16 + 0x##idb); \ 12 | } 13 | 14 | #define WRAP_METHODS(ida) \ 15 | WRAP_METHOD(ida, 0) \ 16 | WRAP_METHOD(ida, 1) \ 17 | WRAP_METHOD(ida, 2) \ 18 | WRAP_METHOD(ida, 3) \ 19 | WRAP_METHOD(ida, 4) \ 20 | WRAP_METHOD(ida, 5) \ 21 | WRAP_METHOD(ida, 6) \ 22 | WRAP_METHOD(ida, 7) \ 23 | WRAP_METHOD(ida, 8) \ 24 | WRAP_METHOD(ida, 9) \ 25 | WRAP_METHOD(ida, a) \ 26 | WRAP_METHOD(ida, b) \ 27 | WRAP_METHOD(ida, c) \ 28 | WRAP_METHOD(ida, d) \ 29 | WRAP_METHOD(ida, e) \ 30 | WRAP_METHOD(ida, f) 31 | 32 | #define WRAP_METHODS_WITH_KWARGS(ida) \ 33 | WRAP_METHOD_WITH_KWARGS(ida, 0) \ 34 | WRAP_METHOD_WITH_KWARGS(ida, 1) \ 35 | WRAP_METHOD_WITH_KWARGS(ida, 2) \ 36 | WRAP_METHOD_WITH_KWARGS(ida, 3) \ 37 | WRAP_METHOD_WITH_KWARGS(ida, 4) \ 38 | WRAP_METHOD_WITH_KWARGS(ida, 5) \ 39 | WRAP_METHOD_WITH_KWARGS(ida, 6) \ 40 | WRAP_METHOD_WITH_KWARGS(ida, 7) \ 41 | WRAP_METHOD_WITH_KWARGS(ida, 8) \ 42 | WRAP_METHOD_WITH_KWARGS(ida, 9) \ 43 | WRAP_METHOD_WITH_KWARGS(ida, a) \ 44 | WRAP_METHOD_WITH_KWARGS(ida, b) \ 45 | WRAP_METHOD_WITH_KWARGS(ida, c) \ 46 | WRAP_METHOD_WITH_KWARGS(ida, d) \ 47 | WRAP_METHOD_WITH_KWARGS(ida, e) \ 48 | WRAP_METHOD_WITH_KWARGS(ida, f) 49 | 50 | #define WRAP_METHOD_ALL() \ 51 | WRAP_METHODS(0) \ 52 | WRAP_METHODS(1) \ 53 | WRAP_METHODS(2) \ 54 | WRAP_METHODS(3) \ 55 | WRAP_METHODS(4) \ 56 | WRAP_METHODS(5) \ 57 | WRAP_METHODS(6) \ 58 | WRAP_METHODS(7) \ 59 | WRAP_METHODS(8) \ 60 | WRAP_METHODS(9) \ 61 | WRAP_METHODS(a) \ 62 | WRAP_METHODS(b) \ 63 | WRAP_METHODS(c) \ 64 | WRAP_METHODS(d) \ 65 | WRAP_METHODS(e) \ 66 | WRAP_METHODS(f) 67 | 68 | #define WRAP_METHOD_ALL_WITH_KWARGS() \ 69 | WRAP_METHODS_WITH_KWARGS(0) \ 70 | WRAP_METHODS_WITH_KWARGS(1) \ 71 | WRAP_METHODS_WITH_KWARGS(2) \ 72 | WRAP_METHODS_WITH_KWARGS(3) \ 73 | WRAP_METHODS_WITH_KWARGS(4) \ 74 | WRAP_METHODS_WITH_KWARGS(5) \ 75 | WRAP_METHODS_WITH_KWARGS(6) \ 76 | WRAP_METHODS_WITH_KWARGS(7) \ 77 | WRAP_METHODS_WITH_KWARGS(8) \ 78 | WRAP_METHODS_WITH_KWARGS(9) \ 79 | WRAP_METHODS_WITH_KWARGS(a) \ 80 | WRAP_METHODS_WITH_KWARGS(b) \ 81 | WRAP_METHODS_WITH_KWARGS(c) \ 82 | WRAP_METHODS_WITH_KWARGS(d) \ 83 | WRAP_METHODS_WITH_KWARGS(e) \ 84 | WRAP_METHODS_WITH_KWARGS(f) 85 | 86 | WRAP_METHOD_ALL() 87 | WRAP_METHOD_ALL_WITH_KWARGS() 88 | 89 | #define WARP_METHOD_NAME(ida, idb) wrapperMethod##ida##idb, 90 | #define WARP_METHOD_NAME_WITH_KWARGS(ida, idb) wrapperMethodWithKwargs##ida##idb, 91 | 92 | #define WARP_METHOD_NAMES(ida) \ 93 | WARP_METHOD_NAME(ida, 0) \ 94 | WARP_METHOD_NAME(ida, 1) \ 95 | WARP_METHOD_NAME(ida, 2) \ 96 | WARP_METHOD_NAME(ida, 3) \ 97 | WARP_METHOD_NAME(ida, 4) \ 98 | WARP_METHOD_NAME(ida, 5) \ 99 | WARP_METHOD_NAME(ida, 6) \ 100 | WARP_METHOD_NAME(ida, 7) \ 101 | WARP_METHOD_NAME(ida, 8) \ 102 | WARP_METHOD_NAME(ida, 9) \ 103 | WARP_METHOD_NAME(ida, a) \ 104 | WARP_METHOD_NAME(ida, b) \ 105 | WARP_METHOD_NAME(ida, c) \ 106 | WARP_METHOD_NAME(ida, d) \ 107 | WARP_METHOD_NAME(ida, e) \ 108 | WARP_METHOD_NAME(ida, f) 109 | 110 | 111 | #define WARP_METHOD_NAMES_WITH_KWARGS(ida) \ 112 | WARP_METHOD_NAME_WITH_KWARGS(ida, 0) \ 113 | WARP_METHOD_NAME_WITH_KWARGS(ida, 1) \ 114 | WARP_METHOD_NAME_WITH_KWARGS(ida, 2) \ 115 | WARP_METHOD_NAME_WITH_KWARGS(ida, 3) \ 116 | WARP_METHOD_NAME_WITH_KWARGS(ida, 4) \ 117 | WARP_METHOD_NAME_WITH_KWARGS(ida, 5) \ 118 | WARP_METHOD_NAME_WITH_KWARGS(ida, 6) \ 119 | WARP_METHOD_NAME_WITH_KWARGS(ida, 7) \ 120 | WARP_METHOD_NAME_WITH_KWARGS(ida, 8) \ 121 | WARP_METHOD_NAME_WITH_KWARGS(ida, 9) \ 122 | WARP_METHOD_NAME_WITH_KWARGS(ida, a) \ 123 | WARP_METHOD_NAME_WITH_KWARGS(ida, b) \ 124 | WARP_METHOD_NAME_WITH_KWARGS(ida, c) \ 125 | WARP_METHOD_NAME_WITH_KWARGS(ida, d) \ 126 | WARP_METHOD_NAME_WITH_KWARGS(ida, e) \ 127 | WARP_METHOD_NAME_WITH_KWARGS(ida, f) 128 | 129 | #define WARP_METHOD_NAMES_ALL() \ 130 | WARP_METHOD_NAMES(0) \ 131 | WARP_METHOD_NAMES(1) \ 132 | WARP_METHOD_NAMES(2) \ 133 | WARP_METHOD_NAMES(3) \ 134 | WARP_METHOD_NAMES(4) \ 135 | WARP_METHOD_NAMES(5) \ 136 | WARP_METHOD_NAMES(6) \ 137 | WARP_METHOD_NAMES(7) \ 138 | WARP_METHOD_NAMES(8) \ 139 | WARP_METHOD_NAMES(9) \ 140 | WARP_METHOD_NAMES(a) \ 141 | WARP_METHOD_NAMES(b) \ 142 | WARP_METHOD_NAMES(c) \ 143 | WARP_METHOD_NAMES(d) \ 144 | WARP_METHOD_NAMES(e) \ 145 | WARP_METHOD_NAMES(f) 146 | 147 | 148 | #define WARP_METHOD_NAMES_ALL_WITH_KWARGS() \ 149 | WARP_METHOD_NAMES_WITH_KWARGS(0) \ 150 | WARP_METHOD_NAMES_WITH_KWARGS(1) \ 151 | WARP_METHOD_NAMES_WITH_KWARGS(2) \ 152 | WARP_METHOD_NAMES_WITH_KWARGS(3) \ 153 | WARP_METHOD_NAMES_WITH_KWARGS(4) \ 154 | WARP_METHOD_NAMES_WITH_KWARGS(5) \ 155 | WARP_METHOD_NAMES_WITH_KWARGS(6) \ 156 | WARP_METHOD_NAMES_WITH_KWARGS(7) \ 157 | WARP_METHOD_NAMES_WITH_KWARGS(8) \ 158 | WARP_METHOD_NAMES_WITH_KWARGS(9) \ 159 | WARP_METHOD_NAMES_WITH_KWARGS(a) \ 160 | WARP_METHOD_NAMES_WITH_KWARGS(b) \ 161 | WARP_METHOD_NAMES_WITH_KWARGS(c) \ 162 | WARP_METHOD_NAMES_WITH_KWARGS(d) \ 163 | WARP_METHOD_NAMES_WITH_KWARGS(e) \ 164 | WARP_METHOD_NAMES_WITH_KWARGS(f) 165 | 166 | PyObject* (*wrapperMethods[256])(PyObject *self, PyObject *args) = { 167 | WARP_METHOD_NAMES_ALL() 168 | }; 169 | 170 | PyObject* (*wrapperMethodsWithKwargs[256])(PyObject *self, PyObject *args, PyObject *kwargs) = { 171 | WARP_METHOD_NAMES_ALL_WITH_KWARGS() 172 | }; 173 | 174 | #define GETTER_METHOD(ida, idb) \ 175 | PyObject *getterMethod##ida##idb(PyObject *self, void *closure) { \ 176 | return getterMethod(self, closure, 0x##ida * 16 + 0x##idb); \ 177 | } 178 | 179 | #define SETTER_METHOD(ida, idb) \ 180 | int setterMethod##ida##idb(PyObject *self, PyObject *value, void *closure) { \ 181 | return setterMethod(self, value, closure, 0x##ida * 16 + 0x##idb); \ 182 | } 183 | 184 | #define GETTER_METHODS(ida) \ 185 | GETTER_METHOD(ida, 0) \ 186 | GETTER_METHOD(ida, 1) \ 187 | GETTER_METHOD(ida, 2) \ 188 | GETTER_METHOD(ida, 3) \ 189 | GETTER_METHOD(ida, 4) \ 190 | GETTER_METHOD(ida, 5) \ 191 | GETTER_METHOD(ida, 6) \ 192 | GETTER_METHOD(ida, 7) \ 193 | GETTER_METHOD(ida, 8) \ 194 | GETTER_METHOD(ida, 9) \ 195 | GETTER_METHOD(ida, a) \ 196 | GETTER_METHOD(ida, b) \ 197 | GETTER_METHOD(ida, c) \ 198 | GETTER_METHOD(ida, d) \ 199 | GETTER_METHOD(ida, e) \ 200 | GETTER_METHOD(ida, f) 201 | 202 | #define SETTER_METHODS(ida) \ 203 | SETTER_METHOD(ida, 0) \ 204 | SETTER_METHOD(ida, 1) \ 205 | SETTER_METHOD(ida, 2) \ 206 | SETTER_METHOD(ida, 3) \ 207 | SETTER_METHOD(ida, 4) \ 208 | SETTER_METHOD(ida, 5) \ 209 | SETTER_METHOD(ida, 6) \ 210 | SETTER_METHOD(ida, 7) \ 211 | SETTER_METHOD(ida, 8) \ 212 | SETTER_METHOD(ida, 9) \ 213 | SETTER_METHOD(ida, a) \ 214 | SETTER_METHOD(ida, b) \ 215 | SETTER_METHOD(ida, c) \ 216 | SETTER_METHOD(ida, d) \ 217 | SETTER_METHOD(ida, e) \ 218 | SETTER_METHOD(ida, f) 219 | 220 | #define GETTER_METHOD_ALL() \ 221 | GETTER_METHODS(0) \ 222 | GETTER_METHODS(1) \ 223 | GETTER_METHODS(2) \ 224 | GETTER_METHODS(3) \ 225 | GETTER_METHODS(4) \ 226 | GETTER_METHODS(5) \ 227 | GETTER_METHODS(6) \ 228 | GETTER_METHODS(7) \ 229 | GETTER_METHODS(8) \ 230 | GETTER_METHODS(9) \ 231 | GETTER_METHODS(a) \ 232 | GETTER_METHODS(b) \ 233 | GETTER_METHODS(c) \ 234 | GETTER_METHODS(d) \ 235 | GETTER_METHODS(e) \ 236 | GETTER_METHODS(f) 237 | 238 | #define SETTER_METHOD_ALL() \ 239 | SETTER_METHODS(0) \ 240 | SETTER_METHODS(1) \ 241 | SETTER_METHODS(2) \ 242 | SETTER_METHODS(3) \ 243 | SETTER_METHODS(4) \ 244 | SETTER_METHODS(5) \ 245 | SETTER_METHODS(6) \ 246 | SETTER_METHODS(7) \ 247 | SETTER_METHODS(8) \ 248 | SETTER_METHODS(9) \ 249 | SETTER_METHODS(a) \ 250 | SETTER_METHODS(b) \ 251 | SETTER_METHODS(c) \ 252 | SETTER_METHODS(d) \ 253 | SETTER_METHODS(e) \ 254 | SETTER_METHODS(f) 255 | 256 | GETTER_METHOD_ALL() 257 | SETTER_METHOD_ALL() 258 | 259 | #define WARP_GETTER_METHOD_NAME(ida, idb) getterMethod##ida##idb, 260 | #define WARP_SETTER_METHOD_NAME(ida, idb) setterMethod##ida##idb, 261 | 262 | #define WARP_GETTER_METHOD_NAMES(ida) \ 263 | WARP_GETTER_METHOD_NAME(ida, 0) \ 264 | WARP_GETTER_METHOD_NAME(ida, 1) \ 265 | WARP_GETTER_METHOD_NAME(ida, 2) \ 266 | WARP_GETTER_METHOD_NAME(ida, 3) \ 267 | WARP_GETTER_METHOD_NAME(ida, 4) \ 268 | WARP_GETTER_METHOD_NAME(ida, 5) \ 269 | WARP_GETTER_METHOD_NAME(ida, 6) \ 270 | WARP_GETTER_METHOD_NAME(ida, 7) \ 271 | WARP_GETTER_METHOD_NAME(ida, 8) \ 272 | WARP_GETTER_METHOD_NAME(ida, 9) \ 273 | WARP_GETTER_METHOD_NAME(ida, a) \ 274 | WARP_GETTER_METHOD_NAME(ida, b) \ 275 | WARP_GETTER_METHOD_NAME(ida, c) \ 276 | WARP_GETTER_METHOD_NAME(ida, d) \ 277 | WARP_GETTER_METHOD_NAME(ida, e) \ 278 | WARP_GETTER_METHOD_NAME(ida, f) 279 | 280 | #define WARP_SETTER_METHOD_NAMES(ida) \ 281 | WARP_SETTER_METHOD_NAME(ida, 0) \ 282 | WARP_SETTER_METHOD_NAME(ida, 1) \ 283 | WARP_SETTER_METHOD_NAME(ida, 2) \ 284 | WARP_SETTER_METHOD_NAME(ida, 3) \ 285 | WARP_SETTER_METHOD_NAME(ida, 4) \ 286 | WARP_SETTER_METHOD_NAME(ida, 5) \ 287 | WARP_SETTER_METHOD_NAME(ida, 6) \ 288 | WARP_SETTER_METHOD_NAME(ida, 7) \ 289 | WARP_SETTER_METHOD_NAME(ida, 8) \ 290 | WARP_SETTER_METHOD_NAME(ida, 9) \ 291 | WARP_SETTER_METHOD_NAME(ida, a) \ 292 | WARP_SETTER_METHOD_NAME(ida, b) \ 293 | WARP_SETTER_METHOD_NAME(ida, c) \ 294 | WARP_SETTER_METHOD_NAME(ida, d) \ 295 | WARP_SETTER_METHOD_NAME(ida, e) \ 296 | WARP_SETTER_METHOD_NAME(ida, f) 297 | 298 | #define WARP_GETTER_METHOD_NAMES_ALL() \ 299 | WARP_GETTER_METHOD_NAMES(0) \ 300 | WARP_GETTER_METHOD_NAMES(1) \ 301 | WARP_GETTER_METHOD_NAMES(2) \ 302 | WARP_GETTER_METHOD_NAMES(3) \ 303 | WARP_GETTER_METHOD_NAMES(4) \ 304 | WARP_GETTER_METHOD_NAMES(5) \ 305 | WARP_GETTER_METHOD_NAMES(6) \ 306 | WARP_GETTER_METHOD_NAMES(7) \ 307 | WARP_GETTER_METHOD_NAMES(8) \ 308 | WARP_GETTER_METHOD_NAMES(9) \ 309 | WARP_GETTER_METHOD_NAMES(a) \ 310 | WARP_GETTER_METHOD_NAMES(b) \ 311 | WARP_GETTER_METHOD_NAMES(c) \ 312 | WARP_GETTER_METHOD_NAMES(d) \ 313 | WARP_GETTER_METHOD_NAMES(e) \ 314 | WARP_GETTER_METHOD_NAMES(f) 315 | 316 | #define WARP_SETTER_METHOD_NAMES_ALL() \ 317 | WARP_SETTER_METHOD_NAMES(0) \ 318 | WARP_SETTER_METHOD_NAMES(1) \ 319 | WARP_SETTER_METHOD_NAMES(2) \ 320 | WARP_SETTER_METHOD_NAMES(3) \ 321 | WARP_SETTER_METHOD_NAMES(4) \ 322 | WARP_SETTER_METHOD_NAMES(5) \ 323 | WARP_SETTER_METHOD_NAMES(6) \ 324 | WARP_SETTER_METHOD_NAMES(7) \ 325 | WARP_SETTER_METHOD_NAMES(8) \ 326 | WARP_SETTER_METHOD_NAMES(9) \ 327 | WARP_SETTER_METHOD_NAMES(a) \ 328 | WARP_SETTER_METHOD_NAMES(b) \ 329 | WARP_SETTER_METHOD_NAMES(c) \ 330 | WARP_SETTER_METHOD_NAMES(d) \ 331 | WARP_SETTER_METHOD_NAMES(e) \ 332 | WARP_SETTER_METHOD_NAMES(f) 333 | 334 | getter getterMethods[256] = { 335 | WARP_GETTER_METHOD_NAMES_ALL() 336 | }; 337 | 338 | setter setterMethods[256] = { 339 | WARP_SETTER_METHOD_NAMES_ALL() 340 | }; 341 | -------------------------------------------------------------------------------- /wrap.h: -------------------------------------------------------------------------------- 1 | #ifndef __WRAP_H__ 2 | #define __WRAP_H__ 3 | 4 | #include 5 | 6 | extern PyObject *wrapperMethod(PyObject *self, PyObject *args, int methodId); 7 | extern PyObject *wrapperMethodWithKwargs(PyObject *self, PyObject *args, PyObject *kwargs, int methodId); 8 | extern PyObject *getterMethod(PyObject *self, void *closure, int methodId); 9 | extern int setterMethod(PyObject *self, PyObject *value, void *closure, int methodId); 10 | 11 | extern PyObject *(*wrapperMethods[256])(PyObject *self, PyObject *args); 12 | extern PyObject *(*wrapperMethodsWithKwargs[256])(PyObject *self, PyObject *args, PyObject *kwargs); 13 | 14 | extern getter getterMethods[256]; 15 | extern setter setterMethods[256]; 16 | 17 | #define DECLARE_GETTER_METHOD(ida, idb) \ 18 | extern PyObject *getterMethod##ida##idb(PyObject *self, void *closure); 19 | 20 | #define DECLARE_SETTER_METHOD(ida, idb) \ 21 | extern int setterMethod##ida##idb(PyObject *self, PyObject *value, void *closure); 22 | 23 | #define DECLARE_WRAP_METHOD(ida, idb) \ 24 | extern PyObject *wrapperMethod##ida##idb(PyObject *self, PyObject *args); 25 | 26 | #define DECLARE_WRAP_METHOD_WITH_KWARGS(ida, idb) \ 27 | extern PyObject *wrapperMethodWithKwargs##ida##idb(PyObject *self, PyObject *args, PyObject *kwargs); 28 | 29 | #define DECLARE_WRAP_METHODS(ida) \ 30 | DECLARE_WRAP_METHOD(ida, 0) \ 31 | DECLARE_WRAP_METHOD(ida, 1) \ 32 | DECLARE_WRAP_METHOD(ida, 2) \ 33 | DECLARE_WRAP_METHOD(ida, 3) \ 34 | DECLARE_WRAP_METHOD(ida, 4) \ 35 | DECLARE_WRAP_METHOD(ida, 5) \ 36 | DECLARE_WRAP_METHOD(ida, 6) \ 37 | DECLARE_WRAP_METHOD(ida, 7) \ 38 | DECLARE_WRAP_METHOD(ida, 8) \ 39 | DECLARE_WRAP_METHOD(ida, 9) \ 40 | DECLARE_WRAP_METHOD(ida, a) \ 41 | DECLARE_WRAP_METHOD(ida, b) \ 42 | DECLARE_WRAP_METHOD(ida, c) \ 43 | DECLARE_WRAP_METHOD(ida, d) \ 44 | DECLARE_WRAP_METHOD(ida, e) \ 45 | DECLARE_WRAP_METHOD(ida, f) 46 | 47 | #define DECLARE_WRAP_METHODS_WITH_KWARGS(ida) \ 48 | DECLARE_WRAP_METHOD_WITH_KWARGS(ida, 0) \ 49 | DECLARE_WRAP_METHOD_WITH_KWARGS(ida, 1) \ 50 | DECLARE_WRAP_METHOD_WITH_KWARGS(ida, 2) \ 51 | DECLARE_WRAP_METHOD_WITH_KWARGS(ida, 3) \ 52 | DECLARE_WRAP_METHOD_WITH_KWARGS(ida, 4) \ 53 | DECLARE_WRAP_METHOD_WITH_KWARGS(ida, 5) \ 54 | DECLARE_WRAP_METHOD_WITH_KWARGS(ida, 6) \ 55 | DECLARE_WRAP_METHOD_WITH_KWARGS(ida, 7) \ 56 | DECLARE_WRAP_METHOD_WITH_KWARGS(ida, 8) \ 57 | DECLARE_WRAP_METHOD_WITH_KWARGS(ida, 9) \ 58 | DECLARE_WRAP_METHOD_WITH_KWARGS(ida, a) \ 59 | DECLARE_WRAP_METHOD_WITH_KWARGS(ida, b) \ 60 | DECLARE_WRAP_METHOD_WITH_KWARGS(ida, c) \ 61 | DECLARE_WRAP_METHOD_WITH_KWARGS(ida, d) \ 62 | DECLARE_WRAP_METHOD_WITH_KWARGS(ida, e) \ 63 | DECLARE_WRAP_METHOD_WITH_KWARGS(ida, f) 64 | 65 | #define DECLARE_WRAPPER_ALL_METHODS() \ 66 | DECLARE_WRAP_METHODS(0) \ 67 | DECLARE_WRAP_METHODS(1) \ 68 | DECLARE_WRAP_METHODS(2) \ 69 | DECLARE_WRAP_METHODS(3) \ 70 | DECLARE_WRAP_METHODS(4) \ 71 | DECLARE_WRAP_METHODS(5) \ 72 | DECLARE_WRAP_METHODS(6) \ 73 | DECLARE_WRAP_METHODS(7) \ 74 | DECLARE_WRAP_METHODS(8) \ 75 | DECLARE_WRAP_METHODS(9) \ 76 | DECLARE_WRAP_METHODS(a) \ 77 | DECLARE_WRAP_METHODS(b) \ 78 | DECLARE_WRAP_METHODS(c) \ 79 | DECLARE_WRAP_METHODS(d) \ 80 | DECLARE_WRAP_METHODS(e) \ 81 | DECLARE_WRAP_METHODS(f) 82 | 83 | #define DECLARE_WRAPPER_ALL_METHODS_WITH_KWARGS() \ 84 | DECLARE_WRAP_METHODS_WITH_KWARGS(0) \ 85 | DECLARE_WRAP_METHODS_WITH_KWARGS(1) \ 86 | DECLARE_WRAP_METHODS_WITH_KWARGS(2) \ 87 | DECLARE_WRAP_METHODS_WITH_KWARGS(3) \ 88 | DECLARE_WRAP_METHODS_WITH_KWARGS(4) \ 89 | DECLARE_WRAP_METHODS_WITH_KWARGS(5) \ 90 | DECLARE_WRAP_METHODS_WITH_KWARGS(6) \ 91 | DECLARE_WRAP_METHODS_WITH_KWARGS(7) \ 92 | DECLARE_WRAP_METHODS_WITH_KWARGS(8) \ 93 | DECLARE_WRAP_METHODS_WITH_KWARGS(9) \ 94 | DECLARE_WRAP_METHODS_WITH_KWARGS(a) \ 95 | DECLARE_WRAP_METHODS_WITH_KWARGS(b) \ 96 | DECLARE_WRAP_METHODS_WITH_KWARGS(c) \ 97 | DECLARE_WRAP_METHODS_WITH_KWARGS(d) \ 98 | DECLARE_WRAP_METHODS_WITH_KWARGS(e) \ 99 | DECLARE_WRAP_METHODS_WITH_KWARGS(f) 100 | 101 | DECLARE_WRAPPER_ALL_METHODS() 102 | DECLARE_WRAPPER_ALL_METHODS_WITH_KWARGS() 103 | 104 | #define DECLARE_GETTER_METHODS(ida) \ 105 | DECLARE_GETTER_METHOD(ida, 0) \ 106 | DECLARE_GETTER_METHOD(ida, 1) \ 107 | DECLARE_GETTER_METHOD(ida, 2) \ 108 | DECLARE_GETTER_METHOD(ida, 3) \ 109 | DECLARE_GETTER_METHOD(ida, 4) \ 110 | DECLARE_GETTER_METHOD(ida, 5) \ 111 | DECLARE_GETTER_METHOD(ida, 6) \ 112 | DECLARE_GETTER_METHOD(ida, 7) \ 113 | DECLARE_GETTER_METHOD(ida, 8) \ 114 | DECLARE_GETTER_METHOD(ida, 9) \ 115 | DECLARE_GETTER_METHOD(ida, a) \ 116 | DECLARE_GETTER_METHOD(ida, b) \ 117 | DECLARE_GETTER_METHOD(ida, c) \ 118 | DECLARE_GETTER_METHOD(ida, d) \ 119 | DECLARE_GETTER_METHOD(ida, e) \ 120 | DECLARE_GETTER_METHOD(ida, f) 121 | 122 | #define DECLARE_SETTER_METHODS(ida) \ 123 | DECLARE_SETTER_METHOD(ida, 0) \ 124 | DECLARE_SETTER_METHOD(ida, 1) \ 125 | DECLARE_SETTER_METHOD(ida, 2) \ 126 | DECLARE_SETTER_METHOD(ida, 3) \ 127 | DECLARE_SETTER_METHOD(ida, 4) \ 128 | DECLARE_SETTER_METHOD(ida, 5) \ 129 | DECLARE_SETTER_METHOD(ida, 6) \ 130 | DECLARE_SETTER_METHOD(ida, 7) \ 131 | DECLARE_SETTER_METHOD(ida, 8) \ 132 | DECLARE_SETTER_METHOD(ida, 9) \ 133 | DECLARE_SETTER_METHOD(ida, a) \ 134 | DECLARE_SETTER_METHOD(ida, b) \ 135 | DECLARE_SETTER_METHOD(ida, c) \ 136 | DECLARE_SETTER_METHOD(ida, d) \ 137 | DECLARE_SETTER_METHOD(ida, e) \ 138 | DECLARE_SETTER_METHOD(ida, f) 139 | 140 | #define DECLARE_WRAPPER_ALL_GETTERS() \ 141 | DECLARE_GETTER_METHODS(0) \ 142 | DECLARE_GETTER_METHODS(1) \ 143 | DECLARE_GETTER_METHODS(2) \ 144 | DECLARE_GETTER_METHODS(3) \ 145 | DECLARE_GETTER_METHODS(4) \ 146 | DECLARE_GETTER_METHODS(5) \ 147 | DECLARE_GETTER_METHODS(6) \ 148 | DECLARE_GETTER_METHODS(7) \ 149 | DECLARE_GETTER_METHODS(8) \ 150 | DECLARE_GETTER_METHODS(9) \ 151 | DECLARE_GETTER_METHODS(a) \ 152 | DECLARE_GETTER_METHODS(b) \ 153 | DECLARE_GETTER_METHODS(c) \ 154 | DECLARE_GETTER_METHODS(d) \ 155 | DECLARE_GETTER_METHODS(e) \ 156 | DECLARE_GETTER_METHODS(f) 157 | 158 | #define DECLARE_WRAPPER_ALL_SETTERS() \ 159 | DECLARE_SETTER_METHODS(0) \ 160 | DECLARE_SETTER_METHODS(1) \ 161 | DECLARE_SETTER_METHODS(2) \ 162 | DECLARE_SETTER_METHODS(3) \ 163 | DECLARE_SETTER_METHODS(4) \ 164 | DECLARE_SETTER_METHODS(5) \ 165 | DECLARE_SETTER_METHODS(6) \ 166 | DECLARE_SETTER_METHODS(7) \ 167 | DECLARE_SETTER_METHODS(8) \ 168 | DECLARE_SETTER_METHODS(9) \ 169 | DECLARE_SETTER_METHODS(a) \ 170 | DECLARE_SETTER_METHODS(b) \ 171 | DECLARE_SETTER_METHODS(c) \ 172 | DECLARE_SETTER_METHODS(d) \ 173 | DECLARE_SETTER_METHODS(e) \ 174 | DECLARE_SETTER_METHODS(f) 175 | 176 | DECLARE_WRAPPER_ALL_GETTERS() 177 | DECLARE_WRAPPER_ALL_SETTERS() 178 | 179 | #endif 180 | --------------------------------------------------------------------------------