diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index e438bf2b1..a303643b2 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -10,12 +10,10 @@ jobs: matrix: os: [ubuntu-latest, windows-latest, macos-latest] nim-channel: [stable, devel] - python-version: ["2.7", "3.5", "3.6", "3.7", "3.8", "3.9", "3.10"] + python-version: ["2.7", "3.7", "3.8", "3.9", "3.10", "3.11"] exclude: - os: windows-latest python-version: "2.7" # DLL not found, specific to CI? - - os: windows-latest - python-version: "3.10" # FIXME: eval doesn't work name: ${{ matrix.os }}-${{ matrix.python-version }}-${{ matrix.nim-channel }} runs-on: ${{ matrix.os }} diff --git a/nimpy.nim b/nimpy.nim index 2fc889dc1..ba5b9dc7f 100644 --- a/nimpy.nim +++ b/nimpy.nim @@ -941,11 +941,15 @@ proc pyBuiltins*(): PyObject = proc pyGlobals*(): PyObject = initPyLibIfNeeded() - newPyObject(pyLib.PyEval_GetGlobals()) + let r = pyLib.PyEval_GetGlobals() + if not r.isNil: + result = newPyObject(r) proc pyLocals*(): PyObject = initPyLibIfNeeded() - newPyObject(pyLib.PyEval_GetLocals()) + let r = pyLib.PyEval_GetLocals() + if not r.isNil: + result = newPyObject(r) proc dir*(v: PyObject): seq[string] = let lst = pyLib.PyObject_Dir(v.rawPyObj) @@ -1002,6 +1006,8 @@ macro toPyDictRaw(a: untyped): PPyObject = template toPyDict*(a: untyped): PyObject = newPyObjectConsumingRef(toPyDictRaw(a)) +proc pyDict*(): PyObject = + newPyObjectConsumingRef(toPyDictRaw(())) ################################################################################ ################################################################################ diff --git a/nimpy/py_lib.nim b/nimpy/py_lib.nim index fb22a531c..c84126d24 100644 --- a/nimpy/py_lib.nim +++ b/nimpy/py_lib.nim @@ -488,10 +488,13 @@ proc initPyThreadFrame() = echo "Testing libpython: ", nimpyTestLibPython pyInitLibPath(nimpyTestLibPython) - # https://stackoverflow.com/questions/42974139/valueerror-call-stack-is-not-deep-enough-when-calling-ipython-embed-method - # needed for eval and stuff like pandas.query() otherwise crash (call stack is not deep enough) if unlikely pyLib.isNil: initPyLib(pythonLibHandleFromExternalLib()) + + # https://stackoverflow.com/questions/42974139/valueerror-call-stack-is-not-deep-enough-when-calling-ipython-embed-method + # needed for eval and stuff like pandas.query() otherwise crash (call stack is not deep enough) + # + # XXX Unfortunately this doesn't work with python 3.11 and later. pyThreadFrameInited = true let @@ -502,7 +505,8 @@ proc initPyThreadFrame() = of 2: if not cast[ptr PyThreadState2](pyThread).frame.isNil: return of 3: - if not cast[ptr PyThreadState3](pyThread).frame.isNil: return + if pyLib.pythonVersion < (3, 11, 0): + if not cast[ptr PyThreadState3](pyThread).frame.isNil: return else: doAssert(false, "unreachable") @@ -513,17 +517,19 @@ proc initPyThreadFrame() = pyFrameNew = cast[proc(p1, p2, p3, p4: pointer): pointer {.pyfunc.}](pyLib.module.symAddr("PyFrame_New")) if not pyImportAddModule.isNil and not pyModuleGetDict.isNil and not pyCodeNewEmpty.isNil and not pyFrameNew.isNil: - let - main_module = pyImportAddModule("__main__") - main_dict = pyModuleGetDict(main_module) - code_object = pyCodeNewEmpty("null.py", "f", 0) - root_frame = pyFrameNew(pyThread, code_object, main_dict, main_dict) + proc makeRootFrame(): pointer = + let + main_module = pyImportAddModule("__main__") + main_dict = pyModuleGetDict(main_module) + code_object = pyCodeNewEmpty("null.py", "f", 0) + pyFrameNew(pyThread, code_object, main_dict, main_dict) case pyLib.pythonVersion.major of 2: - cast[ptr PyThreadState2](pyThread).frame = root_frame + cast[ptr PyThreadState2](pyThread).frame = makeRootFrame() of 3: - cast[ptr PyThreadState3](pyThread).frame = root_frame + if pyLib.pythonVersion < (3, 11, 0): + cast[ptr PyThreadState3](pyThread).frame = makeRootFrame() else: doAssert(false, "unreachable") diff --git a/tests/tpyfromnim.nim b/tests/tpyfromnim.nim index 6a18052db..a7c17c387 100644 --- a/tests/tpyfromnim.nim +++ b/tests/tpyfromnim.nim @@ -48,8 +48,8 @@ proc test*() {.gcsafe.} = block: # eval let py = pyBuiltinsModule() - doAssert(py.eval("3+3").to(int) == 6) - doAssert(py.eval(""" "hello" * 2 """).to(string) == "hellohello") + doAssert(py.eval("3+3", pyDict(), pyDict()).to(int) == 6) + doAssert(py.eval(""" "hello" * 2 """, pyDict(), pyDict()).to(string) == "hellohello") block: var ints = newSeq[int]() @@ -244,43 +244,45 @@ proc test*() {.gcsafe.} = doAssert(pfn.test_nil_marshalling(myNil).to(bool)) block: # Kinda subclassing python objects in nim and calling super - if pyImport("sys").version_info.major.to(int) >= 3: # Only test with python 3 - let py = pyBuiltinsModule() + let py = pyBuiltinsModule() + let locals = toPyDict(()) # Create empty dict + + # Let's say there's this python code: + discard py.exec(""" + class Foo: + def overrideMe(self): + return 2 - # Let's say there's this python code: - discard py.exec(""" - class Foo: - def overrideMe(self): - return 2 + def useFoo(foo): + return foo.overrideMe() + """.dedent(), pyDict(), locals) - def useFoo(foo): - return foo.overrideMe() - """.dedent()) + let fooClass = locals["Foo"] - # Create a subclass of Foo in Nim: - proc createFooSubclassInstance(): PyObject = - # The subclass is created with python `type` function - # Currently we don't have means to get `self` argument inside a method, - # so we keep `self` around in the closure environment + # Create a subclass of Foo in Nim: + proc createFooSubclassInstance(): PyObject = + # The subclass is created with python `type` function + # Currently we don't have means to get `self` argument inside a method, + # so we keep `self` around in the closure environment - var self: PyObject + var self: PyObject - proc overrideMe(): int = - self.super.overrideMe().to(int) + 123 # Call super + proc overrideMe(): int = + self.super.overrideMe().to(int) + 123 # Call super - self = py.`type`("_", (pyGlobals()["Foo"], ), toPyDict({ - "overrideMe": overrideMe - })).to(proc(): PyObject {.gcsafe.})() - return self + self = py.`type`("_", (fooClass, ), toPyDict({ + "overrideMe": overrideMe + })).to(proc(): PyObject {.gcsafe.})() + return self - # Create an instance of Bar - let b = createFooSubclassInstance() + # Create an instance of Bar + let b = createFooSubclassInstance() - # Get `useFoo` proc - let useFoo = pyGlobals()["useFoo"].to(proc(self: PyObject): int {.gcsafe.}) + # Get `useFoo` proc + let useFoo = locals["useFoo"].to(proc(self: PyObject): int {.gcsafe.}) - # Pass b to `useFoo` - doAssert(useFoo(b) == 125) + # Pass b to `useFoo` + doAssert(useFoo(b) == 125) when isMainModule: test()