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
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ $(VENDOR_DIR):
@$(GO) mod tidy

.PHONY: lint
lint:
lint: $(GOLANGCI_LINT)
@$(GOLANGCI_LINT) run

.PHONY: test
Expand Down
72 changes: 67 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,23 +10,85 @@
> [!IMPORTANT]
> **Currently supports python-3.11 only.**

The package provides a higher level for interacting with Python-3 C-API without directly using the `PyObject`.

Main goals:
- Provide generic a `Object` that interact with Golang types.
- Automatically marshal and unmarshal Golang types to Python types.

## Prerequisites

- `Go >= 1.21`
- `Go >= 1.22`
- `Python = 3.11`

## Install

```bash
go get go.nhat.io/python3
```

## Usage
## Examples

```go
package python3_test

TBD
import (
"fmt"

## Examples
"go.nhat.io/python3"
)

func ExampleVersion() {
sys := python3.MustImportModule("sys")
version := sys.GetAttr("version_info")

pyMajor := version.GetAttr("major")
defer pyMajor.DecRef()

pyMinor := version.GetAttr("minor")
defer pyMinor.DecRef()

pyReleaseLevel := version.GetAttr("releaselevel")
defer pyReleaseLevel.DecRef()

major := python3.AsInt(pyMajor)
minor := python3.AsInt(pyMinor)
releaseLevel := python3.AsString(pyReleaseLevel)

fmt.Println("major:", major)
fmt.Println("minor:", minor)
fmt.Println("release level:", releaseLevel)

TBA
// Output:
// major: 3
// minor: 11
// release level: final
}
```

```go
package python3_test

import (
"fmt"

"go.nhat.io/python3"
)

func ExampleMath() {
math := python3.MustImportModule("math")

pyResult := math.CallMethodArgs("sqrt", 4)
defer pyResult.DecRef()

result := python3.AsFloat64(pyResult)

fmt.Printf("sqrt(4) = %.2f\n", result)

// Output:
// sqrt(4) = 2.00
}
```

## Donation

Expand Down
2 changes: 0 additions & 2 deletions bool_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@ import (
)

