From 6c3d86233409b688551968e8cb72a792d2a4cb42 Mon Sep 17 00:00:00 2001 From: nhatthm Date: Mon, 19 Feb 2024 15:24:38 +0100 Subject: [PATCH] Fix broken tests --- Makefile | 2 +- README.md | 72 ++++++++++++++++++++++++++++++--- bool_test.go | 2 - error.go | 4 +- examples/import-modules/main.go | 34 ++++++++++++++++ examples/math/main.go | 21 ++++++++++ import_test.go | 2 +- init_test.go | 2 +- list.go | 7 ++-- list_test.go | 18 ++++++--- marshal.go | 8 ++-- marshal_test.go | 14 +++---- tuple.go | 18 +++++++++ tuple_test.go | 6 +-- type_test.go | 4 -- 15 files changed, 174 insertions(+), 40 deletions(-) create mode 100644 examples/import-modules/main.go create mode 100644 examples/math/main.go diff --git a/Makefile b/Makefile index 88bf168..3b4e26c 100644 --- a/Makefile +++ b/Makefile @@ -15,7 +15,7 @@ $(VENDOR_DIR): @$(GO) mod tidy .PHONY: lint -lint: +lint: $(GOLANGCI_LINT) @$(GOLANGCI_LINT) run .PHONY: test diff --git a/README.md b/README.md index a4fc402..8aa1076 100644 --- a/README.md +++ b/README.md @@ -10,9 +10,16 @@ > [!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 @@ -20,13 +27,68 @@ 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 diff --git a/bool_test.go b/bool_test.go index 2bf10bd..73ed410 100644 --- a/bool_test.go +++ b/bool_test.go @@ -9,8 +9,6 @@ import ( ) func TestBool(t *testing.T) { - t.Parallel() - boolT := python3.NewBool(true) boolF := python3.NewBool(false) diff --git a/error.go b/error.go index eb2c8de..3dabcfc 100644 --- a/error.go +++ b/error.go @@ -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 } diff --git a/examples/import-modules/main.go b/examples/import-modules/main.go new file mode 100644 index 0000000..49731c2 --- /dev/null +++ b/examples/import-modules/main.go @@ -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 +} diff --git a/examples/math/main.go b/examples/math/main.go new file mode 100644 index 0000000..4362364 --- /dev/null +++ b/examples/math/main.go @@ -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 +} diff --git a/import_test.go b/import_test.go index 51af171..6b58cdf 100644 --- a/import_test.go +++ b/import_test.go @@ -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'`) } diff --git a/init_test.go b/init_test.go index 35ee64d..48df4ec 100644 --- a/init_test.go +++ b/init_test.go @@ -11,5 +11,5 @@ import ( func TestMain(m *testing.M) { defer python3.Finalize() - os.Exit(m.Run()) + os.Exit(m.Run()) // nolint: gocritic } diff --git a/list.go b/list.go index 17827bf..71a0560 100644 --- a/list.go +++ b/list.go @@ -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) diff --git a/list_test.go b/list_test.go index a3f68d1..15f0c50 100644 --- a/list_test.go +++ b/list_test.go @@ -4,6 +4,7 @@ import ( "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "go.nhat.io/cpy3" "go.nhat.io/python3" @@ -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()) } @@ -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}}) @@ -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)) @@ -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()) +} diff --git a/marshal.go b/marshal.go index c06b351..d99ebc1 100644 --- a/marshal.go +++ b/marshal.go @@ -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) { @@ -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)} diff --git a/marshal_test.go b/marshal_test.go index d938e02..e4fdbb7 100644 --- a/marshal_test.go +++ b/marshal_test.go @@ -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", @@ -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", @@ -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", @@ -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 diff --git a/tuple.go b/tuple.go index eb1d4dc..246f181 100644 --- a/tuple.go +++ b/tuple.go @@ -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)) @@ -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() diff --git a/tuple_test.go b/tuple_test.go index cb81152..ed026ef 100644 --- a/tuple_test.go +++ b/tuple_test.go @@ -36,7 +36,7 @@ func TestTuple_Capacity(t *testing.T) { assert.True(t, cpy3.PyTuple_Check(tuple.PyObject())) assert.True(t, cpy3.PyTuple_CheckExact(tuple.PyObject())) - assert.True(t, pyTuple.RichCompareBool(tuple.PyObject(), cpy3.Py_EQ) == 1) + assert.True(t, pyTuple.RichCompareBool(tuple.PyObject(), cpy3.Py_EQ) == 1) //nolint: testifylint assert.Equal(t, 10, tuple.Length()) } @@ -116,8 +116,6 @@ func TestNewTupleFromAny(t *testing.T) { } func TestTupleOfList(t *testing.T) { - t.Parallel() - tuple := python3.NewTupleForType[[]int](1) tuple.Set(0, []int{1, 2}) @@ -131,8 +129,6 @@ func TestTupleOfList(t *testing.T) { } func TestTupleOfTuple(t *testing.T) { - t.Parallel() - tuple := python3.NewTupleForType[*python3.Tuple[int]](1) tuple.Set(0, python3.NewTupleFromValues(1, 2)) diff --git a/type_test.go b/type_test.go index 95396c8..51a7763 100644 --- a/type_test.go +++ b/type_test.go @@ -9,8 +9,6 @@ import ( ) func TestTypeName(t *testing.T) { - t.Parallel() - testCases := []struct { scenario string value *python3.Object @@ -56,8 +54,6 @@ func TestTypeName(t *testing.T) { for _, tc := range testCases { tc := tc t.Run(tc.scenario, func(t *testing.T) { - t.Parallel() - actual := python3.TypeName(tc.value) assert.Equal(t, tc.expected, actual)