From c21c0812e7e535f363664dfa684e5e79ad448faf Mon Sep 17 00:00:00 2001 From: Yuriy Glukhov Date: Fri, 5 May 2023 13:22:47 +0100 Subject: [PATCH] Dont try to initialize frame for python 3.11 and later (#282) --- .github/workflows/test.yml | 19 ++++++++++++++++--- nimpy.nim | 10 ++++++++-- nimpy/py_lib.nim | 26 ++++++++++++++++---------- tests/tpyfromnim.nim | 13 ++++++++----- 4 files changed, 48 insertions(+), 20 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index e438bf2b1..2ea5f3394 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -10,12 +10,12 @@ 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 + - os: macos-latest + python-version: "2.7" # DLL not found, specific to CI? name: ${{ matrix.os }}-${{ matrix.python-version }}-${{ matrix.nim-channel }} runs-on: ${{ matrix.os }} @@ -35,12 +35,25 @@ jobs: shell: bash run: | python -m pip install numpy + python -m pip install find_libpython - name: Test shell: bash run: | export NIMPY_PY_EXES=python python --version + if [ "${{ matrix.python-version }}" != "2.7" ] + then + LIBPYTHON=$(python -c 'import find_libpython; print(find_libpython.find_libpython())') + echo "libpython: $LIBPYTHON" + fi + python -c "import numpy; print('numpy: ', numpy.__version__)" nim --version + + if [ "${{ matrix.os }}" = "macos-latest" ] + then + export NIMPY_LIBPYTHONS=$LIBPYTHON + fi + nimble test 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..a6784198d 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]() @@ -246,6 +246,7 @@ proc test*() {.gcsafe.} = 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 locals = toPyDict(()) # Create empty dict # Let's say there's this python code: discard py.exec(""" @@ -255,7 +256,9 @@ proc test*() {.gcsafe.} = def useFoo(foo): return foo.overrideMe() - """.dedent()) + """.dedent(), pyDict(), locals) + + let fooClass = locals["Foo"] # Create a subclass of Foo in Nim: proc createFooSubclassInstance(): PyObject = @@ -268,7 +271,7 @@ proc test*() {.gcsafe.} = proc overrideMe(): int = self.super.overrideMe().to(int) + 123 # Call super - self = py.`type`("_", (pyGlobals()["Foo"], ), toPyDict({ + self = py.`type`("_", (fooClass, ), toPyDict({ "overrideMe": overrideMe })).to(proc(): PyObject {.gcsafe.})() return self @@ -277,7 +280,7 @@ proc test*() {.gcsafe.} = let b = createFooSubclassInstance() # Get `useFoo` proc - let useFoo = pyGlobals()["useFoo"].to(proc(self: PyObject): int {.gcsafe.}) + let useFoo = locals["useFoo"].to(proc(self: PyObject): int {.gcsafe.}) # Pass b to `useFoo` doAssert(useFoo(b) == 125)