func TestBool(t *testing.T) {
t.Parallel()

boolT := python3.NewBool(true)
boolF := python3.NewBool(false)

Expand Down
4 changes: 2 additions & 2 deletions error.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@ import (
)

// Exception is a Python exception.
type Exception struct {
type Exception struct { //nolint: errname,stylecheck
Message string
}

// Exception returns a string representation of the Exception.
// Error returns a string representation of the Exception.
func (e Exception) Error() string {
return e.Message
}
Expand Down
34 changes: 34 additions & 0 deletions examples/import-modules/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package main

import (
"fmt"

"go.nhat.io/python3"
)

func main() {
sys := python3.MustImportModule("sys")
version := sys.GetAttr("version_info")

pyMajor := version.GetAttr("major")
defer pyMajor.DecRef()

pyMinor := version.GetAttr("minor")
defer pyMinor.DecRef()

pyReleaseLevel := version.GetAttr("releaselevel")
defer pyReleaseLevel.DecRef()

major := python3.AsInt(pyMajor)
minor := python3.AsInt(pyMinor)
releaseLevel := python3.AsString(pyReleaseLevel)

fmt.Println("major:", major)
fmt.Println("minor:", minor)
fmt.Println("release level:", releaseLevel)

// Output:
// major: 3
// minor: 11
// release level: final
}
21 changes: 21 additions & 0 deletions examples/math/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package main

import (
"fmt"

"go.nhat.io/python3"
)

func main() { //nolint: govet
math := python3.MustImportModule("math")

pyResult := math.CallMethodArgs("sqrt", 4)
defer pyResult.DecRef()

result := python3.AsFloat64(pyResult)

fmt.Printf("sqrt(4) = %.2f\n", result)

// Output:
// sqrt(4) = 2.00
}
2 changes: 1 addition & 1 deletion import_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,6 @@ func TestImportModule_NotExists(t *testing.T) {
},
}

require.Equal(t, actual, expected)
require.Equal(t, expected, actual)
require.EqualError(t, actual, `No module named 'not_exists'`)
}
2 changes: 1 addition & 1 deletion init_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,5 @@ import (
func TestMain(m *testing.M) {
defer python3.Finalize()

os.Exit(m.Run())
os.Exit(m.Run()) // nolint: gocritic
}
7 changes: 4 additions & 3 deletions list.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,13 +74,14 @@ type List[T any] struct {
obj *ListObject
}

// UnmarshalPyObject unmarshals a Python object to a list.
func (l *List[T]) UnmarshalPyObject(o *Object) error {
if !IsList(o) && !IsList(o) {
if !IsList(o) && !IsTuple(o) {
return &UnmarshalTypeError{Value: TypeName(o), Type: reflect.TypeOf(l)}
}

if IsList(o) {

if IsTuple(o) {
o = (*Object)((*TupleObject)(o).AsList())
}

l.obj = (*ListObject)(o)
Expand Down
18 changes: 13 additions & 5 deletions list_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.nhat.io/cpy3"

"go.nhat.io/python3"
Expand Down Expand Up @@ -36,7 +37,7 @@ func TestList_Capacity(t *testing.T) {

assert.True(t, cpy3.PyList_Check(list.PyObject()))
assert.True(t, cpy3.PyList_CheckExact(list.PyObject()))
assert.True(t, pyList.RichCompareBool(list.PyObject(), cpy3.Py_EQ) == 1)
assert.True(t, pyList.RichCompareBool(list.PyObject(), cpy3.Py_EQ) == 1) //nolint: testifylint

assert.Equal(t, 10, list.Length())
}
Expand Down Expand Up @@ -116,8 +117,6 @@ func TestNewListFromAny(t *testing.T) {
}

func TestListOfListOfList(t *testing.T) {
t.Parallel()

list := python3.NewListForType[[][]int](1)

list.Set(0, [][]int{{1, 2}, {2, 3}})
Expand All @@ -129,8 +128,6 @@ func TestListOfListOfList(t *testing.T) {
}

func TestListOfTuple(t *testing.T) {
t.Parallel()

list := python3.NewListForType[*python3.Tuple[int]](1)

list.Set(0, python3.NewTupleFromValues(1, 2))
Expand All @@ -142,3 +139,14 @@ func TestListOfTuple(t *testing.T) {

assert.True(t, expected.AsObject().Equal(actual.AsObject()))
}

func TestListFromTuple(t *testing.T) {
tuple := python3.NewTupleFromValues(1, 2, 3)

var list python3.List[int]

err := list.UnmarshalPyObject(tuple.AsObject())
require.NoError(t, err)

assert.Equal(t, `[1, 2, 3]`, list.String())
}
8 changes: 4 additions & 4 deletions marshal.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,17 @@ type Marshaler interface {

// Unmarshaler is the interface implemented by types that can unmarshal a Python object of themselves.
type Unmarshaler interface {
UnmarshalPyObject(*Object) error
UnmarshalPyObject(o *Object) error
}

// Marshal returns the Python object for v.
func Marshal(v any) (*Object, error) {
func Marshal(v any) (*Object, error) { //nolint: cyclop,funlen,gocyclo
if v, ok := v.(Marshaler); ok {
return v.MarshalPyObject(), nil
}

if v := reflect.ValueOf(v); v.Kind() == reflect.Ptr && v.IsNil() {
return nil, nil
return nil, nil //nolint: nilnil
}

switch v := v.(type) {
Expand Down Expand Up @@ -147,7 +147,7 @@ func (e *UnmarshalTypeError) Error() string {
}

// Unmarshal converts the Python object to a value of the same type as v.
func Unmarshal(o *Object, v any) error {
func Unmarshal(o *Object, v any) error { //nolint: cyclop,funlen,gocognit,gocyclo
rv := reflect.ValueOf(v)
if rv.Kind() != reflect.Pointer || rv.IsNil() {
return &InvalidUnmarshalError{reflect.TypeOf(v)}
Expand Down
14 changes: 7 additions & 7 deletions marshal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -235,25 +235,25 @@ func TestUnmarshal_Int(t *testing.T) {
scenario: "bool",
object: python3.True,
expectedResult: 0,
expectedError: `python3: cannot unmarshal bool into Go value of type int`,
expectedError: `python3: cannot unmarshal bool into Go value of type int64`,
},
{
scenario: "empty string",
object: python3.NewString(""),
expectedResult: 0,
expectedError: `python3: cannot unmarshal str into Go value of type int`,
expectedError: `python3: cannot unmarshal str into Go value of type int64`,
},
{
scenario: "string",
object: python3.NewString("hello"),
expectedResult: 0,
expectedError: `python3: cannot unmarshal str into Go value of type int`,
expectedError: `python3: cannot unmarshal str into Go value of type int64`,
},
{
scenario: "float",
object: python3.NewFloat64(3.14),
expectedResult: 0,
expectedError: `python3: cannot unmarshal float into Go value of type int`,
expectedError: `python3: cannot unmarshal float into Go value of type int64`,
},
{
scenario: "int",
Expand Down Expand Up @@ -424,7 +424,7 @@ func TestUnmarshal_Slice(t *testing.T) {
scenario: "list of string",
object: python3.NewListFromValues("hello", "world").AsObject(),
expectedResult: []int(nil),
expectedError: `python3: cannot unmarshal str into Go value of type int`,
expectedError: `python3: cannot unmarshal str into Go value of type int64`,
},
{
scenario: "list of int",
Expand All @@ -445,7 +445,7 @@ func TestUnmarshal_Slice(t *testing.T) {
scenario: "tuple of string",
object: python3.NewTupleFromValues("hello", "world").AsObject(),
expectedResult: []int(nil),
expectedError: `python3: cannot unmarshal str into Go value of type int`,
expectedError: `python3: cannot unmarshal str into Go value of type int64`,
},
{
scenario: "tuple of int",
Expand Down Expand Up @@ -485,7 +485,7 @@ func (i integer) MarshalPyObject() *python3.Object {
return python3.NewInt(int(i))
}

func (i *integer) UnmarshalPyObject(o *python3.Object) error {
func (i *integer) UnmarshalPyObject(o *python3.Object) error { //nolint: unparam
*i = integer(python3.AsInt(o))

return nil
Expand Down
18 changes: 18 additions & 0 deletions tuple.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,17 @@ func (o *TupleObject) AsObject() *Object {
return (*Object)(o)
}

// AsList converts a tuple to a list.
func (o *TupleObject) AsList() *ListObject {
l := (*ListObject)(cpy3.PyList_New(o.Length()))

for i := 0; i < o.Length(); i++ {
l.Set(i, o.Get(i))
}

return l
}

// String returns the string representation of the object.
func (o *TupleObject) String() string {
return asString((*cpy3.PyObject)(o))
Expand Down Expand Up @@ -120,6 +131,13 @@ func (t *Tuple[T]) AsObject() *Object {
return t.obj.AsObject()
}

// AsList converts a tuple to a list.
func (t *Tuple[T]) AsList() *List[T] {
return &List[T]{
obj: t.obj.AsList(),
}
}

// AsSlice converts a tuple to a slice.
func (t *Tuple[T]) AsSlice() []T {
length := t.Length()
Expand Down
Loading