Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 2 additions & 7 deletions .github/workflows/go.yml
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,6 @@ jobs:
os:
- macos-latest
- ubuntu-24.04
go:
- '1.20'
- '1.21'
- '1.22'
- '1.23'
runs-on: ${{matrix.os}}
steps:
- name: Install Python 3.12 (macOS only)
Expand All @@ -65,7 +60,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v4
with:
go-version: ${{matrix.go}}
go-version: 1.23

- name: Build
run: go build -v ./...
Expand All @@ -74,7 +69,7 @@ jobs:
run: go test -v -coverprofile=coverage.txt -covermode=atomic ./...

- name: Upload coverage to Codecov
if: matrix.os == 'ubuntu-24.04' && matrix.go == '1.23'
if: matrix.os == 'ubuntu-24.04'
uses: codecov/codecov-action@v4
with:
token: ${{ secrets.CODECOV_TOKEN }}
Expand Down
13 changes: 9 additions & 4 deletions convert.go
Original file line number Diff line number Diff line change
Expand Up @@ -149,14 +149,15 @@ func ToValue(obj Object, v reflect.Value) bool {
t := v.Type()
v.Set(reflect.MakeMap(t))
dict := Cast[Dict](obj)
dict.ForEach(func(key, value Object) {
for key, value := range dict.Items() {
vk := reflect.New(t.Key()).Elem()
vv := reflect.New(t.Elem()).Elem()
if !ToValue(key, vk) || !ToValue(value, vv) {
panic(fmt.Errorf("failed to convert key or value to %v", t.Key()))
return false
}
v.SetMapIndex(vk, vv)
})
}
return true
} else {
return false
}
Expand All @@ -167,9 +168,13 @@ func ToValue(obj Object, v reflect.Value) bool {
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
key := goNameToPythonName(field.Name)
if !dict.HasKey(MakeStr(key)) {
continue
}
value := dict.Get(MakeStr(key))
if !ToValue(value, v.Field(i)) {
panic(fmt.Errorf("failed to convert value to %v", field.Name))
SetTypeError(fmt.Errorf("failed to convert value to %v", field.Name))
return false
}
}
} else {
Expand Down
38 changes: 21 additions & 17 deletions dict.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ func MakeDict(m map[any]any) Dict {
return dict
}

func (d Dict) Has(key any) bool {
func (d Dict) HasKey(key any) bool {
keyObj := From(key)
return C.PyDict_Contains(d.obj, keyObj.obj) != 0
}
Expand Down Expand Up @@ -75,22 +75,26 @@ func (d Dict) Del(key Objecter) {
C.PyDict_DelItem(d.obj, key.Obj())
}

func (d Dict) ForEach(fn func(key, value Object)) {
items := C.PyDict_Items(d.obj)
check(items != nil, "failed to get items of dict")
defer C.Py_DecRef(items)
iter := C.PyObject_GetIter(items)
for {
item := C.PyIter_Next(iter)
if item == nil {
break
func (d Dict) Items() func(fn func(key, value Object) bool) {
return func(fn func(key, value Object) bool) {
items := C.PyDict_Items(d.obj)
check(items != nil, "failed to get items of dict")
defer C.Py_DecRef(items)
iter := C.PyObject_GetIter(items)
for {
item := C.PyIter_Next(iter)
if item == nil {
break
}
C.Py_IncRef(item)
key := C.PyTuple_GetItem(item, 0)
value := C.PyTuple_GetItem(item, 1)
C.Py_IncRef(key)
C.Py_IncRef(value)
C.Py_DecRef(item)
if !fn(newObject(key), newObject(value)) {
break
}
}
C.Py_IncRef(item)
key := C.PyTuple_GetItem(item, 0)
value := C.PyTuple_GetItem(item, 1)
C.Py_IncRef(key)
C.Py_IncRef(value)
C.Py_DecRef(item)
fn(newObject(key), newObject(value))
}
}
6 changes: 3 additions & 3 deletions dict_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ func TestDictDel(t *testing.T) {
dict.Del(key)

// After deletion, the key should not exist
if dict.Has(key) {
if dict.HasKey(key) {
t.Errorf("After deletion, key %v should not exist", key)
}
}
Expand All @@ -155,14 +155,14 @@ func TestDictForEach(t *testing.T) {
"key3": "value3",
}

dict.ForEach(func(key, value Object) {
for key, value := range dict.Items() {
count++
k := key.String()
v := value.String()
if expectedVal, ok := expectedPairs[k]; !ok || expectedVal != v {
t.Errorf("ForEach() unexpected pair: %v: %v", k, v)
}
})
}

if count != len(expectedPairs) {
t.Errorf("ForEach() visited %d pairs, want %d", count, len(expectedPairs))
Expand Down
27 changes: 22 additions & 5 deletions extension.go
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,9 @@ func getterMethod(self *C.PyObject, _closure unsafe.Pointer, methodId C.int) *C.

fieldType := field.Type()
if fieldType.Kind() == reflect.Ptr && fieldType.Elem().Kind() == reflect.Struct {
if field.IsNil() {
return C.Py_None
}
if pyType, ok := maps.pyTypes[fieldType.Elem()]; ok {
newWrapper := allocWrapper((*C.PyTypeObject)(unsafe.Pointer(pyType)), field.Interface())
if newWrapper == nil {
Expand Down Expand Up @@ -187,15 +190,24 @@ func setterMethod(self, value *C.PyObject, _closure unsafe.Pointer, methodId C.i

fieldType := field.Type()
if fieldType.Kind() == reflect.Ptr && fieldType.Elem().Kind() == reflect.Struct {
if C.Py_Is(value, C.Py_None) != 0 {
field.Set(reflect.Zero(fieldType))
return 0
}
if C.Py_IS_TYPE(value, &C.PyDict_Type) != 0 {
if field.IsNil() {
field.Set(reflect.New(fieldType.Elem()))
}
if !ToValue(FromPy(value), field.Elem()) {
SetError(fmt.Errorf("failed to convert dict to %s", fieldType.Elem()))
SetTypeError(fmt.Errorf("failed to convert dict to %s", fieldType.Elem()))
return -1
}
} else {
pyType := C.Py_TYPE(value)
if _, ok := maps.typeMetas[(*C.PyObject)(unsafe.Pointer(pyType))]; !ok {
SetTypeError(fmt.Errorf("invalid value of type %v for struct pointer field", FromPy((*C.PyObject)(unsafe.Pointer(pyType)))))
return -1
}
valueWrapper := (*wrapperType)(unsafe.Pointer(value))
if valueWrapper == nil {
SetError(fmt.Errorf("invalid value for struct pointer field"))
Expand All @@ -207,25 +219,30 @@ func setterMethod(self, value *C.PyObject, _closure unsafe.Pointer, methodId C.i
} else if field.Kind() == reflect.Struct {
if C.Py_IS_TYPE(value, &C.PyDict_Type) != 0 {
if !ToValue(FromPy(value), field) {
SetError(fmt.Errorf("failed to convert dict to %s", field.Type()))
SetTypeError(fmt.Errorf("failed to convert dict to %s", field.Type()))
return -1
}
} else {
pyType := (*C.PyTypeObject)(unsafe.Pointer(value.ob_type))
if _, ok := maps.typeMetas[(*C.PyObject)(unsafe.Pointer(pyType))]; !ok {
SetTypeError(fmt.Errorf("invalid value of type %v for struct field", FromPy((*C.PyObject)(unsafe.Pointer(pyType)))))
return -1
}
valueWrapper := (*wrapperType)(unsafe.Pointer(value))
if valueWrapper == nil {
SetError(fmt.Errorf("invalid value for struct field"))
return -1
}
baseAddr := goPtr.UnsafePointer()
fieldAddr := unsafe.Add(baseAddr, typeMeta.typ.Field(methodMeta.index).Offset)
fieldPtr := reflect.NewAt(fieldType, fieldAddr).Interface()
reflect.ValueOf(fieldPtr).Set(reflect.ValueOf(valueWrapper.goObj))
fieldPtr := reflect.NewAt(fieldType, fieldAddr)
fieldPtr.Elem().Set(reflect.ValueOf(valueWrapper.goObj).Elem())
}
return 0
}

if !ToValue(FromPy(value), field) {
SetError(fmt.Errorf("failed to convert value to %s", methodMeta.typ))
SetTypeError(fmt.Errorf("failed to convert value to %s", methodMeta.typ))
return -1
}
return 0
Expand Down
Loading
Loading